Skip to content
Open
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
109 changes: 94 additions & 15 deletions src/api/integrations/chatbot/chatwoot/services/chatwoot.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -979,9 +979,15 @@ export class ChatwootService {
return null;
}

const contactId = contact.id || (contact as any)?.payload?.contact?.id;
if (!contactId) {
Comment on lines +982 to +983

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: Consider extracting contactId resolution into a small helper to avoid duplication.

This contactId resolution is duplicated with getOrOpenBotConversation. A shared helper (e.g. getContactId(contact)) would keep the behavior consistent and easier to update if the Chatwoot payload shape changes.

Suggested implementation:

    const contactId = this.getContactId(contact);
    if (!contactId) {
      this.logger.warn('contact id not found');
      return null;
    }

To fully implement the refactor and avoid duplication:

  1. Inside the ChatwootService class, add a helper method (placement: near other private helpers):
  private getContactId(contact: any): string | null {
    const contactId = contact?.id || (contact as any)?.payload?.contact?.id;
    return contactId ?? null;
  }
  1. In the getOrOpenBotConversation method (where the same contactId resolution is currently duplicated), replace the inline logic:
    const contactId = contact.id || (contact as any)?.payload?.contact?.id;

with:

    const contactId = this.getContactId(contact);

This will centralize the payload shape knowledge in one place and keep behavior consistent across usages.

this.logger.warn('contact id not found');
return null;
}

const conversations = (await client.contacts.listConversations({
accountId: this.provider.accountId,
id: contact.id,
id: contactId,
})) as any;

return (
Expand All @@ -991,6 +997,81 @@ export class ChatwootService {
);
}

/**
* Resolves the bot contact conversation used for QR/status commands.
* Reopens the latest inbox conversation when automations resolve it after init.
*/
public async getOrOpenBotConversation(
instance: InstanceDto,
inbox: inbox,
contact: generic_id & contact,
): Promise<conversation | null> {
const openConversation = await this.getOpenConversationByContact(instance, inbox, contact);
if (openConversation) {
return openConversation;
}

const client = await this.clientCw(instance);
if (!client) {
this.logger.warn('client not found');
return null;
}

const contactId = contact.id || (contact as any)?.payload?.contact?.id;
if (!contactId) {
this.logger.warn('contact id not found');
return null;
}

const conversations = (await client.contacts.listConversations({
accountId: this.provider.accountId,
id: contactId,
})) as any;

const inboxConversations = (conversations?.payload ?? [])
.filter((conversation: conversation) => conversation.inbox_id === inbox.id)
.sort((a: conversation, b: conversation) => b.id - a.id);

const latestConversation = inboxConversations[0];

if (latestConversation) {
if (latestConversation.status !== 'open') {
await client.conversations.toggleStatus({
accountId: this.provider.accountId,
conversationId: latestConversation.id,
data: {
status: 'open',
},
});
latestConversation.status = 'open';
}

this.logger.log(`Reopened bot conversation ID: ${latestConversation.id} for ${instance.instanceName}`);
return latestConversation;
}

const created = await client.conversations.create({
accountId: this.provider.accountId,
data: {
contact_id: contactId.toString(),
inbox_id: inbox.id.toString(),
},
});

if (!created) {
this.logger.warn('bot conversation could not be created');
return null;
}

this.logger.log(`Created bot conversation ID: ${created.id} for ${instance.instanceName}`);
return created;
}

private isBotInitCommand(command: string): boolean {
const normalized = command.trim().toLowerCase();
return normalized === 'init' || normalized === 'iniciar' || normalized.startsWith('init:');
}

public async createBotMessage(
instance: InstanceDto,
content: string,
Expand Down Expand Up @@ -1022,10 +1103,10 @@ export class ChatwootService {
return null;
}

const conversation = await this.getOpenConversationByContact(instance, filterInbox, contact);
const conversation = await this.getOrOpenBotConversation(instance, filterInbox, contact);

if (!conversation) {
this.logger.warn('conversation not found');
this.logger.warn('bot conversation not found');
return;
}

Expand Down Expand Up @@ -1152,10 +1233,10 @@ export class ChatwootService {
return null;
}

const conversation = await this.getOpenConversationByContact(instance, filterInbox, contact);
const conversation = await this.getOrOpenBotConversation(instance, filterInbox, contact);

if (!conversation) {
this.logger.warn('conversation not found');
this.logger.warn('bot conversation not found');
return;
}

Expand Down Expand Up @@ -1346,6 +1427,12 @@ export class ChatwootService {

const senderName = body?.conversation?.messages[0]?.sender?.available_name || body?.sender?.name;
const waInstance = this.waMonitor.waInstances[instance.instanceName];

if (!waInstance) {
this.logger.warn(`wa instance not found: ${instance.instanceName}`);
return { message: 'bot' };
}

instance.instanceId = waInstance.instanceId;

if (body.event === 'message_updated' && body.content_attributes?.deleted) {
Expand Down Expand Up @@ -1376,7 +1463,7 @@ export class ChatwootService {
if (chatId === '123456' && body.message_type === 'outgoing') {
const command = messageReceived.replace('/', '');

if (cwBotContact && (command.includes('init') || command.includes('iniciar'))) {
if (cwBotContact && this.isBotInitCommand(command)) {
const state = waInstance?.connectionStatus?.state;

if (state !== 'open') {
Expand Down Expand Up @@ -2500,14 +2587,6 @@ export class ChatwootService {
fileStream.push(fileData);
fileStream.push(null);

await this.createBotQr(
instance,
i18next.t('qrgeneratedsuccesfully'),
'incoming',
fileStream,
`${instance.instanceName}.png`,
);

let msgQrCode = `⚡️${i18next.t('qrgeneratedsuccesfully')}\n\n${i18next.t('scanqr')}`;

if (body?.qrcode?.pairingCode) {
Expand All @@ -2519,7 +2598,7 @@ export class ChatwootService {
)}`;
}

await this.createBotMessage(instance, msgQrCode, 'incoming');
await this.createBotQr(instance, msgQrCode, 'incoming', fileStream, `${instance.instanceName}.png`);
}
}
} catch (error) {
Expand Down