refactor(chatwoot): simplify findContact method and update contact creation logic

This commit is contained in:
Willian Coqueiro 2025-06-13 19:36:13 +00:00
parent bfb044b234
commit 1afa8df556
2 changed files with 63 additions and 105 deletions

View File

@ -822,10 +822,10 @@ export class BaileysStartupService extends ChannelStartupService {
if (this.configService.get<Chatwoot>('CHATWOOT').ENABLED && this.localChatwoot?.enabled) { if (this.configService.get<Chatwoot>('CHATWOOT').ENABLED && this.localChatwoot?.enabled) {
const instance = { instanceName: this.instance.name, instanceId: this.instance.id }; const instance = { instanceName: this.instance.name, instanceId: this.instance.id };
const findParticipant = await this.chatwootService.findContact(instance, { const findParticipant = await this.chatwootService.findContact(
identifier: contact.remoteJid, instance,
phone_number: contact.remoteJid.split('@')[0], contact.remoteJid.split('@')[0],
}); );
if (!findParticipant) { if (!findParticipant) {
return; return;

View File

@ -226,13 +226,12 @@ export class ChatwootService {
this.logger.log('Creating chatwoot bot contact'); this.logger.log('Creating chatwoot bot contact');
const contact = const contact =
(await this.findContact(instance, { phone_number: '123456', identifier: '123456' })) || (await this.findContact(instance, '123456')) ||
((await this.createContact( ((await this.createContact(
instance, instance,
{ phone_number: '123456', identifier: '123456' }, '123456',
inboxId, inboxId,
false, false,
false,
organization ? organization : 'EvolutionAPI', organization ? organization : 'EvolutionAPI',
logo ? logo : 'https://evolution-api.com/files/evolution-api-favicon.png', logo ? logo : 'https://evolution-api.com/files/evolution-api-favicon.png',
)) as any); )) as any);
@ -289,10 +288,9 @@ export class ChatwootService {
public async createContact( public async createContact(
instance: InstanceDto, instance: InstanceDto,
phoneNumber: { phone_number: string; identifier: string }, phoneNumber: string,
inboxId: number, inboxId: number,
isGroup: boolean, isGroup: boolean,
isLid: boolean,
name?: string, name?: string,
avatar_url?: string, avatar_url?: string,
jid?: string, jid?: string,
@ -306,22 +304,22 @@ export class ChatwootService {
} }
let data: any = {}; let data: any = {};
if (!isGroup && !isLid) { if (!isGroup) {
data = { data = {
inbox_id: inboxId, inbox_id: inboxId,
name: name || phoneNumber.phone_number, name: name || phoneNumber,
identifier: phoneNumber.identifier, identifier: jid,
avatar_url: avatar_url, avatar_url: avatar_url,
}; };
if (jid && jid.endsWith('@s.whatsapp.net')) { if ((jid && jid.includes('@')) || !jid) {
data['phone_number'] = `+${phoneNumber.phone_number}`; data['phone_number'] = `+${phoneNumber}`;
} }
} else { } else {
data = { data = {
inbox_id: inboxId, inbox_id: inboxId,
name: name || phoneNumber.phone_number, name: name || phoneNumber,
identifier: phoneNumber.identifier, identifier: phoneNumber,
avatar_url: avatar_url, avatar_url: avatar_url,
}; };
} }
@ -413,8 +411,7 @@ export class ChatwootService {
} }
} }
public async findContact(instance: InstanceDto, phoneNumber: { phone_number?: string; identifier: string }) { public async findContact(instance: InstanceDto, phoneNumber: string) {
console.log('findContact phoneNumber', phoneNumber);
const client = await this.clientCw(instance); const client = await this.clientCw(instance);
if (!client) { if (!client) {
@ -423,18 +420,17 @@ export class ChatwootService {
} }
let query: any; let query: any;
const isGroup = phoneNumber.identifier?.includes('@g.us'); const isGroup = phoneNumber.includes('@g.us');
const isLid = phoneNumber.identifier?.includes('@lid');
if (!isGroup && !isLid) { if (!isGroup) {
query = `+${phoneNumber.phone_number}`; query = `+${phoneNumber}`;
} else { } else {
query = phoneNumber.identifier; query = phoneNumber;
} }
let contact: any; let contact: any;
if (isGroup || isLid) { if (isGroup) {
contact = await client.contacts.search({ contact = await client.contacts.search({
accountId: this.provider.accountId, accountId: this.provider.accountId,
q: query, q: query,
@ -449,25 +445,12 @@ export class ChatwootService {
}); });
} }
// Se não encontrou e não é @lid, tenta buscar pelo número limpo if (!contact && contact?.payload?.length === 0) {
if ((!contact || contact?.payload?.length === 0) && !isLid) {
this.logger.verbose(`Contact not found by identifier, trying clean number: ${phoneNumber.phone_number}`);
contact = await chatwootRequest(this.getClientCwConfig(), {
method: 'POST',
url: `/api/v1/accounts/${this.provider.accountId}/contacts/filter`,
body: {
payload: this.getFilterPayload(phoneNumber.phone_number),
},
});
}
if (!contact || contact?.payload?.length === 0) {
this.logger.warn('contact not found'); this.logger.warn('contact not found');
return null; return null;
} }
if (!isGroup && !isLid) { if (!isGroup) {
return contact.payload.length > 1 ? this.findContactInContactList(contact.payload, query) : contact.payload[0]; return contact.payload.length > 1 ? this.findContactInContactList(contact.payload, query) : contact.payload[0];
} else { } else {
return contact.payload.find((contact) => contact.identifier === query); return contact.payload.find((contact) => contact.identifier === query);
@ -565,46 +548,27 @@ export class ChatwootService {
return filterPayload; return filterPayload;
} }
private normalizeContactIdentifier(msg: any) {
// Priority: senderLid > participantLid > remoteJid with @lid > normal number
const normalizedContact = {
phone_number: null,
identifier: null,
};
if (msg.key.remoteJid?.includes('@lid')) {
if (msg.key.SenderPn && msg.key.SenderPn?.includes('@s.whatsapp.net')) {
normalizedContact.phone_number = msg.key.SenderPn.split('@')[0];
normalizedContact.identifier = msg.key.SenderPn;
} else {
normalizedContact.identifier = msg.key.remoteJid;
}
}
if (msg.key.remoteJid && msg.key.remoteJid?.includes('@s.whatsapp.net')) {
normalizedContact.phone_number = msg.key.remoteJid.split('@')[0];
normalizedContact.identifier = msg.key.remoteJid;
}
if (msg.key.remoteJid && msg.key.remoteJid?.includes('@g.us')) {
normalizedContact.identifier = msg.key.remoteJid;
}
if (msg.key.participant && msg.key.participant?.includes('@s.whatsapp.net')) {
normalizedContact.phone_number = msg.key.participant.split('@')[0];
normalizedContact.identifier = msg.key.participant;
}
return normalizedContact;
}
public async createConversation(instance: InstanceDto, body: any) { public async createConversation(instance: InstanceDto, body: any) {
const remoteJid = body.key.remoteJid; const isLid = body.key.remoteJid.includes('@lid') && body.key.senderPn;
const remoteJid = isLid ? body.key.senderPn : body.key.remoteJid;
const cacheKey = `${instance.instanceName}:createConversation-${remoteJid}`; const cacheKey = `${instance.instanceName}:createConversation-${remoteJid}`;
const lockKey = `${instance.instanceName}:lock:createConversation-${remoteJid}`; const lockKey = `${instance.instanceName}:lock:createConversation-${remoteJid}`;
const maxWaitTime = 5000; // 5 secounds const maxWaitTime = 5000; // 5 secounds
try { try {
// Processa atualização de contatos já criados @lid
if (body.key.remoteJid.includes('@lid') && body.key.senderPn && body.key.senderPn !== body.key.remoteJid) {
const contact = await this.findContact(instance, body.key.remoteJid.split('@')[0]);
if (contact && contact.identifier !== body.key.senderPn) {
this.logger.verbose(
`Identifier needs update: (contact.identifier: ${contact.identifier}, body.key.remoteJid: ${body.key.remoteJid}, body.key.senderPn: ${body.key.senderPn})`,
);
await this.updateContact(instance, contact.id, {
identifier: body.key.senderPn,
phone_number: `+${body.key.senderPn.split('@')[0]}`,
});
}
}
this.logger.verbose(`--- Start createConversation ---`); this.logger.verbose(`--- Start createConversation ---`);
this.logger.verbose(`Instance: ${JSON.stringify(instance)}`); this.logger.verbose(`Instance: ${JSON.stringify(instance)}`);
@ -650,20 +614,14 @@ export class ChatwootService {
if (!client) return null; if (!client) return null;
const isGroup = remoteJid.includes('@g.us'); const isGroup = remoteJid.includes('@g.us');
const isLid = remoteJid.includes('@lid'); const chatId = isGroup ? remoteJid : remoteJid.split('@')[0];
this.logger.verbose('is group: ' + isGroup); let nameContact = !body.key.fromMe ? body.pushName : chatId;
const chat = this.normalizeContactIdentifier(body);
this.logger.verbose('chat id: ' + chat.identifier);
const filterInbox = await this.getInbox(instance); const filterInbox = await this.getInbox(instance);
if (!filterInbox) return null; if (!filterInbox) return null;
let nameContact = !body.key.fromMe ? body.pushName : chat.phone_number; if (isGroup) {
if (isGroup || isLid) {
this.logger.verbose(`Processing group conversation`); this.logger.verbose(`Processing group conversation`);
const group = await this.waMonitor.waInstances[instance.instanceName].client.groupMetadata(chat.identifier); const group = await this.waMonitor.waInstances[instance.instanceName].client.groupMetadata(chatId);
this.logger.verbose(`Group metadata: ${JSON.stringify(group)}`); this.logger.verbose(`Group metadata: ${JSON.stringify(group)}`);
nameContact = `${group.subject} (GROUP)`; nameContact = `${group.subject} (GROUP)`;
@ -673,14 +631,11 @@ export class ChatwootService {
); );
this.logger.verbose(`Participant profile picture URL: ${JSON.stringify(picture_url)}`); this.logger.verbose(`Participant profile picture URL: ${JSON.stringify(picture_url)}`);
const participantIdentifier = this.normalizeContactIdentifier(body); const findParticipant = await this.findContact(instance, body.key.participant.split('@')[0]);
this.logger.verbose(`Normalized participant identifier: ${participantIdentifier}`);
const findParticipant = await this.findContact(instance, participantIdentifier);
this.logger.verbose(`Found participant: ${JSON.stringify(findParticipant)}`); this.logger.verbose(`Found participant: ${JSON.stringify(findParticipant)}`);
if (findParticipant) { if (findParticipant) {
if (!findParticipant.name || findParticipant.name === chat.phone_number) { if (!findParticipant.name || findParticipant.name === chatId) {
await this.updateContact(instance, findParticipant.id, { await this.updateContact(instance, findParticipant.id, {
name: body.pushName, name: body.pushName,
avatar_url: picture_url.profilePictureUrl || null, avatar_url: picture_url.profilePictureUrl || null,
@ -689,9 +644,9 @@ export class ChatwootService {
} else { } else {
await this.createContact( await this.createContact(
instance, instance,
participantIdentifier, body.key.participant.split('@')[0],
filterInbox.id, filterInbox.id,
false, isGroup,
body.pushName, body.pushName,
picture_url.profilePictureUrl || null, picture_url.profilePictureUrl || null,
body.key.participant, body.key.participant,
@ -699,10 +654,10 @@ export class ChatwootService {
} }
} }
const picture_url = await this.waMonitor.waInstances[instance.instanceName].profilePicture(chat.identifier); const picture_url = await this.waMonitor.waInstances[instance.instanceName].profilePicture(chatId);
this.logger.verbose(`Contact profile picture URL: ${JSON.stringify(picture_url)}`); this.logger.verbose(`Contact profile picture URL: ${JSON.stringify(picture_url)}`);
let contact = await this.findContact(instance, chat); let contact = await this.findContact(instance, chatId);
if (contact) { if (contact) {
this.logger.verbose(`Found contact: ${JSON.stringify(contact)}`); this.logger.verbose(`Found contact: ${JSON.stringify(contact)}`);
@ -713,16 +668,14 @@ export class ChatwootService {
const pictureNeedsUpdate = waProfilePictureFile !== chatwootProfilePictureFile; const pictureNeedsUpdate = waProfilePictureFile !== chatwootProfilePictureFile;
const nameNeedsUpdate = const nameNeedsUpdate =
!contact.name || !contact.name ||
contact.name === chat.phone_number || contact.name === chatId ||
(`+${chat.phone_number}`.startsWith('+55') (`+${chatId}`.startsWith('+55')
? this.getNumbers(`+${chat.phone_number}`).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}`);
if (pictureNeedsUpdate || nameNeedsUpdate) { if (pictureNeedsUpdate || nameNeedsUpdate) {
contact = await this.updateContact(instance, contact.id, { contact = await this.updateContact(instance, contact.id, {
...(nameNeedsUpdate && { name: nameContact }), ...(nameNeedsUpdate && { name: nameContact }),
@ -732,13 +685,12 @@ export class ChatwootService {
} }
} }
} else { } else {
const jid = body.key.remoteJid; const jid = isLid && body?.key?.senderPn ? body.key.senderPn : body.key.remoteJid;
contact = await this.createContact( contact = await this.createContact(
instance, instance,
chat, chatId,
filterInbox.id, filterInbox.id,
isGroup, isGroup,
isLid,
nameContact, nameContact,
picture_url.profilePictureUrl || null, picture_url.profilePictureUrl || null,
jid, jid,
@ -770,7 +722,6 @@ export class ChatwootService {
if (inboxConversation) { if (inboxConversation) {
if (this.provider.reopenConversation) { if (this.provider.reopenConversation) {
this.logger.verbose(`Found conversation in reopenConversation mode: ${JSON.stringify(inboxConversation)}`); this.logger.verbose(`Found conversation in reopenConversation mode: ${JSON.stringify(inboxConversation)}`);
if (inboxConversation && this.provider.conversationPending && inboxConversation.status !== 'open') { if (inboxConversation && this.provider.conversationPending && inboxConversation.status !== 'open') {
await client.conversations.toggleStatus({ await client.conversations.toggleStatus({
accountId: this.provider.accountId, accountId: this.provider.accountId,
@ -804,6 +755,14 @@ export class ChatwootService {
data['status'] = 'pending'; data['status'] = 'pending';
} }
/*
Triple check after lock
Utilizei uma nova verificação para evitar que outra thread execute entre o terminio do while e o set lock
*/
if (await this.cache.has(cacheKey)) {
return (await this.cache.get(cacheKey)) as number;
}
const conversation = await client.conversations.create({ const conversation = await client.conversations.create({
accountId: this.provider.accountId, accountId: this.provider.accountId,
data, data,
@ -814,7 +773,7 @@ export class ChatwootService {
return null; return null;
} }
this.logger.verbose(`New conversation created with ID: ${conversation.id}`); this.logger.verbose(`New conversation created of ${remoteJid} with ID: ${conversation.id}`);
this.cache.set(cacheKey, conversation.id); this.cache.set(cacheKey, conversation.id);
return conversation.id; return conversation.id;
} finally { } finally {
@ -822,7 +781,6 @@ export class ChatwootService {
this.logger.verbose(`Block released for: ${lockKey}`); this.logger.verbose(`Block released for: ${lockKey}`);
} }
} catch (error) { } catch (error) {
console.log(error);
this.logger.error(`Error in createConversation: ${error}`); this.logger.error(`Error in createConversation: ${error}`);
return null; return null;
} }
@ -952,7 +910,7 @@ export class ChatwootService {
return null; return null;
} }
const contact = await this.findContact(instance, { phone_number: '123456', identifier: '123456' }); const contact = await this.findContact(instance, '123456');
if (!contact) { if (!contact) {
this.logger.warn('contact not found'); this.logger.warn('contact not found');
@ -1082,7 +1040,7 @@ export class ChatwootService {
return true; return true;
} }
const contact = await this.findContact(instance, { phone_number: '123456', identifier: '123456' }); const contact = await this.findContact(instance, '123456');
if (!contact) { if (!contact) {
this.logger.warn('contact not found'); this.logger.warn('contact not found');