Merge branch 'develop' of github.com:EvolutionAPI/evolution-api into develop

This commit is contained in:
Davidson Gomes 2023-09-17 13:35:58 -03:00
commit 32da15fa8a
5 changed files with 236 additions and 21 deletions

View File

@ -43,7 +43,7 @@
"dependencies": { "dependencies": {
"@adiwajshing/keyed-db": "^0.2.4", "@adiwajshing/keyed-db": "^0.2.4",
"@ffmpeg-installer/ffmpeg": "^1.1.0", "@ffmpeg-installer/ffmpeg": "^1.1.0",
"@figuro/chatwoot-sdk": "^1.1.14", "@figuro/chatwoot-sdk": "^1.1.16",
"@hapi/boom": "^10.0.1", "@hapi/boom": "^10.0.1",
"@sentry/node": "^7.59.2", "@sentry/node": "^7.59.2",
"@whiskeysockets/baileys": "^6.4.0", "@whiskeysockets/baileys": "^6.4.0",

View File

@ -70,6 +70,10 @@ export type Websocket = {
ENABLED: boolean; ENABLED: boolean;
}; };
export type Chatwoot = {
USE_REPLY_ID: boolean;
};
export type EventsWebhook = { export type EventsWebhook = {
APPLICATION_STARTUP: boolean; APPLICATION_STARTUP: boolean;
QRCODE_UPDATED: boolean; QRCODE_UPDATED: boolean;
@ -139,6 +143,7 @@ export interface Env {
QRCODE: QrCode; QRCODE: QrCode;
AUTHENTICATION: Auth; AUTHENTICATION: Auth;
PRODUCTION?: Production; PRODUCTION?: Production;
CHATWOOT?: Chatwoot;
} }
export type Key = keyof Env; export type Key = keyof Env;
@ -297,6 +302,9 @@ export class ConfigService {
SECRET: process.env.AUTHENTICATION_JWT_SECRET || 'L=0YWt]b2w[WF>#>:&E`', SECRET: process.env.AUTHENTICATION_JWT_SECRET || 'L=0YWt]b2w[WF>#>:&E`',
}, },
}, },
CHATWOOT: {
USE_REPLY_ID: process.env?.USE_REPLY_ID === 'true',
},
}; };
} }
} }

View File

