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

This commit is contained in:
Davidson Gomes
2025-12-16 14:31:24 -03:00
2 changed files with 110 additions and 43 deletions

View File

@@ -1251,10 +1251,10 @@ export class BaileysStartupService extends ChannelStartupService {
} }
} }
const messageRaw = this.prepareMessage(received); const messageRaw = this.prepareMessage(received) as any;
if (messageRaw.messageType === 'pollUpdateMessage') { 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 pollMessage = (await this.getMessage(pollCreationKey, true)) as proto.IWebMessageInfo;
const pollMessageSecret = (await this.getMessage(pollCreationKey)) as any; const pollMessageSecret = (await this.getMessage(pollCreationKey)) as any;
@@ -1263,7 +1263,7 @@ export class BaileysStartupService extends ChannelStartupService {
(pollMessage.message as any).pollCreationMessage?.options || (pollMessage.message as any).pollCreationMessage?.options ||
(pollMessage.message as any).pollCreationMessageV3?.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 const voterJid = received.key.fromMe
? this.instance.wuid ? this.instance.wuid
@@ -1343,14 +1343,14 @@ export class BaileysStartupService extends ChannelStartupService {
}) })
.map((option) => option.optionName); .map((option) => option.optionName);
messageRaw.message.pollUpdateMessage.vote.selectedOptions = selectedOptionNames; (messageRaw.message as any).pollUpdateMessage.vote.selectedOptions = selectedOptionNames;
const pollUpdates = pollOptions.map((option) => ({ const pollUpdates = pollOptions.map((option) => ({
name: option.optionName, name: option.optionName,
voters: selectedOptionNames.includes(option.optionName) ? [successfulVoterJid] : [], voters: selectedOptionNames.includes(option.optionName) ? [successfulVoterJid] : [],
})); }));
messageRaw.pollUpdates = pollUpdates; (messageRaw as any).pollUpdates = pollUpdates;
} }
} }
@@ -1398,13 +1398,14 @@ export class BaileysStartupService extends ChannelStartupService {
}); });
if (openAiDefaultSettings && openAiDefaultSettings.openaiCredsId && openAiDefaultSettings.speechToText) { 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>('DATABASE').SAVE_DATA.NEW_MESSAGE) { if (this.configService.get<Database>('DATABASE').SAVE_DATA.NEW_MESSAGE) {
// eslint-disable-next-line @typescript-eslint/no-unused-vars // 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 msg = await this.prismaRepository.message.create({ data: messageData });
const { remoteJid } = received.key; const { remoteJid } = received.key;
@@ -1480,7 +1481,7 @@ export class BaileysStartupService extends ChannelStartupService {
const mediaUrl = await s3Service.getObjectUrl(fullName); 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 }); await this.prismaRepository.message.update({ where: { id: msg.id }, data: messageRaw });
} }
@@ -1502,7 +1503,7 @@ export class BaileysStartupService extends ChannelStartupService {
); );
if (buffer) { if (buffer) {
messageRaw.message.base64 = buffer.toString('base64'); (messageRaw.message as any).base64 = buffer.toString('base64');
} else { } else {
// retry to download media // retry to download media
const buffer = await downloadMediaMessage( const buffer = await downloadMediaMessage(
@@ -1513,7 +1514,7 @@ export class BaileysStartupService extends ChannelStartupService {
); );
if (buffer) { if (buffer) {
messageRaw.message.base64 = buffer.toString('base64'); (messageRaw.message as any).base64 = buffer.toString('base64');
} }
} }
} catch (error) { } catch (error) {
@@ -1525,8 +1526,8 @@ export class BaileysStartupService extends ChannelStartupService {
this.logger.verbose(messageRaw); this.logger.verbose(messageRaw);
sendTelemetry(`received.message.${messageRaw.messageType ?? 'unknown'}`); sendTelemetry(`received.message.${messageRaw.messageType ?? 'unknown'}`);
if (messageRaw.key.remoteJid?.includes('@lid') && messageRaw.key.remoteJidAlt) { if ((messageRaw.key as any).remoteJid?.includes('@lid') && (messageRaw.key as any).remoteJidAlt) {
messageRaw.key.remoteJid = messageRaw.key.remoteJidAlt; (messageRaw.key as any).remoteJid = (messageRaw.key as any).remoteJidAlt;
} }
console.log(messageRaw); console.log(messageRaw);
@@ -1534,7 +1535,7 @@ export class BaileysStartupService extends ChannelStartupService {
await chatbotController.emit({ await chatbotController.emit({
instance: { instanceName: this.instance.name, instanceId: this.instanceId }, instance: { instanceName: this.instance.name, instanceId: this.instanceId },
remoteJid: messageRaw.key.remoteJid, remoteJid: (messageRaw.key as any).remoteJid,
msg: messageRaw, msg: messageRaw,
pushName: messageRaw.pushName, pushName: messageRaw.pushName,
}); });
@@ -1563,9 +1564,11 @@ export class BaileysStartupService extends ChannelStartupService {
await saveOnWhatsappCache([ await saveOnWhatsappCache([
{ {
remoteJid: remoteJid:
messageRaw.key.addressingMode === 'lid' ? messageRaw.key.remoteJidAlt : messageRaw.key.remoteJid, (messageRaw.key as any).addressingMode === 'lid'
remoteJidAlt: messageRaw.key.remoteJidAlt, ? (messageRaw.key as any).remoteJidAlt
lid: messageRaw.key.addressingMode === 'lid' ? 'lid' : null, : (messageRaw.key as any).remoteJid,
remoteJidAlt: (messageRaw.key as any).remoteJidAlt,
lid: (messageRaw.key as any).addressingMode === 'lid' ? 'lid' : null,
}, },
]); ]);
} }
@@ -1611,7 +1614,18 @@ export class BaileysStartupService extends ChannelStartupService {
const readChatToUpdate: Record<string, true> = {}; // {remoteJid: true} const readChatToUpdate: Record<string, true> = {}; // {remoteJid: true}
for await (const { key, update } of args) { for await (const { key, update } of args) {
if (settings?.groupsIgnore && key.remoteJid?.includes('@g.us')) { 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;
const normalizedParticipant = keyAny.participant;
if (settings?.groupsIgnore && normalizedRemoteJid?.includes('@g.us')) {
continue; continue;
} }
@@ -1662,9 +1676,9 @@ export class BaileysStartupService extends ChannelStartupService {
const message: any = { const message: any = {
keyId: key.id, keyId: key.id,
remoteJid: key?.remoteJid, remoteJid: normalizedRemoteJid,
fromMe: key.fromMe, fromMe: key.fromMe,
participant: key?.participant, participant: normalizedParticipant,
status: status[update.status] ?? 'SERVER_ACK', status: status[update.status] ?? 'SERVER_ACK',
pollUpdates, pollUpdates,
instanceId: this.instanceId, instanceId: this.instanceId,
@@ -1687,18 +1701,48 @@ export class BaileysStartupService extends ChannelStartupService {
const searchId = originalMessageId || key.id; const searchId = originalMessageId || key.id;
const messages = (await this.prismaRepository.$queryRaw` let retries = 0;
SELECT * FROM "Message" const maxRetries = 3;
WHERE "instanceId" = ${this.instanceId} const retryDelay = 500; // 500ms delay to avoid blocking for too long
AND "key"->>'id' = ${searchId}
LIMIT 1 while (retries < maxRetries) {
`) as any[]; const messages = (await this.prismaRepository.$queryRaw`
findMessage = messages[0] || null; 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(retryDelay);
}
}
if (!findMessage?.id) { 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; 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`,
);
key.remoteJid = findMessage.key.remoteJid;
}
message.messageId = findMessage.id; message.messageId = findMessage.id;
} }
@@ -2472,7 +2516,7 @@ export class BaileysStartupService extends ChannelStartupService {
messageSent.messageTimestamp = messageSent.messageTimestamp?.toNumber(); messageSent.messageTimestamp = messageSent.messageTimestamp?.toNumber();
} }
const messageRaw = this.prepareMessage(messageSent); const messageRaw = this.prepareMessage(messageSent) as any;
const isMedia = const isMedia =
messageSent?.message?.imageMessage || messageSent?.message?.imageMessage ||
@@ -2494,14 +2538,15 @@ export class BaileysStartupService extends ChannelStartupService {
); );
} }
if (this.configService.get<Openai>('OPENAI').ENABLED && messageRaw?.message?.audioMessage) { if (this.configService.get<Openai>('OPENAI').ENABLED && (messageRaw as any)?.message?.audioMessage) {
const openAiDefaultSettings = await this.prismaRepository.openaiSetting.findFirst({ const openAiDefaultSettings = await this.prismaRepository.openaiSetting.findFirst({
where: { instanceId: this.instanceId }, where: { instanceId: this.instanceId },
include: { OpenaiCreds: true }, include: { OpenaiCreds: true },
}); });
if (openAiDefaultSettings && openAiDefaultSettings.openaiCredsId && openAiDefaultSettings.speechToText) { 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)}`;
} }
} }
@@ -4712,26 +4757,28 @@ export class BaileysStartupService extends ChannelStartupService {
return obj; return obj;
} }
private prepareMessage(message: proto.IWebMessageInfo): any { private prepareMessage(message: WAMessage): Message {
const contentType = getContentType(message.message); const keyAny = message.key as any;
const contentMsg = message?.message[contentType] as any; const messageRaw: any = {
key: {
const messageRaw = { ...message.key,
key: message.key, // Save key exactly as it comes from Baileys remoteJid: keyAny.remoteJid?.replace(/:.*$/, ''),
participant: keyAny.participant?.replace(/:.*$/, ''),
},
pushName: pushName:
message.pushName || message.pushName ||
(message.key.fromMe (message.key.fromMe
? 'Você' ? 'Você'
: message?.participant || (message.key?.participant ? message.key.participant.split('@')[0] : null)), : message?.participant || (message.key?.participant ? message.key.participant.split('@')[0] : null)),
status: status[message.status],
message: this.deserializeMessageBuffers({ ...message.message }), message: this.deserializeMessageBuffers({ ...message.message }),
contextInfo: this.deserializeMessageBuffers(contentMsg?.contextInfo), messageType: getContentType(message.message),
messageType: contentType || 'unknown',
messageTimestamp: Long.isLong(message.messageTimestamp) messageTimestamp: Long.isLong(message.messageTimestamp)
? message.messageTimestamp.toNumber() ? message.messageTimestamp.toNumber()
: (message.messageTimestamp as number), : (message.messageTimestamp as number),
source: getDevice(keyAny.id),
instanceId: this.instanceId, instanceId: this.instanceId,
source: getDevice(message.key.id), status: status[message.status],
contextInfo: this.deserializeMessageBuffers(message.message?.messageContextInfo),
}; };
if (!messageRaw.status && message.key.fromMe === false) { if (!messageRaw.status && message.key.fromMe === false) {

View File

@@ -1,6 +1,7 @@
import { prismaRepository } from '@api/server.module'; import { prismaRepository } from '@api/server.module';
import { configService, Database } from '@config/env.config'; import { configService, Database } from '@config/env.config';
import { Logger } from '@config/logger.config'; import { Logger } from '@config/logger.config';
import { Prisma } from '@prisma/client';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
const logger = new Logger('OnWhatsappCache'); const logger = new Logger('OnWhatsappCache');
@@ -164,9 +165,28 @@ export async function saveOnWhatsappCache(data: ISaveOnWhatsappCacheParams[]) {
logger.verbose( logger.verbose(
`[saveOnWhatsappCache] Register does not exist, creating: remoteJid=${remoteJid}, jidOptions=${dataPayload.jidOptions}, lid=${dataPayload.lid}`, `[saveOnWhatsappCache] Register does not exist, creating: remoteJid=${remoteJid}, jidOptions=${dataPayload.jidOptions}, lid=${dataPayload.lid}`,
); );
await prismaRepository.isOnWhatsapp.create({ try {
data: dataPayload, await prismaRepository.isOnWhatsapp.create({
}); data: dataPayload,
});
} catch (error: any) {
// Check for unique constraint violation (Prisma error code P2002)
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.`,
);
await prismaRepository.isOnWhatsapp.update({
where: { remoteJid: remoteJid },
data: dataPayload,
});
} else {
throw error;
}
}
} }
} catch (e) { } catch (e) {
// Loga o erro mas não para a execução dos outros promises // Loga o erro mas não para a execução dos outros promises