mirror of
https://github.com/EvolutionAPI/evolution-api.git
synced 2025-07-26 18:38:39 -06:00
custom route to fetch chats with additional information and some more refined filtering and sorting for the findMessage
This commit is contained in:
parent
309d101b32
commit
0ce0c4fe97
38
src/api/integrations/kwik/controllers/kwik.controller.ts
Normal file
38
src/api/integrations/kwik/controllers/kwik.controller.ts
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
34
src/api/integrations/kwik/routes/kwik.router.ts
Normal file
34
src/api/integrations/kwik/routes/kwik.router.ts
Normal 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();
|
||||||
|
}
|
230
src/api/integrations/kwik/services/kwik.service.ts
Normal file
230
src/api/integrations/kwik/services/kwik.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 { 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',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -7,6 +7,7 @@ export class ChatRaw {
|
|||||||
id?: string;
|
id?: string;
|
||||||
owner: string;
|
owner: string;
|
||||||
lastMsgTimestamp?: number;
|
lastMsgTimestamp?: number;
|
||||||
|
lastAllMsgTimestamp?: number | Long.Long;
|
||||||
labels?: string[];
|
labels?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { opendirSync, readFileSync, rmSync } from 'fs';
|
import { opendirSync, readFileSync, rmSync } from 'fs';
|
||||||
|
import { SortOrder } from 'mongoose';
|
||||||
import { join } from 'path';
|
import { join } from 'path';
|
||||||
|
|
||||||
import { ConfigService, StoreConf } from '../../config/env.config';
|
import { ConfigService, StoreConf } from '../../config/env.config';
|
||||||
@ -10,6 +11,7 @@ export class MessageQuery {
|
|||||||
select?: MessageRawSelect;
|
select?: MessageRawSelect;
|
||||||
where: MessageRaw;
|
where: MessageRaw;
|
||||||
limit?: number;
|
limit?: number;
|
||||||
|
sort?: { [key: string]: SortOrder };
|
||||||
}
|
}
|
||||||
|
|
||||||
export class MessageRepository extends Repository {
|
export class MessageRepository extends Repository {
|
||||||
@ -22,6 +24,10 @@ export class MessageRepository extends Repository {
|
|||||||
public buildQuery(query: MessageQuery): MessageQuery {
|
public buildQuery(query: MessageQuery): MessageQuery {
|
||||||
for (const [o, p] of Object.entries(query?.where || {})) {
|
for (const [o, p] of Object.entries(query?.where || {})) {
|
||||||
if (typeof p === 'object' && p !== null && !Array.isArray(p)) {
|
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)) {
|
for (const [k, v] of Object.entries(p)) {
|
||||||
query.where[`${o}.${k}`] = v;
|
query.where[`${o}.${k}`] = v;
|
||||||
}
|
}
|
||||||
@ -119,7 +125,7 @@ export class MessageRepository extends Repository {
|
|||||||
return await this.messageModel
|
return await this.messageModel
|
||||||
.find({ ...query.where })
|
.find({ ...query.where })
|
||||||
.select(query.select || {})
|
.select(query.select || {})
|
||||||
.sort({ messageTimestamp: -1 })
|
.sort(query?.sort ?? { messageTimestamp: -1 })
|
||||||
.limit(query?.limit ?? 0);
|
.limit(query?.limit ?? 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,6 +6,7 @@ import { authGuard } from '../guards/auth.guard';
|
|||||||
import { instanceExistsGuard, instanceLoggedGuard } from '../guards/instance.guard';
|
import { instanceExistsGuard, instanceLoggedGuard } from '../guards/instance.guard';
|
||||||
import { ChamaaiRouter } from '../integrations/chamaai/routes/chamaai.router';
|
import { ChamaaiRouter } from '../integrations/chamaai/routes/chamaai.router';
|
||||||
import { ChatwootRouter } from '../integrations/chatwoot/routes/chatwoot.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 { RabbitmqRouter } from '../integrations/rabbitmq/routes/rabbitmq.router';
|
||||||
import { SqsRouter } from '../integrations/sqs/routes/sqs.router';
|
import { SqsRouter } from '../integrations/sqs/routes/sqs.router';
|
||||||
import { TypebotRouter } from '../integrations/typebot/routes/typebot.router';
|
import { TypebotRouter } from '../integrations/typebot/routes/typebot.router';
|
||||||
@ -63,6 +64,7 @@ router
|
|||||||
.use('/typebot', new TypebotRouter(...guards).router)
|
.use('/typebot', new TypebotRouter(...guards).router)
|
||||||
.use('/proxy', new ProxyRouter(...guards).router)
|
.use('/proxy', new ProxyRouter(...guards).router)
|
||||||
.use('/chamaai', new ChamaaiRouter(...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 };
|
export { HttpStatus, router };
|
||||||
|
@ -17,6 +17,7 @@ import { ChamaaiService } from './integrations/chamaai/services/chamaai.service'
|
|||||||
import { ChatwootController } from './integrations/chatwoot/controllers/chatwoot.controller';
|
import { ChatwootController } from './integrations/chatwoot/controllers/chatwoot.controller';
|
||||||
import { ChatwootRepository } from './integrations/chatwoot/repository/chatwoot.repository';
|
import { ChatwootRepository } from './integrations/chatwoot/repository/chatwoot.repository';
|
||||||
import { ChatwootService } from './integrations/chatwoot/services/chatwoot.service';
|
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 { RabbitmqController } from './integrations/rabbitmq/controllers/rabbitmq.controller';
|
||||||
import { RabbitmqRepository } from './integrations/rabbitmq/repository/rabbitmq.repository';
|
import { RabbitmqRepository } from './integrations/rabbitmq/repository/rabbitmq.repository';
|
||||||
import { RabbitmqService } from './integrations/rabbitmq/services/rabbitmq.service';
|
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 chatController = new ChatController(waMonitor);
|
||||||
export const groupController = new GroupController(waMonitor);
|
export const groupController = new GroupController(waMonitor);
|
||||||
export const labelController = new LabelController(waMonitor);
|
export const labelController = new LabelController(waMonitor);
|
||||||
|
export const kwikController = new KwikController(waMonitor);
|
||||||
|
|
||||||
logger.info('Module - ON');
|
logger.info('Module - ON');
|
||||||
|
Loading…
Reference in New Issue
Block a user