mirror of
https://github.com/EvolutionAPI/evolution-api.git
synced 2025-07-14 09:51:24 -06:00
feat: chatwoot service and webhook endpoint
This commit is contained in:
parent
57b61070d9
commit
514fb56209
@ -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",
|
||||||
|
@ -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' });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user