Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
139 changes: 121 additions & 18 deletions src/api/integrations/channel/meta/whatsapp.business.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,20 +127,99 @@ export class BusinessStartupService extends ChannelStartupService {
if (!data) return;

const content = data.entry[0].changes[0].value;
const normalizedContent = this.normalizeWebhookContent(content);
const remoteId = this.resolveRemoteId(normalizedContent);

try {
this.loadChatwoot();

const senderJid = createJid(content.messages ? content.messages[0].from : content.statuses[0]?.recipient_id);
this.phoneNumber = senderJid;
await this.eventHandler(normalizedContent);

await this.eventHandler(content, senderJid);
if (remoteId) {
this.phoneNumber = createJid(remoteId);
}
} catch (error) {
this.logger.error(error);
throw new InternalServerErrorException(error?.toString());
}
}

private normalizeWebhookContent(content: any) {
if (!content || typeof content !== 'object') return content;

const normalized = { ...content };
const messageEchoes = Array.isArray(normalized?.message_echoes) ? normalized.message_echoes : undefined;
const smbMessageEchoes = Array.isArray(normalized?.smb_message_echoes) ? normalized.smb_message_echoes : undefined;
const echoes = messageEchoes?.length ? messageEchoes : smbMessageEchoes?.length ? smbMessageEchoes : undefined;

if (!Array.isArray(normalized.messages) && Array.isArray(echoes) && echoes.length > 0) {
normalized.messages = echoes;
}
Comment thread
sourcery-ai[bot] marked this conversation as resolved.

return normalized;
}

private normalizePhoneNumber(value?: string) {
return typeof value === 'string' ? value.replace(/\D/g, '') : '';
}

private resolveRemoteId(content: any) {
const firstMessage = content?.messages?.[0];
const recipient = content?.statuses?.[0]?.recipient_id;

const candidates = [firstMessage?.from, firstMessage?.to, recipient].filter(Boolean) as string[];
if (candidates.length === 0) return undefined;

const businessNumbers = [
this.normalizePhoneNumber(content?.metadata?.display_phone_number),
this.normalizePhoneNumber(content?.metadata?.phone_number_id),
].filter(Boolean);

const externalCounterpart = candidates.find((candidate) => {
const normalizedCandidate = this.normalizePhoneNumber(candidate);
return normalizedCandidate && !businessNumbers.includes(normalizedCandidate);
});

return externalCounterpart ?? candidates[0];
}

private isCloudApiEchoPayload(received: any) {
return (
(Array.isArray(received?.message_echoes) && received.message_echoes.length > 0) ||
(Array.isArray(received?.smb_message_echoes) && received.smb_message_echoes.length > 0)
);
}

private resolveMessageRemoteId(message: any, received: any) {
if (this.isCloudApiEchoPayload(received)) {
return message?.to ?? message?.from;
}

return message?.from ?? message?.to;
}

private isCloudApiFromMe(message: any, received: any) {
if (this.isCloudApiEchoPayload(received)) return true;

const from = this.normalizePhoneNumber(message?.from);
const displayPhone = this.normalizePhoneNumber(received?.metadata?.display_phone_number);
const phoneNumberId = this.normalizePhoneNumber(received?.metadata?.phone_number_id);

if (!from) return false;

return from === displayPhone || from === phoneNumberId;
}

private isCloudApiStatusFromMe(item: any, received: any) {
const recipient = this.normalizePhoneNumber(item?.recipient_id);
if (!recipient) return true;

const displayPhone = this.normalizePhoneNumber(received?.metadata?.display_phone_number);
const phoneNumberId = this.normalizePhoneNumber(received?.metadata?.phone_number_id);

return recipient !== displayPhone && recipient !== phoneNumberId;
}

