Merge branch 'develop' of https://github.com/EvolutionAPI/evolution-api into EvolutionAPI-develop

This commit is contained in:
raimartinsb
2023-09-11 08:08:18 -03:00
41 changed files with 1217 additions and 210 deletions

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

@@ -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');

View File

@@ -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);

View File

@@ -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 } };
}

View File

@@ -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,
};

View File

@@ -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) {