feat: Inegration with Chama AI

This commit is contained in:
Davidson Gomes
2023-08-19 10:42:41 -03:00
parent bf09a70096
commit a2cd57d9c6
14 changed files with 477 additions and 4 deletions

View File

@@ -0,0 +1,196 @@
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 { 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 = 10;
const characterCount = count;
const speakingTimeInSeconds = characterCount / averageCharactersPerSecond;
return speakingTimeInSeconds;
}
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}`,
},
});
console.log(request.data);
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,
},
});
}
}
}
}

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,6 +126,7 @@ 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 { TypebotService } from './typebot.service';
@@ -151,6 +152,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 +166,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) {
@@ -579,6 +583,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;
@@ -1065,6 +1115,7 @@ export class WAStartupService {
this.loadRabbitmq();
this.loadTypebot();
this.loadProxy();
this.loadChamaai();
this.instance.authState = await this.defineAuthState();
@@ -1383,7 +1434,7 @@ export class WAStartupService {
);
}
if (this.localTypebot.enabled && messageRaw.key.remoteJid.includes('@s.whatsapp.net')) {
if (this.localTypebot.enabled && messageRaw.key.fromMe === false) {
await this.typebotService.sendTypebot(
{ instanceName: this.instance.name },
messageRaw.key.remoteJid,
@@ -1391,6 +1442,14 @@ export class WAStartupService {
);
}
if (this.localChamaai.enabled && messageRaw.key.fromMe === false) {
await this.chamaaiService.sendChamaai(
{ instanceName: this.instance.name },
messageRaw.key.remoteJid,
messageRaw,
);
}
this.logger.verbose('Inserting message in database');
await this.repository.message.insert([messageRaw], this.instance.name, database.SAVE_DATA.NEW_MESSAGE);
@@ -2315,7 +2374,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;