private async downloadMediaMessage(message: any) {
try {
const id = message[message.type].id;
Expand Down Expand Up @@ -383,20 +462,34 @@ export class BusinessStartupService extends ChannelStartupService {
return messageType;
}

protected async messageHandle(received: any, database: Database, settings: any, senderJid: string) {
protected async messageHandle(received: any, database: Database, settings: any) {
try {
let messageRaw: any;
let pushName: any;
const incomingContact = received?.contacts?.[0];

if (received.contacts) pushName = received.contacts[0].profile.name;
if (incomingContact) {
pushName = incomingContact?.profile?.name ?? incomingContact?.name ?? incomingContact?.wa_id ?? undefined;
}

if (received.messages) {
const message = received.messages[0];
const remoteId = this.resolveMessageRemoteId(message, received);
if (!remoteId) return;

const remoteJid = createJid(remoteId);
const contact = await this.prismaRepository.contact.findFirst({
where: { instanceId: this.instanceId, remoteJid },
});

if (!pushName) {
pushName = contact?.pushName ?? incomingContact?.user_id ?? incomingContact?.wa_id ?? undefined;
}

const key = {
id: message.id,
remoteJid: senderJid,
fromMe: message.from === received.metadata.phone_number_id,
remoteJid,
fromMe: this.isCloudApiFromMe(message, received),
};

if (message.type === 'sticker') {
Expand Down Expand Up @@ -699,12 +792,11 @@ export class BusinessStartupService extends ChannelStartupService {
});
}

const contact = await this.prismaRepository.contact.findFirst({
where: { instanceId: this.instanceId, remoteJid: key.remoteJid },
});
const contactPhone = incomingContact?.profile?.phone ?? incomingContact?.wa_id ?? remoteId;
if (!contactPhone) return;

const contactRaw: any = {
remoteJid: received.contacts[0].profile.phone,
remoteJid: createJid(contactPhone),
pushName,
// profilePicUrl: '',
instanceId: this.instanceId,
Expand All @@ -716,7 +808,7 @@ export class BusinessStartupService extends ChannelStartupService {

if (contact) {
const contactRaw: any = {
remoteJid: received.contacts[0].profile.phone,
remoteJid: createJid(contactPhone),
pushName,
// profilePicUrl: '',
instanceId: this.instanceId,
Expand Down Expand Up @@ -747,10 +839,13 @@ export class BusinessStartupService extends ChannelStartupService {
}
if (received.statuses) {
for await (const item of received.statuses) {
const key = {
const remoteId = item?.recipient_id ?? this.phoneNumber;
if (!remoteId) continue;

const key: any = {
id: item.id,
remoteJid: senderJid,
fromMe: senderJid === received.metadata.phone_number_id,
remoteJid: createJid(remoteId),
fromMe: this.isCloudApiStatusFromMe(item, received),
};
if (settings?.groups_ignore && key.remoteJid.includes('@g.us')) {
return;
Expand All @@ -770,6 +865,14 @@ export class BusinessStartupService extends ChannelStartupService {
return;
}

const findMessageKey: any = findMessage?.key ?? {};
if (findMessageKey?.remoteJid) {
key.remoteJid = findMessageKey.remoteJid;
}
if (typeof findMessageKey?.fromMe === 'boolean') {
key.fromMe = findMessageKey.fromMe;
}

if (item.message === null && item.status === undefined) {
this.sendDataWebhook(Events.MESSAGES_DELETE, key);

Expand Down Expand Up @@ -895,7 +998,7 @@ export class BusinessStartupService extends ChannelStartupService {
return message;
}

protected async eventHandler(content: any, senderJid: string) {
protected async eventHandler(content: any) {
try {
this.logger.log('Contenido recibido en eventHandler:');
this.logger.log(JSON.stringify(content, null, 2));
Expand All @@ -920,12 +1023,12 @@ export class BusinessStartupService extends ChannelStartupService {
message.type === 'button' ||
message.type === 'reaction'
) {
await this.messageHandle(content, database, settings, senderJid);
await this.messageHandle(content, database, settings);
} else {
this.logger.warn(`Tipo de mensaje no reconocido: ${message.type}`);
}
} else if (content.statuses) {
await this.messageHandle(content, database, settings, senderJid);
await this.messageHandle(content, database, settings);
} else {
this.logger.warn('No se encontraron mensajes ni estados en el contenido recibido');
}
Expand Down