custom route to fetch chats with additional information and some more refined filtering and sorting for the findMessage

This commit is contained in:
Pedro Howat 2024-08-08 18:41:23 -03:00
parent 309d101b32
commit 0ce0c4fe97
7 changed files with 315 additions and 2 deletions

View File

@ -0,0 +1,38 @@
// import { Logger } from '../../../../config/logger.config';
import { WAMonitoringService } from '../../../../services/monitor.service';
import { InstanceDto } from '../../../dto/instance.dto';
// const logger = new Logger('KwikController');
export class KwikController {
constructor(private readonly waMonitor: WAMonitoringService) {}
public async fetchChats({ instanceName }: InstanceDto) {
const chats = await this.waMonitor.waInstances[instanceName].repository.chat.find({
where: { owner: instanceName },
});
const mm = await Promise.all(
chats.map(async (chat) => {
const lastMsg = await this.waMonitor.waInstances[instanceName].repository.message.find({
where: {
owner: instanceName,
key: {
remoteJid: chat.id,
},
},
limit: 1,
sort: {
messageTimestamp: -1,
},
});
return {
...chat._doc,
lastAllMsgTimestamp: lastMsg[0].messageTimestamp,
};
}),
);
return mm;
}
}

View File

@ -0,0 +1,34 @@
import { RequestHandler, Router } from 'express';
import { Logger } from '../../../../config/logger.config';
import { RouterBroker } from '../../../abstract/abstract.router';
import { InstanceDto } from '../../../dto/instance.dto';
import { HttpStatus } from '../../../routes/index.router';
import { kwikController } from '../../../server.module';
const logger = new Logger('KwikRouter');
export class KwikRouter extends RouterBroker {
constructor(...guards: RequestHandler[]) {
super();
this.router.get(this.routerPath('findChats'), ...guards, async (req, res) => {
logger.verbose('request received in findChats');
logger.verbose('request body: ');
logger.verbose(req.body);
logger.verbose('request query: ');
logger.verbose(req.query);
const response = await this.dataValidate<InstanceDto>({
request: req,
schema: null,
ClassRef: InstanceDto,
execute: (instance) => kwikController.fetchChats(instance),
});
return res.status(HttpStatus.OK).json(response);
});
}
public readonly router = Router();
}

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 { InstanceDto } from '../../../dto/instance.dto';
import { ChamaaiRaw } from '../../../models';
import { WAMonitoringService } from '../../../services/monitor.service';
import { Events } from '../../../types/wa.types';
import { ChamaaiDto } from '../dto/chamaai.dto';
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

@ -7,6 +7,7 @@ export class ChatRaw {
id?: string;
owner: string;
lastMsgTimestamp?: number;
lastAllMsgTimestamp?: number | Long.Long;
labels?: string[];
}

View File

@ -1,4 +1,5 @@
import { opendirSync, readFileSync, rmSync } from 'fs';
import { SortOrder } from 'mongoose';
import { join } from 'path';
import { ConfigService, StoreConf } from '../../config/env.config';
@ -10,6 +11,7 @@ export class MessageQuery {
select?: MessageRawSelect;
where: MessageRaw;
limit?: number;
sort?: { [key: string]: SortOrder };
}
export class MessageRepository extends Repository {
@ -22,6 +24,10 @@ export class MessageRepository extends Repository {
public buildQuery(query: MessageQuery): MessageQuery {
for (const [o, p] of Object.entries(query?.where || {})) {
if (typeof p === 'object' && p !== null && !Array.isArray(p)) {
if (o === 'messageTimestamp') {
this.logger.verbose("Don't touch the messageTimestamp in where clause");
continue;
}
for (const [k, v] of Object.entries(p)) {
query.where[`${o}.${k}`] = v;
}
@ -119,7 +125,7 @@ export class MessageRepository extends Repository {
return await this.messageModel
.find({ ...query.where })
.select(query.select || {})
.sort({ messageTimestamp: -1 })
.sort(query?.sort ?? { messageTimestamp: -1 })
.limit(query?.limit ?? 0);
}

View File

@ -6,6 +6,7 @@ import { authGuard } from '../guards/auth.guard';
import { instanceExistsGuard, instanceLoggedGuard } from '../guards/instance.guard';
import { ChamaaiRouter } from '../integrations/chamaai/routes/chamaai.router';
import { ChatwootRouter } from '../integrations/chatwoot/routes/chatwoot.router';
import { KwikRouter } from '../integrations/kwik/routes/kwik.router';
import { RabbitmqRouter } from '../integrations/rabbitmq/routes/rabbitmq.router';
import { SqsRouter } from '../integrations/sqs/routes/sqs.router';
import { TypebotRouter } from '../integrations/typebot/routes/typebot.router';
@ -63,6 +64,7 @@ router
.use('/typebot', new TypebotRouter(...guards).router)
.use('/proxy', new ProxyRouter(...guards).router)
.use('/chamaai', new ChamaaiRouter(...guards).router)
.use('/label', new LabelRouter(...guards).router);
.use('/label', new LabelRouter(...guards).router)
.use('/kwik', new KwikRouter(...guards).router);
export { HttpStatus, router };

View File

@ -17,6 +17,7 @@ import { ChamaaiService } from './integrations/chamaai/services/chamaai.service'
import { ChatwootController } from './integrations/chatwoot/controllers/chatwoot.controller';
import { ChatwootRepository } from './integrations/chatwoot/repository/chatwoot.repository';
import { ChatwootService } from './integrations/chatwoot/services/chatwoot.service';
import { KwikController } from './integrations/kwik/controllers/kwik.controller';
import { RabbitmqController } from './integrations/rabbitmq/controllers/rabbitmq.controller';
import { RabbitmqRepository } from './integrations/rabbitmq/repository/rabbitmq.repository';
import { RabbitmqService } from './integrations/rabbitmq/services/rabbitmq.service';
@ -182,5 +183,6 @@ export const sendMessageController = new SendMessageController(waMonitor);
export const chatController = new ChatController(waMonitor);
export const groupController = new GroupController(waMonitor);
export const labelController = new LabelController(waMonitor);
export const kwikController = new KwikController(waMonitor);
logger.info('Module - ON');