feat: chatwoot service and webhook endpoint

This commit is contained in:
Davidson Gomes 2023-07-12 16:37:21 -03:00
parent 57b61070d9
commit 514fb56209
3 changed files with 343 additions and 0 deletions

View File

@ -42,6 +42,7 @@
"dependencies": { "dependencies": {
"@adiwajshing/keyed-db": "^0.2.4", "@adiwajshing/keyed-db": "^0.2.4",
"@ffmpeg-installer/ffmpeg": "^1.1.0", "@ffmpeg-installer/ffmpeg": "^1.1.0",
"@figuro/chatwoot-sdk": "^1.1.14",
"@hapi/boom": "^10.0.1", "@hapi/boom": "^10.0.1",
"@whiskeysockets/baileys": "github:vphelipe/WhiskeySockets-Baileys#master", "@whiskeysockets/baileys": "github:vphelipe/WhiskeySockets-Baileys#master",
"axios": "^1.3.5", "axios": "^1.3.5",

View File

@ -4,6 +4,7 @@ import { RouterBroker } from '../abstract/abstract.router';
import { InstanceDto } from '../dto/instance.dto'; import { InstanceDto } from '../dto/instance.dto';
import { ChatwootDto } from '../dto/chatwoot.dto'; import { ChatwootDto } from '../dto/chatwoot.dto';
import { chatwootController } from '../whatsapp.module'; import { chatwootController } from '../whatsapp.module';
import { ChatwootService } from '../services/chatwoot.service';
import { HttpStatus } from './index.router'; import { HttpStatus } from './index.router';
import { Logger } from '../../config/logger.config'; import { Logger } from '../../config/logger.config';
@ -44,6 +45,12 @@ export class ChatwootRouter extends RouterBroker {
}); });
res.status(HttpStatus.OK).json(response); res.status(HttpStatus.OK).json(response);
})
.post(this.routerPath('webhook'), ...guards, async (req, res) => {
const { body } = req;
const { instance } = req.query;
res.status(HttpStatus.OK).json({ message: 'bot' });
}); });
} }

View File

