From 2e3c8184ef4d1b843f8253e5ec0c8527ac7e5f9f Mon Sep 17 00:00:00 2001 From: Vitordotpy Date: Mon, 15 Dec 2025 21:38:45 -0300 Subject: [PATCH 1/6] fix(baileys): normalize remote JIDs for consistent database lookups --- .../whatsapp/whatsapp.baileys.service.ts | 43 +++++++++---------- 1 file changed, 21 insertions(+), 22 deletions(-) diff --git a/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts b/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts index b75e31a8..8255f5fd 100644 --- a/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts +++ b/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts @@ -1561,7 +1561,12 @@ export class BaileysStartupService extends ChannelStartupService { const readChatToUpdate: Record = {}; // {remoteJid: true} for await (const { key, update } of args) { - if (settings?.groupsIgnore && key.remoteJid?.includes('@g.us')) { + // Normalize JIDs immediately to ensure consistent DB lookups + const keyAny = key as any; + if (keyAny.remoteJid) keyAny.remoteJid = keyAny.remoteJid.replace(/:.*$/, ''); + if (keyAny.participant) keyAny.participant = keyAny.participant.replace(/:.*$/, ''); + + if (settings?.groupsIgnore && keyAny.remoteJid?.includes('@g.us')) { continue; } @@ -1612,9 +1617,9 @@ export class BaileysStartupService extends ChannelStartupService { const message: any = { keyId: key.id, - remoteJid: key?.remoteJid, + remoteJid: keyAny?.remoteJid?.replace(/:.*$/, ''), fromMe: key.fromMe, - participant: key?.participant, + participant: keyAny?.participant?.replace(/:.*$/, ''), status: status[update.status] ?? 'SERVER_ACK', pollUpdates, instanceId: this.instanceId, @@ -4662,26 +4667,20 @@ export class BaileysStartupService extends ChannelStartupService { return obj; } - private prepareMessage(message: proto.IWebMessageInfo): any { - const contentType = getContentType(message.message); - const contentMsg = message?.message[contentType] as any; - - const messageRaw = { - key: message.key, // Save key exactly as it comes from Baileys - pushName: - message.pushName || - (message.key.fromMe - ? 'Você' - : message?.participant || (message.key?.participant ? message.key.participant.split('@')[0] : null)), - status: status[message.status], - message: this.deserializeMessageBuffers({ ...message.message }), - contextInfo: this.deserializeMessageBuffers(contentMsg?.contextInfo), - messageType: contentType || 'unknown', - messageTimestamp: Long.isLong(message.messageTimestamp) - ? message.messageTimestamp.toNumber() - : (message.messageTimestamp as number), + private prepareMessage(message: WAMessage): Message { + const keyAny = message.key as any; + const messageRaw: any = { + key: { + ...message.key, + remoteJid: keyAny.remoteJid?.replace(/:.*$/, ''), + participant: keyAny.participant?.replace(/:.*$/, ''), + }, + pushName: message.pushName, + message: message.message, + messageType: getContentType(message.message), + messageTimestamp: message.messageTimestamp, + source: getDevice(keyAny.id), instanceId: this.instanceId, - source: getDevice(message.key.id), }; if (!messageRaw.status && message.key.fromMe === false) { From 72b0833ce26751b9b075752a64423da2077e76be Mon Sep 17 00:00:00 2001 From: Vitordotpy Date: Mon, 15 Dec 2025 22:26:52 -0300 Subject: [PATCH 2/6] fix(baileys): cast messageRaw and its properties to any for type safety --- .../whatsapp/whatsapp.baileys.service.ts | 42 ++++++++++--------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts b/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts index 8255f5fd..84b9e785 100644 --- a/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts +++ b/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts @@ -1201,10 +1201,10 @@ export class BaileysStartupService extends ChannelStartupService { } } - const messageRaw = this.prepareMessage(received); + const messageRaw = this.prepareMessage(received) as any; if (messageRaw.messageType === 'pollUpdateMessage') { - const pollCreationKey = messageRaw.message.pollUpdateMessage.pollCreationMessageKey; + const pollCreationKey = (messageRaw.message as any).pollUpdateMessage.pollCreationMessageKey; const pollMessage = (await this.getMessage(pollCreationKey, true)) as proto.IWebMessageInfo; const pollMessageSecret = (await this.getMessage(pollCreationKey)) as any; @@ -1213,7 +1213,7 @@ export class BaileysStartupService extends ChannelStartupService { (pollMessage.message as any).pollCreationMessage?.options || (pollMessage.message as any).pollCreationMessageV3?.options || []; - const pollVote = messageRaw.message.pollUpdateMessage.vote; + const pollVote = (messageRaw.message as any).pollUpdateMessage.vote; const voterJid = received.key.fromMe ? this.instance.wuid @@ -1293,14 +1293,14 @@ export class BaileysStartupService extends ChannelStartupService { }) .map((option) => option.optionName); - messageRaw.message.pollUpdateMessage.vote.selectedOptions = selectedOptionNames; + (messageRaw.message as any).pollUpdateMessage.vote.selectedOptions = selectedOptionNames; const pollUpdates = pollOptions.map((option) => ({ name: option.optionName, voters: selectedOptionNames.includes(option.optionName) ? [successfulVoterJid] : [], })); - messageRaw.pollUpdates = pollUpdates; + (messageRaw as any).pollUpdates = pollUpdates; } } @@ -1348,13 +1348,14 @@ export class BaileysStartupService extends ChannelStartupService { }); if (openAiDefaultSettings && openAiDefaultSettings.openaiCredsId && openAiDefaultSettings.speechToText) { - messageRaw.message.speechToText = `[audio] ${await this.openaiService.speechToText(received, this)}`; + (messageRaw.message as any).speechToText = + `[audio] ${await this.openaiService.speechToText(received, this)}`; } } if (this.configService.get('DATABASE').SAVE_DATA.NEW_MESSAGE) { // eslint-disable-next-line @typescript-eslint/no-unused-vars - const { pollUpdates, ...messageData } = messageRaw; + const { pollUpdates, ...messageData } = messageRaw as any; const msg = await this.prismaRepository.message.create({ data: messageData }); const { remoteJid } = received.key; @@ -1430,7 +1431,7 @@ export class BaileysStartupService extends ChannelStartupService { const mediaUrl = await s3Service.getObjectUrl(fullName); - messageRaw.message.mediaUrl = mediaUrl; + (messageRaw.message as any).mediaUrl = mediaUrl; await this.prismaRepository.message.update({ where: { id: msg.id }, data: messageRaw }); } @@ -1452,7 +1453,7 @@ export class BaileysStartupService extends ChannelStartupService { ); if (buffer) { - messageRaw.message.base64 = buffer.toString('base64'); + (messageRaw.message as any).base64 = buffer.toString('base64'); } else { // retry to download media const buffer = await downloadMediaMessage( @@ -1463,7 +1464,7 @@ export class BaileysStartupService extends ChannelStartupService { ); if (buffer) { - messageRaw.message.base64 = buffer.toString('base64'); + (messageRaw.message as any).base64 = buffer.toString('base64'); } } } catch (error) { @@ -1475,8 +1476,8 @@ export class BaileysStartupService extends ChannelStartupService { this.logger.verbose(messageRaw); sendTelemetry(`received.message.${messageRaw.messageType ?? 'unknown'}`); - if (messageRaw.key.remoteJid?.includes('@lid') && messageRaw.key.remoteJidAlt) { - messageRaw.key.remoteJid = messageRaw.key.remoteJidAlt; + if ((messageRaw.key as any).remoteJid?.includes('@lid') && (messageRaw.key as any).remoteJidAlt) { + (messageRaw.key as any).remoteJid = (messageRaw.key as any).remoteJidAlt; } console.log(messageRaw); @@ -1484,7 +1485,7 @@ export class BaileysStartupService extends ChannelStartupService { await chatbotController.emit({ instance: { instanceName: this.instance.name, instanceId: this.instanceId }, - remoteJid: messageRaw.key.remoteJid, + remoteJid: (messageRaw.key as any).remoteJid, msg: messageRaw, pushName: messageRaw.pushName, }); @@ -1513,9 +1514,11 @@ export class BaileysStartupService extends ChannelStartupService { await saveOnWhatsappCache([ { remoteJid: - messageRaw.key.addressingMode === 'lid' ? messageRaw.key.remoteJidAlt : messageRaw.key.remoteJid, - remoteJidAlt: messageRaw.key.remoteJidAlt, - lid: messageRaw.key.addressingMode === 'lid' ? 'lid' : null, + (messageRaw.key as any).addressingMode === 'lid' + ? (messageRaw.key as any).remoteJidAlt + : (messageRaw.key as any).remoteJid, + remoteJidAlt: (messageRaw.key as any).remoteJidAlt, + lid: (messageRaw.key as any).addressingMode === 'lid' ? 'lid' : null, }, ]); } @@ -2427,7 +2430,7 @@ export class BaileysStartupService extends ChannelStartupService { messageSent.messageTimestamp = messageSent.messageTimestamp?.toNumber(); } - const messageRaw = this.prepareMessage(messageSent); + const messageRaw = this.prepareMessage(messageSent) as any; const isMedia = messageSent?.message?.imageMessage || @@ -2449,14 +2452,15 @@ export class BaileysStartupService extends ChannelStartupService { ); } - if (this.configService.get('OPENAI').ENABLED && messageRaw?.message?.audioMessage) { + if (this.configService.get('OPENAI').ENABLED && (messageRaw as any)?.message?.audioMessage) { const openAiDefaultSettings = await this.prismaRepository.openaiSetting.findFirst({ where: { instanceId: this.instanceId }, include: { OpenaiCreds: true }, }); if (openAiDefaultSettings && openAiDefaultSettings.openaiCredsId && openAiDefaultSettings.speechToText) { - messageRaw.message.speechToText = `[audio] ${await this.openaiService.speechToText(messageRaw, this)}`; + (messageRaw.message as any).speechToText = + `[audio] ${await this.openaiService.speechToText(messageRaw, this)}`; } } From f46699ef3f9cdaa3ddcb1f7b6a9f25bf91af5aee Mon Sep 17 00:00:00 2001 From: Vitordotpy Date: Mon, 15 Dec 2025 22:35:07 -0300 Subject: [PATCH 3/6] fix(baileys): cast messageRaw and its properties to any for type safety --- .../whatsapp/whatsapp.baileys.service.ts | 25 ++++++++++++------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts b/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts index 84b9e785..33e5eca9 100644 --- a/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts +++ b/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts @@ -1564,12 +1564,11 @@ export class BaileysStartupService extends ChannelStartupService { const readChatToUpdate: Record = {}; // {remoteJid: true} for await (const { key, update } of args) { - // Normalize JIDs immediately to ensure consistent DB lookups const keyAny = key as any; - if (keyAny.remoteJid) keyAny.remoteJid = keyAny.remoteJid.replace(/:.*$/, ''); - if (keyAny.participant) keyAny.participant = keyAny.participant.replace(/:.*$/, ''); + const normalizedRemoteJid = keyAny.remoteJid?.replace(/:.*$/, ''); + const normalizedParticipant = keyAny.participant?.replace(/:.*$/, ''); - if (settings?.groupsIgnore && keyAny.remoteJid?.includes('@g.us')) { + if (settings?.groupsIgnore && normalizedRemoteJid?.includes('@g.us')) { continue; } @@ -1620,9 +1619,9 @@ export class BaileysStartupService extends ChannelStartupService { const message: any = { keyId: key.id, - remoteJid: keyAny?.remoteJid?.replace(/:.*$/, ''), + remoteJid: normalizedRemoteJid, fromMe: key.fromMe, - participant: keyAny?.participant?.replace(/:.*$/, ''), + participant: normalizedParticipant, status: status[update.status] ?? 'SERVER_ACK', pollUpdates, instanceId: this.instanceId, @@ -4679,12 +4678,20 @@ export class BaileysStartupService extends ChannelStartupService { remoteJid: keyAny.remoteJid?.replace(/:.*$/, ''), participant: keyAny.participant?.replace(/:.*$/, ''), }, - pushName: message.pushName, - message: message.message, + pushName: + message.pushName || + (message.key.fromMe + ? 'Você' + : message?.participant || (message.key?.participant ? message.key.participant.split('@')[0] : null)), + message: this.deserializeMessageBuffers({ ...message.message }), messageType: getContentType(message.message), - messageTimestamp: message.messageTimestamp, + messageTimestamp: Long.isLong(message.messageTimestamp) + ? message.messageTimestamp.toNumber() + : (message.messageTimestamp as number), source: getDevice(keyAny.id), instanceId: this.instanceId, + status: status[message.status], + contextInfo: this.deserializeMessageBuffers(message.message?.messageContextInfo), }; if (!messageRaw.status && message.key.fromMe === false) { From 52a8d9ea715505d454d46a837fa4ff439353b52e Mon Sep 17 00:00:00 2001 From: Vitordotpy Date: Tue, 16 Dec 2025 11:00:11 -0300 Subject: [PATCH 4/6] fix: normalize remoteJid in message updates and handle race condition in contact cache --- .../whatsapp/whatsapp.baileys.service.ts | 45 +++++++++++++++---- src/utils/onWhatsappCache.ts | 21 +++++++-- 2 files changed, 54 insertions(+), 12 deletions(-) diff --git a/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts b/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts index 33e5eca9..fc9b0b63 100644 --- a/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts +++ b/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts @@ -1565,8 +1565,15 @@ export class BaileysStartupService extends ChannelStartupService { for await (const { key, update } of args) { const keyAny = key as any; - const normalizedRemoteJid = keyAny.remoteJid?.replace(/:.*$/, ''); - const normalizedParticipant = keyAny.participant?.replace(/:.*$/, ''); + if (keyAny.remoteJid) { + keyAny.remoteJid = keyAny.remoteJid.replace(/:.*$/, ''); + } + if (keyAny.participant) { + keyAny.participant = keyAny.participant.replace(/:.*$/, ''); + } + + const normalizedRemoteJid = keyAny.remoteJid; + const normalizedParticipant = keyAny.participant; if (settings?.groupsIgnore && normalizedRemoteJid?.includes('@g.us')) { continue; @@ -1644,18 +1651,38 @@ export class BaileysStartupService extends ChannelStartupService { const searchId = originalMessageId || key.id; - const messages = (await this.prismaRepository.$queryRaw` - SELECT * FROM "Message" - WHERE "instanceId" = ${this.instanceId} - AND "key"->>'id' = ${searchId} - LIMIT 1 - `) as any[]; - findMessage = messages[0] || null; + let retries = 0; + const maxRetries = 3; + + while (retries < maxRetries) { + const messages = (await this.prismaRepository.$queryRaw` + SELECT * FROM "Message" + WHERE "instanceId" = ${this.instanceId} + AND "key"->>'id' = ${searchId} + LIMIT 1 + `) as any[]; + findMessage = messages[0] || null; + + if (findMessage?.id) { + break; + } + + retries++; + if (retries < maxRetries) { + await delay(2000); + } + } if (!findMessage?.id) { this.logger.warn(`Original message not found for update. Skipping. Key: ${JSON.stringify(key)}`); continue; } + if (findMessage?.key?.remoteJid && findMessage.key.remoteJid !== key.remoteJid) { + this.logger.verbose( + `Updating key.remoteJid from ${key.remoteJid} to ${findMessage.key.remoteJid} based on stored message`, + ); + key.remoteJid = findMessage.key.remoteJid; + } message.messageId = findMessage.id; } diff --git a/src/utils/onWhatsappCache.ts b/src/utils/onWhatsappCache.ts index 08de0714..50fa08c6 100644 --- a/src/utils/onWhatsappCache.ts +++ b/src/utils/onWhatsappCache.ts @@ -164,9 +164,24 @@ export async function saveOnWhatsappCache(data: ISaveOnWhatsappCacheParams[]) { logger.verbose( `[saveOnWhatsappCache] Register does not exist, creating: remoteJid=${remoteJid}, jidOptions=${dataPayload.jidOptions}, lid=${dataPayload.lid}`, ); - await prismaRepository.isOnWhatsapp.create({ - data: dataPayload, - }); + try { + await prismaRepository.isOnWhatsapp.create({ + data: dataPayload, + }); + } catch (error: any) { + // Check for unique constraint violation (Prisma error code P2002) + if (error.code === 'P2002' && error.meta?.target?.includes('remoteJid')) { + logger.verbose( + `[saveOnWhatsappCache] Race condition detected for ${remoteJid}, updating existing record instead.`, + ); + await prismaRepository.isOnWhatsapp.update({ + where: { remoteJid: remoteJid }, + data: dataPayload, + }); + } else { + throw error; + } + } } } catch (e) { // Loga o erro mas não para a execução dos outros promises From cb41e65e29ee549adb23c429f1132c3c559d2ed1 Mon Sep 17 00:00:00 2001 From: Vitordotpy Date: Tue, 16 Dec 2025 11:32:53 -0300 Subject: [PATCH 5/6] fix: enhance logging for missing original messages during updates --- .../integrations/channel/whatsapp/whatsapp.baileys.service.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts b/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts index fc9b0b63..cc0acdf8 100644 --- a/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts +++ b/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts @@ -1674,7 +1674,9 @@ export class BaileysStartupService extends ChannelStartupService { } if (!findMessage?.id) { - this.logger.warn(`Original message not found for update. Skipping. Key: ${JSON.stringify(key)}`); + this.logger.verbose( + `Original message not found for update after ${maxRetries} retries. Skipping. This is expected for protocol messages or ephemeral events not saved to the database. Key: ${JSON.stringify(key)}`, + ); continue; } if (findMessage?.key?.remoteJid && findMessage.key.remoteJid !== key.remoteJid) { From bb831d590f3a605cb87dbdaa625de6ba0b3942c6 Mon Sep 17 00:00:00 2001 From: Vitordotpy Date: Tue, 16 Dec 2025 12:38:47 -0300 Subject: [PATCH 6/6] refactor: optimize retry loop and robustify cache error handling --- .../channel/whatsapp/whatsapp.baileys.service.ts | 10 +++++++++- src/utils/onWhatsappCache.ts | 7 ++++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts b/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts index cc0acdf8..52be6339 100644 --- a/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts +++ b/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts @@ -1653,6 +1653,7 @@ export class BaileysStartupService extends ChannelStartupService { let retries = 0; const maxRetries = 3; + const retryDelay = 500; // 500ms delay to avoid blocking for too long while (retries < maxRetries) { const messages = (await this.prismaRepository.$queryRaw` @@ -1669,7 +1670,7 @@ export class BaileysStartupService extends ChannelStartupService { retries++; if (retries < maxRetries) { - await delay(2000); + await delay(retryDelay); } } @@ -1679,6 +1680,13 @@ export class BaileysStartupService extends ChannelStartupService { ); continue; } + + // Sync the incoming key.remoteJid with the stored one. + // This mutation is safe and necessary because Baileys events might use LIDs while we store Phone JIDs (or vice versa). + // Normalizing ensuring downstream logic uses the identifier that exists in our database. + if (findMessage?.key?.remoteJid && key.remoteJid !== findMessage.key.remoteJid) { + key.remoteJid = findMessage.key.remoteJid; + } if (findMessage?.key?.remoteJid && findMessage.key.remoteJid !== key.remoteJid) { this.logger.verbose( `Updating key.remoteJid from ${key.remoteJid} to ${findMessage.key.remoteJid} based on stored message`, diff --git a/src/utils/onWhatsappCache.ts b/src/utils/onWhatsappCache.ts index 50fa08c6..8d7a2c16 100644 --- a/src/utils/onWhatsappCache.ts +++ b/src/utils/onWhatsappCache.ts @@ -1,6 +1,7 @@ import { prismaRepository } from '@api/server.module'; import { configService, Database } from '@config/env.config'; import { Logger } from '@config/logger.config'; +import { Prisma } from '@prisma/client'; import dayjs from 'dayjs'; const logger = new Logger('OnWhatsappCache'); @@ -170,7 +171,11 @@ export async function saveOnWhatsappCache(data: ISaveOnWhatsappCacheParams[]) { }); } catch (error: any) { // Check for unique constraint violation (Prisma error code P2002) - if (error.code === 'P2002' && error.meta?.target?.includes('remoteJid')) { + if ( + error instanceof Prisma.PrismaClientKnownRequestError && + error.code === 'P2002' && + (error.meta?.target as string[])?.includes('remoteJid') + ) { logger.verbose( `[saveOnWhatsappCache] Race condition detected for ${remoteJid}, updating existing record instead.`, );