Skip to content

Commit ec2d7e3

Browse files
committed
feat: send media with form-data
1 parent 44e152b commit ec2d7e3

12 files changed

+197
-48
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
* Sync lost messages on chatwoot
66
* Set the maximum number of listeners that can be registered for events
7+
* Now is possible send medias with form-data
78

89
### Fixed
910

Docker/swarm/evolution_api_v2.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ version: "3.7"
22

33
services:
44
evolution_v2:
5-
image: atendai/evolution-api:v2.0.10
5+
image: atendai/evolution-api:v2.1.2
66
volumes:
77
- evolution_instances:/evolution/instances
88
networks:

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@
8080
"long": "^5.2.3",
8181
"mime": "^3.0.0",
8282
"minio": "^8.0.1",
83+
"multer": "^1.4.5-lts.1",
8384
"node-cache": "^5.1.2",
8485
"node-cron": "^3.0.3",
8586
"node-windows": "^1.0.0-beta.8",

src/@types/express.d.ts

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { Multer } from 'multer';
2+
3+
declare global {
4+
namespace Express {
5+
interface Request {
6+
file?: Multer.File;
7+
}
8+
}
9+
}

src/api/controllers/sendMessage.controller.ts

+11-11
Original file line numberDiff line numberDiff line change
@@ -28,27 +28,27 @@ export class SendMessageController {
2828
return await this.waMonitor.waInstances[instanceName].textMessage(data);
2929
}
3030