@ -2,12 +2,47 @@ import { InstanceDto } from '../dto/instance.dto';
import { ChatwootDto } from '../dto/chatwoot.dto'; import { ChatwootDto } from '../dto/chatwoot.dto';
import { WAMonitoringService } from './monitor.service'; import { WAMonitoringService } from './monitor.service';
import { Logger } from '../../config/logger.config'; import { Logger } from '../../config/logger.config';
import ChatwootClient from '@figuro/chatwoot-sdk';
import { createReadStream, unlinkSync } from 'fs';
import axios from 'axios';
import FormData from 'form-data';
export class ChatwootService { export class ChatwootService {
constructor(private readonly waMonitor: WAMonitoringService) {} constructor(private readonly waMonitor: WAMonitoringService) {}
private readonly logger = new Logger(ChatwootService.name); private readonly logger = new Logger(ChatwootService.name);
private provider: any;
private async getProvider(instance: InstanceDto) {
const provider = await this.waMonitor.waInstances[
instance.instanceName
].findChatwoot();
return provider;
}
private async clientCw(instance: InstanceDto) {
const provider = await this.getProvider(instance);
if (!provider) {
throw new Error('provider not found');
}
this.provider = provider;
const client = new ChatwootClient({
config: {
basePath: provider.url,
with_credentials: true,
credentials: 'include',
token: provider.token,
},
});
return client;
}
public create(instance: InstanceDto, data: ChatwootDto) { public create(instance: InstanceDto, data: ChatwootDto) {
this.logger.verbose('create chatwoot: ' + instance.instanceName); this.logger.verbose('create chatwoot: ' + instance.instanceName);
this.waMonitor.waInstances[instance.instanceName].setChatwoot(data); this.waMonitor.waInstances[instance.instanceName].setChatwoot(data);
@ -23,4 +58,304 @@ export class ChatwootService {
return { enabled: null, url: '' }; return { enabled: null, url: '' };
} }
} }
public async getContact(instance: InstanceDto, id: number) {
const client = await this.clientCw(instance);
if (!client) {
throw new Error('client not found');
}
if (!id) {
throw new Error('id is required');
}
const contact = await client.contact.getContactable({
accountId: this.provider.accountId,
id,
});
return contact;
}
public async createContact(
instance: InstanceDto,
phoneNumber: string,
inboxId: number,
accountId: number,
name?: string,
) {
const client = await this.clientCw(instance);
if (!client) {
throw new Error('client not found');
}
const contact = await client.contacts.create({
accountId: this.provider.accountId,
data: {
inbox_id: inboxId,
name: name || phoneNumber,
phone_number: `+${phoneNumber}`,
},
});
return contact;
}
public async updateContact(instance: InstanceDto, id: number, data: any) {
const client = await this.clientCw(instance);
if (!client) {
throw new Error('client not found');
}
if (!id) {
throw new Error('id is required');
}
const contact = await client.contacts.update({
accountId: this.provider.accountId,
id,
data,
});
return contact;
}
public async findContact(instance: InstanceDto, phoneNumber: string) {
const client = await this.clientCw(instance);
if (!client) {
throw new Error('client not found');
}
const contact = await client.contacts.search({
accountId: this.provider.accountId,
q: `+${phoneNumber}`,
});
return contact.payload.find((contact) => contact.phone_number === `+${phoneNumber}`);
}
public async createConversation(instance: InstanceDto, body: any) {
const client = await this.clientCw(instance);
if (!client) {
throw new Error('client not found');
}
const chatId = body.data.key.remoteJid.split('@')[0];
const nameContact = !body.data.key.fromMe ? body.data.pushName : chatId;
const filterInbox = await this.getInbox(instance);
const contact =
(await this.findContact(instance, chatId)) ||
((await this.createContact(instance, chatId, filterInbox.id, nameContact)) as any);
const contactId = contact.id || contact.payload.contact.id;
if (!body.data.key.fromMe && contact.name === chatId && nameContact !== chatId) {
await this.updateContact(instance, contactId, {
name: nameContact,
});
}
const contactConversations = (await client.contacts.listConversations({
accountId: this.provider.accountId,
id: contactId,
})) as any;
if (contactConversations) {
const conversation = contactConversations.payload.find(
(conversation) =>
conversation.status !== 'resolved' && conversation.inbox_id == filterInbox.id,
);
if (conversation) {
return conversation.id;
}
}
const conversation = await client.conversations.create({
accountId: this.provider.accountId,
data: {
contact_id: `${contactId}`,
inbox_id: `${filterInbox.id}`,
},
});
return conversation.id;
}
public async getInbox(instance: InstanceDto) {
const client = await this.clientCw(instance);
if (!client) {
throw new Error('client not found');
}
const inbox = (await client.inboxes.list({
accountId: this.provider.accountId,
})) as any;
const findByName = inbox.payload.find((inbox) => inbox.name === instance);
return findByName;
}
public async createMessage(
instance: InstanceDto,
conversationId: number,
content: string,
messageType: 'incoming' | 'outgoing' | undefined,
attachments?: {
content: unknown;
encoding: string;
filename: string;
}[],
) {
const client = await this.clientCw(instance);
const message = await client.messages.create({
accountId: this.provider.accountId,
conversationId: conversationId,
data: {
content: content,
message_type: messageType,
attachments: attachments,
},
});
return message;
}
public async createBotMessage(
instance: InstanceDto,
content: string,
messageType: 'incoming' | 'outgoing' | undefined,
attachments?: {
content: unknown;
encoding: string;
filename: string;
}[],
) {
const client = await this.clientCw(instance);
const contact = await this.findContact(instance, '123456');
const filterInbox = await this.getInbox(instance);
const findConversation = await client.conversations.list({
accountId: this.provider.accountId,
inboxId: filterInbox.id,
});
const conversation = findConversation.data.payload.find(
(conversation) =>
conversation?.meta?.sender?.id === contact.id && conversation.status === 'open',
);
const message = await client.messages.create({
accountId: this.provider.accountId,
conversationId: conversation.id,
data: {
content: content,
message_type: messageType,
attachments: attachments,
},
});
return message;
}
private async sendData(
conversationId: number,
file: string,
messageType: 'incoming' | 'outgoing' | undefined,
content?: string,
) {
const data = new FormData();
if (content) {
data.append('content', content);
}
data.append('message_type', messageType);
data.append('attachments[]', createReadStream(file));
const config = {
method: 'post',
maxBodyLength: Infinity,
url: `${this.provider.url}/api/v1/accounts/${this.provider.accountId}/conversations/${conversationId}/messages`,
headers: {
api_access_token: this.provider.token,
...data.getHeaders(),
},
data: data,
};
try {
const { data } = await axios.request(config);
unlinkSync(file);
return data;
} catch (error) {
console.log(error);
}
}
public async createBotQr(
instance: InstanceDto,
content: string,
messageType: 'incoming' | 'outgoing' | undefined,
file?: string,
) {
const client = await this.clientCw(instance);
const contact = await this.findContact(instance, '123456');
const filterInbox = await this.getInbox(instance);
const findConversation = await client.conversations.list({
accountId: this.provider.accountId,
inboxId: filterInbox.id,
});
const conversation = findConversation.data.payload.find(
(conversation) =>
conversation?.meta?.sender?.id === contact.id && conversation.status === 'open',
);
const data = new FormData();
if (content) {
data.append('content', content);
}
data.append('message_type', messageType);
if (file) {
data.append('attachments[]', createReadStream(file));
}
const config = {
method: 'post',
maxBodyLength: Infinity,
url: `${this.provider.url}/api/v1/accounts/${this.provider.accountId}/conversations/${conversation.id}/messages`,
headers: {
api_access_token: this.provider.token,
...data.getHeaders(),
},
data: data,
};
try {
const { data } = await axios.request(config);
unlinkSync(file);
return data;
} catch (error) {
console.log(error);
}
}
public async chatwootWebhook(instance: InstanceDto, body: any) {
return true;
}
} }