Skip to content

Commit dd0dfd4

Browse files
Merge pull request #1504 from KokeroO/develop
fix: Melhora o método createConversation (evita conversas criadas duplicadas Chatwoot)
2 parents 623efd8 + fb18267 commit dd0dfd4

File tree

2 files changed

+174
-169
lines changed

2 files changed

+174
-169
lines changed

src/api/integrations/chatbot/chatwoot/services/chatwoot.service.ts

Lines changed: 172 additions & 167 deletions
Original file line numberDiff line numberDiff line change
@@ -543,215 +543,220 @@ export class ChatwootService {
543543
}
544544

545545
public async createConversation(instance: InstanceDto, body: any) {
546+
const remoteJid = body.key.remoteJid;
547+
const cacheKey = `${instance.instanceName}:createConversation-${remoteJid}`;
548+
const lockKey = `${instance.instanceName}:lock:createConversation-${remoteJid}`;
549+
const maxWaitTime = 5000; // 5 secounds
550+
546551
try {
547-
this.logger.verbose('--- Start createConversation ---');
552+
this.logger.verbose(`--- Start createConversation ---`);
548553
this.logger.verbose(`Instance: ${JSON.stringify(instance)}`);
549554

550-
const client = await this.clientCw(instance);
551-
552-
if (!client) {
553-
this.logger.warn(`Client not found for instance: ${JSON.stringify(instance)}`);
554-
return null;
555-
}
556-
557-
const cacheKey = `${instance.instanceName}:createConversation-${body.key.remoteJid}`;
558-
this.logger.verbose(`Cache key: ${cacheKey}`);
559-
555+
// If it already exists in the cache, return conversationId
560556
if (await this.cache.has(cacheKey)) {
561-
this.logger.verbose(`Cache hit for key: ${cacheKey}`);
562557
const conversationId = (await this.cache.get(cacheKey)) as number;
563-
this.logger.verbose(`Cached conversation ID: ${conversationId}`);
564-
let conversationExists: conversation | boolean;
565-
try {
566-
conversationExists = await client.conversations.get({
567-
accountId: this.provider.accountId,
568-
conversationId: conversationId,
569-
});
570-
this.logger.verbose(`Conversation exists: ${JSON.stringify(conversationExists)}`);
571-
} catch (error) {
572-
this.logger.error(`Error getting conversation: ${error}`);
573-
conversationExists = false;
574-
}
575-
if (!conversationExists) {
576-
this.logger.verbose('Conversation does not exist, re-calling createConversation');
577-
this.cache.delete(cacheKey);
578-
return await this.createConversation(instance, body);
579-
}
580-
558+
this.logger.verbose(`Found conversation to: ${remoteJid}, conversation ID: ${conversationId}`);
581559
return conversationId;
582560
}
583561

584-
const isGroup = body.key.remoteJid.includes('@g.us');
585-
this.logger.verbose(`Is group: ${isGroup}`);
562+
// If lock already exists, wait until release or timeout
563+
if (await this.cache.has(lockKey)) {
564+
this.logger.verbose(`Operação de criação já em andamento para ${remoteJid}, aguardando resultado...`);
565+
const start = Date.now();
566+
while (await this.cache.has(lockKey)) {
567+
if (Date.now() - start > maxWaitTime) {
568+
this.logger.warn(`Timeout aguardando lock para ${remoteJid}`);
569+
break;
570+
}
571+
await new Promise((res) => setTimeout(res, 300));
572+
if (await this.cache.has(cacheKey)) {
573+
const conversationId = (await this.cache.get(cacheKey)) as number;
574+
this.logger.verbose(`Resolves creation of: ${remoteJid}, conversation ID: ${conversationId}`);
575+
return conversationId;
576+
}
577+
}
578+
}
586579

587-
const chatId = isGroup ? body.key.remoteJid : body.key.remoteJid.split('@')[0];
588-
this.logger.verbose(`Chat ID: ${chatId}`);
580+
// Adquire lock
581+
await this.cache.set(lockKey, true, 30);
582+
this.logger.verbose(`Bloqueio adquirido para: ${lockKey}`);
583+
584+
try {
585+
/*
586+
Double check after lock
587+
Utilizei uma nova verificação para evitar que outra thread execute entre o terminio do while e o set lock
588+
*/
589+
if (await this.cache.has(cacheKey)) {
590+
return (await this.cache.get(cacheKey)) as number;
591+
}
589592

590-
let nameContact: string;
593+
const client = await this.clientCw(instance);
594+
if (!client) return null;
591595

592-
nameContact = !body.key.fromMe ? body.pushName : chatId;
593-
this.logger.verbose(`Name contact: ${nameContact}`);
596+
const isGroup = remoteJid.includes('@g.us');
597+
const chatId = isGroup ? remoteJid : remoteJid.split('@')[0];
598+
let nameContact = !body.key.fromMe ? body.pushName : chatId;
599+
const filterInbox = await this.getInbox(instance);
600+
if (!filterInbox) return null;
594601

595-
const filterInbox = await this.getInbox(instance);
602+
if (isGroup) {
603+
this.logger.verbose(`Processing group conversation`);
604+
const group = await this.waMonitor.waInstances[instance.instanceName].client.groupMetadata(chatId);
605+
this.logger.verbose(`Group metadata: ${JSON.stringify(group)}`);
596606

597-
if (!filterInbox) {
598-
this.logger.warn(`Inbox not found for instance: ${JSON.stringify(instance)}`);
599-
return null;
600-
}
607+
nameContact = `${group.subject} (GROUP)`;
608+
609+
const picture_url = await this.waMonitor.waInstances[instance.instanceName].profilePicture(
610+
body.key.participant.split('@')[0],
611+
);
612+
this.logger.verbose(`Participant profile picture URL: ${JSON.stringify(picture_url)}`);
601613

602-
if (isGroup) {
603-
this.logger.verbose('Processing group conversation');
604-
const group = await this.waMonitor.waInstances[instance.instanceName].client.groupMetadata(chatId);
605-
this.logger.verbose(`Group metadata: ${JSON.stringify(group)}`);
614+
const findParticipant = await this.findContact(instance, body.key.participant.split('@')[0]);
615+
this.logger.verbose(`Found participant: ${JSON.stringify(findParticipant)}`);
606616

607-
nameContact = `${group.subject} (GROUP)`;
617+
if (findParticipant) {
618+
if (!findParticipant.name || findParticipant.name === chatId) {
619+
await this.updateContact(instance, findParticipant.id, {
620+
name: body.pushName,
621+
avatar_url: picture_url.profilePictureUrl || null,
622+
});
623+
}
624+
} else {
625+
await this.createContact(
626+
instance,
627+
body.key.participant.split('@')[0],
628+
filterInbox.id,
629+
false,
630+
body.pushName,
631+
picture_url.profilePictureUrl || null,
632+
body.key.participant,
633+
);
634+
}
635+
}
608636

609-
const picture_url = await this.waMonitor.waInstances[instance.instanceName].profilePicture(
610-
body.key.participant.split('@')[0],
611-
);
612-
this.logger.verbose(`Participant profile picture URL: ${JSON.stringify(picture_url)}`);
637+
const picture_url = await this.waMonitor.waInstances[instance.instanceName].profilePicture(chatId);
638+
this.logger.verbose(`Contact profile picture URL: ${JSON.stringify(picture_url)}`);
613639

614-
const findParticipant = await this.findContact(instance, body.key.participant.split('@')[0]);
615-
this.logger.verbose(`Found participant: ${JSON.stringify(findParticipant)}`);
640+
let contact = await this.findContact(instance, chatId);
616641

617-
if (findParticipant) {
618-
if (!findParticipant.name || findParticipant.name === chatId) {
619-
await this.updateContact(instance, findParticipant.id, {
620-
name: body.pushName,
621-
avatar_url: picture_url.profilePictureUrl || null,
622-
});
642+
if (contact) {
643+
this.logger.verbose(`Found contact: ${JSON.stringify(contact)}`);
644+
if (!body.key.fromMe) {
645+
const waProfilePictureFile =
646+
picture_url?.profilePictureUrl?.split('#')[0].split('?')[0].split('/').pop() || '';
647+
const chatwootProfilePictureFile = contact?.thumbnail?.split('#')[0].split('?')[0].split('/').pop() || '';
648+
const pictureNeedsUpdate = waProfilePictureFile !== chatwootProfilePictureFile;
649+
const nameNeedsUpdate =
650+
!contact.name ||
651+
contact.name === chatId ||
652+
(`+${chatId}`.startsWith('+55')
653+
? this.getNumbers(`+${chatId}`).some(
654+
(v) => contact.name === v || contact.name === v.substring(3) || contact.name === v.substring(1),
655+
)
656+
: false);
657+
658+
this.logger.verbose(`Picture needs update: ${pictureNeedsUpdate}`);
659+
this.logger.verbose(`Name needs update: ${nameNeedsUpdate}`);
660+
661+
if (pictureNeedsUpdate || nameNeedsUpdate) {
662+
contact = await this.updateContact(instance, contact.id, {
663+
...(nameNeedsUpdate && { name: nameContact }),
664+
...(waProfilePictureFile === '' && { avatar: null }),
665+
...(pictureNeedsUpdate && { avatar_url: picture_url?.profilePictureUrl }),
666+
});
667+
}
623668
}
624669
} else {
625-
await this.createContact(
670+
const jid = body.key.remoteJid;
671+
contact = await this.createContact(
626672
instance,
627-
body.key.participant.split('@')[0],
673+
chatId,
628674
filterInbox.id,
629-
false,
630-
body.pushName,
675+
isGroup,
676+
nameContact,
631677
picture_url.profilePictureUrl || null,
632-
body.key.participant,
678+
jid,
633679
);
634680
}
635-
}
636-
637-
const picture_url = await this.waMonitor.waInstances[instance.instanceName].profilePicture(chatId);
638-
this.logger.verbose(`Contact profile picture URL: ${JSON.stringify(picture_url)}`);
639-
640-
let contact = await this.findContact(instance, chatId);
641-
this.logger.verbose(`Found contact: ${JSON.stringify(contact)}`);
642681

643-
if (contact) {
644-
if (!body.key.fromMe) {
645-
const waProfilePictureFile =
646-
picture_url?.profilePictureUrl?.split('#')[0].split('?')[0].split('/').pop() || '';
647-
const chatwootProfilePictureFile = contact?.thumbnail?.split('#')[0].split('?')[0].split('/').pop() || '';
648-
const pictureNeedsUpdate = waProfilePictureFile !== chatwootProfilePictureFile;
649-
const nameNeedsUpdate =
650-
!contact.name ||
651-
contact.name === chatId ||
652-
(`+${chatId}`.startsWith('+55')
653-
? this.getNumbers(`+${chatId}`).some(
654-
(v) => contact.name === v || contact.name === v.substring(3) || contact.name === v.substring(1),
655-
)
656-
: false);
657-
658-
this.logger.verbose(`Picture needs update: ${pictureNeedsUpdate}`);
659-
this.logger.verbose(`Name needs update: ${nameNeedsUpdate}`);
660-
661-
if (pictureNeedsUpdate || nameNeedsUpdate) {
662-
contact = await this.updateContact(instance, contact.id, {
663-
...(nameNeedsUpdate && { name: nameContact }),
664-
...(waProfilePictureFile === '' && { avatar: null }),
665-
...(pictureNeedsUpdate && { avatar_url: picture_url?.profilePictureUrl }),
666-
});
667-
}
682+
if (!contact) {
683+
this.logger.warn(`Contact not created or found`);
684+
return null;
668685
}
669-
} else {
670-
const jid = body.key.remoteJid;
671-
contact = await this.createContact(
672-
instance,
673-
chatId,
674-
filterInbox.id,
675-
isGroup,
676-
nameContact,
677-
picture_url.profilePictureUrl || null,
678-
jid,
679-
);
680-
}
681-
682-
if (!contact) {
683-
this.logger.warn('Contact not created or found');
684-
return null;
685-
}
686686

687-
const contactId = contact?.payload?.id || contact?.payload?.contact?.id || contact?.id;
688-
this.logger.verbose(`Contact ID: ${contactId}`);
687+
const contactId = contact?.payload?.id || contact?.payload?.contact?.id || contact?.id;
688+
this.logger.verbose(`Contact ID: ${contactId}`);
689689

690-
const contactConversations = (await client.contacts.listConversations({
691-
accountId: this.provider.accountId,
692-
id: contactId,
693-
})) as any;
694-
this.logger.verbose(`Contact conversations: ${JSON.stringify(contactConversations)}`);
690+
const contactConversations = (await client.contacts.listConversations({
691+
accountId: this.provider.accountId,
692+
id: contactId,
693+
})) as any;
694+
this.logger.verbose(`Contact conversations: ${JSON.stringify(contactConversations)}`);
695695

696-
if (!contactConversations || !contactConversations.payload) {
697-
this.logger.error('No conversations found or payload is undefined');
698-
return null;
699-
}
696+
if (!contactConversations || !contactConversations.payload) {
697+
this.logger.error(`No conversations found or payload is undefined`);
698+
return null;
699+
}
700700

701-
let inboxConversation = contactConversations.payload.find(
702-
(conversation) => conversation.inbox_id == filterInbox.id,
703-
);
704-
if (inboxConversation) {
705-
if (this.provider.reopenConversation) {
706-
this.logger.verbose(`Found conversation in reopenConversation mode: ${JSON.stringify(inboxConversation)}`);
701+
let inboxConversation = contactConversations.payload.find(
702+
(conversation) => conversation.inbox_id == filterInbox.id,
703+
);
704+
if (inboxConversation) {
705+
if (this.provider.reopenConversation) {
706+
this.logger.verbose(`Found conversation in reopenConversation mode: ${JSON.stringify(inboxConversation)}`);
707+
708+
if (this.provider.conversationPending && inboxConversation.status !== 'open') {
709+
await client.conversations.toggleStatus({
710+
accountId: this.provider.accountId,
711+
conversationId: inboxConversation.id,
712+
data: {
713+
status: 'pending',
714+
},
715+
});
716+
}
717+
} else {
718+
inboxConversation = contactConversations.payload.find(
719+
(conversation) => conversation.status !== 'resolved' && conversation.inbox_id == filterInbox.id,
720+
);
721+
this.logger.verbose(`Found conversation: ${JSON.stringify(inboxConversation)}`);
722+
}
707723

708-
if (this.provider.conversationPending && inboxConversation.status !== 'open') {
709-
await client.conversations.toggleStatus({
710-
accountId: this.provider.accountId,
711-
conversationId: inboxConversation.id,
712-
data: {
713-
status: 'pending',
714-
},
715-
});
724+
if (inboxConversation) {
725+
this.logger.verbose(`Returning existing conversation ID: ${inboxConversation.id}`);
726+
this.cache.set(cacheKey, inboxConversation.id);
727+
return inboxConversation.id;
716728
}
717-
} else {
718-
inboxConversation = contactConversations.payload.find(
719-
(conversation) => conversation.status !== 'resolved' && conversation.inbox_id == filterInbox.id,
720-
);
721-
this.logger.verbose(`Found conversation: ${JSON.stringify(inboxConversation)}`);
722729
}
723730

724-
if (inboxConversation) {
725-
this.logger.verbose(`Returning existing conversation ID: ${inboxConversation.id}`);
726-
this.cache.set(cacheKey, inboxConversation.id);
727-
return inboxConversation.id;
728-
}
729-
}
731+
const data = {
732+
contact_id: contactId.toString(),
733+
inbox_id: filterInbox.id.toString(),
734+
};
730735

731-
const data = {
732-
contact_id: contactId.toString(),
733-
inbox_id: filterInbox.id.toString(),
734-
};
736+
if (this.provider.conversationPending) {
737+
data['status'] = 'pending';
738+
}
735739

736-
if (this.provider.conversationPending) {
737-
data['status'] = 'pending';
738-
}
740+
const conversation = await client.conversations.create({
741+
accountId: this.provider.accountId,
742+
data,
743+
});
739744

740-
const conversation = await client.conversations.create({
741-
accountId: this.provider.accountId,
742-
data,
743-
});
745+
if (!conversation) {
746+
this.logger.warn(`Conversation not created or found`);
747+
return null;
748+
}
744749

745-
if (!conversation) {
746-
this.logger.warn('Conversation not created or found');
747-
return null;
750+
this.logger.verbose(`New conversation created with ID: ${conversation.id}`);
751+
this.cache.set(cacheKey, conversation.id);
752+
return conversation.id;
753+
} finally {
754+
await this.cache.delete(lockKey);
755+
this.logger.verbose(`Block released for: ${lockKey}`);
748756
}
749-
750-
this.logger.verbose(`New conversation created with ID: ${conversation.id}`);
751-
this.cache.set(cacheKey, conversation.id);
752-
return conversation.id;
753757
} catch (error) {
754758
this.logger.error(`Error in createConversation: ${error}`);
759+
return null;
755760
}
756761
}
757762

src/api/integrations/event/rabbitmq/rabbitmq.controller.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,8 @@ export class RabbitmqController extends EventController implements EventControll
3131
port: url.port || 5672,
3232
username: url.username || 'guest',
3333
password: url.password || 'guest',
34-
vhost: url.pathname.slice(1) || '/',
35-
frameMax: frameMax
34+
vhost: url.pathname.slice(1) || '/',
35+
frameMax: frameMax,
3636
};
3737

3838
amqp.connect(connectionOptions, (error, connection) => {

0 commit comments

Comments
 (0)