mirror of
https://github.com/EvolutionAPI/evolution-api.git
synced 2026-01-09 21:32:20 -06:00
feat(baileys,chatwoot,on-whatsapp-cache): implementações e correções na baileys e chatwoot
* corrige cache de números PN, LIDs e g.us para enviar o número correto * atualiza para os últimos commits da baileys * corrige envio de áudio e documentos via chatwoot no canal baileys * diversas correções na integração com chatwoot * corrige mensagens ignoradas no recebimento de leads
This commit is contained in:
@@ -133,7 +133,6 @@ import { Label } from 'baileys/lib/Types/Label';
|
||||
import { LabelAssociation } from 'baileys/lib/Types/LabelAssociation';
|
||||
import { spawn } from 'child_process';
|
||||
import { isArray, isBase64, isURL } from 'class-validator';
|
||||
import { randomBytes } from 'crypto';
|
||||
import EventEmitter2 from 'eventemitter2';
|
||||
import ffmpeg from 'fluent-ffmpeg';
|
||||
import FormData from 'form-data';
|
||||
@@ -876,6 +875,7 @@ export class BaileysStartupService extends ChannelStartupService {
|
||||
'contacts.update': async (contacts: Partial<Contact>[]) => {
|
||||
const contactsRaw: { remoteJid: string; pushName?: string; profilePicUrl?: string; instanceId: string }[] = [];
|
||||
for await (const contact of contacts) {
|
||||
this.logger.debug(`Updating contact: ${JSON.stringify(contact, null, 2)}`);
|
||||
contactsRaw.push({
|
||||
remoteJid: contact.id,
|
||||
pushName: contact?.name ?? contact?.verifiedName,
|
||||
@@ -895,10 +895,7 @@ export class BaileysStartupService extends ChannelStartupService {
|
||||
);
|
||||
await this.prismaRepository.$transaction(updateTransactions);
|
||||
|
||||
const usersContacts = contactsRaw.filter((c) => c.remoteJid.includes('@s.whatsapp'));
|
||||
if (usersContacts) {
|
||||
await saveOnWhatsappCache(usersContacts.map((c) => ({ remoteJid: c.remoteJid })));
|
||||
}
|
||||
//const usersContacts = contactsRaw.filter((c) => c.remoteJid.includes('@s.whatsapp'));
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1136,7 +1133,7 @@ export class BaileysStartupService extends ChannelStartupService {
|
||||
const messageKey = `${this.instance.id}_${received.key.id}`;
|
||||
const cached = await this.baileysCache.get(messageKey);
|
||||
|
||||
if (cached && !editedMessage) {
|
||||
if (cached && !editedMessage && !requestId) {
|
||||
this.logger.info(`Message duplicated ignored: ${received.key.id}`);
|
||||
continue;
|
||||
}
|
||||
@@ -1349,7 +1346,7 @@ export class BaileysStartupService extends ChannelStartupService {
|
||||
}
|
||||
}
|
||||
|
||||
this.logger.log(messageRaw);
|
||||
this.logger.verbose(messageRaw);
|
||||
|
||||
sendTelemetry(`received.message.${messageRaw.messageType ?? 'unknown'}`);
|
||||
|
||||
@@ -1366,7 +1363,12 @@ export class BaileysStartupService extends ChannelStartupService {
|
||||
where: { remoteJid: received.key.remoteJid, instanceId: this.instanceId },
|
||||
});
|
||||
|
||||
const contactRaw: { remoteJid: string; pushName: string; profilePicUrl?: string; instanceId: string } = {
|
||||
const contactRaw: {
|
||||
remoteJid: string;
|
||||
pushName: string;
|
||||
profilePicUrl?: string;
|
||||
instanceId: string;
|
||||
} = {
|
||||
remoteJid: received.key.remoteJid,
|
||||
pushName: received.key.fromMe ? '' : received.key.fromMe == null ? '' : received.pushName,
|
||||
profilePicUrl: (await this.profilePicture(received.key.remoteJid)).profilePictureUrl,
|
||||
@@ -1377,6 +1379,17 @@ export class BaileysStartupService extends ChannelStartupService {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (contactRaw.remoteJid.includes('@s.whatsapp') || contactRaw.remoteJid.includes('@lid')) {
|
||||
await saveOnWhatsappCache([
|
||||
{
|
||||
remoteJid:
|
||||
messageRaw.key.addressingMode === 'lid' ? messageRaw.key.remoteJidAlt : messageRaw.key.remoteJid,
|
||||
remoteJidAlt: messageRaw.key.remoteJidAlt,
|
||||
lid: messageRaw.key.addressingMode === 'lid' ? 'lid' : null,
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
if (contact) {
|
||||
this.sendDataWebhook(Events.CONTACTS_UPDATE, contactRaw);
|
||||
|
||||
@@ -1406,10 +1419,6 @@ export class BaileysStartupService extends ChannelStartupService {
|
||||
update: contactRaw,
|
||||
create: contactRaw,
|
||||
});
|
||||
|
||||
if (contactRaw.remoteJid.includes('@s.whatsapp')) {
|
||||
await saveOnWhatsappCache([{ remoteJid: contactRaw.remoteJid }]);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
this.logger.error(error);
|
||||
@@ -1417,7 +1426,7 @@ export class BaileysStartupService extends ChannelStartupService {
|
||||
},
|
||||
|
||||
'messages.update': async (args: { update: Partial<WAMessage>; key: WAMessageKey }[], settings: any) => {
|
||||
this.logger.log(`Update messages ${JSON.stringify(args, undefined, 2)}`);
|
||||
this.logger.verbose(`Update messages ${JSON.stringify(args, undefined, 2)}`);
|
||||
|
||||
const readChatToUpdate: Record<string, true> = {}; // {remoteJid: true}
|
||||
|
||||
@@ -1798,7 +1807,7 @@ export class BaileysStartupService extends ChannelStartupService {
|
||||
}
|
||||
|
||||
if (events['group-participants.update']) {
|
||||
const payload = events['group-participants.update'];
|
||||
const payload = events['group-participants.update'] as any;
|
||||
this.groupHandler['group-participants.update'](payload);
|
||||
}
|
||||
}
|
||||
@@ -1966,6 +1975,7 @@ export class BaileysStartupService extends ChannelStartupService {
|
||||
quoted: any,
|
||||
messageId?: string,
|
||||
ephemeralExpiration?: number,
|
||||
contextInfo?: any,
|
||||
// participants?: GroupParticipant[],
|
||||
) {
|
||||
sender = sender.toLowerCase();
|
||||
@@ -1982,8 +1992,8 @@ export class BaileysStartupService extends ChannelStartupService {
|
||||
|
||||
if (ephemeralExpiration) option.ephemeralExpiration = ephemeralExpiration;
|
||||
|
||||
// NOTE: NÃO DEVEMOS GERAR O messageId AQUI, SOMENTE SE VIER INFORMADO POR PARAMETRO. A GERAÇÃO ANTERIOR IMPEDE O WZAP DE IDENTIFICAR A SOURCE.
|
||||
if (messageId) option.messageId = messageId;
|
||||
else option.messageId = '3EB0' + randomBytes(18).toString('hex').toUpperCase();
|
||||
|
||||
if (message['viewOnceMessage']) {
|
||||
const m = generateWAMessageFromContent(sender, message, {
|
||||
@@ -2020,10 +2030,19 @@ export class BaileysStartupService extends ChannelStartupService {
|
||||
}
|
||||
}
|
||||
|
||||
if (contextInfo) {
|
||||
message['contextInfo'] = contextInfo;
|
||||
}
|
||||
|
||||
if (message['conversation']) {
|
||||
return await this.client.sendMessage(
|
||||
sender,
|
||||
{ text: message['conversation'], mentions, linkPreview: linkPreview } as unknown as AnyMessageContent,
|
||||
{
|
||||
text: message['conversation'],
|
||||
mentions,
|
||||
linkPreview: linkPreview,
|
||||
contextInfo: message['contextInfo'],
|
||||
} as unknown as AnyMessageContent,
|
||||
option as unknown as MiscMessageGenerationOptions,
|
||||
);
|
||||
}
|
||||
@@ -2031,7 +2050,11 @@ export class BaileysStartupService extends ChannelStartupService {
|
||||
if (!message['audio'] && !message['poll'] && !message['sticker'] && sender != 'status@broadcast') {
|
||||
return await this.client.sendMessage(
|
||||
sender,
|
||||
{ forward: { key: { remoteJid: this.instance.wuid, fromMe: true }, message }, mentions },
|
||||
{
|
||||
forward: { key: { remoteJid: this.instance.wuid, fromMe: true }, message },
|
||||
mentions,
|
||||
contextInfo: message['contextInfo'],
|
||||
},
|
||||
option as unknown as MiscMessageGenerationOptions,
|
||||
);
|
||||
}
|
||||
@@ -2162,7 +2185,7 @@ export class BaileysStartupService extends ChannelStartupService {
|
||||
if (options?.quoted) {
|
||||
const m = options?.quoted;
|
||||
|
||||
const msg = m?.message ? m : ((await this.getMessage(m.key, true)) as proto.IWebMessageInfo);
|
||||
const msg = m?.message ? m : ((await this.getMessage(m.key, true)) as WAMessage);
|
||||
|
||||
if (msg) {
|
||||
quoted = msg;
|
||||
@@ -2172,6 +2195,8 @@ export class BaileysStartupService extends ChannelStartupService {
|
||||
let messageSent: WAMessage;
|
||||
|
||||
let mentions: string[];
|
||||
let contextInfo: any;
|
||||
|
||||
if (isJidGroup(sender)) {
|
||||
let group;
|
||||
try {
|
||||
@@ -2210,7 +2235,27 @@ export class BaileysStartupService extends ChannelStartupService {
|
||||
// group?.participants,
|
||||
);
|
||||
} else {
|
||||
messageSent = await this.sendMessage(sender, message, mentions, linkPreview, quoted);
|
||||
contextInfo = {
|
||||
mentionedJid: [],
|
||||
groupMentions: [],
|
||||
//expiration: 7776000,
|
||||
ephemeralSettingTimestamp: {
|
||||
low: Math.floor(Date.now() / 1000) - 172800,
|
||||
high: 0,
|
||||
unsigned: false,
|
||||
},
|
||||
disappearingMode: { initiator: 0 },
|
||||
};
|
||||
messageSent = await this.sendMessage(
|
||||
sender,
|
||||
message,
|
||||
mentions,
|
||||
linkPreview,
|
||||
quoted,
|
||||
null,
|
||||
undefined,
|
||||
contextInfo,
|
||||
);
|
||||
}
|
||||
|
||||
if (Long.isLong(messageSent?.messageTimestamp)) {
|
||||
@@ -2330,7 +2375,7 @@ export class BaileysStartupService extends ChannelStartupService {
|
||||
}
|
||||
}
|
||||
|
||||
this.logger.log(messageRaw);
|
||||
this.logger.verbose(JSON.stringify(messageSent, null, 2));
|
||||
|
||||
this.sendDataWebhook(Events.SEND_MESSAGE, messageRaw);
|
||||
|
||||
@@ -3337,120 +3382,128 @@ export class BaileysStartupService extends ChannelStartupService {
|
||||
where: { instanceId: this.instanceId, remoteJid: { in: jids.users.map(({ jid }) => jid) } },
|
||||
});
|
||||
|
||||
// Separate @lid numbers from normal numbers
|
||||
const lidUsers = jids.users.filter(({ jid }) => jid.includes('@lid'));
|
||||
const normalUsers = jids.users.filter(({ jid }) => !jid.includes('@lid'));
|
||||
// TODO: Unificar verificação de cache para todos os números (normais e LID)
|
||||
const numbersToVerify = jids.users.map(({ jid }) => jid.replace('+', ''));
|
||||
|
||||
// For normal numbers, use traditional Baileys verification
|
||||
let normalVerifiedUsers: OnWhatsAppDto[] = [];
|
||||
if (normalUsers.length > 0) {
|
||||
console.log('normalUsers', normalUsers);
|
||||
const numbersToVerify = normalUsers.map(({ jid }) => jid.replace('+', ''));
|
||||
console.log('numbersToVerify', numbersToVerify);
|
||||
// TODO: Busca TODOS os números no cache
|
||||
const cachedNumbers = await getOnWhatsappCache(numbersToVerify);
|
||||
|
||||
const cachedNumbers = await getOnWhatsappCache(numbersToVerify);
|
||||
console.log('cachedNumbers', cachedNumbers);
|
||||
// Separa números que estão e não estão no cache
|
||||
const cachedJids = new Set(cachedNumbers.flatMap((cached) => cached.jidOptions));
|
||||
const numbersNotInCache = numbersToVerify.filter((jid) => !cachedJids.has(jid));
|
||||
|
||||
const filteredNumbers = numbersToVerify.filter(
|
||||
(jid) => !cachedNumbers.some((cached) => cached.jidOptions.includes(jid)),
|
||||
);
|
||||
console.log('filteredNumbers', filteredNumbers);
|
||||
// TODO: Só chama Baileys para números normais (@s.whatsapp.net) que não estão no cache
|
||||
let verify: { jid: string; exists: boolean }[] = [];
|
||||
const normalNumbersNotInCache = numbersNotInCache.filter((jid) => !jid.includes('@lid'));
|
||||
|
||||
const verify = await this.client.onWhatsApp(...filteredNumbers);
|
||||
console.log('verify', verify);
|
||||
normalVerifiedUsers = await Promise.all(
|
||||
normalUsers.map(async (user) => {
|
||||
let numberVerified: (typeof verify)[0] | null = null;
|
||||
|
||||
const cached = cachedNumbers.find((cached) => cached.jidOptions.includes(user.jid.replace('+', '')));
|
||||
if (cached) {
|
||||
return new OnWhatsAppDto(
|
||||
cached.remoteJid,
|
||||
true,
|
||||
user.number,
|
||||
contacts.find((c) => c.remoteJid === cached.remoteJid)?.pushName,
|
||||
cached.lid || (cached.remoteJid.includes('@lid') ? cached.remoteJid.split('@')[1] : undefined),
|
||||
);
|
||||
}
|
||||
|
||||
// Brazilian numbers
|
||||
if (user.number.startsWith('55')) {
|
||||
const numberWithDigit =
|
||||
user.number.slice(4, 5) === '9' && user.number.length === 13
|
||||
? user.number
|
||||
: `${user.number.slice(0, 4)}9${user.number.slice(4)}`;
|
||||
const numberWithoutDigit =
|
||||
user.number.length === 12 ? user.number : user.number.slice(0, 4) + user.number.slice(5);
|
||||
|
||||
numberVerified = verify.find(
|
||||
(v) => v.jid === `${numberWithDigit}@s.whatsapp.net` || v.jid === `${numberWithoutDigit}@s.whatsapp.net`,
|
||||
);
|
||||
}
|
||||
|
||||
// Mexican/Argentina numbers
|
||||
// Ref: https://faq.whatsapp.com/1294841057948784
|
||||
if (!numberVerified && (user.number.startsWith('52') || user.number.startsWith('54'))) {
|
||||
let prefix = '';
|
||||
if (user.number.startsWith('52')) {
|
||||
prefix = '1';
|
||||
}
|
||||
if (user.number.startsWith('54')) {
|
||||
prefix = '9';
|
||||
}
|
||||
|
||||
const numberWithDigit =
|
||||
user.number.slice(2, 3) === prefix && user.number.length === 13
|
||||
? user.number
|
||||
: `${user.number.slice(0, 2)}${prefix}${user.number.slice(2)}`;
|
||||
const numberWithoutDigit =
|
||||
user.number.length === 12 ? user.number : user.number.slice(0, 2) + user.number.slice(3);
|
||||
|
||||
numberVerified = verify.find(
|
||||
(v) => v.jid === `${numberWithDigit}@s.whatsapp.net` || v.jid === `${numberWithoutDigit}@s.whatsapp.net`,
|
||||
);
|
||||
}
|
||||
|
||||
if (!numberVerified) {
|
||||
numberVerified = verify.find((v) => v.jid === user.jid);
|
||||
}
|
||||
|
||||
const numberJid = numberVerified?.jid || user.jid;
|
||||
|
||||
return new OnWhatsAppDto(
|
||||
numberJid,
|
||||
!!numberVerified?.exists,
|
||||
user.number,
|
||||
contacts.find((c) => c.remoteJid === numberJid)?.pushName,
|
||||
undefined,
|
||||
);
|
||||
}),
|
||||
);
|
||||
if (normalNumbersNotInCache.length > 0) {
|
||||
this.logger.verbose(`Checking ${normalNumbersNotInCache.length} numbers via Baileys (not found in cache)`);
|
||||
verify = await this.client.onWhatsApp(...normalNumbersNotInCache);
|
||||
}
|
||||
|
||||
// For @lid numbers, always consider them as valid
|
||||
const lidVerifiedUsers: OnWhatsAppDto[] = lidUsers.map((user) => {
|
||||
return new OnWhatsAppDto(
|
||||
user.jid,
|
||||
true,
|
||||
user.number,
|
||||
contacts.find((c) => c.remoteJid === user.jid)?.pushName,
|
||||
user.jid.split('@')[1],
|
||||
);
|
||||
});
|
||||
const verifiedUsers = await Promise.all(
|
||||
jids.users.map(async (user) => {
|
||||
// TODO: Tenta pegar do cache primeiro (funciona para todos: normais e LID)
|
||||
const cached = cachedNumbers.find((cached) => cached.jidOptions.includes(user.jid.replace('+', '')));
|
||||
|
||||
if (cached) {
|
||||
this.logger.verbose(`Number ${user.number} found in cache`);
|
||||
return new OnWhatsAppDto(
|
||||
cached.remoteJid,
|
||||
true,
|
||||
user.number,
|
||||
contacts.find((c) => c.remoteJid === cached.remoteJid)?.pushName,
|
||||
cached.lid || (cached.remoteJid.includes('@lid') ? 'lid' : undefined),
|
||||
);
|
||||
}
|
||||
|
||||
// TODO: Se é número LID e não está no cache, considerar como válido
|
||||
if (user.jid.includes('@lid')) {
|
||||
return new OnWhatsAppDto(
|
||||
user.jid,
|
||||
true,
|
||||
user.number,
|
||||
contacts.find((c) => c.remoteJid === user.jid)?.pushName,
|
||||
'lid',
|
||||
);
|
||||
}
|
||||
|
||||
// TODO: Se não está no cache e é número normal, usa a verificação do Baileys
|
||||
let numberVerified: (typeof verify)[0] | null = null;
|
||||
|
||||
// Brazilian numbers
|
||||
if (user.number.startsWith('55')) {
|
||||
const numberWithDigit =
|
||||
user.number.slice(4, 5) === '9' && user.number.length === 13
|
||||
? user.number
|
||||
: `${user.number.slice(0, 4)}9${user.number.slice(4)}`;
|
||||
const numberWithoutDigit =
|
||||
user.number.length === 12 ? user.number : user.number.slice(0, 4) + user.number.slice(5);
|
||||
|
||||
numberVerified = verify.find(
|
||||
(v) => v.jid === `${numberWithDigit}@s.whatsapp.net` || v.jid === `${numberWithoutDigit}@s.whatsapp.net`,
|
||||
);
|
||||
}
|
||||
|
||||
// Mexican/Argentina numbers
|
||||
// Ref: https://faq.whatsapp.com/1294841057948784
|
||||
if (!numberVerified && (user.number.startsWith('52') || user.number.startsWith('54'))) {
|
||||
let prefix = '';
|
||||
if (user.number.startsWith('52')) {
|
||||
prefix = '1';
|
||||
}
|
||||
if (user.number.startsWith('54')) {
|
||||
prefix = '9';
|
||||
}
|
||||
|
||||
const numberWithDigit =
|
||||
user.number.slice(2, 3) === prefix && user.number.length === 13
|
||||
? user.number
|
||||
: `${user.number.slice(0, 2)}${prefix}${user.number.slice(2)}`;
|
||||
const numberWithoutDigit =
|
||||
user.number.length === 12 ? user.number : user.number.slice(0, 2) + user.number.slice(3);
|
||||
|
||||
numberVerified = verify.find(
|
||||
(v) => v.jid === `${numberWithDigit}@s.whatsapp.net` || v.jid === `${numberWithoutDigit}@s.whatsapp.net`,
|
||||
);
|
||||
}
|
||||
|
||||
if (!numberVerified) {
|
||||
numberVerified = verify.find((v) => v.jid === user.jid);
|
||||
}
|
||||
|
||||
const numberJid = numberVerified?.jid || user.jid;
|
||||
|
||||
return new OnWhatsAppDto(
|
||||
numberJid,
|
||||
!!numberVerified?.exists,
|
||||
user.number,
|
||||
contacts.find((c) => c.remoteJid === numberJid)?.pushName,
|
||||
undefined,
|
||||
);
|
||||
}),
|
||||
);
|
||||
|
||||
// Combine results
|
||||
onWhatsapp.push(...normalVerifiedUsers, ...lidVerifiedUsers);
|
||||
onWhatsapp.push(...verifiedUsers);
|
||||
|
||||
// Save to cache only valid numbers
|
||||
await saveOnWhatsappCache(
|
||||
onWhatsapp
|
||||
.filter((user) => user.exists)
|
||||
.map((user) => ({
|
||||
// TODO: Salvar no cache apenas números que NÃO estavam no cache
|
||||
const numbersToCache = onWhatsapp.filter((user) => {
|
||||
if (!user.exists) return false;
|
||||
// Verifica se estava no cache usando jidOptions
|
||||
const cached = cachedNumbers?.find((cached) => cached.jidOptions.includes(user.jid.replace('+', '')));
|
||||
return !cached;
|
||||
});
|
||||
|
||||
if (numbersToCache.length > 0) {
|
||||
this.logger.verbose(`Salvando ${numbersToCache.length} números no cache`);
|
||||
await saveOnWhatsappCache(
|
||||
numbersToCache.map((user) => ({
|
||||
remoteJid: user.jid,
|
||||
jidOptions: user.jid.replace('+', ''),
|
||||
lid: user.lid,
|
||||
lid: user.lid === 'lid' ? 'lid' : undefined,
|
||||
})),
|
||||
);
|
||||
);
|
||||
}
|
||||
|
||||
return onWhatsapp;
|
||||
}
|
||||
@@ -3633,10 +3686,7 @@ export class BaileysStartupService extends ChannelStartupService {
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
Object.keys(msg.message).length === 1 &&
|
||||
Object.prototype.hasOwnProperty.call(msg.message, 'messageContextInfo')
|
||||
) {
|
||||
if ('messageContextInfo' in msg.message && Object.keys(msg.message).length === 1) {
|
||||
throw 'The message is messageContextInfo';
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user