@ -154,3 +154,7 @@ AUTHENTICATION:
JWT: JWT:
EXPIRIN_IN: 0 # seconds - 3600s === 1h | zero (0) - never expires EXPIRIN_IN: 0 # seconds - 3600s === 1h | zero (0) - never expires
SECRET: L=0YWt]b2w[WF>#>:&E` SECRET: L=0YWt]b2w[WF>#>:&E`
# Configure to chatwoot
CHATWOOT:
USE_REPLY_ID: false

View File

@ -20,6 +20,8 @@ export class MessageRaw {
messageTimestamp?: number | Long.Long; messageTimestamp?: number | Long.Long;
owner: string; owner: string;
source?: 'android' | 'web' | 'ios'; source?: 'android' | 'web' | 'ios';
source_id?: string;
source_reply_id?: string;
} }
const messageSchema = new Schema<MessageRaw>({ const messageSchema = new Schema<MessageRaw>({

View File

@ -2,10 +2,11 @@ import ChatwootClient from '@figuro/chatwoot-sdk';
import axios from 'axios'; import axios from 'axios';
import FormData from 'form-data'; import FormData from 'form-data';
import { createReadStream, readFileSync, unlinkSync, writeFileSync } from 'fs'; import { createReadStream, readFileSync, unlinkSync, writeFileSync } from 'fs';
import Jimp from 'jimp';
import mimeTypes from 'mime-types'; import mimeTypes from 'mime-types';
import path from 'path'; import path from 'path';
import { ConfigService } from '../../config/env.config'; import { Chatwoot, ConfigService } from '../../config/env.config';
import { Logger } from '../../config/logger.config'; import { Logger } from '../../config/logger.config';
import { ROOT_DIR } from '../../config/path.config'; import { ROOT_DIR } from '../../config/path.config';
import { ChatwootDto } from '../dto/chatwoot.dto'; import { ChatwootDto } from '../dto/chatwoot.dto';
@ -612,6 +613,8 @@ export class ChatwootService {
conversationId: number, conversationId: number,
content: string, content: string,
messageType: 'incoming' | 'outgoing' | undefined, messageType: 'incoming' | 'outgoing' | undefined,
source_id?: string,
source_reply_id?: string,
privateMessage?: boolean, privateMessage?: boolean,
attachments?: { attachments?: {
content: unknown; content: unknown;
@ -637,6 +640,8 @@ export class ChatwootService {
message_type: messageType, message_type: messageType,
attachments: attachments, attachments: attachments,
private: privateMessage || false, private: privateMessage || false,
source_id: source_id,
source_reply_id: source_reply_id,
}, },
}); });
@ -732,6 +737,8 @@ export class ChatwootService {
file: string, file: string,
messageType: 'incoming' | 'outgoing' | undefined, messageType: 'incoming' | 'outgoing' | undefined,
content?: string, content?: string,
source_id?: string,
source_reply_id?: string,
) { ) {
this.logger.verbose('send data to chatwoot'); this.logger.verbose('send data to chatwoot');
@ -748,6 +755,16 @@ export class ChatwootService {
this.logger.verbose('temp file found'); this.logger.verbose('temp file found');
data.append('attachments[]', createReadStream(file)); data.append('attachments[]', createReadStream(file));
if (source_id) {
this.logger.verbose('source_id found');
data.append('source_id', source_id);
}
if (source_reply_id) {
this.logger.verbose('source_reply_id found');
data.append('source_reply_id', source_reply_id);
}
this.logger.verbose('get client to instance: ' + this.provider.instanceName); this.logger.verbose('get client to instance: ' + this.provider.instanceName);
const config = { const config = {
method: 'post', method: 'post',
@ -913,10 +930,10 @@ export class ChatwootService {
}, },
}; };
await waInstance?.audioWhatsapp(data); const audioWhatsapp = await waInstance?.audioWhatsapp(data);
this.logger.verbose('audio sent'); this.logger.verbose('audio sent');
return; return audioWhatsapp;
} }
this.logger.verbose('send media to instance: ' + waInstance.instanceName); this.logger.verbose('send media to instance: ' + waInstance.instanceName);
@ -938,15 +955,48 @@ export class ChatwootService {
data.mediaMessage.caption = caption; data.mediaMessage.caption = caption;
} }
await waInstance?.mediaMessage(data); const mediaMessage = await waInstance?.mediaMessage(data);
this.logger.verbose('media sent'); this.logger.verbose('media sent');
return; return mediaMessage;
} catch (error) { } catch (error) {
this.logger.error(error); this.logger.error(error);
} }
} }
public async updateMessage(
instance: InstanceDto,
accountId: number,
conversationId: number,
messageId: number,
sourceId: string | null,
) {
// const useReplyId = this.configService.get<DelInstance>('DEL_INSTANCE');
const useReplyId = this.configService.get<Chatwoot>('CHATWOOT')?.USE_REPLY_ID;
if (useReplyId === true) {
this.logger.verbose('update message to chatwoot instance: ' + instance.instanceName);
const client = await this.clientCw(instance);
if (!client) {
this.logger.warn('client not found');
return null;
}
this.logger.verbose('check if sourceId to update');
if (sourceId) {
this.logger.verbose('update message to chatwoot');
const dataUpdated = {
source_id: sourceId,
};
await client.messages.update({
accountId,
conversationId,
data: dataUpdated,
messageId,
});
}
}
}
public async receiveWebhook(instance: InstanceDto, body: any) { public async receiveWebhook(instance: InstanceDto, body: any) {
try { try {
// espera 500ms para evitar duplicidade de mensagens // espera 500ms para evitar duplicidade de mensagens
@ -1047,6 +1097,9 @@ export class ChatwootService {
} }
for (const message of body.conversation.messages) { for (const message of body.conversation.messages) {
const messageId = message?.id;
const conversationId = message?.conversation_id;
const accountId = message?.account_id;
this.logger.verbose('check if message is media'); this.logger.verbose('check if message is media');
if (message.attachments && message.attachments.length > 0) { if (message.attachments && message.attachments.length > 0) {
this.logger.verbose('message is media'); this.logger.verbose('message is media');
@ -1057,7 +1110,8 @@ export class ChatwootService {
formatText = null; formatText = null;
} }
await this.sendAttachment(waInstance, chatId, attachment.data_url, formatText); const mediaMessage = await this.sendAttachment(waInstance, chatId, attachment.data_url, formatText);
await this.updateMessage(instance, accountId, conversationId, messageId, mediaMessage?.key?.id);
} }
} else { } else {
this.logger.verbose('message is text'); this.logger.verbose('message is text');
@ -1074,7 +1128,8 @@ export class ChatwootService {
}, },
}; };
await waInstance?.textMessage(data); const message = await waInstance?.textMessage(data);
await this.updateMessage(instance, accountId, conversationId, messageId, message?.key?.id);
} }
} }
} }
@ -1125,6 +1180,20 @@ export class ChatwootService {
return result; return result;
} }
private getAdsMessage(msg: any) {
interface AdsMessage {
title: string;
body: string;
thumbnailUrl: string;
sourceUrl: string;
}
let adsMessage: AdsMessage | undefined = msg.extendedTextMessage?.contextInfo.externalAdReply;
this.logger.verbose('Get ads message if it exist');
adsMessage && this.logger.verbose('Ads message: ' + adsMessage);
return adsMessage;
}
private getTypeMessage(msg: any) { private getTypeMessage(msg: any) {
this.logger.verbose('get type message'); this.logger.verbose('get type message');
@ -1149,6 +1218,38 @@ export class ChatwootService {
return types; return types;
} }
private getContextIdTypeMessage(msg: any) {
this.logger.verbose('get type message');
const types = {
conversation: msg.conversation?.contextInfo?.stanzaId,
imageMessage: msg.imageMessage?.contextInfo?.stanzaId,
videoMessage: msg.videoMessage?.contextInfo?.stanzaId,
extendedTextMessage: msg.extendedTextMessage?.contextInfo?.stanzaId,
messageContextInfo: msg.messageContextInfo?.stanzaId,
stickerMessage: undefined,
documentMessage: msg.documentMessage?.contextInfo?.stanzaId,
documentWithCaptionMessage: msg.documentWithCaptionMessage?.message?.documentMessage?.contextInfo?.stanzaId,
audioMessage: msg.audioMessage?.contextInfo?.stanzaId,
contactMessage: msg.contactMessage?.contextInfo?.stanzaId,
contactsArrayMessage: msg.contactsArrayMessage?.contextInfo?.stanzaId,
locationMessage: msg.locationMessage?.contextInfo?.stanzaId,
liveLocationMessage: msg.liveLocationMessage?.contextInfo?.stanzaId,
};
this.logger.verbose('type message: ' + types);
return types;
}
private getContextMessageContent(types: any) {
this.logger.verbose('get message context content');
const typeKey = Object.keys(types).find((key) => types[key] !== undefined && types[key] !== '');
const result = typeKey ? types[typeKey] : undefined;
return result;
}
private getMessageContent(types: any) { private getMessageContent(types: any) {
this.logger.verbose('get message content'); this.logger.verbose('get message content');
const typeKey = Object.keys(types).find((key) => types[key] !== undefined); const typeKey = Object.keys(types).find((key) => types[key] !== undefined);
@ -1248,6 +1349,18 @@ export class ChatwootService {
return messageContent; return messageContent;
} }
private getContextConversationMessage(msg: any) {
this.logger.verbose('get context conversation message');
const types = this.getContextIdTypeMessage(msg);
const messageContext = this.getContextMessageContent(types);
this.logger.verbose('context conversation message: ' + messageContext);
return messageContext;
}
public async eventWhatsapp(event: string, instance: InstanceDto, body: any) { public async eventWhatsapp(event: string, instance: InstanceDto, body: any) {
this.logger.verbose('event whatsapp to instance: ' + instance.instanceName); this.logger.verbose('event whatsapp to instance: ' + instance.instanceName);
try { try {
@ -1276,23 +1389,29 @@ export class ChatwootService {
this.logger.verbose('get conversation message'); this.logger.verbose('get conversation message');
const bodyMessage = await this.getConversationMessage(body.message); const bodyMessage = await this.getConversationMessage(body.message);
const source_reply_id = this.getContextConversationMessage(body.message);
const isMedia = this.isMediaMessage(body.message); const isMedia = this.isMediaMessage(body.message);
const adsMessage = this.getAdsMessage(body.message);
if (!bodyMessage && !isMedia) { if (!bodyMessage && !isMedia) {
this.logger.warn('no body message found'); this.logger.warn('no body message found');
return; return;
} }
this.logger.verbose('get conversation in chatwoot'); this.logger.verbose('get conversation in chatwoot');
const getConversion = await this.createConversation(instance, body); const getConversation = await this.createConversation(instance, body);
if (!getConversion) { if (!getConversation) {
this.logger.warn('conversation not found'); this.logger.warn('conversation not found');
return; return;
} }
const messageType = body.key.fromMe ? 'outgoing' : 'incoming'; const messageType = body.key.fromMe ? 'outgoing' : 'incoming';
const source_id = body.key?.id;
this.logger.verbose('message type: ' + messageType); this.logger.verbose('message type: ' + messageType);
this.logger.verbose('is media: ' + isMedia); this.logger.verbose('is media: ' + isMedia);
@ -1337,7 +1456,7 @@ export class ChatwootService {
} }
this.logger.verbose('send data to chatwoot'); this.logger.verbose('send data to chatwoot');
const send = await this.sendData(getConversion, fileName, messageType, content); const send = await this.sendData(getConversation, fileName, messageType, content, source_id, source_reply_id);
if (!send) { if (!send) {
this.logger.warn('message not sent'); this.logger.warn('message not sent');
@ -1358,7 +1477,14 @@ export class ChatwootService {
this.logger.verbose('message is not group'); this.logger.verbose('message is not group');
this.logger.verbose('send data to chatwoot'); this.logger.verbose('send data to chatwoot');
const send = await this.sendData(getConversion, fileName, messageType, bodyMessage); const send = await this.sendData(
getConversation,
fileName,
messageType,
bodyMessage,
source_id,
source_reply_id,
);
if (!send) { if (!send) {
this.logger.warn('message not sent'); this.logger.warn('message not sent');
@ -1378,6 +1504,67 @@ export class ChatwootService {
} }
} }
this.logger.verbose('check if has Ads Message');
if (adsMessage) {
this.logger.verbose('message is from Ads');
this.logger.verbose('get base64 from media ads message');
const imgBuffer = await axios.get(adsMessage.thumbnailUrl, { responseType: 'arraybuffer' });
const extension = mimeTypes.extension(imgBuffer.headers['content-type']);
const mimeType = extension && mimeTypes.lookup(extension);
if (!mimeType) {
this.logger.warn('mimetype of Ads message not found');
return;
}
const random = Math.random().toString(36).substring(7);
const nameFile = `${random}.${mimeTypes.extension(mimeType)}`;
const fileData = Buffer.from(imgBuffer.data, 'binary');
const fileName = `${path.join(waInstance?.storePath, 'temp', `${nameFile}`)}`;
this.logger.verbose('temp file name: ' + nameFile);
this.logger.verbose('create temp file');
await Jimp.read(fileData)
.then(async (img) => {
await img.cover(320, 180).writeAsync(fileName);
})
.catch((err) => {
this.logger.error(`image is not write: ${err}`);
});
const truncStr = (str: string, len: number) => {
return str.length > len ? str.substring(0, len) + '...' : str;
};
const title = truncStr(adsMessage.title, 40);
const description = truncStr(adsMessage.body, 75);
this.logger.verbose('send data to chatwoot');
const send = await this.sendData(
getConversation,
fileName,
messageType,
`${bodyMessage}\n\n\n**${title}**\n${description}\n${adsMessage.sourceUrl}`,
);
if (!send) {
this.logger.warn('message not sent');
return;
}
this.messageCacheFile = path.join(ROOT_DIR, 'store', 'chatwoot', `${instance.instanceName}_cache.txt`);
this.messageCache = this.loadMessageCache();
this.messageCache.add(send.id.toString());
this.logger.verbose('save message cache');
this.saveMessageCache();
return send;
}
this.logger.verbose('check if is group'); this.logger.verbose('check if is group');
if (body.key.remoteJid.includes('@g.us')) { if (body.key.remoteJid.includes('@g.us')) {
this.logger.verbose('message is group'); this.logger.verbose('message is group');
@ -1394,7 +1581,14 @@ export class ChatwootService {
} }
this.logger.verbose('send data to chatwoot'); this.logger.verbose('send data to chatwoot');
const send = await this.createMessage(instance, getConversion, content, messageType); const send = await this.createMessage(
instance,
getConversation,
content,
messageType,
source_id,
source_reply_id,
);
if (!send) { if (!send) {
this.logger.warn('message not sent'); this.logger.warn('message not sent');
@ -1415,7 +1609,14 @@ export class ChatwootService {
this.logger.verbose('message is not group'); this.logger.verbose('message is not group');
this.logger.verbose('send data to chatwoot'); this.logger.verbose('send data to chatwoot');
const send = await this.createMessage(instance, getConversion, bodyMessage, messageType); const send = await this.createMessage(
instance,
getConversation,
bodyMessage,
messageType,
source_id,
source_reply_id,
);
if (!send) { if (!send) {
this.logger.warn('message not sent'); this.logger.warn('message not sent');