31-
public async sendMedia({ instanceName }: InstanceDto, data: SendMediaDto) {
31+
public async sendMedia({ instanceName }: InstanceDto, data: SendMediaDto, file?: any) {
3232
if (isBase64(data?.media) && !data?.fileName && data?.mediatype === 'document') {
3333
throw new BadRequestException('For base64 the file name must be informed.');
3434
}
3535

36-
if (isURL(data?.media) || isBase64(data?.media)) {
37-
return await this.waMonitor.waInstances[instanceName].mediaMessage(data);
36+
if (file || isURL(data?.media) || isBase64(data?.media)) {
37+
return await this.waMonitor.waInstances[instanceName].mediaMessage(data, file);
3838
}
3939
throw new BadRequestException('Owned media must be a url or base64');
4040
}
4141

42-
public async sendSticker({ instanceName }: InstanceDto, data: SendStickerDto) {
43-
if (isURL(data.sticker) || isBase64(data.sticker)) {
44-
return await this.waMonitor.waInstances[instanceName].mediaSticker(data);
42+
public async sendSticker({ instanceName }: InstanceDto, data: SendStickerDto, file?: any) {
43+
if (file || isURL(data.sticker) || isBase64(data.sticker)) {
44+
return await this.waMonitor.waInstances[instanceName].mediaSticker(data, file);
4545
}
4646
throw new BadRequestException('Owned media must be a url or base64');
4747
}
4848

49-
public async sendWhatsAppAudio({ instanceName }: InstanceDto, data: SendAudioDto) {
50-
if (isURL(data.audio) || isBase64(data.audio)) {
51-
return await this.waMonitor.waInstances[instanceName].audioWhatsapp(data);
49+
public async sendWhatsAppAudio({ instanceName }: InstanceDto, data: SendAudioDto, file?: any) {
50+
if (file || isURL(data.audio) || isBase64(data.audio)) {
51+
return await this.waMonitor.waInstances[instanceName].audioWhatsapp(data, file);
5252
}
5353
throw new BadRequestException('Owned media must be a url or base64');
5454
}
@@ -80,7 +80,7 @@ export class SendMessageController {
8080
return await this.waMonitor.waInstances[instanceName].pollMessage(data);
8181
}
8282

83-
public async sendStatus({ instanceName }: InstanceDto, data: SendStatusDto) {
84-
return await this.waMonitor.waInstances[instanceName].statusMessage(data);
83+
public async sendStatus({ instanceName }: InstanceDto, data: SendStatusDto, file?: any) {
84+
return await this.waMonitor.waInstances[instanceName].statusMessage(data, file);
8585
}
8686
}

src/api/integrations/channel/evolution/evolution.channel.service.ts

+24-8
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { ChannelStartupService } from '@api/services/channel.service';
77
import { Events, wa } from '@api/types/wa.types';
88
import { Chatwoot, ConfigService, Openai } from '@config/env.config';
99
import { BadRequestException, InternalServerErrorException } from '@exceptions';
10+
import { deleteTempFile, getTempFile } from '@utils/getTempFile';
1011
import { isURL } from 'class-validator';
1112
import EventEmitter2 from 'eventemitter2';
1213
import mime from 'mime';
@@ -164,7 +165,7 @@ export class EvolutionStartupService extends ChannelStartupService {
164165

165166
await this.updateContact({
166167
remoteJid: messageRaw.key.remoteJid,
167-
pushName: messageRaw.key.fromMe ? '' : (messageRaw.key.fromMe == null ? '' : received.pushName),
168+
pushName: messageRaw.key.fromMe ? '' : messageRaw.key.fromMe == null ? '' : received.pushName,
168169
profilePicUrl: received.profilePicUrl,
169170
});
170171
}
@@ -433,11 +434,14 @@ export class EvolutionStartupService extends ChannelStartupService {
433434
}
434435
}
435436

436-
public async mediaMessage(data: SendMediaDto, isIntegration = false) {
437-
const message = await this.prepareMediaMessage(data);
437+
public async mediaMessage(data: SendMediaDto, file?: any, isIntegration = false) {
438+
const mediaData: SendMediaDto = { ...data };
438439

439-
console.log('message', message);
440-
return await this.sendMessageWithTyping(
440+
if (file) mediaData.media = await getTempFile(file, this.instanceId);
441+
442+
const message = await this.prepareMediaMessage(mediaData);
443+
444+
const mediaSent = await this.sendMessageWithTyping(
441445
data.number,
442446
{ ...message },
443447
{
@@ -450,6 +454,10 @@ export class EvolutionStartupService extends ChannelStartupService {
450454
},
451455
isIntegration,
452456
);
457+
458+
if (file) await deleteTempFile(file, this.instanceId);
459+
460+
return mediaSent;
453461
}
454462

455463
public async processAudio(audio: string, number: string) {
@@ -475,10 +483,14 @@ export class EvolutionStartupService extends ChannelStartupService {
475483
return prepareMedia;
476484
}
477485

478-
public async audioWhatsapp(data: SendAudioDto, isIntegration = false) {
479-
const message = await this.processAudio(data.audio, data.number);
486+
public async audioWhatsapp(data: SendAudioDto, file?: any, isIntegration = false) {
487+
const mediaData: SendAudioDto = { ...data };
480488

481-
return await this.sendMessageWithTyping(
489+
if (file) mediaData.audio = await getTempFile(file, this.instanceId);
490+
491+
const message = await this.processAudio(mediaData.audio, data.number);
492+
493+
const audioSent = await this.sendMessageWithTyping(
482494
data.number,
483495
{ ...message },
484496
{
@@ -491,6 +503,10 @@ export class EvolutionStartupService extends ChannelStartupService {
491503
},
492504
isIntegration,
493505
);
506+
507+
if (file) await deleteTempFile(file, this.instanceId);
508+
509+
return audioSent;
494510
}
495511

496512
public async buttonMessage() {

src/api/integrations/channel/meta/whatsapp.business.service.ts

+23-6
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import { ChannelStartupService } from '@api/services/channel.service';
2222
import { Events, wa } from '@api/types/wa.types';
2323
import { Chatwoot, ConfigService, Database, Openai, S3, WaBusiness } from '@config/env.config';
2424
import { BadRequestException, InternalServerErrorException } from '@exceptions';
25+
import { deleteTempFile, getTempFile } from '@utils/getTempFile';
2526
import axios from 'axios';
2627
import { arrayUnique, isURL } from 'class-validator';
2728
import EventEmitter2 from 'eventemitter2';
@@ -1026,10 +1027,14 @@ export class BusinessStartupService extends ChannelStartupService {
10261027
}
10271028
}
10281029

1029-
public async mediaMessage(data: SendMediaDto, isIntegration = false) {
1030-
const message = await this.prepareMediaMessage(data);
1030+
public async mediaMessage(data: SendMediaDto, file?: any, isIntegration = false) {
1031+
const mediaData: SendMediaDto = { ...data };
10311032

1032-
return await this.sendMessageWithTyping(
1033+
if (file) mediaData.media = await getTempFile(file, this.instanceId);
1034+
1035+
const message = await this.prepareMediaMessage(mediaData);
1036+
1037+
const mediaSent = await this.sendMessageWithTyping(
10331038
data.number,
10341039
{ ...message },
10351040
{
@@ -1042,6 +1047,10 @@ export class BusinessStartupService extends ChannelStartupService {
10421047
},
10431048
isIntegration,
10441049
);
1050+
1051+
if (file) await deleteTempFile(file, this.instanceId);
1052+
1053+
return mediaSent;
10451054
}
10461055

10471056
public async processAudio(audio: string, number: string) {
@@ -1072,10 +1081,14 @@ export class BusinessStartupService extends ChannelStartupService {
10721081
return prepareMedia;
10731082
}
10741083

1075-
public async audioWhatsapp(data: SendAudioDto, isIntegration = false) {
1076-
const message = await this.processAudio(data.audio, data.number);
1084+
public async audioWhatsapp(data: SendAudioDto, file?: any, isIntegration = false) {
1085+
const mediaData: SendAudioDto = { ...data };
10771086

1078-
return await this.sendMessageWithTyping(
1087+
if (file) mediaData.audio = await getTempFile(file, this.instanceId);
1088+
1089+
const message = await this.processAudio(mediaData.audio, data.number);
1090+
1091+
const audioSent = await this.sendMessageWithTyping(
10791092
data.number,
10801093
{ ...message },
10811094
{
@@ -1088,6 +1101,10 @@ export class BusinessStartupService extends ChannelStartupService {
10881101
},
10891102
isIntegration,
10901103
);
1104+
1105+
if (file) await deleteTempFile(file, this.instanceId);
1106+
1107+
return audioSent;
10911108
}
10921109

10931110
public async buttonMessage(data: SendButtonDto) {

src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts

+38-9
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ import { BadRequestException, InternalServerErrorException, NotFoundException }
7070
import ffmpegPath from '@ffmpeg-installer/ffmpeg';
7171
import { Boom } from '@hapi/boom';
7272
import { Instance } from '@prisma/client';
73+
import { deleteTempFile, getTempFile } from '@utils/getTempFile';
7374
import { makeProxyAgent } from '@utils/makeProxyAgent';
7475
import { getOnWhatsappCache, saveOnWhatsappCache } from '@utils/onWhatsappCache';
7576
import useMultiFileAuthStatePrisma from '@utils/use-multi-file-auth-state-prisma';
@@ -2266,12 +2267,20 @@ export class BaileysStartupService extends ChannelStartupService {
22662267
throw new BadRequestException('Type not found');
22672268
}
22682269

2269-
public async statusMessage(data: SendStatusDto) {
2270-
const status = await this.formatStatusMessage(data);
2270+
public async statusMessage(data: SendStatusDto, file?: any) {
2271+
const mediaData: SendStatusDto = { ...data };
22712272

2272-
return await this.sendMessageWithTyping('status@broadcast', {
2273+
if (file) mediaData.content = await getTempFile(file, this.instanceId);
2274+
2275+
const status = await this.formatStatusMessage(mediaData);
2276+
2277+
const statusSent = await this.sendMessageWithTyping('status@broadcast', {
22732278
status,
22742279
});
2280+
2281+
if (file) await deleteTempFile(file, this.instanceId);
2282+
2283+
return statusSent;
22752284
}
22762285

22772286
private async prepareMediaMessage(mediaMessage: MediaMessage) {
@@ -2395,7 +2404,11 @@ export class BaileysStartupService extends ChannelStartupService {
23952404
}
23962405
}
23972406

2398-
public async mediaSticker(data: SendStickerDto) {
2407+
public async mediaSticker(data: SendStickerDto, file?: any) {
2408+
const mediaData: SendStickerDto = { ...data };
2409+
2410+
if (file) mediaData.sticker = await getTempFile(file, this.instanceId);
2411+
23992412
const convert = await this.convertToWebP(data.sticker);
24002413
const gifPlayback = data.sticker.includes('.gif');
24012414
const result = await this.sendMessageWithTyping(
@@ -2413,13 +2426,19 @@ export class BaileysStartupService extends ChannelStartupService {
24132426
},
24142427
);
24152428

2429+
if (file) await deleteTempFile(file, this.instanceId);
2430+
24162431
return result;
24172432
}
24182433

2419-
public async mediaMessage(data: SendMediaDto, isIntegration = false) {
2420-
const generate = await this.prepareMediaMessage(data);
2434+
public async mediaMessage(data: SendMediaDto, file?: any, isIntegration = false) {
2435+
const mediaData: SendMediaDto = { ...data };
24212436

2422-
return await this.sendMessageWithTyping(
2437+
if (file) mediaData.media = await getTempFile(file, this.instanceId);
2438+
2439+
const generate = await this.prepareMediaMessage(mediaData);
2440+
2441+
const mediaSent = await this.sendMessageWithTyping(
24232442
data.number,
24242443
{ ...generate.message },
24252444
{
@@ -2431,6 +2450,10 @@ export class BaileysStartupService extends ChannelStartupService {
24312450
},
24322451
isIntegration,
24332452
);
2453+
2454+
if (file) await deleteTempFile(file, this.instanceId);
2455+
2456+
return mediaSent;
24342457
}
24352458

24362459
public async processAudioMp4(audio: string) {
@@ -2534,13 +2557,17 @@ export class BaileysStartupService extends ChannelStartupService {
25342557
});
25352558
}
25362559

2537-
public async audioWhatsapp(data: SendAudioDto, isIntegration = false) {
2560+
public async audioWhatsapp(data: SendAudioDto, file?: any, isIntegration = false) {
2561+
const mediaData: SendAudioDto = { ...data };
2562+
2563+
if (file) mediaData.audio = await getTempFile(file, this.instanceId);
2564+
25382565
if (!data?.encoding && data?.encoding !== false) {
25392566
data.encoding = true;
25402567
}
25412568

25422569
if (data?.encoding) {
2543-
const convert = await this.processAudio(data.audio);
2570+
const convert = await this.processAudio(mediaData.audio);
25442571

25452572
if (Buffer.isBuffer(convert)) {
25462573
const result = this.sendMessageWithTyping<AnyMessageContent>(
@@ -2554,6 +2581,8 @@ export class BaileysStartupService extends ChannelStartupService {
25542581
isIntegration,
25552582
);
25562583

2584+
if (file) await deleteTempFile(file, this.instanceId);
2585+
25572586
return result;
25582587
} else {
25592588
throw new InternalServerErrorException('Failed to convert audio');

src/api/integrations/storage/s3/libs/minio.server.ts

+32-1
Original file line numberDiff line numberDiff line change
@@ -105,4 +105,35 @@ const getObjectUrl = async (fileName: string, expiry?: number) => {
105105
}
106106
};
107107

108-
export { BUCKET, getObjectUrl, uploadFile };
108+
const uploadTempFile = async (
109+
folder: string,
110+
fileName: string,
111+
file: Buffer | Transform | Readable,
112+
size: number,
113+
metadata: Metadata,
114+
) => {
115+
if (minioClient) {
116+
const objectName = join(folder, fileName);
117+
try {
118+
metadata['custom-header-application'] = 'evolution-api';
119+
return await minioClient.putObject(bucketName, objectName, file, size, metadata);
120+
} catch (error) {
121+
logger.error(error);
122+
return error;
123+
}
124+
}
125+
};
126+
127+
const deleteFile = async (folder: string, fileName: string) => {
128+
if (minioClient) {
129+
const objectName = join(folder, fileName);
130+
try {
131+
return await minioClient.removeObject(bucketName, objectName);
132+
} catch (error) {
133+
logger.error(error);
134+
return error;
135+
}
136+
}
137+
};
138+
139+
export { BUCKET, deleteFile, getObjectUrl, uploadFile, uploadTempFile };

0 commit comments

Comments
 (0)