mirror of
https://github.com/EvolutionAPI/evolution-api.git
synced 2025-12-26 07:07:45 -06:00
Merge branch 'develop' of https://github.com/EvolutionAPI/evolution-api into EvolutionAPI-develop
This commit is contained in:
230
src/whatsapp/services/chamaai.service.ts
Normal file
230
src/whatsapp/services/chamaai.service.ts
Normal 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',
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -460,7 +460,9 @@ export class ChatwootService {
|
||||
let contact: any;
|
||||
if (body.key.fromMe) {
|
||||
if (findContact) {
|
||||
contact = findContact;
|
||||
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(
|
||||
@@ -481,7 +483,9 @@ export class ChatwootService {
|
||||
avatar_url: picture_url.profilePictureUrl || null,
|
||||
});
|
||||
} else {
|
||||
contact = findContact;
|
||||
contact = await this.updateContact(instance, findContact.id, {
|
||||
avatar_url: picture_url.profilePictureUrl || null,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
const jid = isGroup ? null : body.key.remoteJid;
|
||||
@@ -994,6 +998,9 @@ export class ChatwootService {
|
||||
|
||||
public async receiveWebhook(instance: InstanceDto, body: any) {
|
||||
try {
|
||||
// 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);
|
||||
|
||||
@@ -1567,16 +1574,16 @@ 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');
|
||||
|
||||
@@ -7,7 +7,6 @@ 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 {
|
||||
@@ -76,77 +75,57 @@ export class WAMonitoringService {
|
||||
|
||||
public async instanceInfo(instanceName?: string) {
|
||||
this.logger.verbose('get instance info');
|
||||
if (instanceName && !this.waInstances[instanceName]) {
|
||||
throw new NotFoundException(`Instance "${instanceName}" not found`);
|
||||
}
|
||||
|
||||
const instances: any[] = [];
|
||||
const urlServer = this.configService.get<HttpServer>('SERVER').URL;
|
||||
|
||||
for await (const [key, value] of Object.entries(this.waInstances)) {
|
||||
if (value) {
|
||||
this.logger.verbose('get instance info: ' + key);
|
||||
let chatwoot: any;
|
||||
const instances: any[] = await Promise.all(
|
||||
Object.entries(this.waInstances).map(async ([key, value]) => {
|
||||
const status = value?.connectionStatus?.state || 'unknown';
|
||||
|
||||
const urlServer = this.configService.get<HttpServer>('SERVER').URL;
|
||||
|
||||
const findChatwoot = await this.waInstances[key].findChatwoot();
|
||||
|
||||
if (findChatwoot && findChatwoot.enabled) {
|
||||
chatwoot = {
|
||||
...findChatwoot,
|
||||
webhook_url: `${urlServer}/chatwoot/webhook/${encodeURIComponent(key)}`,
|
||||
};
|
||||
if (status === 'unknown') {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (value.connectionStatus.state === 'open') {
|
||||
if (status === 'open') {
|
||||
this.logger.verbose('instance: ' + key + ' - connectionStatus: open');
|
||||
|
||||
const instanceData = {
|
||||
instance: {
|
||||
instanceName: key,
|
||||
owner: value.wuid,
|
||||
profileName: (await value.getProfileName()) || 'not loaded',
|
||||
profilePictureUrl: value.profilePictureUrl,
|
||||
profileStatus: (await value.getProfileStatus()) || '',
|
||||
status: value.connectionStatus.state,
|
||||
},
|
||||
};
|
||||
|
||||
if (this.configService.get<Auth>('AUTHENTICATION').EXPOSE_IN_FETCH_INSTANCES) {
|
||||
instanceData.instance['serverUrl'] = this.configService.get<HttpServer>('SERVER').URL;
|
||||
|
||||
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);
|
||||
|
||||
const instanceData = {
|
||||
instance: {
|
||||
instanceName: key,
|
||||
status: value.connectionStatus.state,
|
||||
},
|
||||
};
|
||||
|
||||
if (this.configService.get<Auth>('AUTHENTICATION').EXPOSE_IN_FETCH_INSTANCES) {
|
||||
instanceData.instance['serverUrl'] = this.configService.get<HttpServer>('SERVER').URL;
|
||||
|
||||
instanceData.instance['apikey'] = (await this.repository.auth.find(key)).apikey;
|
||||
|
||||
instanceData.instance['chatwoot'] = chatwoot;
|
||||
}
|
||||
|
||||
instances.push(instanceData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const instanceData: any = {
|
||||
instance: {
|
||||
instanceName: key,
|
||||
owner: value.wuid,
|
||||
profileName: (await value.getProfileName()) || 'not loaded',
|
||||
profilePictureUrl: value.profilePictureUrl,
|
||||
profileStatus: (await value.getProfileStatus()) || '',
|
||||
status: status,
|
||||
},
|
||||
};
|
||||
|
||||
if (this.configService.get<Auth>('AUTHENTICATION').EXPOSE_IN_FETCH_INSTANCES) {
|
||||
instanceData.instance.serverUrl = urlServer;
|
||||
instanceData.instance.apikey = (await this.repository.auth.find(key))?.apikey;
|
||||
|
||||
const findChatwoot = await this.waInstances[key].findChatwoot();
|
||||
if (findChatwoot && findChatwoot.enabled) {
|
||||
instanceData.instance.chatwoot = {
|
||||
...findChatwoot,
|
||||
webhook_url: `${urlServer}/chatwoot/webhook/${encodeURIComponent(key)}`,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return instanceData;
|
||||
}),
|
||||
).then((results) => results.filter((instance) => instance !== null));
|
||||
|
||||
this.logger.verbose('return instance info: ' + instances.length);
|
||||
|
||||
return instances.find((i) => i.instance.instanceName === instanceName) ?? instances;
|
||||
if (instanceName) {
|
||||
const instance = instances.find((i) => i.instance.instanceName === instanceName);
|
||||
return instance || [];
|
||||
}
|
||||
|
||||
return instances;
|
||||
}
|
||||
|
||||
private delInstanceFiles() {
|
||||
@@ -161,8 +140,7 @@ export class WAMonitoringService {
|
||||
});
|
||||
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()) {
|
||||
@@ -220,6 +198,11 @@ 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;
|
||||
@@ -240,68 +223,85 @@ export class WAMonitoringService {
|
||||
}
|
||||
|
||||
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);
|
||||
instance.instanceName = name;
|
||||
this.logger.verbose('instance loaded: ' + name);
|
||||
|
||||
await instance.connectToWhatsapp();
|
||||
this.logger.verbose('connectToWhatsapp: ' + name);
|
||||
|
||||
this.waInstances[name] = instance;
|
||||
};
|
||||
this.logger.verbose('Loading instances');
|
||||
|
||||
try {
|
||||
if (this.redis.ENABLED) {
|
||||
this.logger.verbose('redis enabled');
|
||||
await this.cache.connect(this.redis as Redis);
|
||||
const keys = await this.cache.instanceKeys();
|
||||
if (keys?.length > 0) {
|
||||
this.logger.verbose('reading instance keys and setting instances');
|
||||
keys.forEach(async (k) => await set(k.split(':')[1]));
|
||||
} else {
|
||||
this.logger.verbose('no instance keys found');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.db.ENABLED && this.db.SAVE_DATA.INSTANCE) {
|
||||
this.logger.verbose('database enabled');
|
||||
await this.repository.dbServer.connect();
|
||||
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-]+\./, '')));
|
||||
} else {
|
||||
this.logger.verbose('no collections found');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
this.logger.verbose('store in files enabled');
|
||||
const dir = opendirSync(INSTANCE_DIR, { encoding: 'utf-8' });
|
||||
for await (const dirent of dir) {
|
||||
if (dirent.isDirectory()) {
|
||||
this.logger.verbose('reading instance files and setting instances');
|
||||
const files = readdirSync(join(INSTANCE_DIR, dirent.name), {
|
||||
encoding: 'utf-8',
|
||||
});
|
||||
if (files.length === 0) {
|
||||
rmSync(join(INSTANCE_DIR, dirent.name), { recursive: true, force: true });
|
||||
break;
|
||||
}
|
||||
|
||||
await set(dirent.name);
|
||||
} else {
|
||||
this.logger.verbose('no instance files found');
|
||||
}
|
||||
await this.loadInstancesFromRedis();
|
||||
} else if (this.db.ENABLED && this.db.SAVE_DATA.INSTANCE) {
|
||||
await this.loadInstancesFromDatabase();
|
||||
} else {
|
||||
await this.loadInstancesFromFiles();
|
||||
}
|
||||
} catch (error) {
|
||||
this.logger.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
private async setInstance(name: string) {
|
||||
const instance = new WAStartupService(this.configService, this.eventEmitter, this.repository, this.cache);
|
||||
instance.instanceName = name;
|
||||
this.logger.verbose('Instance loaded: ' + name);
|
||||
|
||||
await instance.connectToWhatsapp();
|
||||
this.logger.verbose('connectToWhatsapp: ' + name);
|
||||
|
||||
this.waInstances[name] = instance;
|
||||
}
|
||||
|
||||
private async loadInstancesFromRedis() {
|
||||
this.logger.verbose('Redis enabled');
|
||||
await this.cache.connect(this.redis as Redis);
|
||||
const keys = await this.cache.instanceKeys();
|
||||
|
||||
if (keys?.length > 0) {
|
||||
this.logger.verbose('Reading instance keys and setting instances');
|
||||
await Promise.all(keys.map((k) => this.setInstance(k.split(':')[1])));
|
||||
} else {
|
||||
this.logger.verbose('No instance keys found');
|
||||
}
|
||||
}
|
||||
|
||||
private async loadInstancesFromDatabase() {
|
||||
this.logger.verbose('Database enabled');
|
||||
await this.repository.dbServer.connect();
|
||||
const collections: any[] = await this.dbInstance.collections();
|
||||
|
||||
if (collections.length > 0) {
|
||||
this.logger.verbose('Reading collections and setting instances');
|
||||
await Promise.all(collections.map((coll) => this.setInstance(coll.namespace.replace(/^[\w-]+\./, ''))));
|
||||
} else {
|
||||
this.logger.verbose('No collections found');
|
||||
}
|
||||
}
|
||||
|
||||
private async loadInstancesFromFiles() {
|
||||
this.logger.verbose('Store in files enabled');
|
||||
const dir = opendirSync(INSTANCE_DIR, { encoding: 'utf-8' });
|
||||
const instanceDirs = [];
|
||||
|
||||
for await (const dirent of dir) {
|
||||
if (dirent.isDirectory()) {
|
||||
instanceDirs.push(dirent.name);
|
||||
} else {
|
||||
this.logger.verbose('No instance files found');
|
||||
}
|
||||
}
|
||||
|
||||
await Promise.all(
|
||||
instanceDirs.map(async (instanceName) => {
|
||||
this.logger.verbose('Reading instance files and setting instances: ' + instanceName);
|
||||
const files = readdirSync(join(INSTANCE_DIR, instanceName), { encoding: 'utf-8' });
|
||||
|
||||
if (files.length === 0) {
|
||||
rmSync(join(INSTANCE_DIR, instanceName), { recursive: true, force: true });
|
||||
} else {
|
||||
await this.setInstance(instanceName);
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
private removeInstance() {
|
||||
this.eventEmitter.on('remove.instance', async (instanceName: string) => {
|
||||
this.logger.verbose('remove instance: ' + instanceName);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
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';
|
||||
@@ -13,6 +14,7 @@ export class RabbitmqService {
|
||||
this.logger.verbose('create rabbitmq: ' + instance.instanceName);
|
||||
this.waMonitor.waInstances[instance.instanceName].setRabbitmq(data);
|
||||
|
||||
initQueues(instance.instanceName, data.events);
|
||||
return { rabbitmq: { ...instance, rabbitmq: data } };
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ 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 {
|
||||
@@ -53,6 +54,7 @@ export class TypebotService {
|
||||
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,
|
||||
};
|
||||
|
||||
@@ -76,11 +78,20 @@ export class TypebotService {
|
||||
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 } };
|
||||
}
|
||||
|
||||
@@ -88,6 +99,15 @@ export class TypebotService {
|
||||
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();
|
||||
|
||||
@@ -95,14 +115,9 @@ export class TypebotService {
|
||||
sessionId: id,
|
||||
startParams: {
|
||||
typebot: data.typebot,
|
||||
prefilledVariables: {
|
||||
remoteJid: data.remoteJid,
|
||||
pushName: data.pushName,
|
||||
instanceName: instance.instanceName,
|
||||
},
|
||||
prefilledVariables: prefilledVariables,
|
||||
},
|
||||
};
|
||||
console.log(reqData);
|
||||
|
||||
const request = await axios.post(data.url + '/api/v1/sendMessage', reqData);
|
||||
|
||||
@@ -114,6 +129,14 @@ export class TypebotService {
|
||||
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,
|
||||
@@ -121,6 +144,7 @@ export class TypebotService {
|
||||
url: url,
|
||||
remoteJid: remoteJid,
|
||||
typebot: typebot,
|
||||
variables: variables,
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -195,6 +219,7 @@ export class TypebotService {
|
||||
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,
|
||||
};
|
||||
|
||||
@@ -354,13 +379,15 @@ export class TypebotService {
|
||||
}
|
||||
|
||||
public async sendTypebot(instance: InstanceDto, remoteJid: string, msg: MessageRaw) {
|
||||
const url = (await this.find(instance)).url;
|
||||
const typebot = (await this.find(instance)).typebot;
|
||||
const sessions = ((await this.find(instance)).sessions as Session[]) ?? [];
|
||||
const expire = (await this.find(instance)).expire;
|
||||
const keyword_finish = (await this.find(instance)).keyword_finish;
|
||||
const delay_message = (await this.find(instance)).delay_message;
|
||||
const unknown_message = (await this.find(instance)).unknown_message;
|
||||
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);
|
||||
|
||||
@@ -381,6 +408,7 @@ export class TypebotService {
|
||||
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,
|
||||
@@ -404,6 +432,7 @@ export class TypebotService {
|
||||
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,
|
||||
@@ -428,6 +457,7 @@ export class TypebotService {
|
||||
keyword_finish: keyword_finish,
|
||||
delay_message: delay_message,
|
||||
unknown_message: unknown_message,
|
||||
listening_from_me: listening_from_me,
|
||||
sessions,
|
||||
};
|
||||
|
||||
@@ -462,6 +492,7 @@ export class TypebotService {
|
||||
keyword_finish: keyword_finish,
|
||||
delay_message: delay_message,
|
||||
unknown_message: unknown_message,
|
||||
listening_from_me: listening_from_me,
|
||||
sessions,
|
||||
};
|
||||
|
||||
|
||||
@@ -113,7 +113,7 @@ import {
|
||||
SendTextDto,
|
||||
StatusMessage,
|
||||
} from '../dto/sendMessage.dto';
|
||||
import { ProxyRaw, RabbitmqRaw, SettingsRaw, TypebotRaw } from '../models';
|
||||
import { ChamaaiRaw, ProxyRaw, RabbitmqRaw, SettingsRaw, TypebotRaw } from '../models';
|
||||
import { ChatRaw } from '../models/chat.model';
|
||||
import { ChatwootRaw } from '../models/chatwoot.model';
|
||||
import { ContactRaw } from '../models/contact.model';
|
||||
@@ -126,7 +126,9 @@ import { MessageUpQuery } from '../repository/messageUp.repository';
|
||||
import { RepositoryBroker } from '../repository/repository.manager';
|
||||
import { Events, MessageSubtype, TypeMediaMessage, wa } from '../types/wa.types';
|
||||
import { waMonitor } from '../whatsapp.module';
|
||||
import { ChamaaiService } from './chamaai.service';
|
||||
import { ChatwootService } from './chatwoot.service';
|
||||
//import { SocksProxyAgent } from './socks-proxy-agent';
|
||||
import { TypebotService } from './typebot.service';
|
||||
|
||||
export class WAStartupService {
|
||||
@@ -151,6 +153,7 @@ export class WAStartupService {
|
||||
private readonly localRabbitmq: wa.LocalRabbitmq = {};
|
||||
public readonly localTypebot: wa.LocalTypebot = {};
|
||||
private readonly localProxy: wa.LocalProxy = {};
|
||||
private readonly localChamaai: wa.LocalChamaai = {};
|
||||
public stateConnection: wa.StateConnection = { state: 'close' };
|
||||
public readonly storePath = join(ROOT_DIR, 'store');
|
||||
private readonly msgRetryCounterCache: CacheStore = new NodeCache();
|
||||
@@ -164,6 +167,8 @@ export class WAStartupService {
|
||||
|
||||
private typebotService = new TypebotService(waMonitor);
|
||||
|
||||
private chamaaiService = new ChamaaiService(waMonitor, this.configService);
|
||||
|
||||
public set instanceName(name: string) {
|
||||
this.logger.verbose(`Initializing instance '${name}'`);
|
||||
if (!name) {
|
||||
@@ -515,6 +520,9 @@ export class WAStartupService {
|
||||
this.localTypebot.unknown_message = data?.unknown_message;
|
||||
this.logger.verbose(`Typebot unknown_message: ${this.localTypebot.unknown_message}`);
|
||||
|
||||
this.localTypebot.listening_from_me = data?.listening_from_me;
|
||||
this.logger.verbose(`Typebot listening_from_me: ${this.localTypebot.listening_from_me}`);
|
||||
|
||||
this.localTypebot.sessions = data?.sessions;
|
||||
|
||||
this.logger.verbose('Typebot loaded');
|
||||
@@ -528,6 +536,7 @@ export class WAStartupService {
|
||||
this.logger.verbose(`Typebot keyword_finish: ${data.keyword_finish}`);
|
||||
this.logger.verbose(`Typebot delay_message: ${data.delay_message}`);
|
||||
this.logger.verbose(`Typebot unknown_message: ${data.unknown_message}`);
|
||||
this.logger.verbose(`Typebot listening_from_me: ${data.listening_from_me}`);
|
||||
Object.assign(this.localTypebot, data);
|
||||
this.logger.verbose('Typebot set');
|
||||
}
|
||||
@@ -579,6 +588,52 @@ export class WAStartupService {
|
||||
return data;
|
||||
}
|
||||
|
||||
private async loadChamaai() {
|
||||
this.logger.verbose('Loading chamaai');
|
||||
const data = await this.repository.chamaai.find(this.instanceName);
|
||||
|
||||
this.localChamaai.enabled = data?.enabled;
|
||||
this.logger.verbose(`Chamaai enabled: ${this.localChamaai.enabled}`);
|
||||
|
||||
this.localChamaai.url = data?.url;
|
||||
this.logger.verbose(`Chamaai url: ${this.localChamaai.url}`);
|
||||
|
||||
this.localChamaai.token = data?.token;
|
||||
this.logger.verbose(`Chamaai token: ${this.localChamaai.token}`);
|
||||
|
||||
this.localChamaai.waNumber = data?.waNumber;
|
||||
this.logger.verbose(`Chamaai waNumber: ${this.localChamaai.waNumber}`);
|
||||
|
||||
this.localChamaai.answerByAudio = data?.answerByAudio;
|
||||
this.logger.verbose(`Chamaai answerByAudio: ${this.localChamaai.answerByAudio}`);
|
||||
|
||||
this.logger.verbose('Chamaai loaded');
|
||||
}
|
||||
|
||||
public async setChamaai(data: ChamaaiRaw) {
|
||||
this.logger.verbose('Setting chamaai');
|
||||
await this.repository.chamaai.create(data, this.instanceName);
|
||||
this.logger.verbose(`Chamaai url: ${data.url}`);
|
||||
this.logger.verbose(`Chamaai token: ${data.token}`);
|
||||
this.logger.verbose(`Chamaai waNumber: ${data.waNumber}`);
|
||||
this.logger.verbose(`Chamaai answerByAudio: ${data.answerByAudio}`);
|
||||
|
||||
Object.assign(this.localChamaai, data);
|
||||
this.logger.verbose('Chamaai set');
|
||||
}
|
||||
|
||||
public async findChamaai() {
|
||||
this.logger.verbose('Finding chamaai');
|
||||
const data = await this.repository.chamaai.find(this.instanceName);
|
||||
|
||||
if (!data) {
|
||||
this.logger.verbose('Chamaai not found');
|
||||
throw new NotFoundException('Chamaai not found');
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
public async sendDataWebhook<T = any>(event: Events, data: T, local = true) {
|
||||
const webhookGlobal = this.configService.get<Webhook>('WEBHOOK');
|
||||
const webhookLocal = this.localWebhook.events;
|
||||
@@ -633,6 +688,25 @@ export class WAStartupService {
|
||||
}
|
||||
|
||||
amqp.publish(exchangeName, event, Buffer.from(JSON.stringify(message)));
|
||||
|
||||
if (this.configService.get<Log>('LOG').LEVEL.includes('WEBHOOKS')) {
|
||||
const logData = {
|
||||
local: WAStartupService.name + '.sendData-RabbitMQ',
|
||||
event,
|
||||
instance: this.instance.name,
|
||||
data,
|
||||
server_url: serverUrl,
|
||||
apikey: (expose && instanceApikey) || null,
|
||||
date_time: now,
|
||||
sender: this.wuid,
|
||||
};
|
||||
|
||||
if (expose && instanceApikey) {
|
||||
logData['apikey'] = instanceApikey;
|
||||
}
|
||||
|
||||
this.logger.log(logData);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -658,6 +732,25 @@ export class WAStartupService {
|
||||
|
||||
this.logger.verbose('Sending data to socket.io in channel: ' + this.instance.name);
|
||||
io.of(`/${this.instance.name}`).emit(event, message);
|
||||
|
||||
if (this.configService.get<Log>('LOG').LEVEL.includes('WEBHOOKS')) {
|
||||
const logData = {
|
||||
local: WAStartupService.name + '.sendData-Websocket',
|
||||
event,
|
||||
instance: this.instance.name,
|
||||
data,
|
||||
server_url: serverUrl,
|
||||
apikey: (expose && instanceApikey) || null,
|
||||
date_time: now,
|
||||
sender: this.wuid,
|
||||
};
|
||||
|
||||
if (expose && instanceApikey) {
|
||||
logData['apikey'] = instanceApikey;
|
||||
}
|
||||
|
||||
this.logger.log(logData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -696,7 +789,7 @@ export class WAStartupService {
|
||||
}
|
||||
|
||||
try {
|
||||
if (this.localWebhook.enabled && isURL(this.localWebhook.url)) {
|
||||
if (this.localWebhook.enabled && isURL(this.localWebhook.url, { require_tld: false })) {
|
||||
const httpService = axios.create({ baseURL });
|
||||
const postData = {
|
||||
event,
|
||||
@@ -1065,6 +1158,7 @@ export class WAStartupService {
|
||||
this.loadRabbitmq();
|
||||
this.loadTypebot();
|
||||
this.loadProxy();
|
||||
this.loadChamaai();
|
||||
|
||||
this.instance.authState = await this.defineAuthState();
|
||||
|
||||
@@ -1338,7 +1432,12 @@ export class WAStartupService {
|
||||
this.logger.verbose('Event received: messages.upsert');
|
||||
const received = messages[0];
|
||||
|
||||
if (type !== 'notify' || received.message?.protocolMessage || received.message?.pollUpdateMessage) {
|
||||
if (
|
||||
type !== 'notify' ||
|
||||
!received.message ||
|
||||
received.message?.protocolMessage ||
|
||||
received.message?.pollUpdateMessage
|
||||
) {
|
||||
this.logger.verbose('message rejected');
|
||||
return;
|
||||
}
|
||||
@@ -1383,8 +1482,18 @@ export class WAStartupService {
|
||||
);
|
||||
}
|
||||
|
||||
if (this.localTypebot.enabled && messageRaw.key.remoteJid.includes('@s.whatsapp.net')) {
|
||||
await this.typebotService.sendTypebot(
|
||||
if (this.localTypebot.enabled) {
|
||||
if (!(this.localTypebot.listening_from_me === false && messageRaw.key.fromMe === true)) {
|
||||
await this.typebotService.sendTypebot(
|
||||
{ instanceName: this.instance.name },
|
||||
messageRaw.key.remoteJid,
|
||||
messageRaw,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.localChamaai.enabled && messageRaw.key.fromMe === false) {
|
||||
await this.chamaaiService.sendChamaai(
|
||||
{ instanceName: this.instance.name },
|
||||
messageRaw.key.remoteJid,
|
||||
messageRaw,
|
||||
@@ -1791,19 +1900,19 @@ export class WAStartupService {
|
||||
|
||||
public async fetchProfile(instanceName: string, number?: string) {
|
||||
const jid = number ? this.createJid(number) : this.client?.user?.id;
|
||||
|
||||
|
||||
this.logger.verbose('Getting profile with jid: ' + jid);
|
||||
try {
|
||||
this.logger.verbose('Getting profile info');
|
||||
const business = await this.fetchBusinessProfile(jid);
|
||||
|
||||
|
||||
if (number) {
|
||||
const info = (await this.whatsappNumber({ numbers: [jid] }))?.shift();
|
||||
const picture = await this.profilePicture(jid);
|
||||
const status = await this.getStatus(jid);
|
||||
|
||||
const picture = await this.profilePicture(info?.jid);
|
||||
const status = await this.getStatus(info?.jid);
|
||||
const business = await this.fetchBusinessProfile(info?.jid);
|
||||
|
||||
return {
|
||||
wuid: jid,
|
||||
wuid: info?.jid || jid,
|
||||
name: info?.name,
|
||||
numberExists: info?.exists,
|
||||
picture: picture?.profilePictureUrl,
|
||||
@@ -1815,6 +1924,7 @@ export class WAStartupService {
|
||||
};
|
||||
} else {
|
||||
const info = await waMonitor.instanceInfo(instanceName);
|
||||
const business = await this.fetchBusinessProfile(jid);
|
||||
|
||||
return {
|
||||
wuid: jid,
|
||||
@@ -2315,7 +2425,7 @@ export class WAStartupService {
|
||||
return await this.sendMessageWithTyping(data.number, { ...generate.message }, data?.options);
|
||||
}
|
||||
|
||||
private async processAudio(audio: string, number: string) {
|
||||
public async processAudio(audio: string, number: string) {
|
||||
this.logger.verbose('Processing audio');
|
||||
let tempAudioPath: string;
|
||||
let outputAudio: string;
|
||||
@@ -2969,7 +3079,9 @@ export class WAStartupService {
|
||||
public async createGroup(create: CreateGroupDto) {
|
||||
this.logger.verbose('Creating group: ' + create.subject);
|
||||
try {
|
||||
const participants = create.participants.map((p) => this.createJid(p));
|
||||
const participants = (await this.whatsappNumber({ numbers: create.participants }))
|
||||
.filter((participant) => participant.exists)
|
||||
.map((participant) => participant.jid);
|
||||
const { id } = await this.client.groupCreate(create.subject, participants);
|
||||
this.logger.verbose('Group created: ' + id);
|
||||
|
||||
@@ -2979,7 +3091,7 @@ export class WAStartupService {
|
||||
}
|
||||
|
||||
if (create?.promoteParticipants) {
|
||||
this.logger.verbose('Prometing group participants: ' + create.description);
|
||||
this.logger.verbose('Prometing group participants: ' + participants);
|
||||
await this.updateGParticipant({
|
||||
groupJid: id,
|
||||
action: 'promote',
|
||||
@@ -2987,8 +3099,8 @@ export class WAStartupService {
|
||||
});
|
||||
}
|
||||
|
||||
const group = await this.client.groupMetadata(id);
|
||||
this.logger.verbose('Getting group metadata');
|
||||
const group = await this.client.groupMetadata(id);
|
||||
|
||||
return group;
|
||||
} catch (error) {
|
||||
|
||||
Reference in New Issue
Block a user