mirror of
https://github.com/EvolutionAPI/evolution-api.git
synced 2025-07-23 04:22:02 -06:00
1474 lines
41 KiB
TypeScript
1474 lines
41 KiB
TypeScript
import { InstanceDto } from '../dto/instance.dto';
|
|
import path from 'path';
|
|
import { ChatwootDto } from '../dto/chatwoot.dto';
|
|
import { WAMonitoringService } from './monitor.service';
|
|
import { Logger } from '../../config/logger.config';
|
|
import ChatwootClient from '@figuro/chatwoot-sdk';
|
|
import { createReadStream, readFileSync, unlinkSync, writeFileSync } from 'fs';
|
|
import axios from 'axios';
|
|
import FormData from 'form-data';
|
|
import { SendTextDto } from '../dto/sendMessage.dto';
|
|
import mimeTypes from 'mime-types';
|
|
import { SendAudioDto } from '../dto/sendMessage.dto';
|
|
import { SendMediaDto } from '../dto/sendMessage.dto';
|
|
import { ROOT_DIR } from '../../config/path.config';
|
|
|
|
export class ChatwootService {
|
|
private messageCacheFile: string;
|
|
private messageCache: Set<string>;
|
|
|
|
private readonly logger = new Logger(ChatwootService.name);
|
|
|
|
private provider: any;
|
|
|
|
constructor(private readonly waMonitor: WAMonitoringService) {
|
|
this.messageCache = new Set();
|
|
}
|
|
|
|
private loadMessageCache(): Set<string> {
|
|
this.logger.verbose('load message cache');
|
|
try {
|
|
const cacheData = readFileSync(this.messageCacheFile, 'utf-8');
|
|
const cacheArray = cacheData.split('\n');
|
|
return new Set(cacheArray);
|
|
} catch (error) {
|
|
return new Set();
|
|
}
|
|
}
|
|
|
|
private saveMessageCache() {
|
|
this.logger.verbose('save message cache');
|
|
const cacheData = Array.from(this.messageCache).join('\n');
|
|
writeFileSync(this.messageCacheFile, cacheData, 'utf-8');
|
|
this.logger.verbose('message cache saved');
|
|
}
|
|
|
|
private clearMessageCache() {
|
|
this.logger.verbose('clear message cache');
|
|
this.messageCache.clear();
|
|
this.saveMessageCache();
|
|
}
|
|
|
|
private async getProvider(instance: InstanceDto) {
|
|
this.logger.verbose('get provider to instance: ' + instance.instanceName);
|
|
try {
|
|
const provider = await this.waMonitor.waInstances[
|
|
instance.instanceName
|
|
].findChatwoot();
|
|
|
|
if (!provider) {
|
|
this.logger.warn('provider not found');
|
|
return null;
|
|
}
|
|
|
|
this.logger.verbose('provider found');
|
|
|
|
return provider;
|
|
} catch (error) {
|
|
this.logger.error('provider not found');
|
|
return null;
|
|
}
|
|
}
|
|
|
|
private async clientCw(instance: InstanceDto) {
|
|
this.logger.verbose('get client to instance: ' + instance.instanceName);
|
|
const provider = await this.getProvider(instance);
|
|
|
|
if (!provider) {
|
|
this.logger.error('provider not found');
|
|
return null;
|
|
}
|
|
|
|
this.logger.verbose('provider found');
|
|
|
|
this.provider = provider;
|
|
|
|
this.logger.verbose('create client to instance: ' + instance.instanceName);
|
|
const client = new ChatwootClient({
|
|
config: {
|
|
basePath: provider.url,
|
|
with_credentials: true,
|
|
credentials: 'include',
|
|
token: provider.token,
|
|
},
|
|
});
|
|
|
|
this.logger.verbose('client created');
|
|
|
|
return client;
|
|
}
|
|
|
|
public create(instance: InstanceDto, data: ChatwootDto) {
|
|
this.logger.verbose('create chatwoot: ' + instance.instanceName);
|
|
this.waMonitor.waInstances[instance.instanceName].setChatwoot(data);
|
|
|
|
this.logger.verbose('chatwoot created');
|
|
return data;
|
|
}
|
|
|
|
public async find(instance: InstanceDto): Promise<ChatwootDto> {
|
|
this.logger.verbose('find chatwoot: ' + instance.instanceName);
|
|
try {
|
|
return await this.waMonitor.waInstances[instance.instanceName].findChatwoot();
|
|
} catch (error) {
|
|
this.logger.error('chatwoot not found');
|
|
return { enabled: null, url: '' };
|
|
}
|
|
}
|
|
|
|
public async getContact(instance: InstanceDto, id: number) {
|
|
this.logger.verbose('get contact to instance: ' + instance.instanceName);
|
|
const client = await this.clientCw(instance);
|
|
|
|
if (!client) {
|
|
this.logger.warn('client not found');
|
|
return null;
|
|
}
|
|
|
|
if (!id) {
|
|
this.logger.warn('id is required');
|
|
return null;
|
|
}
|
|
|
|
this.logger.verbose('find contact in chatwoot');
|
|
const contact = await client.contact.getContactable({
|
|
accountId: this.provider.account_id,
|
|
id,
|
|
});
|
|
|
|
if (!contact) {
|
|
this.logger.warn('contact not found');
|
|
return null;
|
|
}
|
|
|
|
this.logger.verbose('contact found');
|
|
return contact;
|
|
}
|
|
|
|
public async initInstanceChatwoot(
|
|
instance: InstanceDto,
|
|
inboxName: string,
|
|
webhookUrl: string,
|
|
qrcode: boolean,
|
|
) {
|
|
this.logger.verbose('init instance chatwoot: ' + instance.instanceName);
|
|
|
|
const client = await this.clientCw(instance);
|
|
|
|
if (!client) {
|
|
this.logger.warn('client not found');
|
|
return null;
|
|
}
|
|
|
|
this.logger.verbose('find inbox in chatwoot');
|
|
const findInbox: any = await client.inboxes.list({
|
|
accountId: this.provider.account_id,
|
|
});
|
|
|
|
this.logger.verbose('check duplicate inbox');
|
|
const checkDuplicate = findInbox.payload
|
|
.map((inbox) => inbox.name)
|
|
.includes(inboxName);
|
|
|
|
let inboxId: number;
|
|
|
|
if (!checkDuplicate) {
|
|
this.logger.verbose('create inbox in chatwoot');
|
|
const data = {
|
|
type: 'api',
|
|
webhook_url: webhookUrl,
|
|
};
|
|
|
|
const inbox = await client.inboxes.create({
|
|
accountId: this.provider.account_id,
|
|
data: {
|
|
name: inboxName,
|
|
channel: data as any,
|
|
},
|
|
});
|
|
|
|
if (!inbox) {
|
|
this.logger.warn('inbox not found');
|
|
return null;
|
|
}
|
|
|
|
inboxId = inbox.id;
|
|
} else {
|
|
this.logger.verbose('find inbox in chatwoot');
|
|
const inbox = findInbox.payload.find((inbox) => inbox.name === inboxName);
|
|
|
|
if (!inbox) {
|
|
this.logger.warn('inbox not found');
|
|
return null;
|
|
}
|
|
|
|
inboxId = inbox.id;
|
|
}
|
|
|
|
this.logger.verbose('find contact in chatwoot and create if not exists');
|
|
const contact =
|
|
(await this.findContact(instance, '123456')) ||
|
|
((await this.createContact(
|
|
instance,
|
|
'123456',
|
|
inboxId,
|
|
false,
|
|
'EvolutionAPI',
|
|
)) as any);
|
|
|
|
if (!contact) {
|
|
this.logger.warn('contact not found');
|
|
return null;
|
|
}
|
|
|
|
const contactId = contact.id || contact.payload.contact.id;
|
|
|
|
if (qrcode) {
|
|
this.logger.verbose('create conversation in chatwoot');
|
|
const conversation = await client.conversations.create({
|
|
accountId: this.provider.account_id,
|
|
data: {
|
|
contact_id: contactId.toString(),
|
|
inbox_id: inboxId.toString(),
|
|
},
|
|
});
|
|
|
|
if (!conversation) {
|
|
this.logger.warn('conversation not found');
|
|
return null;
|
|
}
|
|
|
|
this.logger.verbose('create message for init instance in chatwoot');
|
|
const message = await client.messages.create({
|
|
accountId: this.provider.account_id,
|
|
conversationId: conversation.id,
|
|
data: {
|
|
content: '/iniciar',
|
|
message_type: 'outgoing',
|
|
},
|
|
});
|
|
|
|
if (!message) {
|
|
this.logger.warn('conversation not found');
|
|
return null;
|
|
}
|
|
}
|
|
|
|
this.logger.verbose('instance chatwoot initialized');
|
|
return true;
|
|
}
|
|
|
|
public async createContact(
|
|
instance: InstanceDto,
|
|
phoneNumber: string,
|
|
inboxId: number,
|
|
isGroup: boolean,
|
|
name?: string,
|
|
avatar_url?: string,
|
|
) {
|
|
this.logger.verbose('create contact to instance: ' + instance.instanceName);
|
|
|
|
const client = await this.clientCw(instance);
|
|
|
|
if (!client) {
|
|
this.logger.warn('client not found');
|
|
return null;
|
|
}
|
|
|
|
let data: any = {};
|
|
if (!isGroup) {
|
|
this.logger.verbose('create contact in chatwoot');
|
|
data = {
|
|
inbox_id: inboxId,
|
|
name: name || phoneNumber,
|
|
phone_number: `+${phoneNumber}`,
|
|
avatar_url: avatar_url,
|
|
};
|
|
} else {
|
|
this.logger.verbose('create contact group in chatwoot');
|
|
data = {
|
|
inbox_id: inboxId,
|
|
name: name || phoneNumber,
|
|
identifier: phoneNumber,
|
|
avatar_url: avatar_url,
|
|
};
|
|
}
|
|
|
|
this.logger.verbose('create contact in chatwoot');
|
|
const contact = await client.contacts.create({
|
|
accountId: this.provider.account_id,
|
|
data,
|
|
});
|
|
|
|
if (!contact) {
|
|
this.logger.warn('contact not found');
|
|
return null;
|
|
}
|
|
|
|
this.logger.verbose('contact created');
|
|
return contact;
|
|
}
|
|
|
|
public async updateContact(instance: InstanceDto, id: number, data: any) {
|
|
this.logger.verbose('update contact to instance: ' + instance.instanceName);
|
|
const client = await this.clientCw(instance);
|
|
|
|
if (!client) {
|
|
this.logger.warn('client not found');
|
|
return null;
|
|
}
|
|
|
|
if (!id) {
|
|
this.logger.warn('id is required');
|
|
return null;
|
|
}
|
|
|
|
this.logger.verbose('update contact in chatwoot');
|
|
const contact = await client.contacts.update({
|
|
accountId: this.provider.account_id,
|
|
id,
|
|
data,
|
|
});
|
|
|
|
this.logger.verbose('contact updated');
|
|
return contact;
|
|
}
|
|
|
|
public async findContact(instance: InstanceDto, phoneNumber: string) {
|
|
this.logger.verbose('find contact to instance: ' + instance.instanceName);
|
|
|
|
const client = await this.clientCw(instance);
|
|
|
|
if (!client) {
|
|
this.logger.warn('client not found');
|
|
return null;
|
|
}
|
|
|
|
let query: any;
|
|
|
|
if (!phoneNumber.includes('@g.us')) {
|
|
this.logger.verbose('format phone number');
|
|
query = `+${phoneNumber}`;
|
|
} else {
|
|
this.logger.verbose('format group id');
|
|
query = phoneNumber;
|
|
}
|
|
|
|
this.logger.verbose('find contact in chatwoot');
|
|
const contact: any = await client.contacts.search({
|
|
accountId: this.provider.account_id,
|
|
q: query,
|
|
});
|
|
|
|
if (!contact) {
|
|
this.logger.warn('contact not found');
|
|
return null;
|
|
}
|
|
|
|
if (!phoneNumber.includes('@g.us')) {
|
|
this.logger.verbose('return contact');
|
|
return contact.payload.find((contact) => contact.phone_number === query);
|
|
} else {
|
|
this.logger.verbose('return group');
|
|
return contact.payload.find((contact) => contact.identifier === query);
|
|
}
|
|
}
|
|
|
|
public async createConversation(instance: InstanceDto, body: any) {
|
|
this.logger.verbose('create conversation to instance: ' + instance.instanceName);
|
|
try {
|
|
const client = await this.clientCw(instance);
|
|
|
|
if (!client) {
|
|
this.logger.warn('client not found');
|
|
return null;
|
|
}
|
|
|
|
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('get inbox to instance: ' + instance.instanceName);
|
|
const filterInbox = await this.getInbox(instance);
|
|
|
|
if (!filterInbox) {
|
|
this.logger.warn('inbox not found');
|
|
return null;
|
|
}
|
|
|
|
if (isGroup) {
|
|
this.logger.verbose('get group name');
|
|
const group = await this.waMonitor.waInstances[
|
|
instance.instanceName
|
|
].client.groupMetadata(chatId);
|
|
|
|
nameContact = `${group.subject} (GROUP)`;
|
|
|
|
this.logger.verbose('find or create participant in chatwoot');
|
|
|
|
const picture_url = await this.waMonitor.waInstances[
|
|
instance.instanceName
|
|
].profilePicture(body.key.participant.split('@')[0]);
|
|
|
|
const findParticipant = await this.findContact(
|
|
instance,
|
|
body.key.participant.split('@')[0],
|
|
);
|
|
|
|
if (findParticipant) {
|
|
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,
|
|
isGroup,
|
|
body.pushName,
|
|
picture_url.profilePictureUrl || null,
|
|
);
|
|
}
|
|
}
|
|
|
|
this.logger.verbose('find or create contact in chatwoot');
|
|
|
|
const picture_url = await this.waMonitor.waInstances[
|
|
instance.instanceName
|
|
].profilePicture(chatId);
|
|
|
|
const findContact = await this.findContact(instance, chatId);
|
|
|
|
let contact: any;
|
|
|
|
if (findContact) {
|
|
contact = await this.updateContact(instance, findContact.id, {
|
|
name: nameContact,
|
|
avatar_url: picture_url.profilePictureUrl || null,
|
|
});
|
|
} else {
|
|
contact = await this.createContact(
|
|
instance,
|
|
chatId,
|
|
filterInbox.id,
|
|
isGroup,
|
|
nameContact,
|
|
picture_url.profilePictureUrl || null,
|
|
);
|
|
}
|
|
|
|
if (!contact) {
|
|
this.logger.warn('contact not found');
|
|
return null;
|
|
}
|
|
|
|
const contactId = contact.payload.id || contact.payload.contact.id;
|
|
|
|
if (!body.key.fromMe && contact.name === chatId && nameContact !== chatId) {
|
|
this.logger.verbose('update contact name in chatwoot');
|
|
await this.updateContact(instance, contactId, {
|
|
name: nameContact,
|
|
});
|
|
}
|
|
|
|
this.logger.verbose('get contact conversations in chatwoot');
|
|
const contactConversations = (await client.contacts.listConversations({
|
|
accountId: this.provider.account_id,
|
|
id: contactId,
|
|
})) as any;
|
|
|
|
if (contactConversations) {
|
|
this.logger.verbose('return conversation if exists');
|
|
const conversation = contactConversations.payload.find(
|
|
(conversation) =>
|
|
conversation.status !== 'resolved' && conversation.inbox_id == filterInbox.id,
|
|
);
|
|
if (conversation) {
|
|
this.logger.verbose('conversation found');
|
|
return conversation.id;
|
|
}
|
|
}
|
|
|
|
this.logger.verbose('create conversation in chatwoot');
|
|
const conversation = await client.conversations.create({
|
|
accountId: this.provider.account_id,
|
|
data: {
|
|
contact_id: `${contactId}`,
|
|
inbox_id: `${filterInbox.id}`,
|
|
},
|
|
});
|
|
|
|
if (!conversation) {
|
|
this.logger.warn('conversation not found');
|
|
return null;
|
|
}
|
|
|
|
this.logger.verbose('conversation created');
|
|
return conversation.id;
|
|
} catch (error) {
|
|
this.logger.error(error);
|
|
}
|
|
}
|
|
|
|
public async getInbox(instance: InstanceDto) {
|
|
this.logger.verbose('get inbox to instance: ' + instance.instanceName);
|
|
|
|
const client = await this.clientCw(instance);
|
|
|
|
if (!client) {
|
|
this.logger.warn('client not found');
|
|
return null;
|
|
}
|
|
|
|
this.logger.verbose('find inboxes in chatwoot');
|
|
const inbox = (await client.inboxes.list({
|
|
accountId: this.provider.account_id,
|
|
})) as any;
|
|
|
|
if (!inbox) {
|
|
this.logger.warn('inbox not found');
|
|
return null;
|
|
}
|
|
|
|
this.logger.verbose('find inbox by name');
|
|
const findByName = inbox.payload.find(
|
|
(inbox) => inbox.name === instance.instanceName,
|
|
);
|
|
|
|
if (!findByName) {
|
|
this.logger.warn('inbox not found');
|
|
return null;
|
|
}
|
|
|
|
this.logger.verbose('return inbox');
|
|
return findByName;
|
|
}
|
|
|
|
public async createMessage(
|
|
instance: InstanceDto,
|
|
conversationId: number,
|
|
content: string,
|
|
messageType: 'incoming' | 'outgoing' | undefined,
|
|
privateMessage?: boolean,
|
|
attachments?: {
|
|
content: unknown;
|
|
encoding: string;
|
|
filename: string;
|
|
}[],
|
|
) {
|
|
this.logger.verbose('create message to instance: ' + instance.instanceName);
|
|
|
|
const client = await this.clientCw(instance);
|
|
|
|
if (!client) {
|
|
this.logger.warn('client not found');
|
|
return null;
|
|
}
|
|
|
|
this.logger.verbose('create message in chatwoot');
|
|
const message = await client.messages.create({
|
|
accountId: this.provider.account_id,
|
|
conversationId: conversationId,
|
|
data: {
|
|
content: content,
|
|
message_type: messageType,
|
|
attachments: attachments,
|
|
private: privateMessage || false,
|
|
},
|
|
});
|
|
|
|
if (!message) {
|
|
this.logger.warn('message not found');
|
|
return null;
|
|
}
|
|
|
|
this.logger.verbose('message created');
|
|
|
|
return message;
|
|
}
|
|
|
|
public async createBotMessage(
|
|
instance: InstanceDto,
|
|
content: string,
|
|
messageType: 'incoming' | 'outgoing' | undefined,
|
|
attachments?: {
|
|
content: unknown;
|
|
encoding: string;
|
|
filename: string;
|
|
}[],
|
|
) {
|
|
this.logger.verbose('create bot message to instance: ' + instance.instanceName);
|
|
|
|
const client = await this.clientCw(instance);
|
|
|
|
if (!client) {
|
|
this.logger.warn('client not found');
|
|
return null;
|
|
}
|
|
|
|
this.logger.verbose('find contact in chatwoot');
|
|
const contact = await this.findContact(instance, '123456');
|
|
|
|
if (!contact) {
|
|
this.logger.warn('contact not found');
|
|
return null;
|
|
}
|
|
|
|
this.logger.verbose('get inbox to instance: ' + instance.instanceName);
|
|
const filterInbox = await this.getInbox(instance);
|
|
|
|
if (!filterInbox) {
|
|
this.logger.warn('inbox not found');
|
|
return null;
|
|
}
|
|
|
|
this.logger.verbose('find conversation in chatwoot');
|
|
const findConversation = await client.conversations.list({
|
|
accountId: this.provider.account_id,
|
|
inboxId: filterInbox.id,
|
|
});
|
|
|
|
if (!findConversation) {
|
|
this.logger.warn('conversation not found');
|
|
return null;
|
|
}
|
|
|
|
this.logger.verbose('find conversation by contact id');
|
|
const conversation = findConversation.data.payload.find(
|
|
(conversation) =>
|
|
conversation?.meta?.sender?.id === contact.id && conversation.status === 'open',
|
|
);
|
|
|
|
if (!conversation) {
|
|
this.logger.warn('conversation not found');
|
|
return;
|
|
}
|
|
|
|
this.logger.verbose('create message in chatwoot');
|
|
const message = await client.messages.create({
|
|
accountId: this.provider.account_id,
|
|
conversationId: conversation.id,
|
|
data: {
|
|
content: content,
|
|
message_type: messageType,
|
|
attachments: attachments,
|
|
},
|
|
});
|
|
|
|
if (!message) {
|
|
this.logger.warn('message not found');
|
|
return null;
|
|
}
|
|
|
|
this.logger.verbose('bot message created');
|
|
|
|
return message;
|
|
}
|
|
|
|
private async sendData(
|
|
conversationId: number,
|
|
file: string,
|
|
messageType: 'incoming' | 'outgoing' | undefined,
|
|
content?: string,
|
|
) {
|
|
this.logger.verbose('send data to chatwoot');
|
|
|
|
const data = new FormData();
|
|
|
|
if (content) {
|
|
this.logger.verbose('content found');
|
|
data.append('content', content);
|
|
}
|
|
|
|
this.logger.verbose('message type: ' + messageType);
|
|
data.append('message_type', messageType);
|
|
|
|
this.logger.verbose('temp file found');
|
|
data.append('attachments[]', createReadStream(file));
|
|
|
|
this.logger.verbose('get client to instance: ' + this.provider.instanceName);
|
|
const config = {
|
|
method: 'post',
|
|
maxBodyLength: Infinity,
|
|
url: `${this.provider.url}/api/v1/accounts/${this.provider.account_id}/conversations/${conversationId}/messages`,
|
|
headers: {
|
|
api_access_token: this.provider.token,
|
|
...data.getHeaders(),
|
|
},
|
|
data: data,
|
|
};
|
|
|
|
this.logger.verbose('send data to chatwoot');
|
|
try {
|
|
const { data } = await axios.request(config);
|
|
|
|
this.logger.verbose('remove temp file');
|
|
unlinkSync(file);
|
|
|
|
this.logger.verbose('data sent');
|
|
return data;
|
|
} catch (error) {
|
|
this.logger.error(error);
|
|
unlinkSync(file);
|
|
}
|
|
}
|
|
|
|
public async createBotQr(
|
|
instance: InstanceDto,
|
|
content: string,
|
|
messageType: 'incoming' | 'outgoing' | undefined,
|
|
file?: string,
|
|
) {
|
|
this.logger.verbose('create bot qr to instance: ' + instance.instanceName);
|
|
const client = await this.clientCw(instance);
|
|
|
|
if (!client) {
|
|
this.logger.warn('client not found');
|
|
return null;
|
|
}
|
|
|
|
this.logger.verbose('find contact in chatwoot');
|
|
const contact = await this.findContact(instance, '123456');
|
|
|
|
if (!contact) {
|
|
this.logger.warn('contact not found');
|
|
return null;
|
|
}
|
|
|
|
this.logger.verbose('get inbox to instance: ' + instance.instanceName);
|
|
const filterInbox = await this.getInbox(instance);
|
|
|
|
if (!filterInbox) {
|
|
this.logger.warn('inbox not found');
|
|
return null;
|
|
}
|
|
|
|
this.logger.verbose('find conversation in chatwoot');
|
|
const findConversation = await client.conversations.list({
|
|
accountId: this.provider.account_id,
|
|
inboxId: filterInbox.id,
|
|
});
|
|
|
|
if (!findConversation) {
|
|
this.logger.warn('conversation not found');
|
|
return null;
|
|
}
|
|
|
|
this.logger.verbose('find conversation by contact id');
|
|
const conversation = findConversation.data.payload.find(
|
|
(conversation) =>
|
|
conversation?.meta?.sender?.id === contact.id && conversation.status === 'open',
|
|
);
|
|
|
|
if (!conversation) {
|
|
this.logger.warn('conversation not found');
|
|
return;
|
|
}
|
|
|
|
this.logger.verbose('send data to chatwoot');
|
|
const data = new FormData();
|
|
|
|
if (content) {
|
|
this.logger.verbose('content found');
|
|
data.append('content', content);
|
|
}
|
|
|
|
this.logger.verbose('message type: ' + messageType);
|
|
data.append('message_type', messageType);
|
|
|
|
if (file) {
|
|
this.logger.verbose('temp file found');
|
|
data.append('attachments[]', createReadStream(file));
|
|
}
|
|
|
|
this.logger.verbose('get client to instance: ' + this.provider.instanceName);
|
|
const config = {
|
|
method: 'post',
|
|
maxBodyLength: Infinity,
|
|
url: `${this.provider.url}/api/v1/accounts/${this.provider.account_id}/conversations/${conversation.id}/messages`,
|
|
headers: {
|
|
api_access_token: this.provider.token,
|
|
...data.getHeaders(),
|
|
},
|
|
data: data,
|
|
};
|
|
|
|
this.logger.verbose('send data to chatwoot');
|
|
try {
|
|
const { data } = await axios.request(config);
|
|
|
|
this.logger.verbose('remove temp file');
|
|
unlinkSync(file);
|
|
|
|
this.logger.verbose('data sent');
|
|
return data;
|
|
} catch (error) {
|
|
this.logger.error(error);
|
|
}
|
|
}
|
|
|
|
public async sendAttachment(
|
|
waInstance: any,
|
|
number: string,
|
|
media: any,
|
|
caption?: string,
|
|
) {
|
|
this.logger.verbose('send attachment to instance: ' + waInstance.instanceName);
|
|
|
|
try {
|
|
this.logger.verbose('get media type');
|
|
const parts = media.split('/');
|
|
|
|
const fileName = decodeURIComponent(parts[parts.length - 1]);
|
|
this.logger.verbose('file name: ' + fileName);
|
|
|
|
const mimeType = mimeTypes.lookup(fileName).toString();
|
|
this.logger.verbose('mime type: ' + mimeType);
|
|
|
|
let type = 'document';
|
|
|
|
switch (mimeType.split('/')[0]) {
|
|
case 'image':
|
|
type = 'image';
|
|
break;
|
|
case 'video':
|
|
type = 'video';
|
|
break;
|
|
case 'audio':
|
|
type = 'audio';
|
|
break;
|
|
default:
|
|
type = 'document';
|
|
break;
|
|
}
|
|
|
|
this.logger.verbose('type: ' + type);
|
|
|
|
if (type === 'audio') {
|
|
this.logger.verbose('send audio to instance: ' + waInstance.instanceName);
|
|
const data: SendAudioDto = {
|
|
number: number,
|
|
audioMessage: {
|
|
audio: media,
|
|
},
|
|
options: {
|
|
delay: 1200,
|
|
presence: 'recording',
|
|
},
|
|
};
|
|
|
|
await waInstance?.audioWhatsapp(data);
|
|
|
|
this.logger.verbose('audio sent');
|
|
return;
|
|
}
|
|
|
|
this.logger.verbose('send media to instance: ' + waInstance.instanceName);
|
|
const data: SendMediaDto = {
|
|
number: number,
|
|
mediaMessage: {
|
|
mediatype: type as any,
|
|
fileName: fileName,
|
|
media: media,
|
|
},
|
|
options: {
|
|
delay: 1200,
|
|
presence: 'composing',
|
|
},
|
|
};
|
|
|
|
if (caption) {
|
|
this.logger.verbose('caption found');
|
|
data.mediaMessage.caption = caption;
|
|
}
|
|
|
|
await waInstance?.mediaMessage(data);
|
|
|
|
this.logger.verbose('media sent');
|
|
return;
|
|
} catch (error) {
|
|
this.logger.error(error);
|
|
}
|
|
}
|
|
|
|
public async receiveWebhook(instance: InstanceDto, body: any) {
|
|
try {
|
|
this.logger.verbose(
|
|
'receive webhook to chatwoot instance: ' + instance.instanceName,
|
|
);
|
|
const client = await this.clientCw(instance);
|
|
|
|
if (!client) {
|
|
this.logger.warn('client not found');
|
|
return null;
|
|
}
|
|
|
|
this.logger.verbose('check if is bot');
|
|
if (!body?.conversation || body.private) return { message: 'bot' };
|
|
|
|
this.logger.verbose('check if is group');
|
|
const chatId =
|
|
body.conversation.meta.sender?.phone_number?.replace('+', '') ||
|
|
body.conversation.meta.sender?.identifier;
|
|
const messageReceived = body.content;
|
|
const senderName = body?.sender?.name;
|
|
const waInstance = this.waMonitor.waInstances[instance.instanceName];
|
|
|
|
if (chatId === '123456' && body.message_type === 'outgoing') {
|
|
this.logger.verbose('check if is command');
|
|
|
|
const command = messageReceived.replace('/', '');
|
|
|
|
if (command === 'iniciar') {
|
|
this.logger.verbose('command iniciar found');
|
|
const state = waInstance?.connectionStatus?.state;
|
|
|
|
if (state !== 'open') {
|
|
this.logger.verbose('connect to whatsapp');
|
|
await waInstance.connectToWhatsapp();
|
|
} else {
|
|
this.logger.verbose('whatsapp already connected');
|
|
await this.createBotMessage(
|
|
instance,
|
|
`🚨 Instância ${body.inbox.name} já está conectada.`,
|
|
'incoming',
|
|
);
|
|
}
|
|
}
|
|
|
|
if (command === 'status') {
|
|
this.logger.verbose('command status found');
|
|
|
|
const state = waInstance?.connectionStatus?.state;
|
|
|
|
if (!state) {
|
|
this.logger.verbose('state not found');
|
|
await this.createBotMessage(
|
|
instance,
|
|
`⚠️ Instância ${body.inbox.name} não existe.`,
|
|
'incoming',
|
|
);
|
|
}
|
|
|
|
if (state) {
|
|
this.logger.verbose('state: ' + state + ' found');
|
|
await this.createBotMessage(
|
|
instance,
|
|
`⚠️ Status da instância ${body.inbox.name}: *${state}*`,
|
|
'incoming',
|
|
);
|
|
}
|
|
}
|
|
|
|
if (command === 'desconectar') {
|
|
this.logger.verbose('command desconectar found');
|
|
|
|
const msgLogout = `🚨 Desconectando Whatsapp da caixa de entrada *${body.inbox.name}*: `;
|
|
|
|
this.logger.verbose('send message to chatwoot');
|
|
await this.createBotMessage(instance, msgLogout, 'incoming');
|
|
|
|
this.logger.verbose('disconnect to whatsapp');
|
|
await waInstance?.client?.logout('Log out instance: ' + instance.instanceName);
|
|
await waInstance?.client?.ws?.close();
|
|
}
|
|
}
|
|
|
|
if (
|
|
body.message_type === 'outgoing' &&
|
|
body?.conversation?.messages?.length &&
|
|
chatId !== '123456'
|
|
) {
|
|
this.logger.verbose('check if is group');
|
|
|
|
this.messageCacheFile = path.join(
|
|
ROOT_DIR,
|
|
'store',
|
|
'chatwoot',
|
|
`${instance.instanceName}_cache.txt`,
|
|
);
|
|
this.logger.verbose('cache file path: ' + this.messageCacheFile);
|
|
|
|
this.messageCache = this.loadMessageCache();
|
|
this.logger.verbose('cache file loaded');
|
|
this.logger.verbose(this.messageCache);
|
|
|
|
this.logger.verbose('check if message is cached');
|
|
if (this.messageCache.has(body.id.toString())) {
|
|
this.logger.verbose('message is cached');
|
|
return { message: 'bot' };
|
|
}
|
|
|
|
this.logger.verbose('clear cache');
|
|
this.clearMessageCache();
|
|
|
|
this.logger.verbose('Format message to send');
|
|
let formatText: string;
|
|
if (senderName === null || senderName === undefined) {
|
|
formatText = messageReceived;
|
|
} else {
|
|
formatText = this.provider.sign_msg
|
|
? `*${senderName}:*\n\n${messageReceived}`
|
|
: messageReceived;
|
|
}
|
|
|
|
for (const message of body.conversation.messages) {
|
|
this.logger.verbose('check if message is media');
|
|
if (message.attachments && message.attachments.length > 0) {
|
|
this.logger.verbose('message is media');
|
|
for (const attachment of message.attachments) {
|
|
this.logger.verbose('send media to whatsapp');
|
|
if (!messageReceived) {
|
|
this.logger.verbose('message do not have text');
|
|
formatText = null;
|
|
}
|
|
|
|
await this.sendAttachment(
|
|
waInstance,
|
|
chatId,
|
|
attachment.data_url,
|
|
formatText,
|
|
);
|
|
}
|
|
} else {
|
|
this.logger.verbose('message is text');
|
|
|
|
this.logger.verbose('send text to whatsapp');
|
|
const data: SendTextDto = {
|
|
number: chatId,
|
|
textMessage: {
|
|
text: formatText,
|
|
},
|
|
options: {
|
|
delay: 1200,
|
|
presence: 'composing',
|
|
},
|
|
};
|
|
|
|
await waInstance?.textMessage(data);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (body.message_type === 'template' && body.content_type === 'input_csat') {
|
|
this.logger.verbose('check if is csat');
|
|
|
|
const data: SendTextDto = {
|
|
number: chatId,
|
|
textMessage: {
|
|
text: body.content,
|
|
},
|
|
options: {
|
|
delay: 1200,
|
|
presence: 'composing',
|
|
},
|
|
};
|
|
|
|
this.logger.verbose('send text to whatsapp');
|
|
|
|
await waInstance?.textMessage(data);
|
|
}
|
|
|
|
return { message: 'bot' };
|
|
} catch (error) {
|
|
this.logger.error(error);
|
|
|
|
return { message: 'bot' };
|
|
}
|
|
}
|
|
|
|
private isMediaMessage(message: any) {
|
|
this.logger.verbose('check if is media message');
|
|
const media = [
|
|
'imageMessage',
|
|
'documentMessage',
|
|
'documentWithCaptionMessage',
|
|
'audioMessage',
|
|
'videoMessage',
|
|
'stickerMessage',
|
|
];
|
|
|
|
const messageKeys = Object.keys(message);
|
|
|
|
const result = messageKeys.some((key) => media.includes(key));
|
|
|
|
this.logger.verbose('is media message: ' + result);
|
|
return result;
|
|
}
|
|
|
|
private getTypeMessage(msg: any) {
|
|
this.logger.verbose('get type message');
|
|
|
|
const types = {
|
|
conversation: msg.conversation,
|
|
imageMessage: msg.imageMessage?.caption,
|
|
videoMessage: msg.videoMessage?.caption,
|
|
extendedTextMessage: msg.extendedTextMessage?.text,
|
|
messageContextInfo: msg.messageContextInfo?.stanzaId,
|
|
stickerMessage: msg.stickerMessage?.fileSha256.toString('base64'),
|
|
documentMessage: msg.documentMessage?.caption,
|
|
documentWithCaptionMessage:
|
|
msg.documentWithCaptionMessage?.message?.documentMessage?.caption,
|
|
audioMessage: msg.audioMessage?.caption,
|
|
};
|
|
|
|
this.logger.verbose('type message: ' + types);
|
|
|
|
return types;
|
|
}
|
|
|
|
private getMessageContent(types: any) {
|
|
this.logger.verbose('get message content');
|
|
const typeKey = Object.keys(types).find((key) => types[key] !== undefined);
|
|
|
|
const result = typeKey ? types[typeKey] : undefined;
|
|
|
|
this.logger.verbose('message content: ' + result);
|
|
|
|
return result;
|
|
}
|
|
|
|
private getConversationMessage(msg: any) {
|
|
this.logger.verbose('get conversation message');
|
|
|
|
const types = this.getTypeMessage(msg);
|
|
|
|
const messageContent = this.getMessageContent(types);
|
|
|
|
this.logger.verbose('conversation message: ' + messageContent);
|
|
|
|
return messageContent;
|
|
}
|
|
|
|
public async eventWhatsapp(event: string, instance: InstanceDto, body: any) {
|
|
this.logger.verbose('event whatsapp to instance: ' + instance.instanceName);
|
|
try {
|
|
const client = await this.clientCw(instance);
|
|
|
|
if (!client) {
|
|
this.logger.warn('client not found');
|
|
return null;
|
|
}
|
|
|
|
const waInstance = this.waMonitor.waInstances[instance.instanceName];
|
|
|
|
if (!waInstance) {
|
|
this.logger.warn('wa instance not found');
|
|
return null;
|
|
}
|
|
|
|
if (event === 'messages.upsert') {
|
|
this.logger.verbose('event messages.upsert');
|
|
|
|
if (body.key.remoteJid === 'status@broadcast') {
|
|
this.logger.verbose('status broadcast found');
|
|
return;
|
|
}
|
|
|
|
this.logger.verbose('get conversation in chatwoot');
|
|
const getConversion = await this.createConversation(instance, body);
|
|
|
|
if (!getConversion) {
|
|
this.logger.warn('conversation not found');
|
|
return;
|
|
}
|
|
|
|
const messageType = body.key.fromMe ? 'outgoing' : 'incoming';
|
|
|
|
this.logger.verbose('message type: ' + messageType);
|
|
|
|
const isMedia = this.isMediaMessage(body.message);
|
|
|
|
this.logger.verbose('is media: ' + isMedia);
|
|
|
|
this.logger.verbose('get conversation message');
|
|
const bodyMessage = await this.getConversationMessage(body.message);
|
|
|
|
if (!bodyMessage && !isMedia) {
|
|
this.logger.warn('no body message found');
|
|
return;
|
|
}
|
|
|
|
this.logger.verbose('check if is media');
|
|
if (isMedia) {
|
|
this.logger.verbose('message is media');
|
|
|
|
this.logger.verbose('get base64 from media message');
|
|
const downloadBase64 = await waInstance?.getBase64FromMediaMessage({
|
|
message: {
|
|
...body,
|
|
},
|
|
});
|
|
|
|
const random = Math.random().toString(36).substring(7);
|
|
const nameFile = `${random}.${mimeTypes.extension(downloadBase64.mimetype)}`;
|
|
|
|
const fileData = Buffer.from(downloadBase64.base64, 'base64');
|
|
|
|
const fileName = `${path.join(waInstance?.storePath, 'temp', `${nameFile}`)}`;
|
|
|
|
this.logger.verbose('temp file name: ' + nameFile);
|
|
|
|
this.logger.verbose('create temp file');
|
|
writeFileSync(fileName, fileData, 'utf8');
|
|
|
|
this.logger.verbose('check if is group');
|
|
if (body.key.remoteJid.includes('@g.us')) {
|
|
this.logger.verbose('message is group');
|
|
|
|
const participantName = body.pushName;
|
|
|
|
let content: string;
|
|
|
|
if (!body.key.fromMe) {
|
|
this.logger.verbose('message is not from me');
|
|
content = `**${participantName}**\n\n${bodyMessage}`;
|
|
} else {
|
|
this.logger.verbose('message is from me');
|
|
content = `${bodyMessage}`;
|
|
}
|
|
|
|
this.logger.verbose('send data to chatwoot');
|
|
const send = await this.sendData(
|
|
getConversion,
|
|
fileName,
|
|
messageType,
|
|
content,
|
|
);
|
|
|
|
if (!send) {
|
|
this.logger.warn('message not sent');
|
|
return;
|
|
}
|
|
|
|
this.messageCacheFile = path.join(
|
|
ROOT_DIR,
|
|
'store',
|
|
'chatwoot',
|
|
`${instance.instanceName}_cache.txt`,
|
|
);
|
|
|
|
this.messageCache = this.loadMessageCache();
|
|
|
|
this.messageCache.add(send.id.toString());
|
|
|
|
this.logger.verbose('save message cache');
|
|
this.saveMessageCache();
|
|
|
|
return send;
|
|
} else {
|
|
this.logger.verbose('message is not group');
|
|
|
|
this.logger.verbose('send data to chatwoot');
|
|
const send = await this.sendData(
|
|
getConversion,
|
|
fileName,
|
|
messageType,
|
|
bodyMessage,
|
|
);
|
|
|
|
if (!send) {
|
|
this.logger.warn('message not sent');
|
|
return;
|
|
}
|
|
|
|
this.messageCacheFile = path.join(
|
|
ROOT_DIR,
|
|
'store',
|
|
'chatwoot',
|
|
`${instance.instanceName}_cache.txt`,
|
|
);
|
|
|
|
this.messageCache = this.loadMessageCache();
|
|
|
|
this.messageCache.add(send.id.toString());
|
|
|
|
this.logger.verbose('save message cache');
|
|
this.saveMessageCache();
|
|
|
|
return send;
|
|
}
|
|
}
|
|
|
|
this.logger.verbose('check if is group');
|
|
if (body.key.remoteJid.includes('@g.us')) {
|
|
this.logger.verbose('message is group');
|
|
const participantName = body.pushName;
|
|
|
|
let content: string;
|
|
|
|
if (!body.key.fromMe) {
|
|
this.logger.verbose('message is not from me');
|
|
content = `**${participantName}**\n\n${bodyMessage}`;
|
|
} else {
|
|
this.logger.verbose('message is from me');
|
|
content = `${bodyMessage}`;
|
|
}
|
|
|
|
this.logger.verbose('send data to chatwoot');
|
|
const send = await this.createMessage(
|
|
instance,
|
|
getConversion,
|
|
content,
|
|
messageType,
|
|
);
|
|
|
|
if (!send) {
|
|
this.logger.warn('message not sent');
|
|
return;
|
|
}
|
|
|
|
this.messageCacheFile = path.join(
|
|
ROOT_DIR,
|
|
'store',
|
|
'chatwoot',
|
|
`${instance.instanceName}_cache.txt`,
|
|
);
|
|
|
|
this.messageCache = this.loadMessageCache();
|
|
|
|
this.messageCache.add(send.id.toString());
|
|
|
|
this.logger.verbose('save message cache');
|
|
this.saveMessageCache();
|
|
|
|
return send;
|
|
} else {
|
|
this.logger.verbose('message is not group');
|
|
|
|
this.logger.verbose('send data to chatwoot');
|
|
const send = await this.createMessage(
|
|
instance,
|
|
getConversion,
|
|
bodyMessage,
|
|
messageType,
|
|
);
|
|
|
|
if (!send) {
|
|
this.logger.warn('message not sent');
|
|
return;
|
|
}
|
|
|
|
this.messageCacheFile = path.join(
|
|
ROOT_DIR,
|
|
'store',
|
|
'chatwoot',
|
|
`${instance.instanceName}_cache.txt`,
|
|
);
|
|
|
|
this.messageCache = this.loadMessageCache();
|
|
|
|
this.messageCache.add(send.id.toString());
|
|
|
|
this.logger.verbose('save message cache');
|
|
this.saveMessageCache();
|
|
|
|
return send;
|
|
}
|
|
}
|
|
|
|
if (event === 'status.instance') {
|
|
this.logger.verbose('event status.instance');
|
|
const data = body;
|
|
const inbox = await this.getInbox(instance);
|
|
|
|
if (!inbox) {
|
|
this.logger.warn('inbox not found');
|
|
return;
|
|
}
|
|
|
|
const msgStatus = `⚡️ Status da instância ${inbox.name}: ${data.status}`;
|
|
|
|
this.logger.verbose('send message to chatwoot');
|
|
await this.createBotMessage(instance, msgStatus, 'incoming');
|
|
}
|
|
|
|
if (event === 'connection.update') {
|
|
this.logger.verbose('event connection.update');
|
|
if (body.state === 'open') {
|
|
const msgConnection = `🚀 Conexão realizada com sucesso!`;
|
|
|
|
this.logger.verbose('send message to chatwoot');
|
|
await this.createBotMessage(instance, msgConnection, 'incoming');
|
|
}
|
|
}
|
|
|
|
if (event === 'contacts.update') {
|
|
this.logger.verbose('event contacts.update');
|
|
const data = body;
|
|
|
|
if (data.length) {
|
|
this.logger.verbose('contacts found');
|
|
for (const item of data) {
|
|
const number = item.id.split('@')[0];
|
|
const photo = item.profilePictureUrl || null;
|
|
this.logger.verbose('find contact in chatwoot');
|
|
const find = await this.findContact(instance, number);
|
|
|
|
if (find) {
|
|
this.logger.verbose('contact found');
|
|
|
|
this.logger.verbose('update contact in chatwoot');
|
|
await this.updateContact(instance, find.id, {
|
|
avatar_url: photo,
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (event === 'qrcode.updated') {
|
|
this.logger.verbose('event qrcode.updated');
|
|
if (body.statusCode === 500) {
|
|
this.logger.verbose('qrcode error');
|
|
const erroQRcode = `🚨 Limite de geração de QRCode atingido, para gerar um novo QRCode, envie a mensagem /iniciar novamente.`;
|
|
|
|
this.logger.verbose('send message to chatwoot');
|
|
return await this.createBotMessage(instance, erroQRcode, 'incoming');
|
|
} else {
|
|
this.logger.verbose('qrcode success');
|
|
const fileData = Buffer.from(
|
|
body?.qrcode.base64.replace('data:image/png;base64,', ''),
|
|
'base64',
|
|
);
|
|
|
|
const fileName = `${path.join(
|
|
waInstance?.storePath,
|
|
'temp',
|
|
`${`${instance}.png`}`,
|
|
)}`;
|
|
|
|
this.logger.verbose('temp file name: ' + fileName);
|
|
|
|
this.logger.verbose('create temp file');
|
|
writeFileSync(fileName, fileData, 'utf8');
|
|
|
|
this.logger.verbose('send qrcode to chatwoot');
|
|
await this.createBotQr(
|
|
instance,
|
|
'QRCode gerado com sucesso!',
|
|
'incoming',
|
|
fileName,
|
|
);
|
|
|
|
const msgQrCode = `⚡️ QRCode gerado com sucesso!\n\nDigitalize este código QR nos próximos 40 segundos:`;
|
|
|
|
this.logger.verbose('send message to chatwoot');
|
|
await this.createBotMessage(instance, msgQrCode, 'incoming');
|
|
}
|
|
}
|
|
} catch (error) {
|
|
this.logger.error(error);
|
|
}
|
|
}
|
|
}
|