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:
Willian Coqueiro
2025-10-19 03:05:11 +00:00
parent cdf06666a1
commit 946dcaeb2e
6 changed files with 366 additions and 560 deletions

View File

@@ -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';
}