Criando WebWidget

This commit is contained in:
Mario 2025-01-12 12:49:50 -05:00
parent 1665654676
commit 2982958c05
12 changed files with 2313 additions and 1127 deletions

16
Dockerfile.dev Normal file
View File

@ -0,0 +1,16 @@
FROM node:20-slim
WORKDIR /evolution
RUN apt-get update && apt-get install -y python3 make g++ libvips-dev git pkg-config
RUN npm install --platform=linux --arch=x64 sharp
COPY package*.json /evolution/
RUN npm install
COPY . /evolution
# Generate your Prisma Client
# RUN npm run db:deploy
# RUN npx prisma generate --schema=/evolution/prisma/postgresql-schema.prisma
CMD ["npm", "run", "dev:server"]

View File

@ -1,25 +1,57 @@
services: services:
api: evolution-api-dev:
container_name: evolution_api container_name: evolution-api-dev
image: evolution/api:local build:
build: . context: .
restart: always dockerfile: Dockerfile.dev
depends_on:
- evolution-redis
- evolution-postgres
volumes:
- type: bind
source: .
target: /evolution
- type: volume
target: /evolution/node_modules
ports: ports:
- 8080:8080 - '8080:8080'
volumes: environment:
- evolution_instances:/evolution/instances - NODE_ENV=development
networks: networks:
- evolution-net - chatwoot-evolution-network
env_file: env_file:
- .env - ./.env
expose:
- 8080 evolution-redis:
image: redis:latest
container_name: evolution-redis
command: >
redis-server --port 6380 --appendonly yes
volumes:
- evolution-redis:/data
networks:
- chatwoot-evolution-network
evolution-postgres:
container_name: evolution-postgres
image: postgres:15
command: [ "postgres", "-c", "max_connections=1000" ]
restart: always
volumes:
- evolution-postgres:/var/lib/postgresql/data
environment:
- POSTGRES_USER=${POSTGRES_USER}
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
- POSTGRES_DB=${POSTGRES_DB}
networks:
- chatwoot-evolution-network
volumes: volumes:
evolution_instances: evolution-instances:
evolution-redis:
evolution-postgres:
networks: networks:
evolution-net: chatwoot-evolution-network:
name: evolution-net external: true
driver: bridge

View File

@ -47,8 +47,17 @@ export class Metadata {
} }
export class SendTextDto extends Metadata { export class SendTextDto extends Metadata {
text: string; text: string; // Conteúdo do texto
number: string; // WhatsApp ou 'webwidget:...'
channel?: string; // ex: 'Channel::WebWidget' ou outro
inbox_id?: number; // se quiser mandar explicitamente ID=3
delay?: number;
quoted?: any;
linkPreview?: boolean;
mentionsEveryOne?: boolean;
mentioned?: string[];
} }
export class SendPresence extends Metadata { export class SendPresence extends Metadata {
text: string; text: string;
} }

View File

