fix: Trata eventos de edição/deleção, caption, undefined e implementa deduplicação e refactoring do LID

This commit is contained in:
Rafael Cunha
2025-10-29 12:45:05 -04:00
parent d5f5b8325e
commit 435696943d
3 changed files with 2328 additions and 1095 deletions

View File

@@ -53,7 +53,7 @@ export class ChatwootService {
private readonly configService: ConfigService, private readonly configService: ConfigService,
private readonly prismaRepository: PrismaRepository, private readonly prismaRepository: PrismaRepository,
private readonly cache: CacheService, private readonly cache: CacheService,
) {} ) { }
private pgClient = postgresClient.getChatwootConnection(); private pgClient = postgresClient.getChatwootConnection();
@@ -720,8 +720,8 @@ export class ChatwootService {
contact.name === chatId || contact.name === chatId ||
(`+${chatId}`.startsWith('+55') (`+${chatId}`.startsWith('+55')
? this.getNumbers(`+${chatId}`).some( ? this.getNumbers(`+${chatId}`).some(
(v) => contact.name === v || contact.name === v.substring(3) || contact.name === v.substring(1), (v) => contact.name === v || contact.name === v.substring(3) || contact.name === v.substring(1),
) )
: false); : false);
this.logger.verbose(`Picture needs update: ${pictureNeedsUpdate}`); this.logger.verbose(`Picture needs update: ${pictureNeedsUpdate}`);
this.logger.verbose(`Name needs update: ${nameNeedsUpdate}`); this.logger.verbose(`Name needs update: ${nameNeedsUpdate}`);
@@ -1288,10 +1288,10 @@ export class ChatwootService {
// Chatwoot to Whatsapp // Chatwoot to Whatsapp
const messageReceived = body.content const messageReceived = body.content
? body.content ? body.content
.replaceAll(/(?<!\*)\*((?!\s)([^\n*]+?)(?<!\s))\*(?!\*)/g, '_$1_') // Substitui * por _ .replaceAll(/(?<!\*)\*((?!\s)([^\n*]+?)(?<!\s))\*(?!\*)/g, '_$1_') // Substitui * por _
.replaceAll(/\*{2}((?!\s)([^\n*]+?)(?<!\s))\*{2}/g, '*$1*') // Substitui ** por * .replaceAll(/\*{2}((?!\s)([^\n*]+?)(?<!\s))\*{2}/g, '*$1*') // Substitui ** por *
.replaceAll(/~{2}((?!\s)([^\n*]+?)(?<!\s))~{2}/g, '~$1~') // Substitui ~~ por ~ .replaceAll(/~{2}((?!\s)([^\n*]+?)(?<!\s))~{2}/g, '~$1~') // Substitui ~~ por ~
.replaceAll(/(?<!`)`((?!\s)([^`*]+?)(?<!\s))`(?!`)/g, '```$1```') // Substitui ` por ``` .replaceAll(/(?<!`)`((?!\s)([^`*]+?)(?<!\s))`(?!`)/g, '```$1```') // Substitui ` por ```
: body.content; : body.content;
const senderName = body?.conversation?.messages[0]?.sender?.available_name || body?.sender?.name; const senderName = body?.conversation?.messages[0]?.sender?.available_name || body?.sender?.name;
@@ -1953,9 +1953,9 @@ export class ChatwootService {
const originalMessage = await this.getConversationMessage(body.message); const originalMessage = await this.getConversationMessage(body.message);
const bodyMessage = originalMessage const bodyMessage = originalMessage
? originalMessage ? originalMessage
.replaceAll(/\*((?!\s)([^\n*]+?)(?<!\s))\*/g, '**$1**') .replaceAll(/\*((?!\s)([^\n*]+?)(?<!\s))\*/g, '**$1**')
.replaceAll(/_((?!\s)([^\n_]+?)(?<!\s))_/g, '*$1*') .replaceAll(/_((?!\s)([^\n_]+?)(?<!\s))_/g, '*$1*')
.replaceAll(/~((?!\s)([^\n~]+?)(?<!\s))~/g, '~~$1~~') .replaceAll(/~((?!\s)([^\n~]+?)(?<!\s))~/g, '~~$1~~')
: originalMessage; : originalMessage;
if (bodyMessage && bodyMessage.includes('/survey/responses/') && bodyMessage.includes('http')) { if (bodyMessage && bodyMessage.includes('/survey/responses/') && bodyMessage.includes('http')) {
@@ -2024,7 +2024,7 @@ export class ChatwootService {
const fileData = Buffer.from(downloadBase64.base64, 'base64'); const fileData = Buffer.from(downloadBase64.base64, 'base64');
const fileStream = new Readable(); const fileStream = new Readable();
fileStream._read = () => {}; fileStream._read = () => { };
fileStream.push(fileData); fileStream.push(fileData);
fileStream.push(null); fileStream.push(null);
@@ -2142,7 +2142,7 @@ export class ChatwootService {
const processedBuffer = await img.getBuffer(JimpMime.png); const processedBuffer = await img.getBuffer(JimpMime.png);
const fileStream = new Readable(); const fileStream = new Readable();
fileStream._read = () => {}; // _read is required but you can noop it fileStream._read = () => { }; // _read is required but you can noop it
fileStream.push(processedBuffer); fileStream.push(processedBuffer);
fileStream.push(null); fileStream.push(null);
@@ -2237,56 +2237,103 @@ export class ChatwootService {
return send; return send;
} }
} }
// DELETE
// Hard delete quando habilitado; senão cria placeholder "apagada pelo remetente"
if (event === Events.MESSAGES_DELETE) { if (event === Events.MESSAGES_DELETE) {
// Anti-dup local (process-wide) por 15s
const dedupKey = `cw_del_${instance.instanceId}_${body?.key?.id}`;
const g = (global as any);
if (!g.__cwDel) g.__cwDel = new Map<string, number>();
const last = g.__cwDel.get(dedupKey);
const now = Date.now();
if (last && now - last < 15000) {
this.logger.info(`[CW.DELETE] Ignorado (duplicado local) para ${body?.key?.id}`);
return;
}
g.__cwDel.set(dedupKey, now);
const chatwootDelete = this.configService.get<Chatwoot>('CHATWOOT').MESSAGE_DELETE; const chatwootDelete = this.configService.get<Chatwoot>('CHATWOOT').MESSAGE_DELETE;
if (chatwootDelete === true) { if (!body?.key?.id) {
if (!body?.key?.id) { this.logger.warn('message id not found');
this.logger.warn('message id not found'); return;
return; }
}
const message = await this.getMessageByKeyId(instance, body.key.id);
const message = await this.getMessageByKeyId(instance, body.key.id); if (!message) {
this.logger.warn('Message not found for delete event');
if (message?.chatwootMessageId && message?.chatwootConversationId) { return;
await this.prismaRepository.message.deleteMany({ }
where: {
key: { if (chatwootDelete === true && message?.chatwootMessageId && message?.chatwootConversationId) {
path: ['id'], await this.prismaRepository.message.deleteMany({
equals: body.key.id, where: {
}, key: { path: ['id'], equals: body.key.id },
instanceId: instance.instanceId, instanceId: instance.instanceId,
}, },
}); });
return await client.messages.delete({ await client.messages.delete({
accountId: this.provider.accountId, accountId: this.provider.accountId,
conversationId: message.chatwootConversationId, conversationId: message.chatwootConversationId,
messageId: message.chatwootMessageId, messageId: message.chatwootMessageId,
}); });
return; // hard delete
} else {
const key = message.key as WAMessageKey;
const messageType = key?.fromMe ? 'outgoing' : 'incoming';
const DELETE_PLACEHOLDER = '🗑️ Mensagem apagada pelo remetente';
if (message.chatwootConversationId) {
const send = await this.createMessage(
instance,
message.chatwootConversationId,
DELETE_PLACEHOLDER,
messageType,
false,
[],
{ message: { extendedTextMessage: { contextInfo: { stanzaId: key.id } } } },
'DEL:' + body.key.id, // mantém a intenção de idempotência
null,
);
if (!send) this.logger.warn('delete placeholder not sent');
} }
return;
} }
} }
// EDIT
// Cria "Mensagem editada: <texto>" SOMENTE se houver texto (evita 'undefined')
// Se vier "edit" sem texto (REVOKE mascarado), não faz nada aqui — o bloco de DELETE trata.
if (event === 'messages.edit' || event === 'send.message.update') { if (event === 'messages.edit' || event === 'send.message.update') {
const editedMessageContent = const editedMessageContentRaw =
body?.editedMessage?.conversation || body?.editedMessage?.extendedTextMessage?.text; body?.editedMessage?.conversation ??
const message = await this.getMessageByKeyId(instance, body?.key?.id); body?.editedMessage?.extendedTextMessage?.text ??
body?.editedMessage?.imageMessage?.caption ??
body?.editedMessage?.videoMessage?.caption ??
body?.editedMessage?.documentMessage?.caption ??
(typeof body?.text === 'string' ? body.text : undefined);
const editedMessageContent = (editedMessageContentRaw ?? '').trim();
// Sem conteúdo? Ignora aqui. O DELETE vai gerar o placeholder se for o caso.
if (!editedMessageContent) {
this.logger.info('[CW.EDIT] Conteúdo vazio — ignorando (DELETE tratará se for revoke).');
return;
}
const message = await this.getMessageByKeyId(instance, body?.key?.id);
if (!message) { if (!message) {
this.logger.warn('Message not found for edit event'); this.logger.warn('Message not found for edit event');
return; return;
} }
const key = message.key as WAMessageKey; const key = message.key as WAMessageKey;
const messageType = key?.fromMe ? 'outgoing' : 'incoming'; const messageType = key?.fromMe ? 'outgoing' : 'incoming';
if (message && message.chatwootConversationId && message.chatwootMessageId) { if (message.chatwootConversationId) {
// Criar nova mensagem com formato: "Mensagem editada:\n\nteste1" const label = `\`${i18next.t('cw.message.edited')}\``; // "Mensagem editada"
const editedText = `\n\n\`${i18next.t('cw.message.edited')}:\`\n\n${editedMessageContent}`; const editedText = `${label}:${editedMessageContent}`;
const send = await this.createMessage( const send = await this.createMessage(
instance, instance,
message.chatwootConversationId, message.chatwootConversationId,
@@ -2294,20 +2341,17 @@ export class ChatwootService {
messageType, messageType,
false, false,
[], [],
{ { message: { extendedTextMessage: { contextInfo: { stanzaId: key.id } } } },
message: { extendedTextMessage: { contextInfo: { stanzaId: key.id } } },
},
'WAID:' + body.key.id, 'WAID:' + body.key.id,
null, null,
); );
if (!send) { if (!send) this.logger.warn('edited message not sent');
this.logger.warn('edited message not sent');
return;
}
} }
return; return;
} }
// FIM DA EDIÇÃO
if (event === 'messages.read') { if (event === 'messages.read') {
if (!body?.key?.id || !body?.key?.remoteJid) { if (!body?.key?.id || !body?.key?.remoteJid) {
this.logger.warn('message id not found'); this.logger.warn('message id not found');
@@ -2399,7 +2443,7 @@ export class ChatwootService {
const fileData = Buffer.from(body?.qrcode.base64.replace('data:image/png;base64,', ''), 'base64'); const fileData = Buffer.from(body?.qrcode.base64.replace('data:image/png;base64,', ''), 'base64');
const fileStream = new Readable(); const fileStream = new Readable();
fileStream._read = () => {}; fileStream._read = () => { };
fileStream.push(fileData); fileStream.push(fileData);
fileStream.push(null); fileStream.push(null);
@@ -2608,4 +2652,4 @@ export class ChatwootService {
return; return;
} }
} }
} }

View File

@@ -116,26 +116,23 @@ export async function saveOnWhatsappCache(data: ISaveOnWhatsappCacheParams[]) {
logger.verbose( logger.verbose(
`Saving: remoteJid=${remoteJid}, jidOptions=${uniqueNumbers.join(',')}, lid=${item.lid === 'lid' || item.remoteJid?.includes('@lid') ? 'lid' : null}`, `Saving: remoteJid=${remoteJid}, jidOptions=${uniqueNumbers.join(',')}, lid=${item.lid === 'lid' || item.remoteJid?.includes('@lid') ? 'lid' : null}`,
); );
await prismaRepository.isOnWhatsapp.upsert({
where: { remoteJid: remoteJid }, // Prisma tenta encontrar o registro aqui
if (existingRecord) { update: {
await prismaRepository.isOnWhatsapp.update({ // Campos de update
where: { id: existingRecord.id }, jidOptions: uniqueNumbers.join(','),
data: { lid: item.lid === 'lid' || item.remoteJid?.includes('@lid') ? 'lid' : null,
remoteJid: remoteJid, }, // Se encontrar, ele atualiza
jidOptions: uniqueNumbers.join(','), create: {
lid: item.lid === 'lid' || item.remoteJid?.includes('@lid') ? 'lid' : null, // Campos de criação
}, remoteJid: remoteJid,
}); jidOptions: uniqueNumbers.join(','),
} else { lid: item.lid === 'lid' || item.remoteJid?.includes('@lid') ? 'lid' : null,
await prismaRepository.isOnWhatsapp.create({ }, // Se NÃO encontrar, ele cria
data: { });
remoteJid: remoteJid, }
jidOptions: uniqueNumbers.join(','),
lid: item.lid === 'lid' || item.remoteJid?.includes('@lid') ? 'lid' : null,
},
});
}
}
} }
} }