diff --git a/src/api/integrations/chatbot/chatwoot/services/chatwoot.service.ts b/src/api/integrations/chatbot/chatwoot/services/chatwoot.service.ts index 5b42e377..3f849370 100644 --- a/src/api/integrations/chatbot/chatwoot/services/chatwoot.service.ts +++ b/src/api/integrations/chatbot/chatwoot/services/chatwoot.service.ts @@ -543,215 +543,220 @@ export class ChatwootService { } public async createConversation(instance: InstanceDto, body: any) { + const remoteJid = body.key.remoteJid; + const cacheKey = `${instance.instanceName}:createConversation-${remoteJid}`; + const lockKey = `${instance.instanceName}:lock:createConversation-${remoteJid}`; + const maxWaitTime = 5000; // 5 secounds + try { - this.logger.verbose('--- Start createConversation ---'); + this.logger.verbose(`--- Start createConversation ---`); this.logger.verbose(`Instance: ${JSON.stringify(instance)}`); - const client = await this.clientCw(instance); - - if (!client) { - this.logger.warn(`Client not found for instance: ${JSON.stringify(instance)}`); - return null; - } - - const cacheKey = `${instance.instanceName}:createConversation-${body.key.remoteJid}`; - this.logger.verbose(`Cache key: ${cacheKey}`); - + // If it already exists in the cache, return conversationId if (await this.cache.has(cacheKey)) { - this.logger.verbose(`Cache hit for key: ${cacheKey}`); - const conversationId = (await this.cache.get(cacheKey)) as number; - this.logger.verbose(`Cached conversation ID: ${conversationId}`); - let conversationExists: conversation | boolean; - try { - conversationExists = await client.conversations.get({ - accountId: this.provider.accountId, - conversationId: conversationId, - }); - this.logger.verbose(`Conversation exists: ${JSON.stringify(conversationExists)}`); - } catch (error) { - this.logger.error(`Error getting conversation: ${error}`); - conversationExists = false; - } - if (!conversationExists) { - this.logger.verbose('Conversation does not exist, re-calling createConversation'); - this.cache.delete(cacheKey); - return await this.createConversation(instance, body); - } - + const conversationId = await this.cache.get(cacheKey) as number; + this.logger.verbose(`Found conversation to: ${remoteJid}, conversation ID: ${conversationId}`); return conversationId; } - const isGroup = body.key.remoteJid.includes('@g.us'); - this.logger.verbose(`Is group: ${isGroup}`); - - const chatId = isGroup ? body.key.remoteJid : body.key.remoteJid.split('@')[0]; - this.logger.verbose(`Chat ID: ${chatId}`); - - let nameContact: string; - - nameContact = !body.key.fromMe ? body.pushName : chatId; - this.logger.verbose(`Name contact: ${nameContact}`); - - const filterInbox = await this.getInbox(instance); - - if (!filterInbox) { - this.logger.warn(`Inbox not found for instance: ${JSON.stringify(instance)}`); - return null; + // If lock already exists, wait until release or timeout + if (await this.cache.has(lockKey)) { + this.logger.verbose(`Operação de criação já em andamento para ${remoteJid}, aguardando resultado...`); + const start = Date.now(); + while (await this.cache.has(lockKey)) { + if (Date.now() - start > maxWaitTime) { + this.logger.warn(`Timeout aguardando lock para ${remoteJid}`); + break; + } + await new Promise(res => setTimeout(res, 300)); + if (await this.cache.has(cacheKey)) { + const conversationId = await this.cache.get(cacheKey) as number; + this.logger.verbose(`Resolves creation of: ${remoteJid}, conversation ID: ${conversationId}`); + return conversationId; + } + } } - if (isGroup) { - this.logger.verbose('Processing group conversation'); - const group = await this.waMonitor.waInstances[instance.instanceName].client.groupMetadata(chatId); - this.logger.verbose(`Group metadata: ${JSON.stringify(group)}`); + // Adquire lock + await this.cache.set(lockKey, true, 30); + this.logger.verbose(`Bloqueio adquirido para: ${lockKey}`); - nameContact = `${group.subject} (GROUP)`; + try { + /* + Double 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 picture_url = await this.waMonitor.waInstances[instance.instanceName].profilePicture( - body.key.participant.split('@')[0], - ); - this.logger.verbose(`Participant profile picture URL: ${JSON.stringify(picture_url)}`); + const client = await this.clientCw(instance); + if (!client) return null; - const findParticipant = await this.findContact(instance, body.key.participant.split('@')[0]); - this.logger.verbose(`Found participant: ${JSON.stringify(findParticipant)}`); + const isGroup = remoteJid.includes('@g.us'); + const chatId = isGroup ? remoteJid : remoteJid.split('@')[0]; + let nameContact = !body.key.fromMe ? body.pushName : chatId; + const filterInbox = await this.getInbox(instance); + if (!filterInbox) return null; - if (findParticipant) { - if (!findParticipant.name || findParticipant.name === chatId) { - await this.updateContact(instance, findParticipant.id, { - name: body.pushName, - avatar_url: picture_url.profilePictureUrl || null, - }); - } - } else { - await this.createContact( - instance, + if (isGroup) { + this.logger.verbose(`Processing group conversation`); + const group = await this.waMonitor.waInstances[instance.instanceName].client.groupMetadata(chatId); + this.logger.verbose(`Group metadata: ${JSON.stringify(group)}`); + + nameContact = `${group.subject} (GROUP)`; + + const picture_url = await this.waMonitor.waInstances[instance.instanceName].profilePicture( body.key.participant.split('@')[0], - filterInbox.id, - false, - body.pushName, - picture_url.profilePictureUrl || null, - body.key.participant, ); - } - } + this.logger.verbose(`Participant profile picture URL: ${JSON.stringify(picture_url)}`); - const picture_url = await this.waMonitor.waInstances[instance.instanceName].profilePicture(chatId); - this.logger.verbose(`Contact profile picture URL: ${JSON.stringify(picture_url)}`); + const findParticipant = await this.findContact(instance, body.key.participant.split('@')[0]); + this.logger.verbose(`Found participant: ${JSON.stringify(findParticipant)}`); - let contact = await this.findContact(instance, chatId); - this.logger.verbose(`Found contact: ${JSON.stringify(contact)}`); - - if (contact) { - if (!body.key.fromMe) { - const waProfilePictureFile = - picture_url?.profilePictureUrl?.split('#')[0].split('?')[0].split('/').pop() || ''; - const chatwootProfilePictureFile = contact?.thumbnail?.split('#')[0].split('?')[0].split('/').pop() || ''; - const pictureNeedsUpdate = waProfilePictureFile !== chatwootProfilePictureFile; - const nameNeedsUpdate = - !contact.name || - contact.name === chatId || - (`+${chatId}`.startsWith('+55') - ? this.getNumbers(`+${chatId}`).some( - (v) => contact.name === v || contact.name === v.substring(3) || contact.name === v.substring(1), - ) - : false); - - this.logger.verbose(`Picture needs update: ${pictureNeedsUpdate}`); - this.logger.verbose(`Name needs update: ${nameNeedsUpdate}`); - - if (pictureNeedsUpdate || nameNeedsUpdate) { - contact = await this.updateContact(instance, contact.id, { - ...(nameNeedsUpdate && { name: nameContact }), - ...(waProfilePictureFile === '' && { avatar: null }), - ...(pictureNeedsUpdate && { avatar_url: picture_url?.profilePictureUrl }), - }); + if (findParticipant) { + if (!findParticipant.name || findParticipant.name === chatId) { + await this.updateContact(instance, findParticipant.id, { + name: body.pushName, + avatar_url: picture_url.profilePictureUrl || null, + }); + } + } else { + await this.createContact( + instance, + body.key.participant.split('@')[0], + filterInbox.id, + false, + body.pushName, + picture_url.profilePictureUrl || null, + body.key.participant, + ); } } - } else { - const jid = body.key.remoteJid; - contact = await this.createContact( - instance, - chatId, - filterInbox.id, - isGroup, - nameContact, - picture_url.profilePictureUrl || null, - jid, - ); - } - if (!contact) { - this.logger.warn('Contact not created or found'); - return null; - } + const picture_url = await this.waMonitor.waInstances[instance.instanceName].profilePicture(chatId); + this.logger.verbose(`Contact profile picture URL: ${JSON.stringify(picture_url)}`); - const contactId = contact?.payload?.id || contact?.payload?.contact?.id || contact?.id; - this.logger.verbose(`Contact ID: ${contactId}`); + let contact = await this.findContact(instance, chatId); - const contactConversations = (await client.contacts.listConversations({ - accountId: this.provider.accountId, - id: contactId, - })) as any; - this.logger.verbose(`Contact conversations: ${JSON.stringify(contactConversations)}`); + if (contact) { + this.logger.verbose(`Found contact: ${JSON.stringify(contact)}`); + if (!body.key.fromMe) { + const waProfilePictureFile = + picture_url?.profilePictureUrl?.split('#')[0].split('?')[0].split('/').pop() || ''; + const chatwootProfilePictureFile = contact?.thumbnail?.split('#')[0].split('?')[0].split('/').pop() || ''; + const pictureNeedsUpdate = waProfilePictureFile !== chatwootProfilePictureFile; + const nameNeedsUpdate = + !contact.name || + contact.name === chatId || + (`+${chatId}`.startsWith('+55') + ? this.getNumbers(`+${chatId}`).some( + (v) => contact.name === v || contact.name === v.substring(3) || contact.name === v.substring(1), + ) + : false); - if (!contactConversations || !contactConversations.payload) { - this.logger.error('No conversations found or payload is undefined'); - return null; - } + this.logger.verbose(`Picture needs update: ${pictureNeedsUpdate}`); + this.logger.verbose(`Name needs update: ${nameNeedsUpdate}`); - let inboxConversation = contactConversations.payload.find( - (conversation) => conversation.inbox_id == filterInbox.id, - ); - if (inboxConversation) { - if (this.provider.reopenConversation) { - this.logger.verbose(`Found conversation in reopenConversation mode: ${JSON.stringify(inboxConversation)}`); - - if (this.provider.conversationPending && inboxConversation.status !== 'open') { - await client.conversations.toggleStatus({ - accountId: this.provider.accountId, - conversationId: inboxConversation.id, - data: { - status: 'pending', - }, - }); + if (pictureNeedsUpdate || nameNeedsUpdate) { + contact = await this.updateContact(instance, contact.id, { + ...(nameNeedsUpdate && { name: nameContact }), + ...(waProfilePictureFile === '' && { avatar: null }), + ...(pictureNeedsUpdate && { avatar_url: picture_url?.profilePictureUrl }), + }); + } } } else { - inboxConversation = contactConversations.payload.find( - (conversation) => conversation.status !== 'resolved' && conversation.inbox_id == filterInbox.id, + const jid = body.key.remoteJid; + contact = await this.createContact( + instance, + chatId, + filterInbox.id, + isGroup, + nameContact, + picture_url.profilePictureUrl || null, + jid, ); - this.logger.verbose(`Found conversation: ${JSON.stringify(inboxConversation)}`); } + if (!contact) { + this.logger.warn(`Contact not created or found`); + return null; + } + + const contactId = contact?.payload?.id || contact?.payload?.contact?.id || contact?.id; + this.logger.verbose(`Contact ID: ${contactId}`); + + const contactConversations = (await client.contacts.listConversations({ + accountId: this.provider.accountId, + id: contactId, + })) as any; + this.logger.verbose(`Contact conversations: ${JSON.stringify(contactConversations)}`); + + if (!contactConversations || !contactConversations.payload) { + this.logger.error(`No conversations found or payload is undefined`); + return null; + } + + let inboxConversation = contactConversations.payload.find( + (conversation) => conversation.inbox_id == filterInbox.id, + ); if (inboxConversation) { - this.logger.verbose(`Returning existing conversation ID: ${inboxConversation.id}`); - this.cache.set(cacheKey, inboxConversation.id); - return inboxConversation.id; + if (this.provider.reopenConversation) { + this.logger.verbose(`Found conversation in reopenConversation mode: ${JSON.stringify(inboxConversation)}`); + + if (this.provider.conversationPending && inboxConversation.status !== 'open') { + await client.conversations.toggleStatus({ + accountId: this.provider.accountId, + conversationId: inboxConversation.id, + data: { + status: 'pending', + }, + }); + } + } else { + inboxConversation = contactConversations.payload.find( + (conversation) => conversation.status !== 'resolved' && conversation.inbox_id == filterInbox.id, + ); + this.logger.verbose(`Found conversation: ${JSON.stringify(inboxConversation)}`); + } + + if (inboxConversation) { + this.logger.verbose(`Returning existing conversation ID: ${inboxConversation.id}`); + this.cache.set(cacheKey, inboxConversation.id); + return inboxConversation.id; + } } + + const data = { + contact_id: contactId.toString(), + inbox_id: filterInbox.id.toString(), + }; + + if (this.provider.conversationPending) { + data['status'] = 'pending'; + } + + const conversation = await client.conversations.create({ + accountId: this.provider.accountId, + data, + }); + + if (!conversation) { + this.logger.warn(`Conversation not created or found`); + return null; + } + + this.logger.verbose(`New conversation created with ID: ${conversation.id}`); + this.cache.set(cacheKey, conversation.id); + return conversation.id; + } finally { + await this.cache.delete(lockKey); + this.logger.verbose(`Block released for: ${lockKey}`); } - - const data = { - contact_id: contactId.toString(), - inbox_id: filterInbox.id.toString(), - }; - - if (this.provider.conversationPending) { - data['status'] = 'pending'; - } - - const conversation = await client.conversations.create({ - accountId: this.provider.accountId, - data, - }); - - if (!conversation) { - this.logger.warn('Conversation not created or found'); - return null; - } - - this.logger.verbose(`New conversation created with ID: ${conversation.id}`); - this.cache.set(cacheKey, conversation.id); - return conversation.id; } catch (error) { this.logger.error(`Error in createConversation: ${error}`); + return null; } } @@ -2520,4 +2525,4 @@ export class ChatwootService { return; } } -} +} \ No newline at end of file