@ -1,4 +1,10 @@
import { MediaMessage, Options, SendAudioDto, SendMediaDto, SendTextDto } from '@api/dto/sendMessage.dto'; import {
MediaMessage,
Options,
SendAudioDto,
SendMediaDto,
SendTextDto,
} from '@api/dto/sendMessage.dto';
import { ProviderFiles } from '@api/provider/sessions'; import { ProviderFiles } from '@api/provider/sessions';
import { PrismaRepository } from '@api/repository/repository.service'; import { PrismaRepository } from '@api/repository/repository.service';
import { chatbotController } from '@api/server.module'; import { chatbotController } from '@api/server.module';
@ -6,7 +12,10 @@ import { CacheService } from '@api/services/cache.service';
import { ChannelStartupService } from '@api/services/channel.service'; import { ChannelStartupService } from '@api/services/channel.service';
import { Events, wa } from '@api/types/wa.types'; import { Events, wa } from '@api/types/wa.types';
import { Chatwoot, ConfigService, Openai } from '@config/env.config'; import { Chatwoot, ConfigService, Openai } from '@config/env.config';
import { BadRequestException, InternalServerErrorException } from '@exceptions'; import {
BadRequestException,
InternalServerErrorException,
} from '@exceptions';
import { status } from '@utils/renderStatus'; import { status } from '@utils/renderStatus';
import { isURL } from 'class-validator'; import { isURL } from 'class-validator';
import EventEmitter2 from 'eventemitter2'; import EventEmitter2 from 'eventemitter2';
@ -36,14 +45,25 @@ export class EvolutionStartupService extends ChannelStartupService {
public mobile: boolean; public mobile: boolean;
public get connectionStatus() { public get connectionStatus() {
this.logger.log('[connectionStatus] Retornando estado da conexão');
return this.stateConnection; return this.stateConnection;
} }
public async closeClient() { public async closeClient() {
this.logger.log('[closeClient] Encerrando cliente...');
try {
this.stateConnection = { state: 'close' }; this.stateConnection = { state: 'close' };
this.logger.debug('[closeClient] stateConnection atualizado para "close"');
} catch (error) {
this.logger.error(
`[closeClient] Erro ao tentar fechar o cliente: ${error?.toString()}`,
);
throw new InternalServerErrorException(error?.toString());
}
} }
public get qrCode(): wa.QrCode { public get qrCode(): wa.QrCode {
this.logger.log('[qrCode] Obtendo informações do QR Code...');
return { return {
pairingCode: this.instance.qrcode?.pairingCode, pairingCode: this.instance.qrcode?.pairingCode,
code: this.instance.qrcode?.code, code: this.instance.qrcode?.code,
@ -53,10 +73,14 @@ export class EvolutionStartupService extends ChannelStartupService {
} }
public async logoutInstance() { public async logoutInstance() {
this.logger.log('[logoutInstance] Realizando logout da instância...');
await this.closeClient(); await this.closeClient();
} }
public async profilePicture(number: string) { public async profilePicture(number: string) {
this.logger.log(
`[profilePicture] Obtendo foto de perfil para o número: ${number}`,
);
const jid = this.createJid(number); const jid = this.createJid(number);
return { return {
@ -66,35 +90,50 @@ export class EvolutionStartupService extends ChannelStartupService {
} }
public async getProfileName() { public async getProfileName() {
this.logger.log('[getProfileName] Método não implementado...');
return null; return null;
} }
public async profilePictureUrl() { public async profilePictureUrl() {
this.logger.log('[profilePictureUrl] Método não implementado...');
return null; return null;
} }
public async getProfileStatus() { public async getProfileStatus() {
this.logger.log('[getProfileStatus] Método não implementado...');
return null; return null;
} }
public async connectToWhatsapp(data?: any): Promise<any> { public async connectToWhatsapp(data?: any): Promise<any> {
if (!data) return; this.logger.log('[connectToWhatsapp] Iniciando conexão com o Whatsapp...');
if (!data) {
this.logger.warn('[connectToWhatsapp] Nenhum dado recebido. Encerrando...');
return;
}
try { try {
this.logger.debug('[connectToWhatsapp] Carregando Chatwoot...');
this.loadChatwoot(); this.loadChatwoot();
this.logger.debug('[connectToWhatsapp] Chamando eventHandler...');
this.eventHandler(data); this.eventHandler(data);
} catch (error) { } catch (error) {
this.logger.error(error); this.logger.error(
`[connectToWhatsapp] Erro ao conectar ao Whatsapp: ${error?.toString()}`,
);
throw new InternalServerErrorException(error?.toString()); throw new InternalServerErrorException(error?.toString());
} }
} }
protected async eventHandler(received: any) { protected async eventHandler(received: any) {
this.logger.log('[eventHandler] Iniciando tratamento de evento...');
try { try {
let messageRaw: any; let messageRaw: any;
if (received.message) { if (received.message) {
this.logger.debug(
`[eventHandler] Mensagem recebida: ${JSON.stringify(received)}`,
);
const key = { const key = {
id: received.key.id || v4(), id: received.key.id || v4(),
remoteJid: received.key.remoteJid, remoteJid: received.key.remoteJid,
@ -110,8 +149,19 @@ export class EvolutionStartupService extends ChannelStartupService {
instanceId: this.instanceId, instanceId: this.instanceId,
}; };
this.logger.debug(
`[eventHandler] Montando objeto messageRaw: ${JSON.stringify(
messageRaw,
)}`,
);
// Verifica OpenAI
if (this.configService.get<Openai>('OPENAI').ENABLED) { if (this.configService.get<Openai>('OPENAI').ENABLED) {
const openAiDefaultSettings = await this.prismaRepository.openaiSetting.findFirst({ this.logger.debug(
'[eventHandler] Verificando configurações do OpenAI...',
);
const openAiDefaultSettings =
await this.prismaRepository.openaiSetting.findFirst({
where: { where: {
instanceId: this.instanceId, instanceId: this.instanceId,
}, },
@ -126,6 +176,9 @@ export class EvolutionStartupService extends ChannelStartupService {
openAiDefaultSettings.speechToText && openAiDefaultSettings.speechToText &&
received?.message?.audioMessage received?.message?.audioMessage
) { ) {
this.logger.debug(
'[eventHandler] Realizando speech-to-text no áudio...',
);
messageRaw.message.speechToText = await this.openaiService.speechToText( messageRaw.message.speechToText = await this.openaiService.speechToText(
openAiDefaultSettings.OpenaiCreds, openAiDefaultSettings.OpenaiCreds,
received, received,
@ -134,52 +187,83 @@ export class EvolutionStartupService extends ChannelStartupService {
} }
} }
this.logger.log(messageRaw); this.logger.log(`[eventHandler] messageRaw final: ${JSON.stringify(messageRaw)}`);
this.sendDataWebhook(Events.MESSAGES_UPSERT, messageRaw); this.sendDataWebhook(Events.MESSAGES_UPSERT, messageRaw);
this.logger.debug('[eventHandler] Emitindo chatbotController...');
await chatbotController.emit({ await chatbotController.emit({
instance: { instanceName: this.instance.name, instanceId: this.instanceId }, instance: {
instanceName: this.instance.name,
instanceId: this.instanceId,
},
remoteJid: messageRaw.key.remoteJid, remoteJid: messageRaw.key.remoteJid,
msg: messageRaw, msg: messageRaw,
pushName: messageRaw.pushName, pushName: messageRaw.pushName,
}); });
if (this.configService.get<Chatwoot>('CHATWOOT').ENABLED && this.localChatwoot?.enabled) { if (
this.configService.get<Chatwoot>('CHATWOOT').ENABLED &&
this.localChatwoot?.enabled
) {
this.logger.debug('[eventHandler] Enviando evento para Chatwoot...');
const chatwootSentMessage = await this.chatwootService.eventWhatsapp( const chatwootSentMessage = await this.chatwootService.eventWhatsapp(
Events.MESSAGES_UPSERT, Events.MESSAGES_UPSERT,
{ instanceName: this.instance.name, instanceId: this.instanceId }, {
instanceName: this.instance.name,
instanceId: this.instanceId,
},
messageRaw, messageRaw,
); );
if (chatwootSentMessage?.id) { if (chatwootSentMessage?.id) {
this.logger.debug(
`[eventHandler] chatwootSentMessage criado com ID: ${chatwootSentMessage.id}`,
);
messageRaw.chatwootMessageId = chatwootSentMessage.id; messageRaw.chatwootMessageId = chatwootSentMessage.id;
messageRaw.chatwootInboxId = chatwootSentMessage.id; messageRaw.chatwootInboxId = chatwootSentMessage.id;
messageRaw.chatwootConversationId = chatwootSentMessage.id; messageRaw.chatwootConversationId = chatwootSentMessage.id;
} }
} }
this.logger.debug('[eventHandler] Salvando mensagem no Prisma...');
await this.prismaRepository.message.create({ await this.prismaRepository.message.create({
data: messageRaw, data: messageRaw,
}); });
this.logger.debug('[eventHandler] Atualizando contato...');
await this.updateContact({ await this.updateContact({
remoteJid: messageRaw.key.remoteJid, remoteJid: messageRaw.key.remoteJid,
pushName: messageRaw.key.fromMe ? '' : messageRaw.key.fromMe == null ? '' : received.pushName, pushName: messageRaw.key.fromMe
? ''
: messageRaw.key.fromMe == null
? ''
: received.pushName,
profilePicUrl: received.profilePicUrl, profilePicUrl: received.profilePicUrl,
}); });
} }
} catch (error) { } catch (error) {
this.logger.error(error); this.logger.error(`[eventHandler] Erro: ${error}`);
} }
} }
private async updateContact(data: { remoteJid: string; pushName?: string; profilePicUrl?: string }) { private async updateContact(data: {
remoteJid: string;
pushName?: string;
profilePicUrl?: string;
}) {
this.logger.log(
`[updateContact] Atualizando ou criando contato para: ${data.remoteJid}`,
);
try {
const contact = await this.prismaRepository.contact.findFirst({ const contact = await this.prismaRepository.contact.findFirst({
where: { instanceId: this.instanceId, remoteJid: data.remoteJid }, where: { instanceId: this.instanceId, remoteJid: data.remoteJid },
}); });
if (contact) { if (contact) {
this.logger.debug(
`[updateContact] Contato já existe. Atualizando...: ${contact.remoteJid}`,
);
const contactRaw: any = { const contactRaw: any = {
remoteJid: data.remoteJid, remoteJid: data.remoteJid,
pushName: data?.pushName, pushName: data?.pushName,
@ -189,7 +273,11 @@ export class EvolutionStartupService extends ChannelStartupService {
this.sendDataWebhook(Events.CONTACTS_UPDATE, contactRaw); this.sendDataWebhook(Events.CONTACTS_UPDATE, contactRaw);
if (this.configService.get<Chatwoot>('CHATWOOT').ENABLED && this.localChatwoot?.enabled) { if (
this.configService.get<Chatwoot>('CHATWOOT').ENABLED &&
this.localChatwoot?.enabled
) {
this.logger.debug('[updateContact] Atualizando contato no Chatwoot...');
await this.chatwootService.eventWhatsapp( await this.chatwootService.eventWhatsapp(
Events.CONTACTS_UPDATE, Events.CONTACTS_UPDATE,
{ instanceName: this.instance.name, instanceId: this.instanceId }, { instanceName: this.instance.name, instanceId: this.instanceId },
@ -197,6 +285,7 @@ export class EvolutionStartupService extends ChannelStartupService {
); );
} }
this.logger.debug('[updateContact] Atualizando contato no Prisma...');
await this.prismaRepository.contact.updateMany({ await this.prismaRepository.contact.updateMany({
where: { remoteJid: contact.remoteJid, instanceId: this.instanceId }, where: { remoteJid: contact.remoteJid, instanceId: this.instanceId },
data: contactRaw, data: contactRaw,
@ -204,6 +293,7 @@ export class EvolutionStartupService extends ChannelStartupService {
return; return;
} }
this.logger.debug('[updateContact] Contato não encontrado. Criando novo...');
const contactRaw: any = { const contactRaw: any = {
remoteJid: data.remoteJid, remoteJid: data.remoteJid,
pushName: data?.pushName, pushName: data?.pushName,
@ -222,6 +312,9 @@ export class EvolutionStartupService extends ChannelStartupService {
}); });
if (chat) { if (chat) {
this.logger.debug(
`[updateContact] Chat já existe para este contato. Atualizando...: ${chat.remoteJid}`,
);
const chatRaw: any = { const chatRaw: any = {
remoteJid: data.remoteJid, remoteJid: data.remoteJid,
instanceId: this.instanceId, instanceId: this.instanceId,
@ -233,8 +326,10 @@ export class EvolutionStartupService extends ChannelStartupService {
where: { remoteJid: chat.remoteJid }, where: { remoteJid: chat.remoteJid },
data: chatRaw, data: chatRaw,
}); });
} } else {
this.logger.debug(
'[updateContact] Nenhum chat encontrado para este contato. Criando novo...',
);
const chatRaw: any = { const chatRaw: any = {
remoteJid: data.remoteJid, remoteJid: data.remoteJid,
instanceId: this.instanceId, instanceId: this.instanceId,
@ -246,44 +341,75 @@ export class EvolutionStartupService extends ChannelStartupService {
data: chatRaw, data: chatRaw,
}); });
} }
} catch (error) {
this.logger.error(`[updateContact] Erro ao atualizar/criar contato: ${error}`);
}
}
protected async sendMessageWithTyping(number: string, message: any, options?: Options, isIntegration = false) { protected async sendMessageWithTyping(
number: string,
message: any,
options?: Options,
isIntegration = false,
) {
this.logger.log(`[sendMessageWithTyping] Enviando mensagem para: ${number}`);
this.logger.debug(
`[sendMessageWithTyping] Mensagem: ${JSON.stringify(message)}, Options: ${JSON.stringify(
options,
)}, isIntegration: ${isIntegration}`,
);
try { try {
let quoted: any; let quoted: any;
let webhookUrl: any; let webhookUrl: any;
if (options?.quoted) { if (options?.quoted) {
this.logger.debug('[sendMessageWithTyping] Opção quoted detectada...');
const m = options?.quoted; const m = options?.quoted;
const msg = m?.key; const msg = m?.key;
if (!msg) { if (!msg) {
this.logger.error('[sendMessageWithTyping] Mensagem de citação não encontrada!');
throw 'Message not found'; throw 'Message not found';
} }
quoted = msg; quoted = msg;
} }
if (options.delay) { if (options?.delay) {
this.logger.debug(`[sendMessageWithTyping] Aguardando delay de ${options.delay}ms...`);
await new Promise((resolve) => setTimeout(resolve, options.delay)); await new Promise((resolve) => setTimeout(resolve, options.delay));
} }
if (options?.webhookUrl) { if (options?.webhookUrl) {
this.logger.debug(
`[sendMessageWithTyping] Usando webhookUrl customizado: ${options.webhookUrl}`,
);
webhookUrl = options.webhookUrl; webhookUrl = options.webhookUrl;
} }
const messageId = v4(); const messageId = v4();
this.logger.debug(
`[sendMessageWithTyping] Gerando UUID para mensagem: ${messageId}`,
);
// debug message
this.logger.debug(
`[sendMessageWithTyping] Mensagem a ser enviada: ${JSON.stringify(message)}`,
);
let messageRaw: any = { let messageRaw: any = {
key: { fromMe: true, id: messageId, remoteJid: number }, key: { fromMe: true, id: messageId, remoteJid: number, channel: message.channel, inbox_id: message.inbox_id },
messageTimestamp: Math.round(new Date().getTime() / 1000), messageTimestamp: Math.round(new Date().getTime() / 1000),
webhookUrl, webhookUrl,
source: 'unknown', source: 'unknown',
instanceId: this.instanceId, instanceId: this.instanceId,
status: status[1], status: status[1],
}; };
// debug messageRaw
this.logger.debug(`[sendMessageWithTyping] messageRaw a ser enviada: ${JSON.stringify(messageRaw)}`);
// Verifica o tipo de mídia para compor a mensagem
if (message?.mediaType === 'image') { if (message?.mediaType === 'image') {
this.logger.debug('[sendMessageWithTyping] Montando mensagem de imagem...');
messageRaw = { messageRaw = {
...messageRaw, ...messageRaw,
message: { message: {
@ -293,6 +419,7 @@ export class EvolutionStartupService extends ChannelStartupService {
messageType: 'imageMessage', messageType: 'imageMessage',
}; };
} else if (message?.mediaType === 'video') { } else if (message?.mediaType === 'video') {
this.logger.debug('[sendMessageWithTyping] Montando mensagem de vídeo...');
messageRaw = { messageRaw = {
...messageRaw, ...messageRaw,
message: { message: {
@ -302,6 +429,7 @@ export class EvolutionStartupService extends ChannelStartupService {
messageType: 'videoMessage', messageType: 'videoMessage',
}; };
} else if (message?.mediaType === 'audio') { } else if (message?.mediaType === 'audio') {
this.logger.debug('[sendMessageWithTyping] Montando mensagem de áudio...');
messageRaw = { messageRaw = {
...messageRaw, ...messageRaw,
message: { message: {
@ -311,6 +439,7 @@ export class EvolutionStartupService extends ChannelStartupService {
messageType: 'audioMessage', messageType: 'audioMessage',
}; };
} else if (message?.mediaType === 'document') { } else if (message?.mediaType === 'document') {
this.logger.debug('[sendMessageWithTyping] Montando mensagem de documento...');
messageRaw = { messageRaw = {
...messageRaw, ...messageRaw,
message: { message: {
@ -320,6 +449,7 @@ export class EvolutionStartupService extends ChannelStartupService {
messageType: 'documentMessage', messageType: 'documentMessage',
}; };
} else { } else {
this.logger.debug('[sendMessageWithTyping] Montando mensagem de texto...');
messageRaw = { messageRaw = {
...messageRaw, ...messageRaw,
message: { message: {
@ -330,11 +460,21 @@ export class EvolutionStartupService extends ChannelStartupService {
}; };
} }
this.logger.log(messageRaw); this.logger.log(
`[sendMessageWithTyping] messageRaw final: ${JSON.stringify(messageRaw)}`,
);
this.sendDataWebhook(Events.SEND_MESSAGE, messageRaw); this.sendDataWebhook(Events.SEND_MESSAGE, messageRaw);
if (this.configService.get<Chatwoot>('CHATWOOT').ENABLED && this.localChatwoot?.enabled && !isIntegration) { // debug a proxima funcao
this.logger.debug(`[sendMessageWithTyping] CHATWOOT: ${this.configService.get<Chatwoot>('CHATWOOT').ENABLED}, LOCAL: ${this.localChatwoot?.enabled}, INTEGRATION: ${isIntegration}`);
if (
this.configService.get<Chatwoot>('CHATWOOT').ENABLED &&
this.localChatwoot?.enabled &&
!isIntegration
) {
this.logger.debug('[sendMessageWithTyping] Enviando evento SEND_MESSAGE ao Chatwoot...');
this.chatwootService.eventWhatsapp( this.chatwootService.eventWhatsapp(
Events.SEND_MESSAGE, Events.SEND_MESSAGE,
{ instanceName: this.instance.name, instanceId: this.instanceId }, { instanceName: this.instance.name, instanceId: this.instanceId },
@ -342,38 +482,54 @@ export class EvolutionStartupService extends ChannelStartupService {
); );
} }
if (this.configService.get<Chatwoot>('CHATWOOT').ENABLED && this.localChatwoot?.enabled && isIntegration) if (
this.configService.get<Chatwoot>('CHATWOOT').ENABLED &&
this.localChatwoot?.enabled &&
isIntegration
) {
this.logger.debug(
'[sendMessageWithTyping] Emitindo mensagem para chatbotController em modo de integração...',
);
await chatbotController.emit({ await chatbotController.emit({
instance: { instanceName: this.instance.name, instanceId: this.instanceId }, instance: { instanceName: this.instance.name, instanceId: this.instanceId },
remoteJid: messageRaw.key.remoteJid, remoteJid: messageRaw.key.remoteJid,
msg: messageRaw, msg: messageRaw,
pushName: messageRaw.pushName, pushName: messageRaw.pushName,
}); });
}
this.logger.debug('[sendMessageWithTyping] Salvando mensagem no Prisma...');
await this.prismaRepository.message.create({ await this.prismaRepository.message.create({
data: messageRaw, data: messageRaw,
}); });
return messageRaw; return messageRaw;
} catch (error) { } catch (error) {
this.logger.error(error); this.logger.error(
`[sendMessageWithTyping] Erro ao enviar mensagem para ${number}: ${error}`,
);
throw new BadRequestException(error.toString()); throw new BadRequestException(error.toString());
} }
} }
public async textMessage(data: SendTextDto, isIntegration = false) { public async textMessage(data2: SendTextDto, isIntegration = false) {
this.logger.log('[textMessage] Enviando mensagem de texto...');
this.logger.debug(`[textMessage] Dados recebidos: ${JSON.stringify(data2)}`);
const res = await this.sendMessageWithTyping( const res = await this.sendMessageWithTyping(
data.number, data2.number,
{ {
conversation: data.text, conversation: data2.text,
channel: data2.channel, // passa channel aqui
inbox_id: data2.inbox_id, // e inbox_id aqui
}, },
{ {
delay: data?.delay, delay: data2?.delay,
presence: 'composing', presence: 'composing',
quoted: data?.quoted, quoted: data2?.quoted,
linkPreview: data?.linkPreview, linkPreview: data2?.linkPreview,
mentionsEveryOne: data?.mentionsEveryOne, mentionsEveryOne: data2?.mentionsEveryOne,
mentioned: data?.mentioned, mentioned: data2?.mentioned,
}, },
isIntegration, isIntegration,
); );
@ -381,18 +537,31 @@ export class EvolutionStartupService extends ChannelStartupService {
} }
protected async prepareMediaMessage(mediaMessage: MediaMessage) { protected async prepareMediaMessage(mediaMessage: MediaMessage) {
this.logger.log('[prepareMediaMessage] Preparando mensagem de mídia...');
this.logger.debug(
`[prepareMediaMessage] Dados recebidos: ${JSON.stringify(mediaMessage)}`,
);
try { try {
if (mediaMessage.mediatype === 'document' && !mediaMessage.fileName) { if (mediaMessage.mediatype === 'document' && !mediaMessage.fileName) {
this.logger.debug(
'[prepareMediaMessage] Definindo filename para documento...',
);
const regex = new RegExp(/.*\/(.+?)\./); const regex = new RegExp(/.*\/(.+?)\./);
const arrayMatch = regex.exec(mediaMessage.media); const arrayMatch = regex.exec(mediaMessage.media);
mediaMessage.fileName = arrayMatch[1]; mediaMessage.fileName = arrayMatch[1];
} }
if (mediaMessage.mediatype === 'image' && !mediaMessage.fileName) { if (mediaMessage.mediatype === 'image' && !mediaMessage.fileName) {
this.logger.debug(
'[prepareMediaMessage] Definindo filename padrão para imagem...',
);
mediaMessage.fileName = 'image.png'; mediaMessage.fileName = 'image.png';
} }
if (mediaMessage.mediatype === 'video' && !mediaMessage.fileName) { if (mediaMessage.mediatype === 'video' && !mediaMessage.fileName) {
this.logger.debug(
'[prepareMediaMessage] Definindo filename padrão para vídeo...',
);
mediaMessage.fileName = 'video.mp4'; mediaMessage.fileName = 'video.mp4';
} }
@ -406,6 +575,7 @@ export class EvolutionStartupService extends ChannelStartupService {
gifPlayback: false, gifPlayback: false,
}; };
this.logger.debug('[prepareMediaMessage] Verificando mimetype...');
if (isURL(mediaMessage.media)) { if (isURL(mediaMessage.media)) {
mimetype = mime.getType(mediaMessage.media); mimetype = mime.getType(mediaMessage.media);
} else { } else {
@ -414,17 +584,32 @@ export class EvolutionStartupService extends ChannelStartupService {
prepareMedia.mimetype = mimetype; prepareMedia.mimetype = mimetype;
this.logger.debug(
`[prepareMediaMessage] Retornando objeto de mídia preparado: ${JSON.stringify(
prepareMedia,
)}`,
);
return prepareMedia; return prepareMedia;
} catch (error) { } catch (error) {
this.logger.error(error); this.logger.error(
`[prepareMediaMessage] Erro ao preparar mensagem de mídia: ${error}`,
);
throw new InternalServerErrorException(error?.toString() || error); throw new InternalServerErrorException(error?.toString() || error);
} }
} }
public async mediaMessage(data: SendMediaDto, file?: any, isIntegration = false) { public async mediaMessage(data: SendMediaDto, file?: any, isIntegration = false) {
this.logger.log('[mediaMessage] Enviando mensagem de mídia...');
this.logger.debug(`[mediaMessage] Dados recebidos: ${JSON.stringify(data)}`);
try {
const mediaData: SendMediaDto = { ...data }; const mediaData: SendMediaDto = { ...data };
if (file) mediaData.media = file.buffer.toString('base64'); if (file) {
this.logger.debug(
'[mediaMessage] Convertendo arquivo em base64 para envio...',
);
mediaData.media = file.buffer.toString('base64');
}
const message = await this.prepareMediaMessage(mediaData); const message = await this.prepareMediaMessage(mediaData);
@ -443,12 +628,22 @@ export class EvolutionStartupService extends ChannelStartupService {
); );
return mediaSent; return mediaSent;
} catch (error) {
this.logger.error(
`[mediaMessage] Erro ao enviar mensagem de mídia: ${error}`,
);
throw new InternalServerErrorException(error?.toString());
}
} }
public async processAudio(audio: string, number: string) { public async processAudio(audio: string, number: string) {
this.logger.log('[processAudio] Processando áudio...');
this.logger.debug(`[processAudio] Áudio: ${audio}, Número: ${number}`);
try {
number = number.replace(/\D/g, ''); number = number.replace(/\D/g, '');
const hash = `${number}-${new Date().getTime()}`; this.logger.debug(`[processAudio] Número formatado: ${number}`);
const hash = `${number}-${new Date().getTime()}`;
let mimetype: string; let mimetype: string;
const prepareMedia: any = { const prepareMedia: any = {
@ -464,17 +659,34 @@ export class EvolutionStartupService extends ChannelStartupService {
} }
prepareMedia.mimetype = mimetype; prepareMedia.mimetype = mimetype;
this.logger.debug(
`[processAudio] Retornando objeto de mídia de áudio: ${JSON.stringify(
prepareMedia,
)}`,
);
return prepareMedia; return prepareMedia;
} catch (error) {
this.logger.error(
`[processAudio] Erro ao processar áudio: ${error.toString()}`,
);
throw new InternalServerErrorException(error?.toString());
}
} }
public async audioWhatsapp(data: SendAudioDto, file?: any, isIntegration = false) { public async audioWhatsapp(data: SendAudioDto, file?: any, isIntegration = false) {
this.logger.log('[audioWhatsapp] Enviando áudio via Whatsapp...');
this.logger.debug(`[audioWhatsapp] Dados recebidos: ${JSON.stringify(data)}`);
try {
const mediaData: SendAudioDto = { ...data }; const mediaData: SendAudioDto = { ...data };
if (file?.buffer) { if (file?.buffer) {
this.logger.debug('[audioWhatsapp] Convertendo buffer em base64...');
mediaData.audio = file.buffer.toString('base64'); mediaData.audio = file.buffer.toString('base64');
} else { } else {
console.error('El archivo o buffer no est<73> definido correctamente.'); this.logger.error(
'[audioWhatsapp] O arquivo ou buffer não está definido corretamente.',
);
throw new Error('File or buffer is undefined.'); throw new Error('File or buffer is undefined.');
} }
@ -495,153 +707,206 @@ export class EvolutionStartupService extends ChannelStartupService {
); );
return audioSent; return audioSent;
} catch (error) {
this.logger.error(`[audioWhatsapp] Erro ao enviar áudio: ${error}`);
throw new InternalServerErrorException(error?.toString());
}
} }
public async buttonMessage() { public async buttonMessage() {
this.logger.warn('[buttonMessage] Método não disponível no Evolution Channel');
throw new BadRequestException('Method not available on Evolution Channel'); throw new BadRequestException('Method not available on Evolution Channel');
} }
public async locationMessage() { public async locationMessage() {
this.logger.warn('[locationMessage] Método não disponível no Evolution Channel');
throw new BadRequestException('Method not available on Evolution Channel'); throw new BadRequestException('Method not available on Evolution Channel');
} }
public async listMessage() { public async listMessage() {
this.logger.warn('[listMessage] Método não disponível no Evolution Channel');
throw new BadRequestException('Method not available on Evolution Channel'); throw new BadRequestException('Method not available on Evolution Channel');
} }
public async templateMessage() { public async templateMessage() {
this.logger.warn('[templateMessage] Método não disponível no Evolution Channel');
throw new BadRequestException('Method not available on Evolution Channel'); throw new BadRequestException('Method not available on Evolution Channel');
} }
public async contactMessage() { public async contactMessage() {
this.logger.warn('[contactMessage] Método não disponível no Evolution Channel');
throw new BadRequestException('Method not available on Evolution Channel'); throw new BadRequestException('Method not available on Evolution Channel');
} }
public async reactionMessage() { public async reactionMessage() {
this.logger.warn('[reactionMessage] Método não disponível no Evolution Channel');
throw new BadRequestException('Method not available on Evolution Channel'); throw new BadRequestException('Method not available on Evolution Channel');
} }
public async getBase64FromMediaMessage() { public async getBase64FromMediaMessage() {
this.logger.warn('[getBase64FromMediaMessage] Método não disponível no Evolution Channel');
throw new BadRequestException('Method not available on Evolution Channel'); throw new BadRequestException('Method not available on Evolution Channel');
} }
public async deleteMessage() { public async deleteMessage() {
this.logger.warn('[deleteMessage] Método não disponível no Evolution Channel');
throw new BadRequestException('Method not available on Evolution Channel'); throw new BadRequestException('Method not available on Evolution Channel');
} }
public async mediaSticker() { public async mediaSticker() {
this.logger.warn('[mediaSticker] Método não disponível no Evolution Channel');
throw new BadRequestException('Method not available on Evolution Channel'); throw new BadRequestException('Method not available on Evolution Channel');
} }
public async pollMessage() { public async pollMessage() {
this.logger.warn('[pollMessage] Método não disponível no Evolution Channel');
throw new BadRequestException('Method not available on Evolution Channel'); throw new BadRequestException('Method not available on Evolution Channel');
} }
public async statusMessage() { public async statusMessage() {
this.logger.warn('[statusMessage] Método não disponível no Evolution Channel');
throw new BadRequestException('Method not available on Evolution Channel'); throw new BadRequestException('Method not available on Evolution Channel');
} }
public async reloadConnection() { public async reloadConnection() {
this.logger.warn('[reloadConnection] Método não disponível no Evolution Channel');
throw new BadRequestException('Method not available on Evolution Channel'); throw new BadRequestException('Method not available on Evolution Channel');
} }
public async whatsappNumber() { public async whatsappNumber() {
this.logger.warn('[whatsappNumber] Método não disponível no Evolution Channel');
throw new BadRequestException('Method not available on Evolution Channel'); throw new BadRequestException('Method not available on Evolution Channel');
} }
public async markMessageAsRead() { public async markMessageAsRead() {
this.logger.warn('[markMessageAsRead] Método não disponível no Evolution Channel');
throw new BadRequestException('Method not available on Evolution Channel'); throw new BadRequestException('Method not available on Evolution Channel');
} }
public async archiveChat() { public async archiveChat() {
this.logger.warn('[archiveChat] Método não disponível no Evolution Channel');
throw new BadRequestException('Method not available on Evolution Channel'); throw new BadRequestException('Method not available on Evolution Channel');
} }
public async markChatUnread() { public async markChatUnread() {
this.logger.warn('[markChatUnread] Método não disponível no Evolution Channel');
throw new BadRequestException('Method not available on Evolution Channel'); throw new BadRequestException('Method not available on Evolution Channel');
} }
public async fetchProfile() { public async fetchProfile() {
this.logger.warn('[fetchProfile] Método não disponível no Evolution Channel');
throw new BadRequestException('Method not available on Evolution Channel'); throw new BadRequestException('Method not available on Evolution Channel');
} }
public async offerCall() { public async offerCall() {
this.logger.warn('[offerCall] Método não disponível no WhatsApp Business API');
throw new BadRequestException('Method not available on WhatsApp Business API'); throw new BadRequestException('Method not available on WhatsApp Business API');
} }
public async sendPresence() { public async sendPresence() {
this.logger.warn('[sendPresence] Método não disponível no Evolution Channel');
throw new BadRequestException('Method not available on Evolution Channel'); throw new BadRequestException('Method not available on Evolution Channel');
} }
public async setPresence() { public async setPresence() {
this.logger.warn('[setPresence] Método não disponível no Evolution Channel');
throw new BadRequestException('Method not available on Evolution Channel'); throw new BadRequestException('Method not available on Evolution Channel');
} }
public async fetchPrivacySettings() { public async fetchPrivacySettings() {
this.logger.warn('[fetchPrivacySettings] Método não disponível no Evolution Channel');
throw new BadRequestException('Method not available on Evolution Channel'); throw new BadRequestException('Method not available on Evolution Channel');
} }
public async updatePrivacySettings() { public async updatePrivacySettings() {
this.logger.warn('[updatePrivacySettings] Método não disponível no Evolution Channel');
throw new BadRequestException('Method not available on Evolution Channel'); throw new BadRequestException('Method not available on Evolution Channel');
} }
public async fetchBusinessProfile() { public async fetchBusinessProfile() {
this.logger.warn('[fetchBusinessProfile] Método não disponível no Evolution Channel');
throw new BadRequestException('Method not available on Evolution Channel'); throw new BadRequestException('Method not available on Evolution Channel');
} }
public async updateProfileName() { public async updateProfileName() {
this.logger.warn('[updateProfileName] Método não disponível no Evolution Channel');
throw new BadRequestException('Method not available on Evolution Channel'); throw new BadRequestException('Method not available on Evolution Channel');
} }
public async updateProfileStatus() { public async updateProfileStatus() {
this.logger.warn('[updateProfileStatus] Método não disponível no Evolution Channel');
throw new BadRequestException('Method not available on Evolution Channel'); throw new BadRequestException('Method not available on Evolution Channel');
} }
public async updateProfilePicture() { public async updateProfilePicture() {
this.logger.warn('[updateProfilePicture] Método não disponível no Evolution Channel');
throw new BadRequestException('Method not available on Evolution Channel'); throw new BadRequestException('Method not available on Evolution Channel');
} }
public async removeProfilePicture() { public async removeProfilePicture() {
this.logger.warn('[removeProfilePicture] Método não disponível no Evolution Channel');
throw new BadRequestException('Method not available on Evolution Channel'); throw new BadRequestException('Method not available on Evolution Channel');
} }
public async blockUser() { public async blockUser() {
this.logger.warn('[blockUser] Método não disponível no Evolution Channel');
throw new BadRequestException('Method not available on Evolution Channel'); throw new BadRequestException('Method not available on Evolution Channel');
} }
public async updateMessage() { public async updateMessage() {
this.logger.warn('[updateMessage] Método não disponível no Evolution Channel');
throw new BadRequestException('Method not available on Evolution Channel'); throw new BadRequestException('Method not available on Evolution Channel');
} }
public async createGroup() { public async createGroup() {
this.logger.warn('[createGroup] Método não disponível no Evolution Channel');
throw new BadRequestException('Method not available on Evolution Channel'); throw new BadRequestException('Method not available on Evolution Channel');
} }
public async updateGroupPicture() { public async updateGroupPicture() {
this.logger.warn('[updateGroupPicture] Método não disponível no Evolution Channel');
throw new BadRequestException('Method not available on Evolution Channel'); throw new BadRequestException('Method not available on Evolution Channel');
} }
public async updateGroupSubject() { public async updateGroupSubject() {
this.logger.warn('[updateGroupSubject] Método não disponível no Evolution Channel');
throw new BadRequestException('Method not available on Evolution Channel'); throw new BadRequestException('Method not available on Evolution Channel');
} }
public async updateGroupDescription() { public async updateGroupDescription() {
this.logger.warn('[updateGroupDescription] Método não disponível no Evolution Channel');
throw new BadRequestException('Method not available on Evolution Channel'); throw new BadRequestException('Method not available on Evolution Channel');
} }
public async findGroup() { public async findGroup() {
this.logger.warn('[findGroup] Método não disponível no Evolution Channel');
throw new BadRequestException('Method not available on Evolution Channel'); throw new BadRequestException('Method not available on Evolution Channel');
} }
public async fetchAllGroups() { public async fetchAllGroups() {
this.logger.warn('[fetchAllGroups] Método não disponível no Evolution Channel');
throw new BadRequestException('Method not available on Evolution Channel'); throw new BadRequestException('Method not available on Evolution Channel');
} }
public async inviteCode() { public async inviteCode() {
this.logger.warn('[inviteCode] Método não disponível no Evolution Channel');
throw new BadRequestException('Method not available on Evolution Channel'); throw new BadRequestException('Method not available on Evolution Channel');
} }
public async inviteInfo() { public async inviteInfo() {
this.logger.warn('[inviteInfo] Método não disponível no Evolution Channel');
throw new BadRequestException('Method not available on Evolution Channel'); throw new BadRequestException('Method not available on Evolution Channel');
} }
public async sendInvite() { public async sendInvite() {
this.logger.warn('[sendInvite] Método não disponível no Evolution Channel');
throw new BadRequestException('Method not available on Evolution Channel'); throw new BadRequestException('Method not available on Evolution Channel');
} }
public async acceptInviteCode() { public async acceptInviteCode() {
this.logger.warn('[acceptInviteCode] Método não disponível no Evolution Channel');
throw new BadRequestException('Method not available on Evolution Channel'); throw new BadRequestException('Method not available on Evolution Channel');
} }
public async revokeInviteCode() { public async revokeInviteCode() {
this.logger.warn('[revokeInviteCode] Método não disponível no Evolution Channel');
throw new BadRequestException('Method not available on Evolution Channel'); throw new BadRequestException('Method not available on Evolution Channel');
} }
public async findParticipants() { public async findParticipants() {
this.logger.warn('[findParticipants] Método não disponível no Evolution Channel');
throw new BadRequestException('Method not available on Evolution Channel'); throw new BadRequestException('Method not available on Evolution Channel');
} }
public async updateGParticipant() { public async updateGParticipant() {
this.logger.warn('[updateGParticipant] Método não disponível no Evolution Channel');
throw new BadRequestException('Method not available on Evolution Channel'); throw new BadRequestException('Method not available on Evolution Channel');
} }
public async updateGSetting() { public async updateGSetting() {
this.logger.warn('[updateGSetting] Método não disponível no Evolution Channel');
throw new BadRequestException('Method not available on Evolution Channel'); throw new BadRequestException('Method not available on Evolution Channel');
} }
public async toggleEphemeral() { public async toggleEphemeral() {
this.logger.warn('[toggleEphemeral] Método não disponível no Evolution Channel');
throw new BadRequestException('Method not available on Evolution Channel'); throw new BadRequestException('Method not available on Evolution Channel');
} }
public async leaveGroup() { public async leaveGroup() {
this.logger.warn('[leaveGroup] Método não disponível no Evolution Channel');
throw new BadRequestException('Method not available on Evolution Channel'); throw new BadRequestException('Method not available on Evolution Channel');
} }
public async fetchLabels() { public async fetchLabels() {
this.logger.warn('[fetchLabels] Método não disponível no Evolution Channel');
throw new BadRequestException('Method not available on Evolution Channel'); throw new BadRequestException('Method not available on Evolution Channel');
} }
public async handleLabel() { public async handleLabel() {
this.logger.warn('[handleLabel] Método não disponível no Evolution Channel');
throw new BadRequestException('Method not available on Evolution Channel'); throw new BadRequestException('Method not available on Evolution Channel');
} }
public async receiveMobileCode() { public async receiveMobileCode() {
this.logger.warn('[receiveMobileCode] Método não disponível no Evolution Channel');
throw new BadRequestException('Method not available on Evolution Channel'); throw new BadRequestException('Method not available on Evolution Channel');
} }
public async fakeCall() { public async fakeCall() {
this.logger.warn('[fakeCall] Método não disponível no Evolution Channel');
throw new BadRequestException('Method not available on Evolution Channel'); throw new BadRequestException('Method not available on Evolution Channel');
} }
} }

View File

@ -1,39 +1,68 @@
import { Logger } from '@config/logger.config';
import { PrismaRepository } from '@api/repository/repository.service'; import { PrismaRepository } from '@api/repository/repository.service';
import { WAMonitoringService } from '@api/services/monitor.service'; import { WAMonitoringService } from '@api/services/monitor.service';
import { Logger } from '@config/logger.config';
import { ChannelController, ChannelControllerInterface } from '../channel.controller'; import { ChannelController, ChannelControllerInterface } from '../channel.controller';
export class EvolutionController extends ChannelController implements ChannelControllerInterface { export class EvolutionController extends ChannelController implements ChannelControllerInterface {
private readonly logger = new Logger('EvolutionController'); private readonly logger = new Logger('EvolutionController');
constructor(prismaRepository: PrismaRepository, waMonitor: WAMonitoringService) { // Flag para indicar se a integração está habilitada
super(prismaRepository, waMonitor);
}
integrationEnabled: boolean; integrationEnabled: boolean;
public async receiveWebhook(data: any) { constructor(prismaRepository: PrismaRepository, waMonitor: WAMonitoringService) {
const numberId = data.numberId; super(prismaRepository, waMonitor);
this.logger.debug('EvolutionController -> constructor called');
// Exemplo de log ao definir flags ou propriedades adicionais
this.integrationEnabled = true;
this.logger.debug(`EvolutionController -> integrationEnabled set to: ${this.integrationEnabled}`);
}
public async receiveWebhook(data: any) {
this.logger.debug('EvolutionController -> receiveWebhook called');
this.logger.debug(`EvolutionController -> receiveWebhook -> data: ${JSON.stringify(data)}`);
// Extraindo número de identificação
const numberId = data.numberId;
this.logger.debug(`EvolutionController -> receiveWebhook -> numberId: ${numberId}`);
// Validando se o numberId foi informado
if (!numberId) { if (!numberId) {
this.logger.error('WebhookService -> receiveWebhookEvolution -> numberId not found'); this.logger.error('WebhookService -> receiveWebhookEvolution -> numberId not found');
return; return;
} }
try {
// Log antes de buscar a instância
this.logger.debug(`EvolutionController -> Looking for instance with numberId: ${numberId}`);
const instance = await this.prismaRepository.instance.findFirst({ const instance = await this.prismaRepository.instance.findFirst({
where: { number: numberId }, where: { number: numberId },
}); });
// Log do resultado da busca
this.logger.debug(`EvolutionController -> Prisma instance result: ${JSON.stringify(instance)}`);
// Validando se a instância foi encontrada
if (!instance) { if (!instance) {
this.logger.error('WebhookService -> receiveWebhook -> instance not found'); this.logger.error('WebhookService -> receiveWebhook -> instance not found');
return; return;
} }
// Log antes de tentar conectar
this.logger.debug(`EvolutionController -> Connecting to WhatsApp instance: ${instance.name}`);
await this.waMonitor.waInstances[instance.name].connectToWhatsapp(data); await this.waMonitor.waInstances[instance.name].connectToWhatsapp(data);
this.logger.debug('EvolutionController -> Successfully connected to WhatsApp instance');
// Retorno de sucesso
this.logger.debug('EvolutionController -> receiveWebhook -> returning success');
return { return {
status: 'success', status: 'success',
}; };
} catch (error) {
this.logger.error(`EvolutionController -> receiveWebhook -> Error: ${error.message}`);
this.logger.debug(`EvolutionController -> receiveWebhook -> Stack trace: ${error.stack}`);
throw error;
}
} }
} }

View File

@ -1,18 +1,64 @@
import { Logger } from '@config/logger.config';
import { RouterBroker } from '@api/abstract/abstract.router'; import { RouterBroker } from '@api/abstract/abstract.router';
import { evolutionController } from '@api/server.module'; import { evolutionController } from '@api/server.module';
import { ConfigService } from '@config/env.config'; import { ConfigService } from '@config/env.config';
import { Router } from 'express'; import { Router, Request, Response } from 'express';
export class EvolutionRouter extends RouterBroker { export class EvolutionRouter extends RouterBroker {
constructor(readonly configService: ConfigService) { private readonly logger = new Logger('EvolutionRouter');
super();
this.router.post(this.routerPath('webhook/evolution', false), async (req, res) => {
const { body } = req;
const response = await evolutionController.receiveWebhook(body);
return res.status(200).json(response);
});
}
public readonly router: Router = Router(); public readonly router: Router = Router();
constructor(readonly configService: ConfigService) {
super();
// Log the initialization of the EvolutionRouter
this.logger.debug('[EvolutionRouter] Initializing router...');
this.router.post(
this.routerPath('webhook/evolution', false),
async (req: Request, res: Response) => {
try {
this.logger.info('[EvolutionRouter] POST /webhook/evolution route called');
// Log the request body for debugging (cuidado com dados sensíveis)
const { body } = req;
this.logger.debug(
`[EvolutionRouter] Received request body: ${JSON.stringify(this.sanitizeBody(body))}`
);
this.logger.debug('[EvolutionRouter] Calling evolutionController.receiveWebhook...');
const response = await evolutionController.receiveWebhook(body);
// Log the response from the controller
this.logger.debug(
`[EvolutionRouter] Response from evolutionController: ${JSON.stringify(response)}`
);
this.logger.debug('[EvolutionRouter] Returning 200 with response');
return res.status(200).json(response);
} catch (error: any) {
// Log the error for debugging
this.logger.error(`[EvolutionRouter] Error in POST /webhook/evolution: ${error.message}`);
return res.status(500).json({
message: 'Internal server error',
error: error.message,
});
}
}
);
this.logger.debug('[EvolutionRouter] Router setup complete');
}
/**
* Filters sensitive information from the request body for safe logging.
*/
private sanitizeBody(body: any): any {
// Implement filtering logic to exclude sensitive data
const sanitizedBody = { ...body };
if (sanitizedBody.password) sanitizedBody.password = '[FILTERED]';
if (sanitizedBody.token) sanitizedBody.token = '[FILTERED]';
return sanitizedBody;
}
} }

View File

@ -1,3 +1,4 @@
import { Logger } from '@config/logger.config';
import { InstanceDto } from '@api/dto/instance.dto'; import { InstanceDto } from '@api/dto/instance.dto';
import { ChatwootDto } from '@api/integrations/chatbot/chatwoot/dto/chatwoot.dto'; import { ChatwootDto } from '@api/integrations/chatbot/chatwoot/dto/chatwoot.dto';
import { ChatwootService } from '@api/integrations/chatbot/chatwoot/services/chatwoot.service'; import { ChatwootService } from '@api/integrations/chatbot/chatwoot/services/chatwoot.service';
@ -10,6 +11,8 @@ import { BadRequestException } from '@exceptions';
import { isURL } from 'class-validator'; import { isURL } from 'class-validator';
export class ChatwootController { export class ChatwootController {
private readonly logger = new Logger(ChatwootController.name);
constructor( constructor(
private readonly chatwootService: ChatwootService, private readonly chatwootService: ChatwootService,
private readonly configService: ConfigService, private readonly configService: ConfigService,
@ -17,51 +20,85 @@ export class ChatwootController {
) {} ) {}
public async createChatwoot(instance: InstanceDto, data: ChatwootDto) { public async createChatwoot(instance: InstanceDto, data: ChatwootDto) {
if (!this.configService.get<Chatwoot>('CHATWOOT').ENABLED) throw new BadRequestException('Chatwoot is disabled'); this.logger.debug(`[createChatwoot] Iniciando criação de Chatwoot para a instância: ${JSON.stringify(instance)}`);
this.logger.debug(`[createChatwoot] Dados recebidos: ${JSON.stringify(data)}`);
const chatwootConfig = this.configService.get<Chatwoot>('CHATWOOT');
if (!chatwootConfig.ENABLED) {
this.logger.warn('[createChatwoot] Chatwoot está desabilitado. Lançando exceção...');
throw new BadRequestException('Chatwoot is disabled');
}
if (data?.enabled) { if (data?.enabled) {
this.logger.debug('[createChatwoot] Validação de dados habilitados...');
if (!isURL(data.url, { require_tld: false })) { if (!isURL(data.url, { require_tld: false })) {
this.logger.error(`[createChatwoot] URL inválida: ${data.url}`);
throw new BadRequestException('url is not valid'); throw new BadRequestException('url is not valid');
} }
if (!data.accountId) { if (!data.accountId) {
this.logger.error('[createChatwoot] accountId não informado');
throw new BadRequestException('accountId is required'); throw new BadRequestException('accountId is required');
} }
if (!data.token) { if (!data.token) {
this.logger.error('[createChatwoot] token não informado');
throw new BadRequestException('token is required'); throw new BadRequestException('token is required');
} }
if (data.signMsg !== true && data.signMsg !== false) { if (data.signMsg !== true && data.signMsg !== false) {
this.logger.error('[createChatwoot] signMsg inválido ou não informado');
throw new BadRequestException('signMsg is required'); throw new BadRequestException('signMsg is required');
} }
if (data.signMsg === false) data.signDelimiter = null;
if (data.signMsg === false) {
this.logger.debug('[createChatwoot] signMsg definido como false, removendo signDelimiter');
data.signDelimiter = null;
}
} else {
this.logger.debug('[createChatwoot] Dados informam que Chatwoot não está habilitado (enabled=false ou undefined).');
} }
if (!data.nameInbox || data.nameInbox === '') { if (!data.nameInbox || data.nameInbox === '') {
this.logger.debug(`[createChatwoot] nameInbox não informado. Usando nome da instância: "${instance.instanceName}"`);
data.nameInbox = instance.instanceName; data.nameInbox = instance.instanceName;
} }
this.logger.debug('[createChatwoot] Chamando ChatwootService.create...');
const result = await this.chatwootService.create(instance, data); const result = await this.chatwootService.create(instance, data);
this.logger.debug(`[createChatwoot] Retorno de ChatwootService.create: ${JSON.stringify(result)}`);
const urlServer = this.configService.get<HttpServer>('SERVER').URL; const urlServer = this.configService.get<HttpServer>('SERVER').URL;
this.logger.debug(`[createChatwoot] urlServer obtido: ${urlServer}`);
const response = { const response = {
...result, ...result,
webhook_url: `${urlServer}/chatwoot/webhook/${encodeURIComponent(instance.instanceName)}`, webhook_url: `${urlServer}/chatwoot/webhook/${encodeURIComponent(instance.instanceName)}`,
}; };
this.logger.debug(`[createChatwoot] Retornando resposta final: ${JSON.stringify(response)}`);
return response; return response;
} }
public async findChatwoot(instance: InstanceDto): Promise<ChatwootDto & { webhook_url: string }> { public async findChatwoot(instance: InstanceDto): Promise<ChatwootDto & { webhook_url: string }> {
if (!this.configService.get<Chatwoot>('CHATWOOT').ENABLED) throw new BadRequestException('Chatwoot is disabled'); this.logger.debug(`[findChatwoot] Buscando configurações Chatwoot para a instância: ${JSON.stringify(instance)}`);
const chatwootConfig = this.configService.get<Chatwoot>('CHATWOOT');
if (!chatwootConfig.ENABLED) {
this.logger.warn('[findChatwoot] Chatwoot está desabilitado. Lançando exceção...');
throw new BadRequestException('Chatwoot is disabled');
}
this.logger.debug('[findChatwoot] Chamando ChatwootService.find...');
const result = await this.chatwootService.find(instance); const result = await this.chatwootService.find(instance);
this.logger.debug(`[findChatwoot] Resposta de ChatwootService.find: ${JSON.stringify(result)}`);
const urlServer = this.configService.get<HttpServer>('SERVER').URL; const urlServer = this.configService.get<HttpServer>('SERVER').URL;
this.logger.debug(`[findChatwoot] urlServer obtido: ${urlServer}`);
if (Object.keys(result || {}).length === 0) { if (Object.keys(result || {}).length === 0) {
this.logger.debug('[findChatwoot] Nenhuma configuração encontrada. Retornando default desabilitado.');
return { return {
enabled: false, enabled: false,
url: '', url: '',
@ -78,15 +115,28 @@ export class ChatwootController {
webhook_url: `${urlServer}/chatwoot/webhook/${encodeURIComponent(instance.instanceName)}`, webhook_url: `${urlServer}/chatwoot/webhook/${encodeURIComponent(instance.instanceName)}`,
}; };
this.logger.debug(`[findChatwoot] Resposta final: ${JSON.stringify(response)}`);
return response; return response;
} }
public async receiveWebhook(instance: InstanceDto, data: any) { public async receiveWebhook(instance: InstanceDto, data: any) {
if (!this.configService.get<Chatwoot>('CHATWOOT').ENABLED) throw new BadRequestException('Chatwoot is disabled'); this.logger.debug(`[receiveWebhook] Recebendo webhook para instância: ${JSON.stringify(instance)}`);
this.logger.debug(`[receiveWebhook] Dados recebidos no webhook: ${JSON.stringify(data)}`);
const chatwootConfig = this.configService.get<Chatwoot>('CHATWOOT');
if (!chatwootConfig.ENABLED) {
this.logger.warn('[receiveWebhook] Chatwoot está desabilitado. Lançando exceção...');
throw new BadRequestException('Chatwoot is disabled');
}
this.logger.debug('[receiveWebhook] Iniciando configuração de CacheService para Chatwoot...');
const chatwootCache = new CacheService(new CacheEngine(this.configService, ChatwootService.name).getEngine()); const chatwootCache = new CacheService(new CacheEngine(this.configService, ChatwootService.name).getEngine());
const chatwootService = new ChatwootService(waMonitor, this.configService, this.prismaRepository, chatwootCache); const chatwootService = new ChatwootService(waMonitor, this.configService, this.prismaRepository, chatwootCache);
return chatwootService.receiveWebhook(instance, data); this.logger.debug('[receiveWebhook] Chamando chatwootService.receiveWebhook...');
const result = await chatwootService.receiveWebhook(instance, data);
this.logger.debug(`[receiveWebhook] Resposta de receiveWebhook: ${JSON.stringify(result)}`);
return result;
} }
} }

View File

@ -25,7 +25,11 @@ export class OpenaiService {
private readonly logger = new Logger('OpenaiService'); private readonly logger = new Logger('OpenaiService');
private async sendMessageToBot(instance: any, openaiBot: OpenaiBot, remoteJid: string, content: string) { private async sendMessageToBot(instance: any, openaiBot: OpenaiBot, remoteJid: string, content: string) {
this.logger.debug('Enviando mensagem para o bot (sendMessageToBot).');
this.logger.debug(`RemoteJid: ${remoteJid}, Content: ${content}`);
const systemMessages: any = openaiBot.systemMessages; const systemMessages: any = openaiBot.systemMessages;
this.logger.debug(`SystemMessages recuperadas: ${systemMessages}`);
const messagesSystem: any[] = systemMessages.map((message) => { const messagesSystem: any[] = systemMessages.map((message) => {
return { return {
@ -35,6 +39,7 @@ export class OpenaiService {
}); });
const assistantMessages: any = openaiBot.assistantMessages; const assistantMessages: any = openaiBot.assistantMessages;
this.logger.debug(`AssistantMessages recuperadas: ${assistantMessages}`);
const messagesAssistant: any[] = assistantMessages.map((message) => { const messagesAssistant: any[] = assistantMessages.map((message) => {
return { return {
@ -44,6 +49,7 @@ export class OpenaiService {
}); });
const userMessages: any = openaiBot.userMessages; const userMessages: any = openaiBot.userMessages;
this.logger.debug(`UserMessages recuperadas: ${userMessages}`);
const messagesUser: any[] = userMessages.map((message) => { const messagesUser: any[] = userMessages.map((message) => {
return { return {
@ -58,9 +64,11 @@ export class OpenaiService {
}; };
if (this.isImageMessage(content)) { if (this.isImageMessage(content)) {
this.logger.debug('Identificada mensagem de imagem no texto.');
const contentSplit = content.split('|'); const contentSplit = content.split('|');
const url = contentSplit[1].split('?')[0]; const url = contentSplit[1].split('?')[0];
this.logger.debug(`URL da imagem extraída: ${url}`);
messageData.content = [ messageData.content = [
{ type: 'text', text: contentSplit[2] || content }, { type: 'text', text: contentSplit[2] || content },
@ -74,22 +82,29 @@ export class OpenaiService {
} }
const messages: any[] = [...messagesSystem, ...messagesAssistant, ...messagesUser, messageData]; const messages: any[] = [...messagesSystem, ...messagesAssistant, ...messagesUser, messageData];
// o logo precisa formatar messages em joson
this.logger.debug(`Mensagens que serão enviadas para a API da OpenAI: ${JSON.stringify(messages)}`);
if (instance.integration === Integration.WHATSAPP_BAILEYS) { if (instance.integration === Integration.WHATSAPP_BAILEYS) {
this.logger.debug('Atualizando presença para WHATSAPP_BAILEYS (composing).');
await instance.client.presenceSubscribe(remoteJid); await instance.client.presenceSubscribe(remoteJid);
await instance.client.sendPresenceUpdate('composing', remoteJid); await instance.client.sendPresenceUpdate('composing', remoteJid);
} }
this.logger.debug('Chamando a API da OpenAI (chat.completions.create).');
const completions = await this.client.chat.completions.create({ const completions = await this.client.chat.completions.create({
model: openaiBot.model, model: openaiBot.model,
messages: messages, messages: messages,
max_tokens: openaiBot.maxTokens, max_tokens: openaiBot.maxTokens,
}); });
if (instance.integration === Integration.WHATSAPP_BAILEYS) if (instance.integration === Integration.WHATSAPP_BAILEYS) {
this.logger.debug('Atualizando presença para WHATSAPP_BAILEYS (paused).');
await instance.client.sendPresenceUpdate('paused', remoteJid); await instance.client.sendPresenceUpdate('paused', remoteJid);
}
const message = completions.choices[0].message.content; const message = completions.choices[0].message.content;
this.logger.debug(`Resposta obtida da OpenAI (sendMessageToBot): ${message}`);
return message; return message;
} }
@ -103,15 +118,20 @@ export class OpenaiService {
content: string, content: string,
threadId: string, threadId: string,
) { ) {
this.logger.debug('Enviando mensagem para o assistente (sendMessageToAssistant).');
this.logger.debug(`RemoteJid: ${remoteJid}, ThreadId: ${threadId}, Content: ${content}`);
const messageData: any = { const messageData: any = {
role: fromMe ? 'assistant' : 'user', role: fromMe ? 'assistant' : 'user',
content: [{ type: 'text', text: content }], content: [{ type: 'text', text: content }],
}; };
if (this.isImageMessage(content)) { if (this.isImageMessage(content)) {
this.logger.debug('Identificada mensagem de imagem no texto para Assistant.');
const contentSplit = content.split('|'); const contentSplit = content.split('|');
const url = contentSplit[1].split('?')[0]; const url = contentSplit[1].split('?')[0];
this.logger.debug(`URL da imagem extraída: ${url}`);
messageData.content = [ messageData.content = [
{ type: 'text', text: contentSplit[2] || content }, { type: 'text', text: contentSplit[2] || content },
@ -124,28 +144,36 @@ export class OpenaiService {
]; ];
} }
this.logger.debug('Criando mensagem no thread do Assistant.');
await this.client.beta.threads.messages.create(threadId, messageData); await this.client.beta.threads.messages.create(threadId, messageData);
if (fromMe) { if (fromMe) {
this.logger.debug('Mensagem enviada foi do próprio bot (fromMe). Enviando Telemetry.');
sendTelemetry('/message/sendText'); sendTelemetry('/message/sendText');
return; return;
} }
this.logger.debug('Iniciando corrida (run) do Assistant com ID do assistant configurado.');
const runAssistant = await this.client.beta.threads.runs.create(threadId, { const runAssistant = await this.client.beta.threads.runs.create(threadId, {
assistant_id: openaiBot.assistantId, assistant_id: openaiBot.assistantId,
}); });
if (instance.integration === Integration.WHATSAPP_BAILEYS) { if (instance.integration === Integration.WHATSAPP_BAILEYS) {
this.logger.debug('Atualizando presença para WHATSAPP_BAILEYS (composing).');
await instance.client.presenceSubscribe(remoteJid); await instance.client.presenceSubscribe(remoteJid);
await instance.client.sendPresenceUpdate('composing', remoteJid); await instance.client.sendPresenceUpdate('composing', remoteJid);
} }
this.logger.debug('Aguardando resposta do Assistant (getAIResponse).');
const response = await this.getAIResponse(threadId, runAssistant.id, openaiBot.functionUrl, remoteJid, pushName); const response = await this.getAIResponse(threadId, runAssistant.id, openaiBot.functionUrl, remoteJid, pushName);
if (instance.integration === Integration.WHATSAPP_BAILEYS) if (instance.integration === Integration.WHATSAPP_BAILEYS) {
this.logger.debug('Atualizando presença para WHATSAPP_BAILEYS (paused).');
await instance.client.sendPresenceUpdate('paused', remoteJid); await instance.client.sendPresenceUpdate('paused', remoteJid);
}
const message = response?.data[0].content[0].text.value; const message = response?.data[0].content[0].text.value;
this.logger.debug(`Resposta obtida do Assistant (sendMessageToAssistant): ${message}`);
return message; return message;
} }
@ -157,8 +185,10 @@ export class OpenaiService {
settings: OpenaiSetting, settings: OpenaiSetting,
message: string, message: string,
) { ) {
const linkRegex = /(!?)\[(.*?)\]\((.*?)\)/g; this.logger.debug('Enviando mensagem para o WhatsApp (sendMessageWhatsapp).');
this.logger.debug(`RemoteJid: ${remoteJid}, Mensagem: ${message}`);
const linkRegex = /(!?)\[(.*?)\]\((.*?)\)/g;
let textBuffer = ''; let textBuffer = '';
let lastIndex = 0; let lastIndex = 0;
@ -178,8 +208,11 @@ export class OpenaiService {
return null; return null;
}; };
// Processa links (ou mídia) dentro do texto
this.logger.debug('Verificando se a mensagem contém mídia (links) no formato [altText](url).');
while ((match = linkRegex.exec(message)) !== null) { while ((match = linkRegex.exec(message)) !== null) {
const [fullMatch, exclMark, altText, url] = match; const [fullMatch, exclMark, altText, url] = match;
this.logger.debug(`Match encontrado: ${fullMatch}, url: ${url}, altText: ${altText}`);
const mediaType = getMediaType(url); const mediaType = getMediaType(url);
const beforeText = message.slice(lastIndex, match.index); const beforeText = message.slice(lastIndex, match.index);
@ -193,22 +226,24 @@ export class OpenaiService {
const minDelay = 1000; const minDelay = 1000;
const maxDelay = 20000; const maxDelay = 20000;
// Envia primeiro o texto que estiver no buffer
if (textBuffer.trim()) { if (textBuffer.trim()) {
if (splitMessages) { if (splitMessages) {
const multipleMessages = textBuffer.trim().split('\n\n'); const multipleMessages = textBuffer.trim().split('\n\n');
for (let index = 0; index < multipleMessages.length; index++) { for (let index = 0; index < multipleMessages.length; index++) {
const message = multipleMessages[index]; const message = multipleMessages[index];
const delay = Math.min(Math.max(message.length * timePerChar, minDelay), maxDelay); const delay = Math.min(Math.max(message.length * timePerChar, minDelay), maxDelay);
if (instance.integration === Integration.WHATSAPP_BAILEYS) { if (instance.integration === Integration.WHATSAPP_BAILEYS) {
this.logger.debug('Atualizando presença (composing) antes de enviar texto em partes.');
await instance.client.presenceSubscribe(remoteJid); await instance.client.presenceSubscribe(remoteJid);
await instance.client.sendPresenceUpdate('composing', remoteJid); await instance.client.sendPresenceUpdate('composing', remoteJid);
} }
await new Promise<void>((resolve) => { await new Promise<void>((resolve) => {
setTimeout(async () => { setTimeout(async () => {
this.logger.debug(`Enviando texto (splitMessage): ${message}`);
await instance.textMessage( await instance.textMessage(
{ {
number: remoteJid.split('@')[0], number: remoteJid.split('@')[0],
@ -222,10 +257,12 @@ export class OpenaiService {
}); });
if (instance.integration === Integration.WHATSAPP_BAILEYS) { if (instance.integration === Integration.WHATSAPP_BAILEYS) {
this.logger.debug('Atualizando presença (paused) após enviar parte do texto.');
await instance.client.sendPresenceUpdate('paused', remoteJid); await instance.client.sendPresenceUpdate('paused', remoteJid);
} }
} }
} else { } else {
this.logger.debug(`Enviando texto inteiro do buffer: ${textBuffer.trim()}`);
await instance.textMessage( await instance.textMessage(
{ {
number: remoteJid.split('@')[0], number: remoteJid.split('@')[0],
@ -238,7 +275,9 @@ export class OpenaiService {
} }
} }
this.logger.debug(`Identificado arquivo de mídia do tipo: ${mediaType}`);
if (mediaType === 'audio') { if (mediaType === 'audio') {
this.logger.debug('Enviando arquivo de áudio para o WhatsApp.');
await instance.audioWhatsapp({ await instance.audioWhatsapp({
number: remoteJid.split('@')[0], number: remoteJid.split('@')[0],
delay: settings?.delayMessage || 1000, delay: settings?.delayMessage || 1000,
@ -246,6 +285,7 @@ export class OpenaiService {
caption: altText, caption: altText,
}); });
} else { } else {
this.logger.debug('Enviando arquivo de mídia (imagem, vídeo ou documento) para o WhatsApp.');
await instance.mediaMessage( await instance.mediaMessage(
{ {
number: remoteJid.split('@')[0], number: remoteJid.split('@')[0],
@ -259,12 +299,14 @@ export class OpenaiService {
); );
} }
} else { } else {
this.logger.debug('Não é um tipo de mídia suportado. Adicionando link no buffer de texto.');
textBuffer += `[${altText}](${url})`; textBuffer += `[${altText}](${url})`;
} }
lastIndex = linkRegex.lastIndex; lastIndex = linkRegex.lastIndex;
} }
// Processa o texto restante, caso exista
if (lastIndex < message.length) { if (lastIndex < message.length) {
const remainingText = message.slice(lastIndex); const remainingText = message.slice(lastIndex);
if (remainingText.trim()) { if (remainingText.trim()) {
@ -277,22 +319,24 @@ export class OpenaiService {
const minDelay = 1000; const minDelay = 1000;
const maxDelay = 20000; const maxDelay = 20000;
// Envia o que restou no textBuffer
if (textBuffer.trim()) { if (textBuffer.trim()) {
if (splitMessages) { if (splitMessages) {
const multipleMessages = textBuffer.trim().split('\n\n'); const multipleMessages = textBuffer.trim().split('\n\n');
for (let index = 0; index < multipleMessages.length; index++) { for (let index = 0; index < multipleMessages.length; index++) {
const message = multipleMessages[index]; const message = multipleMessages[index];
const delay = Math.min(Math.max(message.length * timePerChar, minDelay), maxDelay); const delay = Math.min(Math.max(message.length * timePerChar, minDelay), maxDelay);
if (instance.integration === Integration.WHATSAPP_BAILEYS) { if (instance.integration === Integration.WHATSAPP_BAILEYS) {
this.logger.debug('Atualizando presença (composing) antes de enviar resto do texto em partes.');
await instance.client.presenceSubscribe(remoteJid); await instance.client.presenceSubscribe(remoteJid);
await instance.client.sendPresenceUpdate('composing', remoteJid); await instance.client.sendPresenceUpdate('composing', remoteJid);
} }
await new Promise<void>((resolve) => { await new Promise<void>((resolve) => {
setTimeout(async () => { setTimeout(async () => {
this.logger.debug(`Enviando texto (splitMessage): ${message}`);
await instance.textMessage( await instance.textMessage(
{ {
number: remoteJid.split('@')[0], number: remoteJid.split('@')[0],
@ -306,10 +350,12 @@ export class OpenaiService {
}); });
if (instance.integration === Integration.WHATSAPP_BAILEYS) { if (instance.integration === Integration.WHATSAPP_BAILEYS) {
this.logger.debug('Atualizando presença (paused) após enviar parte final do texto.');
await instance.client.sendPresenceUpdate('paused', remoteJid); await instance.client.sendPresenceUpdate('paused', remoteJid);
} }
} }
} else { } else {
this.logger.debug(`Enviando todo o texto restante no buffer: ${textBuffer.trim()}`);
await instance.textMessage( await instance.textMessage(
{ {
number: remoteJid.split('@')[0], number: remoteJid.split('@')[0],
@ -322,8 +368,10 @@ export class OpenaiService {
} }
} }
this.logger.debug('Enviando telemetria após envio de texto.');
sendTelemetry('/message/sendText'); sendTelemetry('/message/sendText');
this.logger.debug(`Atualizando sessão (id: ${session.id}) para 'opened' e 'awaitUser: true'.`);
await this.prismaRepository.integrationSession.update({ await this.prismaRepository.integrationSession.update({
where: { where: {
id: session.id, id: session.id,
@ -336,7 +384,13 @@ export class OpenaiService {
} }
public async createAssistantNewSession(instance: InstanceDto, data: any) { public async createAssistantNewSession(instance: InstanceDto, data: any) {
if (data.remoteJid === 'status@broadcast') return; this.logger.debug('Iniciando criação de nova sessão do Assistant (createAssistantNewSession).');
this.logger.debug(`Dados recebidos: ${JSON.stringify(data)}`);
if (data.remoteJid === 'status@broadcast') {
this.logger.debug('remoteJid é status@broadcast, abortando criação de sessão.');
return;
}
const creds = await this.prismaRepository.openaiCreds.findFirst({ const creds = await this.prismaRepository.openaiCreds.findFirst({
where: { where: {
@ -344,17 +398,24 @@ export class OpenaiService {
}, },
}); });
if (!creds) throw new Error('Openai Creds not found'); if (!creds) {
this.logger.error('Openai Creds não encontrados, lançando erro.');
throw new Error('Openai Creds not found');
}
try { try {
this.logger.debug('Instanciando cliente OpenAI para Assistant.');
this.client = new OpenAI({ this.client = new OpenAI({
apiKey: creds.apiKey, apiKey: creds.apiKey,
}); });
const threadId = (await this.client.beta.threads.create({})).id; this.logger.debug('Criando thread (beta.threads.create).');
const thread = await this.client.beta.threads.create({});
const threadId = thread.id;
let session = null; let session = null;
if (threadId) { if (threadId) {
this.logger.debug('Thread criada com sucesso. Salvando sessão no banco de dados.');
session = await this.prismaRepository.integrationSession.create({ session = await this.prismaRepository.integrationSession.create({
data: { data: {
remoteJid: data.remoteJid, remoteJid: data.remoteJid,
@ -370,7 +431,7 @@ export class OpenaiService {
} }
return { session }; return { session };
} catch (error) { } catch (error) {
this.logger.error(error); this.logger.error(`Erro ao criar nova sessão do Assistant: ${error}`);
return; return;
} }
} }
@ -385,6 +446,9 @@ export class OpenaiService {
session: IntegrationSession, session: IntegrationSession,
content: string, content: string,
) { ) {
this.logger.debug('Iniciando sessão do Assistant (initAssistantNewSession).');
this.logger.debug(`RemoteJid: ${remoteJid}, PushName: ${pushName}, Content: ${content}`);
const data = await this.createAssistantNewSession(instance, { const data = await this.createAssistantNewSession(instance, {
remoteJid, remoteJid,
pushName, pushName,
@ -394,8 +458,10 @@ export class OpenaiService {
if (data.session) { if (data.session) {
session = data.session; session = data.session;
this.logger.debug(`Sessão criada com sucesso. ID: ${session.id}`);
} }
this.logger.debug('Enviando mensagem para Assistant para iniciar conversa.');
const message = await this.sendMessageToAssistant( const message = await this.sendMessageToAssistant(
instance, instance,
openaiBot, openaiBot,
@ -406,7 +472,11 @@ export class OpenaiService {
session.sessionId, session.sessionId,
); );
this.logger.debug(`Retorno do Assistant: ${message}`);
if (message) {
this.logger.debug('Enviando mensagem do Assistant para WhatsApp.');
await this.sendMessageWhatsapp(instance, session, remoteJid, settings, message); await this.sendMessageWhatsapp(instance, session, remoteJid, settings, message);
}
return; return;
} }
@ -427,10 +497,12 @@ export class OpenaiService {
remoteJid: string, remoteJid: string,
pushName: string, pushName: string,
) { ) {
this.logger.debug(`Consultando run do Assistant (getAIResponse). ThreadId: ${threadId}, RunId: ${runId}`);
const getRun = await this.client.beta.threads.runs.retrieve(threadId, runId); const getRun = await this.client.beta.threads.runs.retrieve(threadId, runId);
let toolCalls; let toolCalls;
switch (getRun.status) { switch (getRun.status) {
case 'requires_action': case 'requires_action':
this.logger.debug('Run requer ação (requires_action). Verificando chamadas de ferramenta (tool_calls).');
toolCalls = getRun?.required_action?.submit_tool_outputs?.tool_calls; toolCalls = getRun?.required_action?.submit_tool_outputs?.tool_calls;
if (toolCalls) { if (toolCalls) {
@ -442,6 +514,7 @@ export class OpenaiService {
: toolCall?.function?.arguments; : toolCall?.function?.arguments;
let output = null; let output = null;
this.logger.debug(`Chamando função externa: ${functionName} com argumentos:`, functionArgument);
try { try {
const { data } = await axios.post(functionUrl, { const { data } = await axios.post(functionUrl, {
@ -449,13 +522,16 @@ export class OpenaiService {
arguments: { ...functionArgument, remoteJid, pushName }, arguments: { ...functionArgument, remoteJid, pushName },
}); });
// Serializa saída para string
output = JSON.stringify(data) output = JSON.stringify(data)
.replace(/\\/g, '\\\\') .replace(/\\/g, '\\\\')
.replace(/"/g, '\\"') .replace(/"/g, '\\"')
.replace(/\n/g, '\\n') .replace(/\n/g, '\\n')
.replace(/\r/g, '\\r') .replace(/\r/g, '\\r')
.replace(/\t/g, '\\t'); .replace(/\t/g, '\\t');
this.logger.debug(`Resposta da função externa (${functionName}):`, data);
} catch (error) { } catch (error) {
this.logger.error(`Erro ao chamar função externa (${functionName}):`, error);
output = JSON.stringify(error) output = JSON.stringify(error)
.replace(/\\/g, '\\\\') .replace(/\\/g, '\\\\')
.replace(/"/g, '\\"') .replace(/"/g, '\\"')
@ -464,6 +540,7 @@ export class OpenaiService {
.replace(/\t/g, '\\t'); .replace(/\t/g, '\\t');
} }
this.logger.debug('Submetendo output para a run do Assistant (submitToolOutputs).');
await this.client.beta.threads.runs.submitToolOutputs(threadId, runId, { await this.client.beta.threads.runs.submitToolOutputs(threadId, runId, {
tool_outputs: [ tool_outputs: [
{ {
@ -475,14 +552,18 @@ export class OpenaiService {
} }
} }
this.logger.debug('Repetindo chamada getAIResponse até status diferente de requires_action.');
return this.getAIResponse(threadId, runId, functionUrl, remoteJid, pushName); return this.getAIResponse(threadId, runId, functionUrl, remoteJid, pushName);
case 'queued': case 'queued':
this.logger.debug('Run está em fila (queued). Aguardando 1 segundo antes de tentar novamente.');
await new Promise((resolve) => setTimeout(resolve, 1000)); await new Promise((resolve) => setTimeout(resolve, 1000));
return this.getAIResponse(threadId, runId, functionUrl, remoteJid, pushName); return this.getAIResponse(threadId, runId, functionUrl, remoteJid, pushName);
case 'in_progress': case 'in_progress':
this.logger.debug('Run está em progresso (in_progress). Aguardando 1 segundo antes de tentar novamente.');
await new Promise((resolve) => setTimeout(resolve, 1000)); await new Promise((resolve) => setTimeout(resolve, 1000));
return this.getAIResponse(threadId, runId, functionUrl, remoteJid, pushName); return this.getAIResponse(threadId, runId, functionUrl, remoteJid, pushName);
case 'completed': case 'completed':
this.logger.debug('Run concluída (completed). Recuperando última mensagem.');
return await this.client.beta.threads.messages.list(threadId, { return await this.client.beta.threads.messages.list(threadId, {
run_id: runId, run_id: runId,
limit: 1, limit: 1,
@ -504,21 +585,27 @@ export class OpenaiService {
settings: OpenaiSetting, settings: OpenaiSetting,
content: string, content: string,
) { ) {
this.logger.debug('Processando mensagem para o Assistant (processOpenaiAssistant).');
this.logger.debug(
`RemoteJid: ${remoteJid}, pushName: ${pushName}, fromMe: ${fromMe}, content: ${content}`,
);
if (session && session.status === 'closed') { if (session && session.status === 'closed') {
this.logger.debug('A sessão está fechada, não será processada.');
return; return;
} }
if (session && settings.expire && settings.expire > 0) { if (session && settings.expire && settings.expire > 0) {
this.logger.debug('Verificando tempo de expiração da sessão...');
const now = Date.now(); const now = Date.now();
const sessionUpdatedAt = new Date(session.updatedAt).getTime(); const sessionUpdatedAt = new Date(session.updatedAt).getTime();
const diff = now - sessionUpdatedAt; const diff = now - sessionUpdatedAt;
const diffInMinutes = Math.floor(diff / 1000 / 60); const diffInMinutes = Math.floor(diff / 1000 / 60);
if (diffInMinutes > settings.expire) { if (diffInMinutes > settings.expire) {
this.logger.debug(`Sessão expirada há ${diffInMinutes} minutos.`);
if (settings.keepOpen) { if (settings.keepOpen) {
this.logger.debug('Atualizando status da sessão para CLOSED.');
await this.prismaRepository.integrationSession.update({ await this.prismaRepository.integrationSession.update({
where: { where: {
id: session.id, id: session.id,
@ -528,6 +615,7 @@ export class OpenaiService {
}, },
}); });
} else { } else {
this.logger.debug('Deletando sessão do banco de dados.');
await this.prismaRepository.integrationSession.deleteMany({ await this.prismaRepository.integrationSession.deleteMany({
where: { where: {
botId: openaiBot.id, botId: openaiBot.id,
@ -536,6 +624,7 @@ export class OpenaiService {
}); });
} }
this.logger.debug('Recriando nova sessão de Assistant...');
await this.initAssistantNewSession( await this.initAssistantNewSession(
instance, instance,
remoteJid, remoteJid,
@ -551,11 +640,13 @@ export class OpenaiService {
} }
if (!session) { if (!session) {
this.logger.debug('Nenhuma sessão ativa encontrada, criando nova sessão de Assistant...');
await this.initAssistantNewSession(instance, remoteJid, pushName, fromMe, openaiBot, settings, session, content); await this.initAssistantNewSession(instance, remoteJid, pushName, fromMe, openaiBot, settings, session, content);
return; return;
} }
if (session.status !== 'paused') if (session.status !== 'paused') {
this.logger.debug('Marcando sessão como aberta e awaitUser = false.');
await this.prismaRepository.integrationSession.update({ await this.prismaRepository.integrationSession.update({
where: { where: {
id: session.id, id: session.id,
@ -565,9 +656,12 @@ export class OpenaiService {
awaitUser: false, awaitUser: false,
}, },
}); });
}
if (!content) { if (!content) {
this.logger.debug('Não há conteúdo na mensagem. Verificando se existe unknownMessage para retorno.');
if (settings.unknownMessage) { if (settings.unknownMessage) {
this.logger.debug(`Enviando unknownMessage para o remoteJid: ${remoteJid}`);
this.waMonitor.waInstances[instance.instanceName].textMessage( this.waMonitor.waInstances[instance.instanceName].textMessage(
{ {
number: remoteJid.split('@')[0], number: remoteJid.split('@')[0],
@ -576,13 +670,13 @@ export class OpenaiService {
}, },
false, false,
); );
sendTelemetry('/message/sendText'); sendTelemetry('/message/sendText');
} }
return; return;
} }
if (settings.keywordFinish && content.toLowerCase() === settings.keywordFinish.toLowerCase()) { if (settings.keywordFinish && content.toLowerCase() === settings.keywordFinish.toLowerCase()) {
this.logger.debug('Keyword finish detectada. Encerrando sessão.');
if (settings.keepOpen) { if (settings.keepOpen) {
await this.prismaRepository.integrationSession.update({ await this.prismaRepository.integrationSession.update({
where: { where: {
@ -603,20 +697,25 @@ export class OpenaiService {
return; return;
} }
this.logger.debug('Buscando OpenaiCreds no banco...');
const creds = await this.prismaRepository.openaiCreds.findFirst({ const creds = await this.prismaRepository.openaiCreds.findFirst({
where: { where: {
id: openaiBot.openaiCredsId, id: openaiBot.openaiCredsId,
}, },
}); });
if (!creds) throw new Error('Openai Creds not found'); if (!creds) {
this.logger.error('Openai Creds não encontrados, lançando erro.');
throw new Error('Openai Creds not found');
}
this.logger.debug('Instanciando cliente OpenAI para processar a mensagem no Assistant.');
this.client = new OpenAI({ this.client = new OpenAI({
apiKey: creds.apiKey, apiKey: creds.apiKey,
}); });
const threadId = session.sessionId; const threadId = session.sessionId;
this.logger.debug(`Enviando mensagem ao Assistant (threadId: ${threadId}).`);
const message = await this.sendMessageToAssistant( const message = await this.sendMessageToAssistant(
instance, instance,
openaiBot, openaiBot,
@ -627,15 +726,25 @@ export class OpenaiService {
threadId, threadId,
); );
if (message) {
this.logger.debug(`Resposta do Assistant recebida. Enviando para WhatsApp: ${message}`);
await this.sendMessageWhatsapp(instance, session, remoteJid, settings, message); await this.sendMessageWhatsapp(instance, session, remoteJid, settings, message);
}
return; return;
} }
public async createChatCompletionNewSession(instance: InstanceDto, data: any) { public async createChatCompletionNewSession(instance: InstanceDto, data: any) {
if (data.remoteJid === 'status@broadcast') return; this.logger.debug('Iniciando criação de nova sessão de chatCompletion (createChatCompletionNewSession).');
this.logger.debug(`Dados recebidos: ${JSON.stringify(data)}`);
if (data.remoteJid === 'status@broadcast') {
this.logger.debug('remoteJid é status@broadcast, abortando criação de sessão.');
return;
}
const id = Math.floor(Math.random() * 10000000000).toString(); const id = Math.floor(Math.random() * 10000000000).toString();
this.logger.debug(`Gerando ID pseudo-aleatório da sessão: ${id}`);
const creds = await this.prismaRepository.openaiCreds.findFirst({ const creds = await this.prismaRepository.openaiCreds.findFirst({
where: { where: {
@ -643,9 +752,13 @@ export class OpenaiService {
}, },
}); });
if (!creds) throw new Error('Openai Creds not found'); if (!creds) {
this.logger.error('Openai Creds não encontrados, lançando erro.');
throw new Error('Openai Creds not found');
}
try { try {
this.logger.debug('Criando sessão no banco de dados.');
const session = await this.prismaRepository.integrationSession.create({ const session = await this.prismaRepository.integrationSession.create({
data: { data: {
remoteJid: data.remoteJid, remoteJid: data.remoteJid,
@ -661,7 +774,7 @@ export class OpenaiService {
return { session, creds }; return { session, creds };
} catch (error) { } catch (error) {
this.logger.error(error); this.logger.error(`Erro ao criar nova sessão de chatCompletion: ${error}`);
return; return;
} }
} }
@ -675,6 +788,9 @@ export class OpenaiService {
session: IntegrationSession, session: IntegrationSession,
content: string, content: string,
) { ) {
this.logger.debug('Iniciando sessão de chatCompletion (initChatCompletionNewSession).');
this.logger.debug(`RemoteJid: ${remoteJid}, PushName: ${pushName}, Content: ${content}`);
const data = await this.createChatCompletionNewSession(instance, { const data = await this.createChatCompletionNewSession(instance, {
remoteJid, remoteJid,
pushName, pushName,
@ -683,16 +799,21 @@ export class OpenaiService {
}); });
session = data.session; session = data.session;
const creds = data.creds; const creds = data.creds;
this.logger.debug(`Sessão criada com sucesso (ID: ${session.id}). Instanciando cliente OpenAI.`);
this.client = new OpenAI({ this.client = new OpenAI({
apiKey: creds.apiKey, apiKey: creds.apiKey,
}); });
this.logger.debug('Enviando mensagem para o Bot usando chatCompletion.');
const message = await this.sendMessageToBot(instance, openaiBot, remoteJid, content); const message = await this.sendMessageToBot(instance, openaiBot, remoteJid, content);
this.logger.debug(`Resposta do Bot: ${message}`);
if (message) {
this.logger.debug('Enviando resposta para o WhatsApp.');
await this.sendMessageWhatsapp(instance, session, remoteJid, settings, message); await this.sendMessageWhatsapp(instance, session, remoteJid, settings, message);
}
return; return;
} }
@ -706,21 +827,27 @@ export class OpenaiService {
settings: OpenaiSetting, settings: OpenaiSetting,
content: string, content: string,
) { ) {
this.logger.debug('Processando ChatCompletion (processOpenaiChatCompletion).');
this.logger.debug(
`RemoteJid: ${remoteJid}, PushName: ${pushName}, Content: ${content}, SessionId: ${session?.id}`,
);
if (session && session.status !== 'opened') { if (session && session.status !== 'opened') {
this.logger.debug('Sessão existente não está aberta. Não será processado.');
return; return;
} }
if (session && settings.expire && settings.expire > 0) { if (session && settings.expire && settings.expire > 0) {
this.logger.debug('Verificando tempo de expiração da sessão...');
const now = Date.now(); const now = Date.now();
const sessionUpdatedAt = new Date(session.updatedAt).getTime(); const sessionUpdatedAt = new Date(session.updatedAt).getTime();
const diff = now - sessionUpdatedAt; const diff = now - sessionUpdatedAt;
const diffInMinutes = Math.floor(diff / 1000 / 60); const diffInMinutes = Math.floor(diff / 1000 / 60);
if (diffInMinutes > settings.expire) { if (diffInMinutes > settings.expire) {
this.logger.debug(`Sessão expirada há ${diffInMinutes} minutos.`);
if (settings.keepOpen) { if (settings.keepOpen) {
this.logger.debug('Atualizando status da sessão para CLOSED.');
await this.prismaRepository.integrationSession.update({ await this.prismaRepository.integrationSession.update({
where: { where: {
id: session.id, id: session.id,
@ -730,6 +857,7 @@ export class OpenaiService {
}, },
}); });
} else { } else {
this.logger.debug('Deletando sessão do banco de dados.');
await this.prismaRepository.integrationSession.deleteMany({ await this.prismaRepository.integrationSession.deleteMany({
where: { where: {
botId: openaiBot.id, botId: openaiBot.id,
@ -738,16 +866,19 @@ export class OpenaiService {
}); });
} }
this.logger.debug('Recriando nova sessão de chatCompletion...');
await this.initChatCompletionNewSession(instance, remoteJid, pushName, openaiBot, settings, session, content); await this.initChatCompletionNewSession(instance, remoteJid, pushName, openaiBot, settings, session, content);
return; return;
} }
} }
if (!session) { if (!session) {
this.logger.debug('Nenhuma sessão encontrada. Criando nova sessão de chatCompletion...');
await this.initChatCompletionNewSession(instance, remoteJid, pushName, openaiBot, settings, session, content); await this.initChatCompletionNewSession(instance, remoteJid, pushName, openaiBot, settings, session, content);
return; return;
} }
this.logger.debug('Marcando sessão como aberta e awaitUser = false.');
await this.prismaRepository.integrationSession.update({ await this.prismaRepository.integrationSession.update({
where: { where: {
id: session.id, id: session.id,
@ -759,7 +890,9 @@ export class OpenaiService {
}); });
if (!content) { if (!content) {
this.logger.debug('Não há conteúdo na mensagem. Verificando se existe unknownMessage para retorno.');
if (settings.unknownMessage) { if (settings.unknownMessage) {
this.logger.debug(`Enviando unknownMessage para o remoteJid: ${remoteJid}`);
this.waMonitor.waInstances[instance.instanceName].textMessage( this.waMonitor.waInstances[instance.instanceName].textMessage(
{ {
number: remoteJid.split('@')[0], number: remoteJid.split('@')[0],
@ -768,13 +901,13 @@ export class OpenaiService {
}, },
false, false,
); );
sendTelemetry('/message/sendText'); sendTelemetry('/message/sendText');
} }
return; return;
} }
if (settings.keywordFinish && content.toLowerCase() === settings.keywordFinish.toLowerCase()) { if (settings.keywordFinish && content.toLowerCase() === settings.keywordFinish.toLowerCase()) {
this.logger.debug('Keyword finish detectada. Encerrando sessão.');
if (settings.keepOpen) { if (settings.keepOpen) {
await this.prismaRepository.integrationSession.update({ await this.prismaRepository.integrationSession.update({
where: { where: {
@ -795,33 +928,47 @@ export class OpenaiService {
return; return;
} }
this.logger.debug('Buscando OpenaiCreds no banco...');
const creds = await this.prismaRepository.openaiCreds.findFirst({ const creds = await this.prismaRepository.openaiCreds.findFirst({
where: { where: {
id: openaiBot.openaiCredsId, id: openaiBot.openaiCredsId,
}, },
}); });
if (!creds) throw new Error('Openai Creds not found'); if (!creds) {
this.logger.error('Openai Creds não encontrados, lançando erro.');
throw new Error('Openai Creds not found');
}
this.logger.debug('Instanciando cliente OpenAI para processar a mensagem (ChatCompletion).');
this.client = new OpenAI({ this.client = new OpenAI({
apiKey: creds.apiKey, apiKey: creds.apiKey,
}); });
this.logger.debug('Enviando mensagem para o Bot usando chatCompletion.');
const message = await this.sendMessageToBot(instance, openaiBot, remoteJid, content); const message = await this.sendMessageToBot(instance, openaiBot, remoteJid, content);
this.logger.debug(`Resposta do Bot: ${message}`);
if (message) {
this.logger.debug('Enviando resposta para o WhatsApp.');
await this.sendMessageWhatsapp(instance, session, remoteJid, settings, message); await this.sendMessageWhatsapp(instance, session, remoteJid, settings, message);
}
return; return;
} }
public async speechToText(creds: OpenaiCreds, msg: any, updateMediaMessage: any) { public async speechToText(creds: OpenaiCreds, msg: any, updateMediaMessage: any) {
this.logger.debug('Iniciando conversão de fala em texto (speechToText).');
let audio; let audio;
if (msg?.message?.mediaUrl) { if (msg?.message?.mediaUrl) {
this.logger.debug('Baixando áudio via URL (mediaUrl).');
audio = await axios.get(msg.message.mediaUrl, { responseType: 'arraybuffer' }).then((response) => { audio = await axios.get(msg.message.mediaUrl, { responseType: 'arraybuffer' }).then((response) => {
return Buffer.from(response.data, 'binary'); return Buffer.from(response.data, 'binary');
}); });
} else { } else {
this.logger.debug('Baixando áudio via downloadMediaMessage (baileys).');
audio = await downloadMediaMessage( audio = await downloadMediaMessage(
{ key: msg.key, message: msg?.message }, { key: msg.key, message: msg?.message },
'buffer', 'buffer',
@ -836,13 +983,14 @@ export class OpenaiService {
const lang = this.configService.get<Language>('LANGUAGE').includes('pt') const lang = this.configService.get<Language>('LANGUAGE').includes('pt')
? 'pt' ? 'pt'
: this.configService.get<Language>('LANGUAGE'); : this.configService.get<Language>('LANGUAGE');
this.logger.debug(`Definindo idioma da transcrição como: ${lang}`);
const formData = new FormData(); const formData = new FormData();
formData.append('file', audio, 'audio.ogg'); formData.append('file', audio, 'audio.ogg');
formData.append('model', 'whisper-1'); formData.append('model', 'whisper-1');
formData.append('language', lang); formData.append('language', lang);
this.logger.debug('Enviando requisição POST para a API de transcrição do OpenAI.');
const response = await axios.post('https://api.openai.com/v1/audio/transcriptions', formData, { const response = await axios.post('https://api.openai.com/v1/audio/transcriptions', formData, {
headers: { headers: {
'Content-Type': 'multipart/form-data', 'Content-Type': 'multipart/form-data',
@ -850,6 +998,7 @@ export class OpenaiService {
}, },
}); });
this.logger.debug(`Status da requisição: ${response.status}`);
return response?.data?.text; return response?.data?.text;
} }
} }

View File

@ -1,3 +1,4 @@
import { Logger } from '@config/logger.config';
import { InstanceDto } from '@api/dto/instance.dto'; import { InstanceDto } from '@api/dto/instance.dto';
import { ProxyDto } from '@api/dto/proxy.dto'; import { ProxyDto } from '@api/dto/proxy.dto';
import { SettingsDto } from '@api/dto/settings.dto'; import { SettingsDto } from '@api/dto/settings.dto';
@ -10,7 +11,6 @@ import { PrismaRepository, Query } from '@api/repository/repository.service';
import { eventManager, waMonitor } from '@api/server.module'; import { eventManager, waMonitor } from '@api/server.module';
import { Events, wa } from '@api/types/wa.types'; import { Events, wa } from '@api/types/wa.types';
import { Auth, Chatwoot, ConfigService, HttpServer } from '@config/env.config'; import { Auth, Chatwoot, ConfigService, HttpServer } from '@config/env.config';
import { Logger } from '@config/logger.config';
import { NotFoundException } from '@exceptions'; import { NotFoundException } from '@exceptions';
import { Contact, Message } from '@prisma/client'; import { Contact, Message } from '@prisma/client';
import { WASocket } from 'baileys'; import { WASocket } from 'baileys';
@ -51,6 +51,7 @@ export class ChannelStartupService {
public difyService = new DifyService(waMonitor, this.configService, this.prismaRepository); public difyService = new DifyService(waMonitor, this.configService, this.prismaRepository);
public setInstance(instance: InstanceDto) { public setInstance(instance: InstanceDto) {
this.logger.debug(`[setInstance] Definindo dados da instância: ${JSON.stringify(instance)}`);
this.logger.setInstance(instance.instanceName); this.logger.setInstance(instance.instanceName);
this.instance.name = instance.instanceName; this.instance.name = instance.instanceName;
@ -61,6 +62,7 @@ export class ChannelStartupService {
this.instance.businessId = instance.businessId; this.instance.businessId = instance.businessId;
if (this.configService.get<Chatwoot>('CHATWOOT').ENABLED && this.localChatwoot?.enabled) { if (this.configService.get<Chatwoot>('CHATWOOT').ENABLED && this.localChatwoot?.enabled) {
this.logger.debug('[setInstance] Enviando evento de STATUS_INSTANCE para Chatwoot');
this.chatwootService.eventWhatsapp( this.chatwootService.eventWhatsapp(
Events.STATUS_INSTANCE, Events.STATUS_INSTANCE,
{ instanceName: this.instance.name }, { instanceName: this.instance.name },
@ -73,6 +75,7 @@ export class ChannelStartupService {
} }
public set instanceName(name: string) { public set instanceName(name: string) {
this.logger.debug(`[setter instanceName] Atribuindo: ${name}`);
this.logger.setInstance(name); this.logger.setInstance(name);
if (!name) { if (!name) {
@ -87,6 +90,7 @@ export class ChannelStartupService {
} }
public set instanceId(id: string) { public set instanceId(id: string) {
this.logger.debug(`[setter instanceId] Atribuindo: ${id}`);
if (!id) { if (!id) {
this.instance.id = v4(); this.instance.id = v4();
return; return;
@ -99,6 +103,7 @@ export class ChannelStartupService {
} }
public set integration(integration: string) { public set integration(integration: string) {
this.logger.debug(`[setter integration] Atribuindo: ${integration}`);
this.instance.integration = integration; this.instance.integration = integration;
} }
@ -107,6 +112,7 @@ export class ChannelStartupService {
} }
public set number(number: string) { public set number(number: string) {
this.logger.debug(`[setter number] Atribuindo número: ${number}`);
this.instance.number = number; this.instance.number = number;
} }
@ -115,6 +121,7 @@ export class ChannelStartupService {
} }
public set token(token: string) { public set token(token: string) {
this.logger.debug(`[setter token] Atribuindo token.`);
this.instance.token = token; this.instance.token = token;
} }
@ -127,6 +134,7 @@ export class ChannelStartupService {
} }
public async loadWebhook() { public async loadWebhook() {
this.logger.debug(`[loadWebhook] Carregando webhook para instanceId: ${this.instanceId}`);
const data = await this.prismaRepository.webhook.findUnique({ const data = await this.prismaRepository.webhook.findUnique({
where: { where: {
instanceId: this.instanceId, instanceId: this.instanceId,
@ -135,9 +143,12 @@ export class ChannelStartupService {
this.localWebhook.enabled = data?.enabled; this.localWebhook.enabled = data?.enabled;
this.localWebhook.webhookBase64 = data?.webhookBase64; this.localWebhook.webhookBase64 = data?.webhookBase64;
this.logger.debug('[loadWebhook] Webhook carregado com sucesso.');
} }
public async loadSettings() { public async loadSettings() {
this.logger.debug(`[loadSettings] Carregando configurações para instanceId: ${this.instanceId}`);
const data = await this.prismaRepository.setting.findUnique({ const data = await this.prismaRepository.setting.findUnique({
where: { where: {
instanceId: this.instanceId, instanceId: this.instanceId,
@ -151,9 +162,12 @@ export class ChannelStartupService {
this.localSettings.readMessages = data?.readMessages; this.localSettings.readMessages = data?.readMessages;
this.localSettings.readStatus = data?.readStatus; this.localSettings.readStatus = data?.readStatus;
this.localSettings.syncFullHistory = data?.syncFullHistory; this.localSettings.syncFullHistory = data?.syncFullHistory;
this.logger.debug('[loadSettings] Configurações carregadas com sucesso.');
} }
public async setSettings(data: SettingsDto) { public async setSettings(data: SettingsDto) {
this.logger.debug(`[setSettings] Atualizando configurações: ${JSON.stringify(data)}`);
await this.prismaRepository.setting.upsert({ await this.prismaRepository.setting.upsert({
where: { where: {
instanceId: this.instanceId, instanceId: this.instanceId,
@ -186,9 +200,12 @@ export class ChannelStartupService {
this.localSettings.readMessages = data?.readMessages; this.localSettings.readMessages = data?.readMessages;
this.localSettings.readStatus = data?.readStatus; this.localSettings.readStatus = data?.readStatus;
this.localSettings.syncFullHistory = data?.syncFullHistory; this.localSettings.syncFullHistory = data?.syncFullHistory;
this.logger.debug('[setSettings] Configurações atualizadas com sucesso.');
} }
public async findSettings() { public async findSettings() {
this.logger.debug(`[findSettings] Buscando configurações para instanceId: ${this.instanceId}`);
const data = await this.prismaRepository.setting.findUnique({ const data = await this.prismaRepository.setting.findUnique({
where: { where: {
instanceId: this.instanceId, instanceId: this.instanceId,
@ -196,9 +213,11 @@ export class ChannelStartupService {
}); });
if (!data) { if (!data) {
this.logger.debug('[findSettings] Nenhuma configuração encontrada.');
return null; return null;
} }
this.logger.debug('[findSettings] Configurações encontradas.');
return { return {
rejectCall: data.rejectCall, rejectCall: data.rejectCall,
msgCall: data.msgCall, msgCall: data.msgCall,
@ -211,7 +230,9 @@ export class ChannelStartupService {
} }
public async loadChatwoot() { public async loadChatwoot() {
this.logger.debug('[loadChatwoot] Carregando dados do Chatwoot...');
if (!this.configService.get<Chatwoot>('CHATWOOT').ENABLED) { if (!this.configService.get<Chatwoot>('CHATWOOT').ENABLED) {
this.logger.debug('[loadChatwoot] Chatwoot não está habilitado nas configurações.');
return; return;
} }
@ -235,10 +256,14 @@ export class ChannelStartupService {
this.localChatwoot.importContacts = data?.importContacts; this.localChatwoot.importContacts = data?.importContacts;
this.localChatwoot.importMessages = data?.importMessages; this.localChatwoot.importMessages = data?.importMessages;
this.localChatwoot.daysLimitImportMessages = data?.daysLimitImportMessages; this.localChatwoot.daysLimitImportMessages = data?.daysLimitImportMessages;
this.logger.debug('[loadChatwoot] Dados do Chatwoot carregados com sucesso.');
} }
public async setChatwoot(data: ChatwootDto) { public async setChatwoot(data: ChatwootDto) {
this.logger.debug(`[setChatwoot] Atualizando dados do Chatwoot: ${JSON.stringify(data)}`);
if (!this.configService.get<Chatwoot>('CHATWOOT').ENABLED) { if (!this.configService.get<Chatwoot>('CHATWOOT').ENABLED) {
this.logger.debug('[setChatwoot] Chatwoot não está habilitado nas configurações.');
return; return;
} }
@ -275,8 +300,8 @@ export class ChannelStartupService {
}); });
Object.assign(this.localChatwoot, { ...data, signDelimiter: data.signMsg ? data.signDelimiter : null }); Object.assign(this.localChatwoot, { ...data, signDelimiter: data.signMsg ? data.signDelimiter : null });
this.clearCacheChatwoot(); this.clearCacheChatwoot();
this.logger.debug('[setChatwoot] Dados do Chatwoot atualizados com sucesso.');
return; return;
} }
@ -303,12 +328,14 @@ export class ChannelStartupService {
}); });
Object.assign(this.localChatwoot, { ...data, signDelimiter: data.signMsg ? data.signDelimiter : null }); Object.assign(this.localChatwoot, { ...data, signDelimiter: data.signMsg ? data.signDelimiter : null });
this.clearCacheChatwoot(); this.clearCacheChatwoot();
this.logger.debug('[setChatwoot] Dados do Chatwoot criados com sucesso.');
} }
public async findChatwoot(): Promise<ChatwootDto | null> { public async findChatwoot(): Promise<ChatwootDto | null> {
this.logger.debug(`[findChatwoot] Buscando dados do Chatwoot para instanceId: ${this.instanceId}`);
if (!this.configService.get<Chatwoot>('CHATWOOT').ENABLED) { if (!this.configService.get<Chatwoot>('CHATWOOT').ENABLED) {
this.logger.debug('[findChatwoot] Chatwoot não está habilitado nas configurações.');
return null; return null;
} }
@ -319,11 +346,12 @@ export class ChannelStartupService {
}); });
if (!data) { if (!data) {
this.logger.debug('[findChatwoot] Nenhum dado de Chatwoot encontrado.');
return null; return null;
} }
const ignoreJidsArray = Array.isArray(data.ignoreJids) ? data.ignoreJids.map((event) => String(event)) : []; const ignoreJidsArray = Array.isArray(data.ignoreJids) ? data.ignoreJids.map((event) => String(event)) : [];
this.logger.debug('[findChatwoot] Dados de Chatwoot encontrados com sucesso.');
return { return {
enabled: data?.enabled, enabled: data?.enabled,
accountId: data.accountId, accountId: data.accountId,
@ -345,12 +373,15 @@ export class ChannelStartupService {
} }
public clearCacheChatwoot() { public clearCacheChatwoot() {
this.logger.debug('[clearCacheChatwoot] Limpando cache do Chatwoot...');
if (this.localChatwoot?.enabled) { if (this.localChatwoot?.enabled) {
this.chatwootService.getCache()?.deleteAll(this.instanceName); this.chatwootService.getCache()?.deleteAll(this.instanceName);
this.logger.debug('[clearCacheChatwoot] Cache do Chatwoot limpo com sucesso.');
} }
} }
public async loadProxy() { public async loadProxy() {
this.logger.debug(`[loadProxy] Carregando dados de proxy para instanceId: ${this.instanceId}`);
this.localProxy.enabled = false; this.localProxy.enabled = false;
if (process.env.PROXY_HOST) { if (process.env.PROXY_HOST) {
@ -376,9 +407,12 @@ export class ChannelStartupService {
this.localProxy.username = data?.username; this.localProxy.username = data?.username;
this.localProxy.password = data?.password; this.localProxy.password = data?.password;
} }
this.logger.debug('[loadProxy] Dados de proxy carregados com sucesso.');
} }
public async setProxy(data: ProxyDto) { public async setProxy(data: ProxyDto) {
this.logger.debug(`[setProxy] Definindo dados de proxy: ${JSON.stringify(data)}`);
await this.prismaRepository.proxy.upsert({ await this.prismaRepository.proxy.upsert({
where: { where: {
instanceId: this.instanceId, instanceId: this.instanceId,
@ -403,9 +437,11 @@ export class ChannelStartupService {
}); });
Object.assign(this.localProxy, data); Object.assign(this.localProxy, data);
this.logger.debug('[setProxy] Dados de proxy atualizados com sucesso.');
} }
public async findProxy() { public async findProxy() {
this.logger.debug(`[findProxy] Buscando dados de proxy para instanceId: ${this.instanceId}`);
const data = await this.prismaRepository.proxy.findUnique({ const data = await this.prismaRepository.proxy.findUnique({
where: { where: {
instanceId: this.instanceId, instanceId: this.instanceId,
@ -413,20 +449,22 @@ export class ChannelStartupService {
}); });
if (!data) { if (!data) {
this.logger.debug('[findProxy] Proxy não encontrado.');
throw new NotFoundException('Proxy not found'); throw new NotFoundException('Proxy not found');
} }
this.logger.debug('[findProxy] Dados de proxy encontrados com sucesso.');
return data; return data;
} }
public async sendDataWebhook<T = any>(event: Events, data: T, local = true) { public async sendDataWebhook<T = any>(event: Events, data: T, local = true) {
this.logger.debug(`[sendDataWebhook] Enviando dados de webhook. Evento: ${event}, local: ${local}`);
const serverUrl = this.configService.get<HttpServer>('SERVER').URL; const serverUrl = this.configService.get<HttpServer>('SERVER').URL;
const tzoffset = new Date().getTimezoneOffset() * 60000; //offset in milliseconds const tzoffset = new Date().getTimezoneOffset() * 60000; //offset in milliseconds
const localISOTime = new Date(Date.now() - tzoffset).toISOString(); const localISOTime = new Date(Date.now() - tzoffset).toISOString();
const now = localISOTime; const now = localISOTime;
const expose = this.configService.get<Auth>('AUTHENTICATION').EXPOSE_IN_FETCH_INSTANCES; const expose = this.configService.get<Auth>('AUTHENTICATION').EXPOSE_IN_FETCH_INSTANCES;
const instanceApikey = this.token || 'Apikey not found'; const instanceApikey = this.token || 'Apikey not found';
await eventManager.emit({ await eventManager.emit({
@ -440,10 +478,12 @@ export class ChannelStartupService {
apiKey: expose && instanceApikey ? instanceApikey : null, apiKey: expose && instanceApikey ? instanceApikey : null,
local, local,
}); });
this.logger.debug('[sendDataWebhook] Evento de webhook enviado com sucesso.');
} }
// Check if the number is MX or AR // Check if the number is MX or AR
public formatMXOrARNumber(jid: string): string { public formatMXOrARNumber(jid: string): string {
this.logger.debug(`[formatMXOrARNumber] Formatando número MX ou AR: ${jid}`);
const countryCode = jid.substring(0, 2); const countryCode = jid.substring(0, 2);
if (Number(countryCode) === 52 || Number(countryCode) === 54) { if (Number(countryCode) === 52 || Number(countryCode) === 54) {
@ -451,7 +491,6 @@ export class ChannelStartupService {
const number = countryCode + jid.substring(3); const number = countryCode + jid.substring(3);
return number; return number;
} }
return jid; return jid;
} }
return jid; return jid;
@ -459,6 +498,7 @@ export class ChannelStartupService {
// Check if the number is br // Check if the number is br
public formatBRNumber(jid: string) { public formatBRNumber(jid: string) {
this.logger.debug(`[formatBRNumber] Formatando número brasileiro: ${jid}`);
const regexp = new RegExp(/^(\d{2})(\d{2})\d{1}(\d{8})$/); const regexp = new RegExp(/^(\d{2})(\d{2})\d{1}(\d{8})$/);
if (regexp.test(jid)) { if (regexp.test(jid)) {
const match = regexp.exec(jid); const match = regexp.exec(jid);
@ -477,11 +517,14 @@ export class ChannelStartupService {
} }
public createJid(number: string): string { public createJid(number: string): string {
this.logger.debug(`[createJid] Criando JID para o número: ${number}`);
if (number.includes('@g.us') || number.includes('@s.whatsapp.net') || number.includes('@lid')) { if (number.includes('@g.us') || number.includes('@s.whatsapp.net') || number.includes('@lid')) {
this.logger.debug('[createJid] Retornando número pois já possui sufixo de grupo ou WhatsApp.');
return number; return number;
} }
if (number.includes('@broadcast')) { if (number.includes('@broadcast')) {
this.logger.debug('[createJid] Retornando número pois já é um broadcast.');
return number; return number;
} }
@ -495,6 +538,7 @@ export class ChannelStartupService {
if (number.includes('-') && number.length >= 24) { if (number.includes('-') && number.length >= 24) {
number = number.replace(/[^\d-]/g, ''); number = number.replace(/[^\d-]/g, '');
this.logger.debug('[createJid] Número identificado como grupo, adicionando @g.us.');
return `${number}@g.us`; return `${number}@g.us`;
} }
@ -502,17 +546,19 @@ export class ChannelStartupService {
if (number.length >= 18) { if (number.length >= 18) {
number = number.replace(/[^\d-]/g, ''); number = number.replace(/[^\d-]/g, '');
this.logger.debug('[createJid] Número extenso, provavelmente grupo, adicionando @g.us.');
return `${number}@g.us`; return `${number}@g.us`;
} }
number = this.formatMXOrARNumber(number); number = this.formatMXOrARNumber(number);
number = this.formatBRNumber(number); number = this.formatBRNumber(number);
this.logger.debug('[createJid] Adicionando sufixo @s.whatsapp.net para número individual.');
return `${number}@s.whatsapp.net`; return `${number}@s.whatsapp.net`;
} }
public async fetchContacts(query: Query<Contact>) { public async fetchContacts(query: Query<Contact>) {
this.logger.debug(`[fetchContacts] Buscando contatos. Query: ${JSON.stringify(query)}`);
const remoteJid = query?.where?.remoteJid const remoteJid = query?.where?.remoteJid
? query?.where?.remoteJid.includes('@') ? query?.where?.remoteJid.includes('@')
? query.where?.remoteJid ? query.where?.remoteJid
@ -527,12 +573,15 @@ export class ChannelStartupService {
where['remoteJid'] = remoteJid; where['remoteJid'] = remoteJid;
} }
return await this.prismaRepository.contact.findMany({ const contacts = await this.prismaRepository.contact.findMany({
where, where,
}); });
this.logger.debug(`[fetchContacts] Retornando ${contacts.length} contato(s).`);
return contacts;
} }
public async fetchMessages(query: Query<Message>) { public async fetchMessages(query: Query<Message>) {
this.logger.debug(`[fetchMessages] Buscando mensagens. Query: ${JSON.stringify(query)}`);
const keyFilters = query?.where?.key as { const keyFilters = query?.where?.key as {
id?: string; id?: string;
fromMe?: boolean; fromMe?: boolean;
@ -599,6 +648,7 @@ export class ChannelStartupService {
}, },
}); });
this.logger.debug(`[fetchMessages] Total de mensagens encontradas: ${count}.`);
return { return {
messages: { messages: {
total: count, total: count,
@ -610,7 +660,8 @@ export class ChannelStartupService {
} }
public async fetchStatusMessage(query: any) { public async fetchStatusMessage(query: any) {
return await this.prismaRepository.messageUpdate.findMany({ this.logger.debug(`[fetchStatusMessage] Buscando status de mensagens. Query: ${JSON.stringify(query)}`);
const results = await this.prismaRepository.messageUpdate.findMany({
where: { where: {
instanceId: this.instanceId, instanceId: this.instanceId,
remoteJid: query.where?.remoteJid, remoteJid: query.where?.remoteJid,
@ -619,9 +670,12 @@ export class ChannelStartupService {
skip: query.offset * (query?.page === 1 ? 0 : (query?.page as number) - 1), skip: query.offset * (query?.page === 1 ? 0 : (query?.page as number) - 1),
take: query.offset, take: query.offset,
}); });
this.logger.debug(`[fetchStatusMessage] Retornando ${results.length} atualização(ões) de status.`);
return results;
} }
public async fetchChats(query: any) { public async fetchChats(query: any) {
this.logger.debug(`[fetchChats] Buscando chats. Query: ${JSON.stringify(query)}`);
const remoteJid = query?.where?.remoteJid const remoteJid = query?.where?.remoteJid
? query?.where?.remoteJid.includes('@') ? query?.where?.remoteJid.includes('@')
? query.where?.remoteJid ? query.where?.remoteJid
@ -703,6 +757,7 @@ export class ChannelStartupService {
} }
if (results && isArray(results) && results.length > 0) { if (results && isArray(results) && results.length > 0) {
this.logger.debug(`[fetchChats] Retornando ${results.length} chat(s).`);
return results.map((chat) => { return results.map((chat) => {
return { return {
id: chat.id, id: chat.id,
@ -734,6 +789,7 @@ export class ChannelStartupService {
}); });
} }
this.logger.debug('[fetchChats] Nenhum chat encontrado.');
return []; return [];
} }
} }

View File

@ -136,7 +136,7 @@ export class Logger {
this.console(value, Type.WARN); this.console(value, Type.WARN);
} }
public error(value: any) { public error(value: any, p0?: { message: any; stack: any; }) {
this.console(value, Type.ERROR); this.console(value, Type.ERROR);
} }
@ -144,7 +144,7 @@ export class Logger {
this.console(value, Type.VERBOSE); this.console(value, Type.VERBOSE);
} }
public debug(value: any) { public debug(value: any, p0?: { config: any; }) {
this.console(value, Type.DEBUG); this.console(value, Type.DEBUG);
} }