Merge branch 'develop' into fetch-profile

This commit is contained in:
Alan Mosko
2023-09-05 17:47:41 -03:00
committed by GitHub
123 changed files with 6462 additions and 1968 deletions

View File

@@ -1,14 +1,15 @@
import { Auth, ConfigService, Webhook } from '../../config/env.config';
import { InstanceDto } from '../dto/instance.dto';
import { name as apiName } from '../../../package.json';
import { verify, sign } from 'jsonwebtoken';
import { Logger } from '../../config/logger.config';
import { v4 } from 'uuid';
import { isJWT } from 'class-validator';
import { BadRequestException } from '../../exceptions';
import axios from 'axios';
import { WAMonitoringService } from './monitor.service';
import { isJWT } from 'class-validator';
import { sign, verify } from 'jsonwebtoken';
import { v4 } from 'uuid';
import { name as apiName } from '../../../package.json';
import { Auth, ConfigService, Webhook } from '../../config/env.config';
import { Logger } from '../../config/logger.config';
import { BadRequestException } from '../../exceptions';
import { InstanceDto } from '../dto/instance.dto';
import { RepositoryBroker } from '../repository/repository.manager';
import { WAMonitoringService } from './monitor.service';
export type JwtPayload = {
instanceName: string;
@@ -63,9 +64,7 @@ export class AuthService {
private async apikey(instance: InstanceDto, token?: string) {
const apikey = token ? token : v4().toUpperCase();
this.logger.verbose(
token ? 'APIKEY defined: ' + apikey : 'APIKEY created: ' + apikey,
);
this.logger.verbose(token ? 'APIKEY defined: ' + apikey : 'APIKEY created: ' + apikey);
const auth = await this.repository.auth.create({ apikey }, instance.instanceName);
@@ -101,13 +100,9 @@ export class AuthService {
public async generateHash(instance: InstanceDto, token?: string) {
const options = this.configService.get<Auth>('AUTHENTICATION');
this.logger.verbose(
'generating hash ' + options.TYPE + ' to instance: ' + instance.instanceName,
);
this.logger.verbose('generating hash ' + options.TYPE + ' to instance: ' + instance.instanceName);
return (await this[options.TYPE](instance, token)) as
| { jwt: string }
| { apikey: string };
return (await this[options.TYPE](instance, token)) as { jwt: string } | { apikey: string };
}
public async refreshToken({ oldToken }: OldToken) {
@@ -150,10 +145,7 @@ export class AuthService {
try {
this.logger.verbose('checking webhook');
const webhook = await this.repository.webhook.find(decode.instanceName);
if (
webhook?.enabled &&
this.configService.get<Webhook>('WEBHOOK').EVENTS.NEW_JWT_TOKEN
) {
if (webhook?.enabled && this.configService.get<Webhook>('WEBHOOK').EVENTS.NEW_JWT_TOKEN) {
this.logger.verbose('sending webhook');
const httpService = axios.create({ baseURL: webhook.url });

View File

@@ -0,0 +1,230 @@
import axios from 'axios';
import { writeFileSync } from 'fs';
import path from 'path';
import { ConfigService, HttpServer } from '../../config/env.config';
import { Logger } from '../../config/logger.config';
import { ChamaaiDto } from '../dto/chamaai.dto';
import { InstanceDto } from '../dto/instance.dto';
import { ChamaaiRaw } from '../models';
import { Events } from '../types/wa.types';
import { WAMonitoringService } from './monitor.service';
export class ChamaaiService {
constructor(private readonly waMonitor: WAMonitoringService, private readonly configService: ConfigService) {}
private readonly logger = new Logger(ChamaaiService.name);
public create(instance: InstanceDto, data: ChamaaiDto) {
this.logger.verbose('create chamaai: ' + instance.instanceName);
this.waMonitor.waInstances[instance.instanceName].setChamaai(data);
return { chamaai: { ...instance, chamaai: data } };
}
public async find(instance: InstanceDto): Promise<ChamaaiRaw> {
try {
this.logger.verbose('find chamaai: ' + instance.instanceName);
const result = await this.waMonitor.waInstances[instance.instanceName].findChamaai();
if (Object.keys(result).length === 0) {
throw new Error('Chamaai not found');
}
return result;
} catch (error) {
return { enabled: false, url: '', token: '', waNumber: '', answerByAudio: false };
}
}
private getTypeMessage(msg: any) {
this.logger.verbose('get type message');
const types = {
conversation: msg.conversation,
extendedTextMessage: msg.extendedTextMessage?.text,
};
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;
}
private calculateTypingTime(text: string) {
const wordsPerMinute = 100;
const wordCount = text.split(' ').length;
const typingTimeInMinutes = wordCount / wordsPerMinute;
const typingTimeInMilliseconds = typingTimeInMinutes * 60;
return typingTimeInMilliseconds;
}
private convertToMilliseconds(count: number) {
const averageCharactersPerSecond = 15;
const characterCount = count;
const speakingTimeInSeconds = characterCount / averageCharactersPerSecond;
return speakingTimeInSeconds;
}
private getRegexPatterns() {
const patternsToCheck = [
'.*atend.*humano.*',
'.*falar.*com.*um.*humano.*',
'.*fala.*humano.*',
'.*atend.*humano.*',
'.*fala.*atend.*',
'.*preciso.*ajuda.*',
'.*quero.*suporte.*',
'.*preciso.*assiste.*',
'.*ajuda.*atend.*',
'.*chama.*atendente.*',
'.*suporte.*urgente.*',
'.*atend.*por.*favor.*',
'.*quero.*falar.*com.*alguém.*',
'.*falar.*com.*um.*humano.*',
'.*transfer.*humano.*',
'.*transfer.*atend.*',
'.*equipe.*humano.*',
'.*suporte.*humano.*',
];
const regexPatterns = patternsToCheck.map((pattern) => new RegExp(pattern, 'iu'));
return regexPatterns;
}
public async sendChamaai(instance: InstanceDto, remoteJid: string, msg: any) {
const content = this.getConversationMessage(msg.message);
const msgType = msg.messageType;
const find = await this.find(instance);
const url = find.url;
const token = find.token;
const waNumber = find.waNumber;
const answerByAudio = find.answerByAudio;
if (!content && msgType !== 'audioMessage') {
return;
}
let data;
let endpoint;
if (msgType === 'audioMessage') {
const downloadBase64 = await this.waMonitor.waInstances[instance.instanceName].getBase64FromMediaMessage({
message: {
...msg,
},
});
const random = Math.random().toString(36).substring(7);
const nameFile = `${random}.ogg`;
const fileData = Buffer.from(downloadBase64.base64, 'base64');
const fileName = `${path.join(
this.waMonitor.waInstances[instance.instanceName].storePath,
'temp',
`${nameFile}`,
)}`;
writeFileSync(fileName, fileData, 'utf8');
const urlServer = this.configService.get<HttpServer>('SERVER').URL;
const url = `${urlServer}/store/temp/${nameFile}`;
data = {
waNumber: waNumber,
audioUrl: url,
queryNumber: remoteJid.split('@')[0],
answerByAudio: answerByAudio,
};
endpoint = 'processMessageAudio';
} else {
data = {
waNumber: waNumber,
question: content,
queryNumber: remoteJid.split('@')[0],
answerByAudio: answerByAudio,
};
endpoint = 'processMessageText';
}
const request = await axios.post(`${url}/${endpoint}`, data, {
headers: {
Authorization: `${token}`,
},
});
const answer = request.data?.answer;
const type = request.data?.type;
const characterCount = request.data?.characterCount;
if (answer) {
if (type === 'text') {
this.waMonitor.waInstances[instance.instanceName].textMessage({
number: remoteJid.split('@')[0],
options: {
delay: this.calculateTypingTime(answer) * 1000 || 1000,
presence: 'composing',
linkPreview: false,
quoted: {
key: msg.key,
message: msg.message,
},
},
textMessage: {
text: answer,
},
});
}
if (type === 'audio') {
this.waMonitor.waInstances[instance.instanceName].audioWhatsapp({
number: remoteJid.split('@')[0],
options: {
delay: characterCount ? this.convertToMilliseconds(characterCount) * 1000 || 1000 : 1000,
presence: 'recording',
encoding: true,
},
audioMessage: {
audio: answer,
},
});
}
if (this.getRegexPatterns().some((pattern) => pattern.test(answer))) {
this.waMonitor.waInstances[instance.instanceName].sendDataWebhook(Events.CHAMA_AI_ACTION, {
remoteJid: remoteJid,
message: msg,
answer: answer,
action: 'transfer',
});
}
}
}
}

View File

@@ -1,18 +1,17 @@
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 { createReadStream, readFileSync, unlinkSync, writeFileSync } from 'fs';
import mimeTypes from 'mime-types';
import { SendAudioDto } from '../dto/sendMessage.dto';
import { SendMediaDto } from '../dto/sendMessage.dto';
import path from 'path';
import { ConfigService } from '../../config/env.config';
import { Logger } from '../../config/logger.config';
import { ROOT_DIR } from '../../config/path.config';
import { ConfigService, HttpServer } from '../../config/env.config';
import { ChatwootDto } from '../dto/chatwoot.dto';
import { InstanceDto } from '../dto/instance.dto';
import { SendAudioDto, SendMediaDto, SendTextDto } from '../dto/sendMessage.dto';
import { WAMonitoringService } from './monitor.service';
export class ChatwootService {
private messageCacheFile: string;
@@ -22,10 +21,7 @@ export class ChatwootService {
private provider: any;
constructor(
private readonly waMonitor: WAMonitoringService,
private readonly configService: ConfigService,
) {
constructor(private readonly waMonitor: WAMonitoringService, private readonly configService: ConfigService) {
this.messageCache = new Set();
}
@@ -56,9 +52,7 @@ export class ChatwootService {
private async getProvider(instance: InstanceDto) {
this.logger.verbose('get provider to instance: ' + instance.instanceName);
try {
const provider = await this.waMonitor.waInstances[
instance.instanceName
].findChatwoot();
const provider = await this.waMonitor.waInstances[instance.instanceName].findChatwoot();
if (!provider) {
this.logger.warn('provider not found');
@@ -154,6 +148,7 @@ export class ChatwootService {
inboxName: string,
webhookUrl: string,
qrcode: boolean,
number: string,
) {
this.logger.verbose('init instance chatwoot: ' + instance.instanceName);
@@ -170,9 +165,7 @@ export class ChatwootService {
});
this.logger.verbose('check duplicate inbox');
const checkDuplicate = findInbox.payload
.map((inbox) => inbox.name)
.includes(inboxName);
const checkDuplicate = findInbox.payload.map((inbox) => inbox.name).includes(inboxName);
let inboxId: number;
@@ -218,6 +211,7 @@ export class ChatwootService {
inboxId,
false,
'EvolutionAPI',
'https://evolution-api.com/files/evolution-api-favicon.png',
)) as any);
if (!contact) {
@@ -229,12 +223,18 @@ export class ChatwootService {
if (qrcode) {
this.logger.verbose('create conversation in chatwoot');
const data = {
contact_id: contactId.toString(),
inbox_id: inboxId.toString(),
};
if (this.provider.conversation_pending) {
data['status'] = 'pending';
}
const conversation = await client.conversations.create({
accountId: this.provider.account_id,
data: {
contact_id: contactId.toString(),
inbox_id: inboxId.toString(),
},
data,
});
if (!conversation) {
@@ -243,11 +243,18 @@ export class ChatwootService {
}
this.logger.verbose('create message for init instance in chatwoot');
let contentMsg = 'init';
if (number) {
contentMsg = `init:${number}`;
}
const message = await client.messages.create({
accountId: this.provider.account_id,
conversationId: conversation.id,
data: {
content: '/init',
content: contentMsg,
message_type: 'outgoing',
},
});
@@ -269,6 +276,7 @@ export class ChatwootService {
isGroup: boolean,
name?: string,
avatar_url?: string,
jid?: string,
) {
this.logger.verbose('create contact to instance: ' + instance.instanceName);
@@ -286,6 +294,7 @@ export class ChatwootService {
inbox_id: inboxId,
name: name || phoneNumber,
phone_number: `+${phoneNumber}`,
identifier: jid,
avatar_url: avatar_url,
};
} else {
@@ -410,28 +419,25 @@ export class ChatwootService {
if (isGroup) {
this.logger.verbose('get group name');
const group = await this.waMonitor.waInstances[
instance.instanceName
].client.groupMetadata(chatId);
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,
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,
});
if (!findParticipant.name || findParticipant.name === chatId) {
await this.updateContact(instance, findParticipant.id, {
name: body.pushName,
avatar_url: picture_url.profilePictureUrl || null,
});
}
} else {
await this.createContact(
instance,
@@ -440,28 +446,25 @@ export class ChatwootService {
false,
body.pushName,
picture_url.profilePictureUrl || null,
body.key.participant,
);
}
}
this.logger.verbose('find or create contact in chatwoot');
const picture_url = await this.waMonitor.waInstances[
instance.instanceName
].profilePicture(chatId);
const picture_url = await this.waMonitor.waInstances[instance.instanceName].profilePicture(chatId);
const findContact = await this.findContact(instance, chatId);
let contact: any;
if (body.key.fromMe) {
contact = findContact;
} else {
if (findContact) {
contact = await this.updateContact(instance, findContact.id, {
name: nameContact,
avatar_url: picture_url.profilePictureUrl || null,
});
} else {
const jid = isGroup ? null : body.key.remoteJid;
contact = await this.createContact(
instance,
chatId,
@@ -469,6 +472,31 @@ export class ChatwootService {
isGroup,
nameContact,
picture_url.profilePictureUrl || null,
jid,
);
}
} else {
if (findContact) {
if (!findContact.name || findContact.name === chatId) {
contact = await this.updateContact(instance, findContact.id, {
name: nameContact,
avatar_url: picture_url.profilePictureUrl || null,
});
} else {
contact = await this.updateContact(instance, findContact.id, {
avatar_url: picture_url.profilePictureUrl || null,
});
}
} else {
const jid = isGroup ? null : body.key.remoteJid;
contact = await this.createContact(
instance,
chatId,
filterInbox.id,
isGroup,
nameContact,
picture_url.profilePictureUrl || null,
jid,
);
}
}
@@ -478,8 +506,7 @@ export class ChatwootService {
return null;
}
const contactId =
contact?.payload?.id || contact?.payload?.contact?.id || contact?.id;
const contactId = contact?.payload?.id || contact?.payload?.contact?.id || contact?.id;
if (!body.key.fromMe && contact.name === chatId && nameContact !== chatId) {
this.logger.verbose('update contact name in chatwoot');
@@ -495,11 +522,26 @@ export class ChatwootService {
})) as any;
if (contactConversations) {
let conversation: any;
if (this.provider.reopen_conversation) {
conversation = contactConversations.payload.find((conversation) => conversation.inbox_id == filterInbox.id);
if (this.provider.conversation_pending) {
await client.conversations.toggleStatus({
accountId: this.provider.account_id,
conversationId: conversation.id,
data: {
status: 'pending',
},
});
}
} else {
conversation = contactConversations.payload.find(
(conversation) => conversation.status !== 'resolved' && conversation.inbox_id == filterInbox.id,
);
}
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;
@@ -507,12 +549,18 @@ export class ChatwootService {
}
this.logger.verbose('create conversation in chatwoot');
const data = {
contact_id: contactId.toString(),
inbox_id: filterInbox.id.toString(),
};
if (this.provider.conversation_pending) {
data['status'] = 'pending';
}
const conversation = await client.conversations.create({
accountId: this.provider.account_id,
data: {
contact_id: `${contactId}`,
inbox_id: `${filterInbox.id}`,
},
data,
});
if (!conversation) {
@@ -548,9 +596,7 @@ export class ChatwootService {
}
this.logger.verbose('find inbox by name');
const findByName = inbox.payload.find(
(inbox) => inbox.name === instance.instanceName,
);
const findByName = inbox.payload.find((inbox) => inbox.name === instance.instanceName.split('-cwId-')[0]);
if (!findByName) {
this.logger.warn('inbox not found');
@@ -652,8 +698,7 @@ export class ChatwootService {
this.logger.verbose('find conversation by contact id');
const conversation = findConversation.data.payload.find(
(conversation) =>
conversation?.meta?.sender?.id === contact.id && conversation.status === 'open',
(conversation) => conversation?.meta?.sender?.id === contact.id && conversation.status === 'open',
);
if (!conversation) {
@@ -773,8 +818,7 @@ export class ChatwootService {
this.logger.verbose('find conversation by contact id');
const conversation = findConversation.data.payload.find(
(conversation) =>
conversation?.meta?.sender?.id === contact.id && conversation.status === 'open',
(conversation) => conversation?.meta?.sender?.id === contact.id && conversation.status === 'open',
);
if (!conversation) {
@@ -824,12 +868,7 @@ export class ChatwootService {
}
}
public async sendAttachment(
waInstance: any,
number: string,
media: any,
caption?: string,
) {
public async sendAttachment(waInstance: any, number: string, media: any, caption?: string) {
this.logger.verbose('send attachment to instance: ' + waInstance.instanceName);
try {
@@ -910,9 +949,10 @@ export class ChatwootService {
public async receiveWebhook(instance: InstanceDto, body: any) {
try {
this.logger.verbose(
'receive webhook to chatwoot instance: ' + instance.instanceName,
);
// espera 500ms para evitar duplicidade de mensagens
await new Promise((resolve) => setTimeout(resolve, 500));
this.logger.verbose('receive webhook to chatwoot instance: ' + instance.instanceName);
const client = await this.clientCw(instance);
if (!client) {
@@ -921,12 +961,11 @@ export class ChatwootService {
}
this.logger.verbose('check if is bot');
if (!body?.conversation || body.private) return { message: 'bot' };
if (!body?.conversation || body.private || body.event === 'message_updated') return { message: 'bot' };
this.logger.verbose('check if is group');
const chatId =
body.conversation.meta.sender?.phone_number?.replace('+', '') ||
body.conversation.meta.sender?.identifier;
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];
@@ -936,20 +975,17 @@ export class ChatwootService {
const command = messageReceived.replace('/', '');
if (command === 'init' || command === 'iniciar') {
if (command.includes('init') || command.includes('iniciar')) {
this.logger.verbose('command init found');
const state = waInstance?.connectionStatus?.state;
if (state !== 'open') {
this.logger.verbose('connect to whatsapp');
await waInstance.connectToWhatsapp();
const number = command.split(':')[1];
await waInstance.connectToWhatsapp(number);
} else {
this.logger.verbose('whatsapp already connected');
await this.createBotMessage(
instance,
`🚨 ${body.inbox.name} instance is connected.`,
'incoming',
);
await this.createBotMessage(instance, `🚨 ${body.inbox.name} instance is connected.`, 'incoming');
}
}
@@ -960,20 +996,12 @@ export class ChatwootService {
if (!state) {
this.logger.verbose('state not found');
await this.createBotMessage(
instance,
`⚠️ ${body.inbox.name} instance not found.`,
'incoming',
);
await this.createBotMessage(instance, `⚠️ ${body.inbox.name} instance not found.`, 'incoming');
}
if (state) {
this.logger.verbose('state: ' + state + ' found');
await this.createBotMessage(
instance,
`⚠️ ${body.inbox.name} instance status: *${state}*`,
'incoming',
);
await this.createBotMessage(instance, `⚠️ ${body.inbox.name} instance status: *${state}*`, 'incoming');
}
}
@@ -989,48 +1017,12 @@ export class ChatwootService {
await waInstance?.client?.logout('Log out instance: ' + instance.instanceName);
await waInstance?.client?.ws?.close();
}
if (command.includes('#inbox_whatsapp')) {
const urlServer = this.configService.get<HttpServer>('SERVER').URL;
const apiKey = this.configService.get('AUTHENTICATION').API_KEY.KEY;
const data = {
instanceName: command.split(':')[1],
qrcode: true,
chatwoot_account_id: this.provider.account_id,
chatwoot_token: this.provider.token,
chatwoot_url: this.provider.url,
chatwoot_sign_msg: this.provider.sign_msg,
};
const config = {
method: 'post',
maxBodyLength: Infinity,
url: `${urlServer}/instance/create`,
headers: {
'Content-Type': 'application/json',
apikey: apiKey,
},
data: data,
};
await axios.request(config);
}
}
if (
body.message_type === 'outgoing' &&
body?.conversation?.messages?.length &&
chatId !== '123456'
) {
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.messageCacheFile = path.join(ROOT_DIR, 'store', 'chatwoot', `${instance.instanceName}_cache.txt`);
this.logger.verbose('cache file path: ' + this.messageCacheFile);
this.messageCache = this.loadMessageCache();
@@ -1051,9 +1043,7 @@ export class ChatwootService {
if (senderName === null || senderName === undefined) {
formatText = messageReceived;
} else {
formatText = this.provider.sign_msg
? `*${senderName}:*\n\n${messageReceived}`
: messageReceived;
formatText = this.provider.sign_msg ? `*${senderName}:*\n${messageReceived}` : messageReceived;
}
for (const message of body.conversation.messages) {
@@ -1067,12 +1057,7 @@ export class ChatwootService {
formatText = null;
}
await this.sendAttachment(
waInstance,
chatId,
attachment.data_url,
formatText,
);
await this.sendAttachment(waInstance, chatId, attachment.data_url, formatText);
}
} else {
this.logger.verbose('message is text');
@@ -1094,17 +1079,13 @@ export class ChatwootService {
}
}
if (
body.message_type === 'template' &&
body.content_type === 'input_csat' &&
body.event === 'message_created'
) {
this.logger.verbose('check if is csat');
if (body.message_type === 'template' && body.event === 'message_created') {
this.logger.verbose('check if is template');
const data: SendTextDto = {
number: chatId,
textMessage: {
text: body.content,
text: body.content.replace(/\\\r\n|\\\n|\n/g, '\n'),
},
options: {
delay: 1200,
@@ -1153,13 +1134,14 @@ export class ChatwootService {
videoMessage: msg.videoMessage?.caption,
extendedTextMessage: msg.extendedTextMessage?.text,
messageContextInfo: msg.messageContextInfo?.stanzaId,
stickerMessage: msg.stickerMessage?.fileSha256.toString('base64'),
stickerMessage: undefined,
documentMessage: msg.documentMessage?.caption,
documentWithCaptionMessage:
msg.documentWithCaptionMessage?.message?.documentMessage?.caption,
documentWithCaptionMessage: msg.documentWithCaptionMessage?.message?.documentMessage?.caption,
audioMessage: msg.audioMessage?.caption,
contactMessage: msg.contactMessage?.vcard,
contactsArrayMessage: msg.contactsArrayMessage,
locationMessage: msg.locationMessage,
liveLocationMessage: msg.liveLocationMessage,
};
this.logger.verbose('type message: ' + types);
@@ -1173,6 +1155,21 @@ export class ChatwootService {
const result = typeKey ? types[typeKey] : undefined;
if (typeKey === 'locationMessage' || typeKey === 'liveLocationMessage') {
const latitude = result.degreesLatitude;
const longitude = result.degreesLongitude;
const formattedLocation = `**Location:**
**latitude:** ${latitude}
**longitude:** ${longitude}
https://www.google.com/maps/search/?api=1&query=${latitude},${longitude}
`;
this.logger.verbose('message content: ' + formattedLocation);
return formattedLocation;
}
if (typeKey === 'contactMessage') {
const vCardData = result.split('\n');
const contactInfo = {};
@@ -1276,6 +1273,16 @@ export class ChatwootService {
return;
}
this.logger.verbose('get conversation message');
const bodyMessage = await this.getConversationMessage(body.message);
const isMedia = this.isMediaMessage(body.message);
if (!bodyMessage && !isMedia) {
this.logger.warn('no body message found');
return;
}
this.logger.verbose('get conversation in chatwoot');
const getConversion = await this.createConversation(instance, body);
@@ -1288,18 +1295,8 @@ export class ChatwootService {
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');
@@ -1333,31 +1330,21 @@ export class ChatwootService {
if (!body.key.fromMe) {
this.logger.verbose('message is not from me');
content = `**${participantName}**\n\n${bodyMessage}`;
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,
);
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.messageCacheFile = path.join(ROOT_DIR, 'store', 'chatwoot', `${instance.instanceName}_cache.txt`);
this.messageCache = this.loadMessageCache();
@@ -1371,24 +1358,14 @@ export class ChatwootService {
this.logger.verbose('message is not group');
this.logger.verbose('send data to chatwoot');
const send = await this.sendData(
getConversion,
fileName,
messageType,
bodyMessage,
);
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.messageCacheFile = path.join(ROOT_DIR, 'store', 'chatwoot', `${instance.instanceName}_cache.txt`);
this.messageCache = this.loadMessageCache();
@@ -1417,24 +1394,14 @@ export class ChatwootService {
}
this.logger.verbose('send data to chatwoot');
const send = await this.createMessage(
instance,
getConversion,
content,
messageType,
);
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.messageCacheFile = path.join(ROOT_DIR, 'store', 'chatwoot', `${instance.instanceName}_cache.txt`);
this.messageCache = this.loadMessageCache();
@@ -1448,24 +1415,14 @@ export class ChatwootService {
this.logger.verbose('message is not group');
this.logger.verbose('send data to chatwoot');
const send = await this.createMessage(
instance,
getConversion,
bodyMessage,
messageType,
);
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.messageCacheFile = path.join(ROOT_DIR, 'store', 'chatwoot', `${instance.instanceName}_cache.txt`);
this.messageCache = this.loadMessageCache();
@@ -1494,37 +1451,30 @@ export class ChatwootService {
await this.createBotMessage(instance, msgStatus, 'incoming');
}
if (event === 'connection.update') {
this.logger.verbose('event connection.update');
// if (event === 'connection.update') {
// this.logger.verbose('event connection.update');
if (body.status === 'open') {
const msgConnection = `🚀 Connection successfully established!`;
// if (body.status === 'open') {
// const msgConnection = `🚀 Connection successfully established!`;
this.logger.verbose('send message to chatwoot');
await this.createBotMessage(instance, msgConnection, 'incoming');
}
}
// this.logger.verbose('send message to chatwoot');
// await this.createBotMessage(instance, msgConnection, 'incoming');
// }
// }
if (event === 'qrcode.updated') {
this.logger.verbose('event qrcode.updated');
if (body.statusCode === 500) {
this.logger.verbose('qrcode error');
const erroQRcode = `🚨 QRCode generation limit reached, to generate a new QRCode, send the /init message again.`;
const erroQRcode = `🚨 QRCode generation limit reached, to generate a new QRCode, send the 'init' message again.`;
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 fileData = Buffer.from(body?.qrcode.base64.replace('data:image/png;base64,', ''), 'base64');
const fileName = `${path.join(
waInstance?.storePath,
'temp',
`${`${instance}.png`}`,
)}`;
const fileName = `${path.join(waInstance?.storePath, 'temp', `${`${instance}.png`}`)}`;
this.logger.verbose('temp file name: ' + fileName);
@@ -1532,14 +1482,18 @@ export class ChatwootService {
writeFileSync(fileName, fileData, 'utf8');
this.logger.verbose('send qrcode to chatwoot');
await this.createBotQr(
instance,
'QRCode successfully generated!',
'incoming',
fileName,
);
await this.createBotQr(instance, 'QRCode successfully generated!', 'incoming', fileName);
const msgQrCode = `⚡️ QRCode successfully generated!\n\nScan this QR code within the next 40 seconds:`;
let msgQrCode = `⚡️ QRCode successfully generated!\n\nScan this QR code within the next 40 seconds.`;
if (body?.qrcode?.pairingCode) {
msgQrCode =
msgQrCode +
`\n\n*Pairing Code:* ${body.qrcode.pairingCode.substring(0, 4)}-${body.qrcode.pairingCode.substring(
4,
8,
)}`;
}
this.logger.verbose('send message to chatwoot');
await this.createBotMessage(instance, msgQrCode, 'incoming');

View File

@@ -1,23 +1,26 @@
import { opendirSync, readdirSync, rmSync } from 'fs';
import { WAStartupService } from './whatsapp.service';
import { INSTANCE_DIR, STORE_DIR } from '../../config/path.config';
import EventEmitter2 from 'eventemitter2';
import { join } from 'path';
import { Logger } from '../../config/logger.config';
import {
Auth,
ConfigService,
Database,
DelInstance,
HttpServer,
Redis,
} from '../../config/env.config';
import { RepositoryBroker } from '../repository/repository.manager';
import { NotFoundException } from '../../exceptions';
import { Db } from 'mongodb';
import { initInstance } from '../whatsapp.module';
import { RedisCache } from '../../db/redis.client';
import { execSync } from 'child_process';
import EventEmitter2 from 'eventemitter2';
import { opendirSync, readdirSync, rmSync } from 'fs';
import { Db } from 'mongodb';
import { join } from 'path';
import { Auth, ConfigService, Database, DelInstance, HttpServer, Redis } from '../../config/env.config';
import { Logger } from '../../config/logger.config';
import { INSTANCE_DIR, STORE_DIR } from '../../config/path.config';
import { NotFoundException } from '../../exceptions';
import { dbserver } from '../../libs/db.connect';
import { RedisCache } from '../../libs/redis.client';
import {
AuthModel,
ChatwootModel,
ContactModel,
MessageModel,
MessageUpModel,
SettingsModel,
WebhookModel,
} from '../models';
import { RepositoryBroker } from '../repository/repository.manager';
import { WAStartupService } from './whatsapp.service';
export class WAMonitoringService {
constructor(
@@ -45,22 +48,20 @@ export class WAMonitoringService {
private dbInstance: Db;
private dbStore = dbserver;
private readonly logger = new Logger(WAMonitoringService.name);
public readonly waInstances: Record<string, WAStartupService> = {};
public delInstanceTime(instance: string) {
const time = this.configService.get<DelInstance>('DEL_INSTANCE');
if (typeof time === 'number' && time > 0) {
this.logger.verbose(
`Instance "${instance}" don't have connection, will be removed in ${time} minutes`,
);
this.logger.verbose(`Instance "${instance}" don't have connection, will be removed in ${time} minutes`);
setTimeout(async () => {
if (this.waInstances[instance]?.connectionStatus?.state !== 'open') {
if (this.waInstances[instance]?.connectionStatus?.state === 'connecting') {
await this.waInstances[instance]?.client?.logout(
'Log out instance: ' + instance,
);
await this.waInstances[instance]?.client?.logout('Log out instance: ' + instance);
this.waInstances[instance]?.client?.ws?.close();
this.waInstances[instance]?.client?.end(undefined);
delete this.waInstances[instance];
@@ -90,10 +91,10 @@ export class WAMonitoringService {
const findChatwoot = await this.waInstances[key].findChatwoot();
if (findChatwoot.enabled) {
if (findChatwoot && findChatwoot.enabled) {
chatwoot = {
...findChatwoot,
webhook_url: `${urlServer}/chatwoot/webhook/${key}`,
webhook_url: `${urlServer}/chatwoot/webhook/${encodeURIComponent(key)}`,
};
}
@@ -112,21 +113,16 @@ export class WAMonitoringService {
};
if (this.configService.get<Auth>('AUTHENTICATION').EXPOSE_IN_FETCH_INSTANCES) {
instanceData.instance['serverUrl'] =
this.configService.get<HttpServer>('SERVER').URL;
instanceData.instance['serverUrl'] = this.configService.get<HttpServer>('SERVER').URL;
instanceData.instance['apikey'] = (
await this.repository.auth.find(key)
).apikey;
instanceData.instance['apikey'] = (await this.repository.auth.find(key))?.apikey;
instanceData.instance['chatwoot'] = chatwoot;
}
instances.push(instanceData);
} else {
this.logger.verbose(
'instance: ' + key + ' - connectionStatus: ' + value.connectionStatus.state,
);
this.logger.verbose('instance: ' + key + ' - connectionStatus: ' + value.connectionStatus.state);
const instanceData = {
instance: {
@@ -136,12 +132,9 @@ export class WAMonitoringService {
};
if (this.configService.get<Auth>('AUTHENTICATION').EXPOSE_IN_FETCH_INSTANCES) {
instanceData.instance['serverUrl'] =
this.configService.get<HttpServer>('SERVER').URL;
instanceData.instance['serverUrl'] = this.configService.get<HttpServer>('SERVER').URL;
instanceData.instance['apikey'] = (
await this.repository.auth.find(key)
).apikey;
instanceData.instance['apikey'] = (await this.repository.auth.find(key))?.apikey;
instanceData.instance['chatwoot'] = chatwoot;
}
@@ -164,15 +157,11 @@ export class WAMonitoringService {
collections.forEach(async (collection) => {
const name = collection.namespace.replace(/^[\w-]+./, '');
await this.dbInstance.collection(name).deleteMany({
$or: [
{ _id: { $regex: /^app.state.*/ } },
{ _id: { $regex: /^session-.*/ } },
],
$or: [{ _id: { $regex: /^app.state.*/ } }, { _id: { $regex: /^session-.*/ } }],
});
this.logger.verbose('instance files deleted: ' + name);
});
} else if (this.redis.ENABLED) {
} else {
} else if (!this.redis.ENABLED) {
const dir = opendirSync(INSTANCE_DIR, { encoding: 'utf-8' });
for await (const dirent of dir) {
if (dirent.isDirectory()) {
@@ -210,6 +199,7 @@ export class WAMonitoringService {
this.logger.verbose('cleaning up instance in redis: ' + instanceName);
this.cache.reference = instanceName;
await this.cache.delAll();
this.cache.disconnect();
return;
}
@@ -218,11 +208,8 @@ export class WAMonitoringService {
}
public async cleaningStoreFiles(instanceName: string) {
this.logger.verbose('cleaning store files instance: ' + instanceName);
if (!this.db.ENABLED) {
const instance = this.waInstances[instanceName];
this.logger.verbose('cleaning store files instance: ' + instanceName);
rmSync(join(INSTANCE_DIR, instanceName), { recursive: true, force: true });
execSync(`rm -rf ${join(STORE_DIR, 'chats', instanceName)}`);
@@ -233,18 +220,34 @@ export class WAMonitoringService {
execSync(`rm -rf ${join(STORE_DIR, 'auth', 'apikey', instanceName + '.json')}`);
execSync(`rm -rf ${join(STORE_DIR, 'webhook', instanceName + '.json')}`);
execSync(`rm -rf ${join(STORE_DIR, 'chatwoot', instanceName + '*')}`);
execSync(`rm -rf ${join(STORE_DIR, 'chamaai', instanceName + '*')}`);
execSync(`rm -rf ${join(STORE_DIR, 'proxy', instanceName + '*')}`);
execSync(`rm -rf ${join(STORE_DIR, 'rabbitmq', instanceName + '*')}`);
execSync(`rm -rf ${join(STORE_DIR, 'typebot', instanceName + '*')}`);
execSync(`rm -rf ${join(STORE_DIR, 'websocket', instanceName + '*')}`);
execSync(`rm -rf ${join(STORE_DIR, 'settings', instanceName + '*')}`);
return;
}
this.logger.verbose('cleaning store database instance: ' + instanceName);
await AuthModel.deleteMany({ owner: instanceName });
await ContactModel.deleteMany({ owner: instanceName });
await MessageModel.deleteMany({ owner: instanceName });
await MessageUpModel.deleteMany({ owner: instanceName });
await AuthModel.deleteMany({ _id: instanceName });
await WebhookModel.deleteMany({ _id: instanceName });
await ChatwootModel.deleteMany({ _id: instanceName });
await SettingsModel.deleteMany({ _id: instanceName });
return;
}
public async loadInstance() {
this.logger.verbose('load instances');
const set = async (name: string) => {
const instance = new WAStartupService(
this.configService,
this.eventEmitter,
this.repository,
this.cache,
);
const instance = new WAStartupService(this.configService, this.eventEmitter, this.repository, this.cache);
instance.instanceName = name;
this.logger.verbose('instance loaded: ' + name);
@@ -264,8 +267,8 @@ export class WAMonitoringService {
keys.forEach(async (k) => await set(k.split(':')[1]));
} else {
this.logger.verbose('no instance keys found');
initInstance();
}
this.cache.disconnect();
return;
}
@@ -275,12 +278,9 @@ export class WAMonitoringService {
const collections: any[] = await this.dbInstance.collections();
if (collections.length > 0) {
this.logger.verbose('reading collections and setting instances');
collections.forEach(
async (coll) => await set(coll.namespace.replace(/^[\w-]+\./, '')),
);
collections.forEach(async (coll) => await set(coll.namespace.replace(/^[\w-]+\./, '')));
} else {
this.logger.verbose('no collections found');
initInstance();
}
return;
}
@@ -301,7 +301,6 @@ export class WAMonitoringService {
await set(dirent.name);
} else {
this.logger.verbose('no instance files found');
initInstance();
}
}
} catch (error) {
@@ -315,7 +314,9 @@ export class WAMonitoringService {
try {
this.logger.verbose('instance: ' + instanceName + ' - removing from memory');
this.waInstances[instanceName] = undefined;
} catch {}
} catch (error) {
this.logger.error(error);
}
try {
this.logger.verbose('request cleaning up instance: ' + instanceName);
@@ -340,11 +341,14 @@ export class WAMonitoringService {
this.logger.verbose('checking instances without connection');
this.eventEmitter.on('no.connection', async (instanceName) => {
try {
this.logger.verbose('instance: ' + instanceName + ' - removing from memory');
this.waInstances[instanceName] = undefined;
this.logger.verbose('logging out instance: ' + instanceName);
await this.waInstances[instanceName]?.client?.logout('Log out instance: ' + instanceName);
this.logger.verbose('request cleaning up instance: ' + instanceName);
this.cleaningUp(instanceName);
this.logger.verbose('close connection instance: ' + instanceName);
this.waInstances[instanceName]?.client?.ws?.close();
this.waInstances[instanceName].instance.qrcode = { count: 0 };
this.waInstances[instanceName].stateConnection.state = 'close';
} catch (error) {
this.logger.error({
localError: 'noConnection',

View File

@@ -0,0 +1,33 @@
import { Logger } from '../../config/logger.config';
import { InstanceDto } from '../dto/instance.dto';
import { ProxyDto } from '../dto/proxy.dto';
import { ProxyRaw } from '../models';
import { WAMonitoringService } from './monitor.service';
export class ProxyService {
constructor(private readonly waMonitor: WAMonitoringService) {}
private readonly logger = new Logger(ProxyService.name);
public create(instance: InstanceDto, data: ProxyDto) {
this.logger.verbose('create proxy: ' + instance.instanceName);
this.waMonitor.waInstances[instance.instanceName].setProxy(data);
return { proxy: { ...instance, proxy: data } };
}
public async find(instance: InstanceDto): Promise<ProxyRaw> {
try {
this.logger.verbose('find proxy: ' + instance.instanceName);
const result = await this.waMonitor.waInstances[instance.instanceName].findProxy();
if (Object.keys(result).length === 0) {
throw new Error('Proxy not found');
}
return result;
} catch (error) {
return { enabled: false, proxy: '' };
}
}
}

View File

@@ -0,0 +1,35 @@
import { Logger } from '../../config/logger.config';
import { initQueues } from '../../libs/amqp.server';
import { InstanceDto } from '../dto/instance.dto';
import { RabbitmqDto } from '../dto/rabbitmq.dto';
import { RabbitmqRaw } from '../models';
import { WAMonitoringService } from './monitor.service';
export class RabbitmqService {
constructor(private readonly waMonitor: WAMonitoringService) {}
private readonly logger = new Logger(RabbitmqService.name);
public create(instance: InstanceDto, data: RabbitmqDto) {
this.logger.verbose('create rabbitmq: ' + instance.instanceName);
this.waMonitor.waInstances[instance.instanceName].setRabbitmq(data);
initQueues(instance.instanceName, data.events);
return { rabbitmq: { ...instance, rabbitmq: data } };
}
public async find(instance: InstanceDto): Promise<RabbitmqRaw> {
try {
this.logger.verbose('find rabbitmq: ' + instance.instanceName);
const result = await this.waMonitor.waInstances[instance.instanceName].findRabbitmq();
if (Object.keys(result).length === 0) {
throw new Error('Rabbitmq not found');
}
return result;
} catch (error) {
return { enabled: false, events: [] };
}
}
}

View File

@@ -0,0 +1,32 @@
import { Logger } from '../../config/logger.config';
import { InstanceDto } from '../dto/instance.dto';
import { SettingsDto } from '../dto/settings.dto';
import { WAMonitoringService } from './monitor.service';
export class SettingsService {
constructor(private readonly waMonitor: WAMonitoringService) {}
private readonly logger = new Logger(SettingsService.name);
public create(instance: InstanceDto, data: SettingsDto) {
this.logger.verbose('create settings: ' + instance.instanceName);
this.waMonitor.waInstances[instance.instanceName].setSettings(data);
return { settings: { ...instance, settings: data } };
}
public async find(instance: InstanceDto): Promise<SettingsDto> {
try {
this.logger.verbose('find settings: ' + instance.instanceName);
const result = await this.waMonitor.waInstances[instance.instanceName].findSettings();
if (Object.keys(result).length === 0) {
throw new Error('Settings not found');
}
return result;
} catch (error) {
return { reject_call: false, msg_call: '', groups_ignore: false };
}
}
}

View File

@@ -0,0 +1,521 @@
import axios from 'axios';
import { Logger } from '../../config/logger.config';
import { InstanceDto } from '../dto/instance.dto';
import { Session, TypebotDto } from '../dto/typebot.dto';
import { MessageRaw } from '../models';
import { Events } from '../types/wa.types';
import { WAMonitoringService } from './monitor.service';
export class TypebotService {
constructor(private readonly waMonitor: WAMonitoringService) {}
private readonly logger = new Logger(TypebotService.name);
public create(instance: InstanceDto, data: TypebotDto) {
this.logger.verbose('create typebot: ' + instance.instanceName);
this.waMonitor.waInstances[instance.instanceName].setTypebot(data);
return { typebot: { ...instance, typebot: data } };
}
public async find(instance: InstanceDto): Promise<TypebotDto> {
try {
this.logger.verbose('find typebot: ' + instance.instanceName);
const result = await this.waMonitor.waInstances[instance.instanceName].findTypebot();
if (Object.keys(result).length === 0) {
throw new Error('Typebot not found');
}
return result;
} catch (error) {
return { enabled: false, url: '', typebot: '', expire: 0, sessions: [] };
}
}
public async changeStatus(instance: InstanceDto, data: any) {
const remoteJid = data.remoteJid;
const status = data.status;
const findData = await this.find(instance);
const session = findData.sessions.find((session) => session.remoteJid === remoteJid);
if (session) {
if (status === 'closed') {
findData.sessions.splice(findData.sessions.indexOf(session), 1);
const typebotData = {
enabled: true,
url: findData.url,
typebot: findData.typebot,
expire: findData.expire,
keyword_finish: findData.keyword_finish,
delay_message: findData.delay_message,
unknown_message: findData.unknown_message,
listening_from_me: findData.listening_from_me,
sessions: findData.sessions,
};
this.create(instance, typebotData);
return { typebot: { ...instance, typebot: typebotData } };
}
findData.sessions.map((session) => {
if (session.remoteJid === remoteJid) {
session.status = status;
}
});
}
const typebotData = {
enabled: true,
url: findData.url,
typebot: findData.typebot,
expire: findData.expire,
keyword_finish: findData.keyword_finish,
delay_message: findData.delay_message,
unknown_message: findData.unknown_message,
listening_from_me: findData.listening_from_me,
sessions: findData.sessions,
};
this.create(instance, typebotData);
this.waMonitor.waInstances[instance.instanceName].sendDataWebhook(Events.TYPEBOT_CHANGE_STATUS, {
remoteJid: remoteJid,
status: status,
url: findData.url,
typebot: findData.typebot,
session,
});
return { typebot: { ...instance, typebot: typebotData } };
}
public async startTypebot(instance: InstanceDto, data: any) {
const remoteJid = data.remoteJid;
const url = data.url;
const typebot = data.typebot;
const variables = data.variables;
const prefilledVariables = {
remoteJid: remoteJid,
};
variables.forEach((variable) => {
prefilledVariables[variable.name] = variable.value;
});
const id = Math.floor(Math.random() * 10000000000).toString();
const reqData = {
sessionId: id,
startParams: {
typebot: data.typebot,
prefilledVariables: prefilledVariables,
},
};
const request = await axios.post(data.url + '/api/v1/sendMessage', reqData);
await this.sendWAMessage(
instance,
remoteJid,
request.data.messages,
request.data.input,
request.data.clientSideActions,
);
this.waMonitor.waInstances[instance.instanceName].sendDataWebhook(Events.TYPEBOT_START, {
remoteJid: remoteJid,
url: url,
typebot: typebot,
variables: variables,
sessionId: id,
});
return {
typebot: {
...instance,
typebot: {
url: url,
remoteJid: remoteJid,
typebot: typebot,
variables: variables,
},
},
};
}
private getTypeMessage(msg: any) {
this.logger.verbose('get type message');
const types = {
conversation: msg.conversation,
extendedTextMessage: msg.extendedTextMessage?.text,
};
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 createNewSession(instance: InstanceDto, data: any) {
const id = Math.floor(Math.random() * 10000000000).toString();
const reqData = {
sessionId: id,
startParams: {
typebot: data.typebot,
prefilledVariables: {
remoteJid: data.remoteJid,
pushName: data.pushName,
instanceName: instance.instanceName,
},
},
};
const request = await axios.post(data.url + '/api/v1/sendMessage', reqData);
if (request.data.sessionId) {
data.sessions.push({
remoteJid: data.remoteJid,
sessionId: `${id}-${request.data.sessionId}`,
status: 'opened',
createdAt: Date.now(),
updateAt: Date.now(),
});
const typebotData = {
enabled: true,
url: data.url,
typebot: data.typebot,
expire: data.expire,
keyword_finish: data.keyword_finish,
delay_message: data.delay_message,
unknown_message: data.unknown_message,
listening_from_me: data.listening_from_me,
sessions: data.sessions,
};
this.create(instance, typebotData);
}
return request.data;
}
public async sendWAMessage(
instance: InstanceDto,
remoteJid: string,
messages: any[],
input: any[],
clientSideActions: any[],
) {
processMessages(this.waMonitor.waInstances[instance.instanceName], messages, input, clientSideActions).catch(
(err) => {
console.error('Erro ao processar mensagens:', err);
},
);
function findItemAndGetSecondsToWait(array, targetId) {
if (!array) return null;
for (const item of array) {
if (item.lastBubbleBlockId === targetId) {
return item.wait?.secondsToWaitFor;
}
}
return null;
}
async function processMessages(instance, messages, input, clientSideActions) {
for (const message of messages) {
const wait = findItemAndGetSecondsToWait(clientSideActions, message.id);
if (message.type === 'text') {
let formattedText = '';
let linkPreview = false;
for (const richText of message.content.richText) {
for (const element of richText.children) {
let text = '';
if (element.text) {
text = element.text;
}
if (element.bold) {
text = `*${text}*`;
}
if (element.italic) {
text = `_${text}_`;
}
if (element.underline) {
text = `~${text}~`;
}
if (element.url) {
const linkText = element.children[0].text;
text = `[${linkText}](${element.url})`;
linkPreview = true;
}
formattedText += text;
}
formattedText += '\n';
}
formattedText = formattedText.replace(/\n$/, '');
await instance.textMessage({
number: remoteJid.split('@')[0],
options: {
delay: wait ? wait * 1000 : instance.localTypebot.delay_message || 1000,
presence: 'composing',
linkPreview: linkPreview,
},
textMessage: {
text: formattedText,
},
});
}
if (message.type === 'image') {
await instance.mediaMessage({
number: remoteJid.split('@')[0],
options: {
delay: wait ? wait * 1000 : instance.localTypebot.delay_message || 1000,
presence: 'composing',
},
mediaMessage: {
mediatype: 'image',
media: message.content.url,
},
});
}
if (message.type === 'video') {
await instance.mediaMessage({
number: remoteJid.split('@')[0],
options: {
delay: wait ? wait * 1000 : instance.localTypebot.delay_message || 1000,
presence: 'composing',
},
mediaMessage: {
mediatype: 'video',
media: message.content.url,
},
});
}
if (message.type === 'audio') {
await instance.audioWhatsapp({
number: remoteJid.split('@')[0],
options: {
delay: wait ? wait * 1000 : instance.localTypebot.delay_message || 1000,
presence: 'recording',
encoding: true,
},
audioMessage: {
audio: message.content.url,
},
});
}
}
if (input) {
if (input.type === 'choice input') {
let formattedText = '';
const items = input.items;
for (const item of items) {
formattedText += `▶️ ${item.content}\n`;
}
formattedText = formattedText.replace(/\n$/, '');
await instance.textMessage({
number: remoteJid.split('@')[0],
options: {
delay: 1200,
presence: 'composing',
linkPreview: false,
},
textMessage: {
text: formattedText,
},
});
}
}
}
}
public async sendTypebot(instance: InstanceDto, remoteJid: string, msg: MessageRaw) {
const findTypebot = await this.find(instance);
const url = findTypebot.url;
const typebot = findTypebot.typebot;
const sessions = (findTypebot.sessions as Session[]) ?? [];
const expire = findTypebot.expire;
const keyword_finish = findTypebot.keyword_finish;
const delay_message = findTypebot.delay_message;
const unknown_message = findTypebot.unknown_message;
const listening_from_me = findTypebot.listening_from_me;
const session = sessions.find((session) => session.remoteJid === remoteJid);
if (session && expire && expire > 0) {
const now = Date.now();
const diff = now - session.updateAt;
const diffInMinutes = Math.floor(diff / 1000 / 60);
if (diffInMinutes > expire) {
sessions.splice(sessions.indexOf(session), 1);
const data = await this.createNewSession(instance, {
url: url,
typebot: typebot,
expire: expire,
keyword_finish: keyword_finish,
delay_message: delay_message,
unknown_message: unknown_message,
listening_from_me: listening_from_me,
sessions: sessions,
remoteJid: remoteJid,
pushName: msg.pushName,
});
await this.sendWAMessage(instance, remoteJid, data.messages, data.input, data.clientSideActions);
return;
}
}
if (session && session.status !== 'opened') {
return;
}
if (!session) {
const data = await this.createNewSession(instance, {
url: url,
typebot: typebot,
expire: expire,
keyword_finish: keyword_finish,
delay_message: delay_message,
unknown_message: unknown_message,
listening_from_me: listening_from_me,
sessions: sessions,
remoteJid: remoteJid,
pushName: msg.pushName,
});
await this.sendWAMessage(instance, remoteJid, data.messages, data.input, data.clientSideActions);
return;
}
sessions.map((session) => {
if (session.remoteJid === remoteJid) {
session.updateAt = Date.now();
}
});
const typebotData = {
enabled: true,
url: url,
typebot: typebot,
expire: expire,
keyword_finish: keyword_finish,
delay_message: delay_message,
unknown_message: unknown_message,
listening_from_me: listening_from_me,
sessions,
};
this.create(instance, typebotData);
const content = this.getConversationMessage(msg.message);
if (!content) {
if (unknown_message) {
this.waMonitor.waInstances[instance.instanceName].textMessage({
number: remoteJid.split('@')[0],
options: {
delay: delay_message || 1000,
presence: 'composing',
},
textMessage: {
text: unknown_message,
},
});
}
return;
}
if (content.toLowerCase() === keyword_finish.toLowerCase()) {
sessions.splice(sessions.indexOf(session), 1);
const typebotData = {
enabled: true,
url: url,
typebot: typebot,
expire: expire,
keyword_finish: keyword_finish,
delay_message: delay_message,
unknown_message: unknown_message,
listening_from_me: listening_from_me,
sessions,
};
this.create(instance, typebotData);
return;
}
const reqData = {
message: content,
sessionId: session.sessionId.split('-')[1],
};
const request = await axios.post(url + '/api/v1/sendMessage', reqData);
await this.sendWAMessage(
instance,
remoteJid,
request.data.messages,
request.data.input,
request.data.clientSideActions,
);
return;
}
}

View File

@@ -1,7 +1,7 @@
import { Logger } from '../../config/logger.config';
import { InstanceDto } from '../dto/instance.dto';
import { WebhookDto } from '../dto/webhook.dto';
import { WAMonitoringService } from './monitor.service';
import { Logger } from '../../config/logger.config';
export class WebhookService {
constructor(private readonly waMonitor: WAMonitoringService) {}
@@ -18,9 +18,7 @@ export class WebhookService {
public async find(instance: InstanceDto): Promise<WebhookDto> {
try {
this.logger.verbose('find webhook: ' + instance.instanceName);
const result = await this.waMonitor.waInstances[
instance.instanceName
].findWebhook();
const result = await this.waMonitor.waInstances[instance.instanceName].findWebhook();
if (Object.keys(result).length === 0) {
throw new Error('Webhook not found');

View File

@@ -0,0 +1,33 @@
import { Logger } from '../../config/logger.config';
import { InstanceDto } from '../dto/instance.dto';
import { WebsocketDto } from '../dto/websocket.dto';
import { WebsocketRaw } from '../models';
import { WAMonitoringService } from './monitor.service';
export class WebsocketService {
constructor(private readonly waMonitor: WAMonitoringService) {}
private readonly logger = new Logger(WebsocketService.name);
public create(instance: InstanceDto, data: WebsocketDto) {
this.logger.verbose('create websocket: ' + instance.instanceName);
this.waMonitor.waInstances[instance.instanceName].setWebsocket(data);
return { websocket: { ...instance, websocket: data } };
}
public async find(instance: InstanceDto): Promise<WebsocketRaw> {
try {
this.logger.verbose('find websocket: ' + instance.instanceName);
const result = await this.waMonitor.waInstances[instance.instanceName].findWebsocket();
if (Object.keys(result).length === 0) {
throw new Error('Websocket not found');
}
return result;
} catch (error) {
return { enabled: false, events: [] };
}
}
}

File diff suppressed because it is too large Load Diff