mirror of
https://github.com/EvolutionAPI/evolution-api.git
synced 2025-07-13 15:14:49 -06:00
Merge pull request #1504 from KokeroO/develop
fix: Melhora o método createConversation (evita conversas criadas duplicadas Chatwoot)
This commit is contained in:
commit
dd0dfd447c
@ -543,215 +543,220 @@ export class ChatwootService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async createConversation(instance: InstanceDto, body: any) {
|
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 {
|
try {
|
||||||
this.logger.verbose('--- Start createConversation ---');
|
this.logger.verbose(`--- Start createConversation ---`);
|
||||||
this.logger.verbose(`Instance: ${JSON.stringify(instance)}`);
|
this.logger.verbose(`Instance: ${JSON.stringify(instance)}`);
|
||||||
|
|
||||||
const client = await this.clientCw(instance);
|
// If it already exists in the cache, return conversationId
|
||||||
|
|
||||||
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 (await this.cache.has(cacheKey)) {
|
if (await this.cache.has(cacheKey)) {
|
||||||
this.logger.verbose(`Cache hit for key: ${cacheKey}`);
|
|
||||||
const conversationId = (await this.cache.get(cacheKey)) as number;
|
const conversationId = (await this.cache.get(cacheKey)) as number;
|
||||||
this.logger.verbose(`Cached conversation ID: ${conversationId}`);
|
this.logger.verbose(`Found conversation to: ${remoteJid}, 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
return conversationId;
|
return conversationId;
|
||||||
}
|
}
|
||||||
|
|
||||||
const isGroup = body.key.remoteJid.includes('@g.us');
|
// If lock already exists, wait until release or timeout
|
||||||
this.logger.verbose(`Is group: ${isGroup}`);
|
if (await this.cache.has(lockKey)) {
|
||||||
|
this.logger.verbose(`Operação de criação já em andamento para ${remoteJid}, aguardando resultado...`);
|
||||||
const chatId = isGroup ? body.key.remoteJid : body.key.remoteJid.split('@')[0];
|
const start = Date.now();
|
||||||
this.logger.verbose(`Chat ID: ${chatId}`);
|
while (await this.cache.has(lockKey)) {
|
||||||
|
if (Date.now() - start > maxWaitTime) {
|
||||||
let nameContact: string;
|
this.logger.warn(`Timeout aguardando lock para ${remoteJid}`);
|
||||||
|
break;
|
||||||
nameContact = !body.key.fromMe ? body.pushName : chatId;
|
}
|
||||||
this.logger.verbose(`Name contact: ${nameContact}`);
|
await new Promise((res) => setTimeout(res, 300));
|
||||||
|
if (await this.cache.has(cacheKey)) {
|
||||||
const filterInbox = await this.getInbox(instance);
|
const conversationId = (await this.cache.get(cacheKey)) as number;
|
||||||
|
this.logger.verbose(`Resolves creation of: ${remoteJid}, conversation ID: ${conversationId}`);
|
||||||
if (!filterInbox) {
|
return conversationId;
|
||||||
this.logger.warn(`Inbox not found for instance: ${JSON.stringify(instance)}`);
|
}
|
||||||
return null;
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isGroup) {
|
// Adquire lock
|
||||||
this.logger.verbose('Processing group conversation');
|
await this.cache.set(lockKey, true, 30);
|
||||||
const group = await this.waMonitor.waInstances[instance.instanceName].client.groupMetadata(chatId);
|
this.logger.verbose(`Bloqueio adquirido para: ${lockKey}`);
|
||||||
this.logger.verbose(`Group metadata: ${JSON.stringify(group)}`);
|
|
||||||
|
|
||||||
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(
|
const client = await this.clientCw(instance);
|
||||||
body.key.participant.split('@')[0],
|
if (!client) return null;
|
||||||
);
|
|
||||||
this.logger.verbose(`Participant profile picture URL: ${JSON.stringify(picture_url)}`);
|
|
||||||
|
|
||||||
const findParticipant = await this.findContact(instance, body.key.participant.split('@')[0]);
|
const isGroup = remoteJid.includes('@g.us');
|
||||||
this.logger.verbose(`Found participant: ${JSON.stringify(findParticipant)}`);
|
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 (isGroup) {
|
||||||
if (!findParticipant.name || findParticipant.name === chatId) {
|
this.logger.verbose(`Processing group conversation`);
|
||||||
await this.updateContact(instance, findParticipant.id, {
|
const group = await this.waMonitor.waInstances[instance.instanceName].client.groupMetadata(chatId);
|
||||||
name: body.pushName,
|
this.logger.verbose(`Group metadata: ${JSON.stringify(group)}`);
|
||||||
avatar_url: picture_url.profilePictureUrl || null,
|
|
||||||
});
|
nameContact = `${group.subject} (GROUP)`;
|
||||||
}
|
|
||||||
} else {
|
const picture_url = await this.waMonitor.waInstances[instance.instanceName].profilePicture(
|
||||||
await this.createContact(
|
|
||||||
instance,
|
|
||||||
body.key.participant.split('@')[0],
|
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);
|
const findParticipant = await this.findContact(instance, body.key.participant.split('@')[0]);
|
||||||
this.logger.verbose(`Contact profile picture URL: ${JSON.stringify(picture_url)}`);
|
this.logger.verbose(`Found participant: ${JSON.stringify(findParticipant)}`);
|
||||||
|
|
||||||
let contact = await this.findContact(instance, chatId);
|
if (findParticipant) {
|
||||||
this.logger.verbose(`Found contact: ${JSON.stringify(contact)}`);
|
if (!findParticipant.name || findParticipant.name === chatId) {
|
||||||
|
await this.updateContact(instance, findParticipant.id, {
|
||||||
if (contact) {
|
name: body.pushName,
|
||||||
if (!body.key.fromMe) {
|
avatar_url: picture_url.profilePictureUrl || null,
|
||||||
const waProfilePictureFile =
|
});
|
||||||
picture_url?.profilePictureUrl?.split('#')[0].split('?')[0].split('/').pop() || '';
|
}
|
||||||
const chatwootProfilePictureFile = contact?.thumbnail?.split('#')[0].split('?')[0].split('/').pop() || '';
|
} else {
|
||||||
const pictureNeedsUpdate = waProfilePictureFile !== chatwootProfilePictureFile;
|
await this.createContact(
|
||||||
const nameNeedsUpdate =
|
instance,
|
||||||
!contact.name ||
|
body.key.participant.split('@')[0],
|
||||||
contact.name === chatId ||
|
filterInbox.id,
|
||||||
(`+${chatId}`.startsWith('+55')
|
false,
|
||||||
? this.getNumbers(`+${chatId}`).some(
|
body.pushName,
|
||||||
(v) => contact.name === v || contact.name === v.substring(3) || contact.name === v.substring(1),
|
picture_url.profilePictureUrl || null,
|
||||||
)
|
body.key.participant,
|
||||||
: 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 }),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
const jid = body.key.remoteJid;
|
|
||||||
contact = await this.createContact(
|
|
||||||
instance,
|
|
||||||
chatId,
|
|
||||||
filterInbox.id,
|
|
||||||
isGroup,
|
|
||||||
nameContact,
|
|
||||||
picture_url.profilePictureUrl || null,
|
|
||||||
jid,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!contact) {
|
const picture_url = await this.waMonitor.waInstances[instance.instanceName].profilePicture(chatId);
|
||||||
this.logger.warn('Contact not created or found');
|
this.logger.verbose(`Contact profile picture URL: ${JSON.stringify(picture_url)}`);
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const contactId = contact?.payload?.id || contact?.payload?.contact?.id || contact?.id;
|
let contact = await this.findContact(instance, chatId);
|
||||||
this.logger.verbose(`Contact ID: ${contactId}`);
|
|
||||||
|
|
||||||
const contactConversations = (await client.contacts.listConversations({
|
if (contact) {
|
||||||
accountId: this.provider.accountId,
|
this.logger.verbose(`Found contact: ${JSON.stringify(contact)}`);
|
||||||
id: contactId,
|
if (!body.key.fromMe) {
|
||||||
})) as any;
|
const waProfilePictureFile =
|
||||||
this.logger.verbose(`Contact conversations: ${JSON.stringify(contactConversations)}`);
|
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.verbose(`Picture needs update: ${pictureNeedsUpdate}`);
|
||||||
this.logger.error('No conversations found or payload is undefined');
|
this.logger.verbose(`Name needs update: ${nameNeedsUpdate}`);
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
let inboxConversation = contactConversations.payload.find(
|
if (pictureNeedsUpdate || nameNeedsUpdate) {
|
||||||
(conversation) => conversation.inbox_id == filterInbox.id,
|
contact = await this.updateContact(instance, contact.id, {
|
||||||
);
|
...(nameNeedsUpdate && { name: nameContact }),
|
||||||
if (inboxConversation) {
|
...(waProfilePictureFile === '' && { avatar: null }),
|
||||||
if (this.provider.reopenConversation) {
|
...(pictureNeedsUpdate && { avatar_url: picture_url?.profilePictureUrl }),
|
||||||
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 {
|
} else {
|
||||||
inboxConversation = contactConversations.payload.find(
|
const jid = body.key.remoteJid;
|
||||||
(conversation) => conversation.status !== 'resolved' && conversation.inbox_id == filterInbox.id,
|
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) {
|
if (inboxConversation) {
|
||||||
this.logger.verbose(`Returning existing conversation ID: ${inboxConversation.id}`);
|
if (this.provider.reopenConversation) {
|
||||||
this.cache.set(cacheKey, inboxConversation.id);
|
this.logger.verbose(`Found conversation in reopenConversation mode: ${JSON.stringify(inboxConversation)}`);
|
||||||
return inboxConversation.id;
|
|
||||||
|
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) {
|
} catch (error) {
|
||||||
this.logger.error(`Error in createConversation: ${error}`);
|
this.logger.error(`Error in createConversation: ${error}`);
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,8 +31,8 @@ export class RabbitmqController extends EventController implements EventControll
|
|||||||
port: url.port || 5672,
|
port: url.port || 5672,
|
||||||
username: url.username || 'guest',
|
username: url.username || 'guest',
|
||||||
password: url.password || 'guest',
|
password: url.password || 'guest',
|
||||||
vhost: url.pathname.slice(1) || '/',
|
vhost: url.pathname.slice(1) || '/',
|
||||||
frameMax: frameMax
|
frameMax: frameMax,
|
||||||
};
|
};
|
||||||
|
|
||||||
amqp.connect(connectionOptions, (error, connection) => {
|
amqp.connect(connectionOptions, (error, connection) => {
|
||||||
|
Loading…
Reference in New Issue
Block a user