diff --git a/src/api/integrations/channel/whatsapp/baileys/whatsapp.baileys.service.ts b/src/api/integrations/channel/whatsapp/baileys/whatsapp.baileys.service.ts index 533045b6..f1c0378a 100644 --- a/src/api/integrations/channel/whatsapp/baileys/whatsapp.baileys.service.ts +++ b/src/api/integrations/channel/whatsapp/baileys/whatsapp.baileys.service.ts @@ -60,13 +60,11 @@ import { configService, ConfigSessionPhone, Database, - Dify, Log, Openai, ProviderSession, QrCode, S3, - Typebot, } from '@config/env.config'; import { BadRequestException, InternalServerErrorException, NotFoundException } from '@exceptions'; import ffmpegPath from '@ffmpeg-installer/ffmpeg'; @@ -2012,35 +2010,12 @@ export class BaileysStartupService extends ChannelStartupService { ); } - if (this.configService.get('CHATWOOT').ENABLED && this.localChatwoot.enabled && isIntegration) { - if (this.configService.get('TYPEBOT').ENABLED) { - if (messageRaw.messageType !== 'reactionMessage') - await this.typebotService.sendTypebot( - { instanceName: this.instance.name, instanceId: this.instanceId }, - messageRaw.key.remoteJid, - messageRaw, - ); - } - - if (this.configService.get('OPENAI').ENABLED) { - if (messageRaw.messageType !== 'reactionMessage') - await this.openaiService.sendOpenai( - { instanceName: this.instance.name, instanceId: this.instanceId }, - messageRaw.key.remoteJid, - messageRaw.pushName, - messageRaw, - ); - } - - if (this.configService.get('DIFY').ENABLED) { - if (messageRaw.messageType !== 'reactionMessage') - await this.difyService.sendDify( - { instanceName: this.instance.name, instanceId: this.instanceId }, - messageRaw.key.remoteJid, - messageRaw, - ); - } - } + await chatbotController.emit({ + instance: { instanceName: this.instance.name, instanceId: this.instanceId }, + remoteJid: messageRaw.key.remoteJid, + msg: messageRaw, + pushName: messageRaw.pushName, + }); if (this.configService.get('DATABASE').SAVE_DATA.NEW_MESSAGE) await this.prismaRepository.message.create({ diff --git a/src/api/integrations/channel/whatsapp/business/whatsapp.business.service.ts b/src/api/integrations/channel/whatsapp/business/whatsapp.business.service.ts index 2207ff29..f6b4b56e 100644 --- a/src/api/integrations/channel/whatsapp/business/whatsapp.business.service.ts +++ b/src/api/integrations/channel/whatsapp/business/whatsapp.business.service.ts @@ -20,7 +20,7 @@ import { chatbotController } from '@api/server.module'; import { CacheService } from '@api/services/cache.service'; import { ChannelStartupService } from '@api/services/channel.service'; import { Events, wa } from '@api/types/wa.types'; -import { Chatwoot, ConfigService, Database, Dify, Openai, S3, Typebot, WaBusiness } from '@config/env.config'; +import { Chatwoot, ConfigService, Database, Openai, S3, WaBusiness } from '@config/env.config'; import { BadRequestException, InternalServerErrorException } from '@exceptions'; import axios from 'axios'; import { arrayUnique, isURL } from 'class-validator'; @@ -923,35 +923,12 @@ export class BusinessStartupService extends ChannelStartupService { ); } - if (this.configService.get('CHATWOOT').ENABLED && this.localChatwoot.enabled && isIntegration) { - if (this.configService.get('TYPEBOT').ENABLED) { - if (messageRaw.messageType !== 'reactionMessage') - await this.typebotService.sendTypebot( - { instanceName: this.instance.name, instanceId: this.instanceId }, - messageRaw.key.remoteJid, - messageRaw, - ); - } - - if (this.configService.get('OPENAI').ENABLED) { - if (messageRaw.messageType !== 'reactionMessage') - await this.openaiService.sendOpenai( - { instanceName: this.instance.name, instanceId: this.instanceId }, - messageRaw.key.remoteJid, - messageRaw.pushName, - messageRaw, - ); - } - - if (this.configService.get('DIFY').ENABLED) { - if (messageRaw.messageType !== 'reactionMessage') - await this.difyService.sendDify( - { instanceName: this.instance.name, instanceId: this.instanceId }, - messageRaw.key.remoteJid, - messageRaw, - ); - } - } + await chatbotController.emit({ + instance: { instanceName: this.instance.name, instanceId: this.instanceId }, + remoteJid: messageRaw.key.remoteJid, + msg: messageRaw, + pushName: messageRaw.pushName, + }); await this.prismaRepository.message.create({ data: messageRaw, diff --git a/src/api/integrations/chatbot/chatbot.controller.ts b/src/api/integrations/chatbot/chatbot.controller.ts index 6c9f531b..7054f4f9 100644 --- a/src/api/integrations/chatbot/chatbot.controller.ts +++ b/src/api/integrations/chatbot/chatbot.controller.ts @@ -6,6 +6,36 @@ import { Logger } from '@config/logger.config'; import { IntegrationSession } from '@prisma/client'; import { findBotByTrigger } from '@utils/findBotByTrigger'; +export type EmitData = { + instance: InstanceDto; + remoteJid: string; + msg: any; + pushName?: string; +}; + +export interface ChatbotControllerInterface { + integrationEnabled: boolean; + botRepository: any; + settingsRepository: any; + sessionRepository: any; + userMessageDebounce: { [key: string]: { message: string; timeoutId: NodeJS.Timeout } }; + + createBot(instance: InstanceDto, data: any): Promise; + findBot(instance: InstanceDto): Promise; + fetchBot(instance: InstanceDto, botId: string): Promise; + updateBot(instance: InstanceDto, botId: string, data: any): Promise; + deleteBot(instance: InstanceDto, botId: string): Promise; + + settings(instance: InstanceDto, data: any): Promise; + fetchSettings(instance: InstanceDto): Promise; + + changeStatus(instance: InstanceDto, botId: string, status: string): Promise; + fetchSessions(instance: InstanceDto, botId: string, remoteJid?: string): Promise; + ignoreJid(instance: InstanceDto, data: any): Promise; + + emit(data: EmitData): Promise; +} + export class ChatbotController { public prismaRepository: PrismaRepository; public waMonitor: WAMonitoringService; diff --git a/src/api/integrations/chatbot/dify/controllers/dify.controller.ts b/src/api/integrations/chatbot/dify/controllers/dify.controller.ts index 79972473..5add7b65 100644 --- a/src/api/integrations/chatbot/dify/controllers/dify.controller.ts +++ b/src/api/integrations/chatbot/dify/controllers/dify.controller.ts @@ -1,84 +1,825 @@ +import { IgnoreJidDto } from '@api/dto/chatbot.dto'; import { InstanceDto } from '@api/dto/instance.dto'; -import { DifyDto, DifyIgnoreJidDto } from '@api/integrations/chatbot/dify/dto/dify.dto'; +import { DifyDto } from '@api/integrations/chatbot/dify/dto/dify.dto'; import { DifyService } from '@api/integrations/chatbot/dify/services/dify.service'; +import { PrismaRepository } from '@api/repository/repository.service'; +import { WAMonitoringService } from '@api/services/monitor.service'; import { configService, Dify } from '@config/env.config'; +import { Logger } from '@config/logger.config'; import { BadRequestException } from '@exceptions'; +import { getConversationMessage } from '@utils/getConversationMessage'; -export class DifyController { - constructor(private readonly difyService: DifyService) {} +import { ChatbotController, ChatbotControllerInterface, EmitData } from '../../chatbot.controller'; - public async createDify(instance: InstanceDto, data: DifyDto) { - if (!configService.get('DIFY').ENABLED) throw new BadRequestException('Dify is disabled'); +export class DifyController extends ChatbotController implements ChatbotControllerInterface { + constructor( + private readonly difyService: DifyService, + prismaRepository: PrismaRepository, + waMonitor: WAMonitoringService, + ) { + super(prismaRepository, waMonitor); - return this.difyService.create(instance, data); + this.botRepository = this.prismaRepository.dify; + this.settingsRepository = this.prismaRepository.difySetting; + this.sessionRepository = this.prismaRepository.integrationSession; } - public async findDify(instance: InstanceDto) { - if (!configService.get('DIFY').ENABLED) throw new BadRequestException('Dify is disabled'); + public readonly logger = new Logger(DifyController.name); - return this.difyService.find(instance); + integrationEnabled = configService.get('DIFY').ENABLED; + botRepository: any; + settingsRepository: any; + sessionRepository: any; + userMessageDebounce: { [key: string]: { message: string; timeoutId: NodeJS.Timeout } } = {}; + + // Bots + public async createBot(instance: InstanceDto, data: DifyDto) { + if (!this.integrationEnabled) throw new BadRequestException('Dify is disabled'); + + const instanceId = await this.prismaRepository.instance + .findFirst({ + where: { + name: instance.instanceName, + }, + }) + .then((instance) => instance.id); + + if ( + !data.expire || + !data.keywordFinish || + !data.delayMessage || + !data.unknownMessage || + !data.listeningFromMe || + !data.stopBotFromMe || + !data.keepOpen || + !data.debounceTime || + !data.ignoreJids + ) { + const defaultSettingCheck = await this.settingsRepository.findFirst({ + where: { + instanceId: instanceId, + }, + }); + + if (!data.expire) data.expire = defaultSettingCheck?.expire || 0; + if (!data.keywordFinish) data.keywordFinish = defaultSettingCheck?.keywordFinish || ''; + if (!data.delayMessage) data.delayMessage = defaultSettingCheck?.delayMessage || 1000; + if (!data.unknownMessage) data.unknownMessage = defaultSettingCheck?.unknownMessage || ''; + if (!data.listeningFromMe) data.listeningFromMe = defaultSettingCheck?.listeningFromMe || false; + if (!data.stopBotFromMe) data.stopBotFromMe = defaultSettingCheck?.stopBotFromMe || false; + if (!data.keepOpen) data.keepOpen = defaultSettingCheck?.keepOpen || false; + if (!data.debounceTime) data.debounceTime = defaultSettingCheck?.debounceTime || 0; + if (!data.ignoreJids) data.ignoreJids = defaultSettingCheck?.ignoreJids || []; + + if (!defaultSettingCheck) { + await this.settings(instance, { + expire: data.expire, + keywordFinish: data.keywordFinish, + delayMessage: data.delayMessage, + unknownMessage: data.unknownMessage, + listeningFromMe: data.listeningFromMe, + stopBotFromMe: data.stopBotFromMe, + keepOpen: data.keepOpen, + debounceTime: data.debounceTime, + ignoreJids: data.ignoreJids, + }); + } + } + + const checkTriggerAll = await this.botRepository.findFirst({ + where: { + enabled: true, + triggerType: 'all', + instanceId: instanceId, + }, + }); + + if (checkTriggerAll && data.triggerType === 'all') { + throw new Error('You already have a dify with an "All" trigger, you cannot have more bots while it is active'); + } + + const checkDuplicate = await this.botRepository.findFirst({ + where: { + instanceId: instanceId, + botType: data.botType, + apiUrl: data.apiUrl, + apiKey: data.apiKey, + }, + }); + + if (checkDuplicate) { + throw new Error('Dify already exists'); + } + + if (data.triggerType === 'keyword') { + if (!data.triggerOperator || !data.triggerValue) { + throw new Error('Trigger operator and value are required'); + } + + const checkDuplicate = await this.botRepository.findFirst({ + where: { + triggerOperator: data.triggerOperator, + triggerValue: data.triggerValue, + instanceId: instanceId, + }, + }); + + if (checkDuplicate) { + throw new Error('Trigger already exists'); + } + } + + if (data.triggerType === 'advanced') { + if (!data.triggerValue) { + throw new Error('Trigger value is required'); + } + + const checkDuplicate = await this.botRepository.findFirst({ + where: { + triggerValue: data.triggerValue, + instanceId: instanceId, + }, + }); + + if (checkDuplicate) { + throw new Error('Trigger already exists'); + } + } + + try { + const bot = await this.botRepository.create({ + data: { + enabled: data.enabled, + description: data.description, + botType: data.botType, + apiUrl: data.apiUrl, + apiKey: data.apiKey, + expire: data.expire, + keywordFinish: data.keywordFinish, + delayMessage: data.delayMessage, + unknownMessage: data.unknownMessage, + listeningFromMe: data.listeningFromMe, + stopBotFromMe: data.stopBotFromMe, + keepOpen: data.keepOpen, + debounceTime: data.debounceTime, + instanceId: instanceId, + triggerType: data.triggerType, + triggerOperator: data.triggerOperator, + triggerValue: data.triggerValue, + ignoreJids: data.ignoreJids, + }, + }); + + return bot; + } catch (error) { + this.logger.error(error); + throw new Error('Error creating dify'); + } } - public async fetchDify(instance: InstanceDto, difyId: string) { - if (!configService.get('DIFY').ENABLED) throw new BadRequestException('Dify is disabled'); + public async findBot(instance: InstanceDto) { + if (!this.integrationEnabled) throw new BadRequestException('Dify is disabled'); - return this.difyService.fetch(instance, difyId); + const instanceId = await this.prismaRepository.instance + .findFirst({ + where: { + name: instance.instanceName, + }, + }) + .then((instance) => instance.id); + + const bots = await this.botRepository.findMany({ + where: { + instanceId: instanceId, + }, + }); + + if (!bots.length) { + return null; + } + + return bots; } - public async updateDify(instance: InstanceDto, difyId: string, data: DifyDto) { - if (!configService.get('DIFY').ENABLED) throw new BadRequestException('Dify is disabled'); + public async fetchBot(instance: InstanceDto, botId: string) { + if (!this.integrationEnabled) throw new BadRequestException('Dify is disabled'); - return this.difyService.update(instance, difyId, data); + const instanceId = await this.prismaRepository.instance + .findFirst({ + where: { + name: instance.instanceName, + }, + }) + .then((instance) => instance.id); + + const bot = await this.botRepository.findFirst({ + where: { + id: botId, + }, + }); + + if (!bot) { + throw new Error('Dify not found'); + } + + if (bot.instanceId !== instanceId) { + throw new Error('Dify not found'); + } + + return bot; } - public async deleteDify(instance: InstanceDto, difyId: string) { - if (!configService.get('DIFY').ENABLED) throw new BadRequestException('Dify is disabled'); + public async updateBot(instance: InstanceDto, botId: string, data: DifyDto) { + if (!this.integrationEnabled) throw new BadRequestException('Dify is disabled'); - return this.difyService.delete(instance, difyId); + const instanceId = await this.prismaRepository.instance + .findFirst({ + where: { + name: instance.instanceName, + }, + }) + .then((instance) => instance.id); + + const bot = await this.botRepository.findFirst({ + where: { + id: botId, + }, + }); + + if (!bot) { + throw new Error('Dify not found'); + } + + if (bot.instanceId !== instanceId) { + throw new Error('Dify not found'); + } + + if (data.triggerType === 'all') { + const checkTriggerAll = await this.botRepository.findFirst({ + where: { + enabled: true, + triggerType: 'all', + id: { + not: botId, + }, + instanceId: instanceId, + }, + }); + + if (checkTriggerAll) { + throw new Error('You already have a dify with an "All" trigger, you cannot have more bots while it is active'); + } + } + + const checkDuplicate = await this.botRepository.findFirst({ + where: { + id: { + not: botId, + }, + instanceId: instanceId, + botType: data.botType, + apiUrl: data.apiUrl, + apiKey: data.apiKey, + }, + }); + + if (checkDuplicate) { + throw new Error('Dify already exists'); + } + + if (data.triggerType === 'keyword') { + if (!data.triggerOperator || !data.triggerValue) { + throw new Error('Trigger operator and value are required'); + } + + const checkDuplicate = await this.botRepository.findFirst({ + where: { + triggerOperator: data.triggerOperator, + triggerValue: data.triggerValue, + id: { not: botId }, + instanceId: instanceId, + }, + }); + + if (checkDuplicate) { + throw new Error('Trigger already exists'); + } + } + + if (data.triggerType === 'advanced') { + if (!data.triggerValue) { + throw new Error('Trigger value is required'); + } + + const checkDuplicate = await this.botRepository.findFirst({ + where: { + triggerValue: data.triggerValue, + id: { not: botId }, + instanceId: instanceId, + }, + }); + + if (checkDuplicate) { + throw new Error('Trigger already exists'); + } + } + + try { + const bot = await this.botRepository.update({ + where: { + id: botId, + }, + data: { + enabled: data.enabled, + botType: data.botType, + apiUrl: data.apiUrl, + apiKey: data.apiKey, + expire: data.expire, + keywordFinish: data.keywordFinish, + delayMessage: data.delayMessage, + unknownMessage: data.unknownMessage, + listeningFromMe: data.listeningFromMe, + stopBotFromMe: data.stopBotFromMe, + keepOpen: data.keepOpen, + debounceTime: data.debounceTime, + instanceId: instanceId, + triggerType: data.triggerType, + triggerOperator: data.triggerOperator, + triggerValue: data.triggerValue, + ignoreJids: data.ignoreJids, + }, + }); + + return bot; + } catch (error) { + this.logger.error(error); + throw new Error('Error updating dify'); + } } + public async deleteBot(instance: InstanceDto, botId: string) { + if (!this.integrationEnabled) throw new BadRequestException('Dify is disabled'); + + const instanceId = await this.prismaRepository.instance + .findFirst({ + where: { + name: instance.instanceName, + }, + }) + .then((instance) => instance.id); + + const bot = await this.botRepository.findFirst({ + where: { + id: botId, + }, + }); + + if (!bot) { + throw new Error('Dify not found'); + } + + if (bot.instanceId !== instanceId) { + throw new Error('Dify not found'); + } + try { + await this.prismaRepository.integrationSession.deleteMany({ + where: { + botId: botId, + }, + }); + + await this.botRepository.delete({ + where: { + id: botId, + }, + }); + + return { bot: { id: botId } }; + } catch (error) { + this.logger.error(error); + throw new Error('Error deleting dify bot'); + } + } + + // Settings public async settings(instance: InstanceDto, data: any) { - if (!configService.get('DIFY').ENABLED) throw new BadRequestException('Dify is disabled'); + if (!this.integrationEnabled) throw new BadRequestException('Dify is disabled'); - return this.difyService.setDefaultSettings(instance, data); + try { + const instanceId = await this.prismaRepository.instance + .findFirst({ + where: { + name: instance.instanceName, + }, + }) + .then((instance) => instance.id); + + const settings = await this.settingsRepository.findFirst({ + where: { + instanceId: instanceId, + }, + }); + + if (settings) { + const updateSettings = await this.settingsRepository.update({ + where: { + id: settings.id, + }, + data: { + expire: data.expire, + keywordFinish: data.keywordFinish, + delayMessage: data.delayMessage, + unknownMessage: data.unknownMessage, + listeningFromMe: data.listeningFromMe, + stopBotFromMe: data.stopBotFromMe, + keepOpen: data.keepOpen, + debounceTime: data.debounceTime, + difyIdFallback: data.difyIdFallback, + ignoreJids: data.ignoreJids, + }, + }); + + return { + expire: updateSettings.expire, + keywordFinish: updateSettings.keywordFinish, + delayMessage: updateSettings.delayMessage, + unknownMessage: updateSettings.unknownMessage, + listeningFromMe: updateSettings.listeningFromMe, + stopBotFromMe: updateSettings.stopBotFromMe, + keepOpen: updateSettings.keepOpen, + debounceTime: updateSettings.debounceTime, + difyIdFallback: updateSettings.difyIdFallback, + ignoreJids: updateSettings.ignoreJids, + }; + } + + const newSetttings = await this.settingsRepository.create({ + data: { + expire: data.expire, + keywordFinish: data.keywordFinish, + delayMessage: data.delayMessage, + unknownMessage: data.unknownMessage, + listeningFromMe: data.listeningFromMe, + stopBotFromMe: data.stopBotFromMe, + keepOpen: data.keepOpen, + debounceTime: data.debounceTime, + difyIdFallback: data.difyIdFallback, + ignoreJids: data.ignoreJids, + instanceId: instanceId, + }, + }); + + return { + expire: newSetttings.expire, + keywordFinish: newSetttings.keywordFinish, + delayMessage: newSetttings.delayMessage, + unknownMessage: newSetttings.unknownMessage, + listeningFromMe: newSetttings.listeningFromMe, + stopBotFromMe: newSetttings.stopBotFromMe, + keepOpen: newSetttings.keepOpen, + debounceTime: newSetttings.debounceTime, + difyIdFallback: newSetttings.difyIdFallback, + ignoreJids: newSetttings.ignoreJids, + }; + } catch (error) { + this.logger.error(error); + throw new Error('Error setting default settings'); + } } public async fetchSettings(instance: InstanceDto) { - if (!configService.get('DIFY').ENABLED) throw new BadRequestException('Dify is disabled'); + if (!this.integrationEnabled) throw new BadRequestException('Dify is disabled'); - return this.difyService.fetchDefaultSettings(instance); + try { + const instanceId = await this.prismaRepository.instance + .findFirst({ + where: { + name: instance.instanceName, + }, + }) + .then((instance) => instance.id); + + const settings = await this.settingsRepository.findFirst({ + where: { + instanceId: instanceId, + }, + include: { + Fallback: true, + }, + }); + + if (!settings) { + return { + expire: 0, + keywordFinish: '', + delayMessage: 0, + unknownMessage: '', + listeningFromMe: false, + stopBotFromMe: false, + keepOpen: false, + ignoreJids: [], + difyIdFallback: '', + fallback: null, + }; + } + + return { + expire: settings.expire, + keywordFinish: settings.keywordFinish, + delayMessage: settings.delayMessage, + unknownMessage: settings.unknownMessage, + listeningFromMe: settings.listeningFromMe, + stopBotFromMe: settings.stopBotFromMe, + keepOpen: settings.keepOpen, + ignoreJids: settings.ignoreJids, + difyIdFallback: settings.difyIdFallback, + fallback: settings.Fallback, + }; + } catch (error) { + this.logger.error(error); + throw new Error('Error fetching default settings'); + } } + // Sessions public async changeStatus(instance: InstanceDto, data: any) { - if (!configService.get('DIFY').ENABLED) throw new BadRequestException('Dify is disabled'); + if (!this.integrationEnabled) throw new BadRequestException('Dify is disabled'); - return this.difyService.changeStatus(instance, data); + try { + const instanceId = await this.prismaRepository.instance + .findFirst({ + where: { + name: instance.instanceName, + }, + }) + .then((instance) => instance.id); + + const defaultSettingCheck = await this.settingsRepository.findFirst({ + where: { + instanceId, + }, + }); + + const remoteJid = data.remoteJid; + const status = data.status; + + if (status === 'delete') { + await this.sessionRepository.deleteMany({ + where: { + remoteJid: remoteJid, + botId: { not: null }, + }, + }); + + return { bot: { remoteJid: remoteJid, status: status } }; + } + + if (status === 'closed') { + if (defaultSettingCheck?.keepOpen) { + await this.sessionRepository.updateMany({ + where: { + remoteJid: remoteJid, + botId: { not: null }, + }, + data: { + status: 'closed', + }, + }); + } else { + await this.sessionRepository.deleteMany({ + where: { + remoteJid: remoteJid, + botId: { not: null }, + }, + }); + } + + return { bot: { ...instance, bot: { remoteJid: remoteJid, status: status } } }; + } else { + const session = await this.sessionRepository.updateMany({ + where: { + instanceId: instanceId, + remoteJid: remoteJid, + botId: { not: null }, + }, + data: { + status: status, + }, + }); + + const botData = { + remoteJid: remoteJid, + status: status, + session, + }; + + return { bot: { ...instance, bot: botData } }; + } + } catch (error) { + this.logger.error(error); + throw new Error('Error changing status'); + } } - public async fetchSessions(instance: InstanceDto, difyId: string) { - if (!configService.get('DIFY').ENABLED) throw new BadRequestException('Dify is disabled'); + public async fetchSessions(instance: InstanceDto, botId: string, remoteJid?: string) { + if (!this.integrationEnabled) throw new BadRequestException('Dify is disabled'); - return this.difyService.fetchSessions(instance, difyId); + try { + const instanceId = await this.prismaRepository.instance + .findFirst({ + where: { + name: instance.instanceName, + }, + }) + .then((instance) => instance.id); + + const bot = await this.botRepository.findFirst({ + where: { + id: botId, + }, + }); + + if (bot && bot.instanceId !== instanceId) { + throw new Error('Dify not found'); + } + + return await this.sessionRepository.findMany({ + where: { + instanceId: instanceId, + remoteJid, + botId: bot ? botId : { not: null }, + }, + }); + } catch (error) { + this.logger.error(error); + throw new Error('Error fetching sessions'); + } } - public async ignoreJid(instance: InstanceDto, data: DifyIgnoreJidDto) { - if (!configService.get('DIFY').ENABLED) throw new BadRequestException('Dify is disabled'); + public async ignoreJid(instance: InstanceDto, data: IgnoreJidDto) { + if (!this.integrationEnabled) throw new BadRequestException('Dify is disabled'); - return this.difyService.ignoreJid(instance, data); + try { + const instanceId = await this.prismaRepository.instance + .findFirst({ + where: { + name: instance.instanceName, + }, + }) + .then((instance) => instance.id); + + const settings = await this.settingsRepository.findFirst({ + where: { + instanceId: instanceId, + }, + }); + + if (!settings) { + throw new Error('Settings not found'); + } + + let ignoreJids: any = settings?.ignoreJids || []; + + if (data.action === 'add') { + if (ignoreJids.includes(data.remoteJid)) return { ignoreJids: ignoreJids }; + + ignoreJids.push(data.remoteJid); + } else { + ignoreJids = ignoreJids.filter((jid) => jid !== data.remoteJid); + } + + const updateSettings = await this.settingsRepository.update({ + where: { + id: settings.id, + }, + data: { + ignoreJids: ignoreJids, + }, + }); + + return { + ignoreJids: updateSettings.ignoreJids, + }; + } catch (error) { + this.logger.error(error); + throw new Error('Error setting default settings'); + } } - public async emit({ - instance, - remoteJid, - msg, - }: { - instance: InstanceDto; - remoteJid: string; - msg: any; - pushName?: string; - }) { - if (!configService.get('DIFY').ENABLED) return; + // Emit + public async emit({ instance, remoteJid, msg }: EmitData) { + if (!this.integrationEnabled) return; - await this.difyService.sendDify(instance, remoteJid, msg); + try { + const settings = await this.prismaRepository.difySetting.findFirst({ + where: { + instanceId: instance.instanceId, + }, + }); + + if (this.checkIgnoreJids(settings?.ignoreJids, remoteJid)) return; + + const session = await this.getSession(remoteJid, instance); + + const content = getConversationMessage(msg); + + const findBot = await this.findBotTrigger( + this.botRepository, + this.settingsRepository, + content, + instance, + session, + ); + + if (!findBot) return; + + let expire = findBot.expire; + let keywordFinish = findBot.keywordFinish; + let delayMessage = findBot.delayMessage; + let unknownMessage = findBot.unknownMessage; + let listeningFromMe = findBot.listeningFromMe; + let stopBotFromMe = findBot.stopBotFromMe; + let keepOpen = findBot.keepOpen; + let debounceTime = findBot.debounceTime; + + if ( + !expire || + !keywordFinish || + !delayMessage || + !unknownMessage || + !listeningFromMe || + !stopBotFromMe || + !keepOpen || + !debounceTime + ) { + if (!expire) expire = settings.expire; + + if (!keywordFinish) keywordFinish = settings.keywordFinish; + + if (!delayMessage) delayMessage = settings.delayMessage; + + if (!unknownMessage) unknownMessage = settings.unknownMessage; + + if (!listeningFromMe) listeningFromMe = settings.listeningFromMe; + + if (!stopBotFromMe) stopBotFromMe = settings.stopBotFromMe; + + if (!keepOpen) keepOpen = settings.keepOpen; + + if (!debounceTime) debounceTime = settings.debounceTime; + } + + const key = msg.key as { + id: string; + remoteJid: string; + fromMe: boolean; + participant: string; + }; + + if (stopBotFromMe && key.fromMe && session) { + await this.prismaRepository.integrationSession.update({ + where: { + id: session.id, + }, + data: { + status: 'paused', + }, + }); + return; + } + + if (!listeningFromMe && key.fromMe) { + return; + } + + if (debounceTime && debounceTime > 0) { + this.processDebounce(this.userMessageDebounce, content, remoteJid, debounceTime, async (debouncedContent) => { + await this.difyService.processDify( + this.waMonitor.waInstances[instance.instanceName], + remoteJid, + findBot, + session, + settings, + debouncedContent, + msg?.pushName, + ); + }); + } else { + await this.difyService.processDify( + this.waMonitor.waInstances[instance.instanceName], + remoteJid, + findBot, + session, + settings, + content, + msg?.pushName, + ); + } + + return; + } catch (error) { + this.logger.error(error); + return; + } } } diff --git a/src/api/integrations/chatbot/dify/dto/dify.dto.ts b/src/api/integrations/chatbot/dify/dto/dify.dto.ts index 7967f491..734e7dd5 100644 --- a/src/api/integrations/chatbot/dify/dto/dify.dto.ts +++ b/src/api/integrations/chatbot/dify/dto/dify.dto.ts @@ -1,13 +1,5 @@ import { $Enums, TriggerOperator, TriggerType } from '@prisma/client'; -export class Session { - remoteJid?: string; - sessionId?: string; - status?: string; - createdAt?: number; - updateAt?: number; -} - export class DifyDto { enabled?: boolean; description?: string; @@ -40,8 +32,3 @@ export class DifySettingDto { difyIdFallback?: string; ignoreJids?: any; } - -export class DifyIgnoreJidDto { - remoteJid?: string; - action?: string; -} diff --git a/src/api/integrations/chatbot/dify/routes/dify.router.ts b/src/api/integrations/chatbot/dify/routes/dify.router.ts index 017c6171..1d80b903 100644 --- a/src/api/integrations/chatbot/dify/routes/dify.router.ts +++ b/src/api/integrations/chatbot/dify/routes/dify.router.ts @@ -1,6 +1,7 @@ import { RouterBroker } from '@api/abstract/abstract.router'; +import { IgnoreJidDto } from '@api/dto/chatbot.dto'; import { InstanceDto } from '@api/dto/instance.dto'; -import { DifyDto, DifyIgnoreJidDto, DifySettingDto } from '@api/integrations/chatbot/dify/dto/dify.dto'; +import { DifyDto, DifySettingDto } from '@api/integrations/chatbot/dify/dto/dify.dto'; import { HttpStatus } from '@api/routes/index.router'; import { difyController } from '@api/server.module'; import { @@ -21,7 +22,7 @@ export class DifyRouter extends RouterBroker { request: req, schema: difySchema, ClassRef: DifyDto, - execute: (instance, data) => difyController.createDify(instance, data), + execute: (instance, data) => difyController.createBot(instance, data), }); res.status(HttpStatus.CREATED).json(response); @@ -31,7 +32,7 @@ export class DifyRouter extends RouterBroker { request: req, schema: instanceSchema, ClassRef: InstanceDto, - execute: (instance) => difyController.findDify(instance), + execute: (instance) => difyController.findBot(instance), }); res.status(HttpStatus.OK).json(response); @@ -41,7 +42,7 @@ export class DifyRouter extends RouterBroker { request: req, schema: instanceSchema, ClassRef: InstanceDto, - execute: (instance) => difyController.fetchDify(instance, req.params.difyId), + execute: (instance) => difyController.fetchBot(instance, req.params.difyId), }); res.status(HttpStatus.OK).json(response); @@ -51,7 +52,7 @@ export class DifyRouter extends RouterBroker { request: req, schema: difySchema, ClassRef: DifyDto, - execute: (instance, data) => difyController.updateDify(instance, req.params.difyId, data), + execute: (instance, data) => difyController.updateBot(instance, req.params.difyId, data), }); res.status(HttpStatus.OK).json(response); @@ -61,7 +62,7 @@ export class DifyRouter extends RouterBroker { request: req, schema: instanceSchema, ClassRef: InstanceDto, - execute: (instance) => difyController.deleteDify(instance, req.params.difyId), + execute: (instance) => difyController.deleteBot(instance, req.params.difyId), }); res.status(HttpStatus.OK).json(response); @@ -107,10 +108,10 @@ export class DifyRouter extends RouterBroker { res.status(HttpStatus.OK).json(response); }) .post(this.routerPath('ignoreJid'), ...guards, async (req, res) => { - const response = await this.dataValidate({ + const response = await this.dataValidate({ request: req, schema: difyIgnoreJidSchema, - ClassRef: DifyIgnoreJidDto, + ClassRef: IgnoreJidDto, execute: (instance, data) => difyController.ignoreJid(instance, data), }); diff --git a/src/api/integrations/chatbot/dify/services/dify.service.ts b/src/api/integrations/chatbot/dify/services/dify.service.ts index a1fbf21a..4a2aea07 100644 --- a/src/api/integrations/chatbot/dify/services/dify.service.ts +++ b/src/api/integrations/chatbot/dify/services/dify.service.ts @@ -1,11 +1,9 @@ import { InstanceDto } from '@api/dto/instance.dto'; -import { DifyDto, DifyIgnoreJidDto, DifySettingDto } from '@api/integrations/chatbot/dify/dto/dify.dto'; import { PrismaRepository } from '@api/repository/repository.service'; import { WAMonitoringService } from '@api/services/monitor.service'; -import { Auth, ConfigService, HttpServer, S3 } from '@config/env.config'; +import { Auth, ConfigService, HttpServer } from '@config/env.config'; import { Logger } from '@config/logger.config'; -import { Dify, DifySetting, IntegrationSession, Message } from '@prisma/client'; -import { advancedOperatorsSearch } from '@utils/advancedOperatorsSearch'; +import { Dify, DifySetting, IntegrationSession } from '@prisma/client'; import { sendTelemetry } from '@utils/sendTelemetry'; import axios from 'axios'; import { Readable } from 'stream'; @@ -17,1067 +15,8 @@ export class DifyService { private readonly prismaRepository: PrismaRepository, ) {} - private userMessageDebounce: { [key: string]: { message: string; timeoutId: NodeJS.Timeout } } = {}; - private readonly logger = new Logger('DifyService'); - public async create(instance: InstanceDto, data: DifyDto) { - const instanceId = await this.prismaRepository.instance - .findFirst({ - where: { - name: instance.instanceName, - }, - }) - .then((instance) => instance.id); - - if ( - !data.expire || - !data.keywordFinish || - !data.delayMessage || - !data.unknownMessage || - !data.listeningFromMe || - !data.stopBotFromMe || - !data.keepOpen || - !data.debounceTime || - !data.ignoreJids - ) { - const defaultSettingCheck = await this.prismaRepository.difySetting.findFirst({ - where: { - instanceId: instanceId, - }, - }); - - if (!data.expire) data.expire = defaultSettingCheck?.expire || 0; - if (!data.keywordFinish) data.keywordFinish = defaultSettingCheck?.keywordFinish || ''; - if (!data.delayMessage) data.delayMessage = defaultSettingCheck?.delayMessage || 1000; - if (!data.unknownMessage) data.unknownMessage = defaultSettingCheck?.unknownMessage || ''; - if (!data.listeningFromMe) data.listeningFromMe = defaultSettingCheck?.listeningFromMe || false; - if (!data.stopBotFromMe) data.stopBotFromMe = defaultSettingCheck?.stopBotFromMe || false; - if (!data.keepOpen) data.keepOpen = defaultSettingCheck?.keepOpen || false; - if (!data.debounceTime) data.debounceTime = defaultSettingCheck?.debounceTime || 0; - if (!data.ignoreJids) data.ignoreJids = defaultSettingCheck?.ignoreJids || []; - - if (!defaultSettingCheck) { - await this.setDefaultSettings(instance, { - expire: data.expire, - keywordFinish: data.keywordFinish, - delayMessage: data.delayMessage, - unknownMessage: data.unknownMessage, - listeningFromMe: data.listeningFromMe, - stopBotFromMe: data.stopBotFromMe, - keepOpen: data.keepOpen, - debounceTime: data.debounceTime, - ignoreJids: data.ignoreJids, - }); - } - } - - const checkTriggerAll = await this.prismaRepository.dify.findFirst({ - where: { - enabled: true, - triggerType: 'all', - instanceId: instanceId, - }, - }); - - if (checkTriggerAll && data.triggerType === 'all') { - throw new Error('You already have a dify with an "All" trigger, you cannot have more bots while it is active'); - } - - const checkDuplicate = await this.prismaRepository.dify.findFirst({ - where: { - instanceId: instanceId, - botType: data.botType, - apiUrl: data.apiUrl, - apiKey: data.apiKey, - }, - }); - - if (checkDuplicate) { - throw new Error('Dify already exists'); - } - - if (data.triggerType === 'keyword') { - if (!data.triggerOperator || !data.triggerValue) { - throw new Error('Trigger operator and value are required'); - } - - const checkDuplicate = await this.prismaRepository.dify.findFirst({ - where: { - triggerOperator: data.triggerOperator, - triggerValue: data.triggerValue, - instanceId: instanceId, - }, - }); - - if (checkDuplicate) { - throw new Error('Trigger already exists'); - } - } - - if (data.triggerType === 'advanced') { - if (!data.triggerValue) { - throw new Error('Trigger value is required'); - } - - const checkDuplicate = await this.prismaRepository.dify.findFirst({ - where: { - triggerValue: data.triggerValue, - instanceId: instanceId, - }, - }); - - if (checkDuplicate) { - throw new Error('Trigger already exists'); - } - } - - try { - const dify = await this.prismaRepository.dify.create({ - data: { - enabled: data.enabled, - description: data.description, - botType: data.botType, - apiUrl: data.apiUrl, - apiKey: data.apiKey, - expire: data.expire, - keywordFinish: data.keywordFinish, - delayMessage: data.delayMessage, - unknownMessage: data.unknownMessage, - listeningFromMe: data.listeningFromMe, - stopBotFromMe: data.stopBotFromMe, - keepOpen: data.keepOpen, - debounceTime: data.debounceTime, - instanceId: instanceId, - triggerType: data.triggerType, - triggerOperator: data.triggerOperator, - triggerValue: data.triggerValue, - ignoreJids: data.ignoreJids, - }, - }); - - return dify; - } catch (error) { - this.logger.error(error); - throw new Error('Error creating dify'); - } - } - - public async fetch(instance: InstanceDto, difyId: string) { - const instanceId = await this.prismaRepository.instance - .findFirst({ - where: { - name: instance.instanceName, - }, - }) - .then((instance) => instance.id); - - const dify = await this.prismaRepository.dify.findFirst({ - where: { - id: difyId, - }, - include: { - sessions: true, - }, - }); - - if (!dify) { - throw new Error('Dify not found'); - } - - if (dify.instanceId !== instanceId) { - throw new Error('Dify not found'); - } - - return dify; - } - - public async update(instance: InstanceDto, difyId: string, data: DifyDto) { - const instanceId = await this.prismaRepository.instance - .findFirst({ - where: { - name: instance.instanceName, - }, - }) - .then((instance) => instance.id); - - const dify = await this.prismaRepository.dify.findFirst({ - where: { - id: difyId, - }, - }); - - if (!dify) { - throw new Error('Dify not found'); - } - - if (dify.instanceId !== instanceId) { - throw new Error('Dify not found'); - } - - if (data.triggerType === 'all') { - const checkTriggerAll = await this.prismaRepository.dify.findFirst({ - where: { - enabled: true, - triggerType: 'all', - id: { - not: difyId, - }, - instanceId: instanceId, - }, - }); - - if (checkTriggerAll) { - throw new Error('You already have a dify with an "All" trigger, you cannot have more bots while it is active'); - } - } - - const checkDuplicate = await this.prismaRepository.dify.findFirst({ - where: { - id: { - not: difyId, - }, - instanceId: instanceId, - botType: data.botType, - apiUrl: data.apiUrl, - apiKey: data.apiKey, - }, - }); - - if (checkDuplicate) { - throw new Error('Dify already exists'); - } - - if (data.triggerType === 'keyword') { - if (!data.triggerOperator || !data.triggerValue) { - throw new Error('Trigger operator and value are required'); - } - - const checkDuplicate = await this.prismaRepository.dify.findFirst({ - where: { - triggerOperator: data.triggerOperator, - triggerValue: data.triggerValue, - id: { not: difyId }, - instanceId: instanceId, - }, - }); - - if (checkDuplicate) { - throw new Error('Trigger already exists'); - } - } - - if (data.triggerType === 'advanced') { - if (!data.triggerValue) { - throw new Error('Trigger value is required'); - } - - const checkDuplicate = await this.prismaRepository.dify.findFirst({ - where: { - triggerValue: data.triggerValue, - id: { not: difyId }, - instanceId: instanceId, - }, - }); - - if (checkDuplicate) { - throw new Error('Trigger already exists'); - } - } - - try { - const dify = await this.prismaRepository.dify.update({ - where: { - id: difyId, - }, - data: { - enabled: data.enabled, - botType: data.botType, - apiUrl: data.apiUrl, - apiKey: data.apiKey, - expire: data.expire, - keywordFinish: data.keywordFinish, - delayMessage: data.delayMessage, - unknownMessage: data.unknownMessage, - listeningFromMe: data.listeningFromMe, - stopBotFromMe: data.stopBotFromMe, - keepOpen: data.keepOpen, - debounceTime: data.debounceTime, - instanceId: instanceId, - triggerType: data.triggerType, - triggerOperator: data.triggerOperator, - triggerValue: data.triggerValue, - ignoreJids: data.ignoreJids, - }, - }); - - return dify; - } catch (error) { - this.logger.error(error); - throw new Error('Error updating dify'); - } - } - - public async find(instance: InstanceDto): Promise { - const instanceId = await this.prismaRepository.instance - .findFirst({ - where: { - name: instance.instanceName, - }, - }) - .then((instance) => instance.id); - - const difys = await this.prismaRepository.dify.findMany({ - where: { - instanceId: instanceId, - }, - include: { - sessions: true, - }, - }); - - if (!difys.length) { - return null; - } - - return difys; - } - - public async delete(instance: InstanceDto, difyId: string) { - const instanceId = await this.prismaRepository.instance - .findFirst({ - where: { - name: instance.instanceName, - }, - }) - .then((instance) => instance.id); - - const dify = await this.prismaRepository.dify.findFirst({ - where: { - id: difyId, - }, - }); - - if (!dify) { - throw new Error('Dify not found'); - } - - if (dify.instanceId !== instanceId) { - throw new Error('Dify not found'); - } - try { - await this.prismaRepository.integrationSession.deleteMany({ - where: { - difyId: difyId, - }, - }); - - await this.prismaRepository.dify.delete({ - where: { - id: difyId, - }, - }); - - return { dify: { id: difyId } }; - } catch (error) { - this.logger.error(error); - throw new Error('Error deleting openai bot'); - } - } - - public async setDefaultSettings(instance: InstanceDto, data: DifySettingDto) { - try { - const instanceId = await this.prismaRepository.instance - .findFirst({ - where: { - name: instance.instanceName, - }, - }) - .then((instance) => instance.id); - - const settings = await this.prismaRepository.difySetting.findFirst({ - where: { - instanceId: instanceId, - }, - }); - - if (settings) { - const updateSettings = await this.prismaRepository.difySetting.update({ - where: { - id: settings.id, - }, - data: { - expire: data.expire, - keywordFinish: data.keywordFinish, - delayMessage: data.delayMessage, - unknownMessage: data.unknownMessage, - listeningFromMe: data.listeningFromMe, - stopBotFromMe: data.stopBotFromMe, - keepOpen: data.keepOpen, - debounceTime: data.debounceTime, - difyIdFallback: data.difyIdFallback, - ignoreJids: data.ignoreJids, - }, - }); - - return { - expire: updateSettings.expire, - keywordFinish: updateSettings.keywordFinish, - delayMessage: updateSettings.delayMessage, - unknownMessage: updateSettings.unknownMessage, - listeningFromMe: updateSettings.listeningFromMe, - stopBotFromMe: updateSettings.stopBotFromMe, - keepOpen: updateSettings.keepOpen, - debounceTime: updateSettings.debounceTime, - difyIdFallback: updateSettings.difyIdFallback, - ignoreJids: updateSettings.ignoreJids, - }; - } - - const newSetttings = await this.prismaRepository.difySetting.create({ - data: { - expire: data.expire, - keywordFinish: data.keywordFinish, - delayMessage: data.delayMessage, - unknownMessage: data.unknownMessage, - listeningFromMe: data.listeningFromMe, - stopBotFromMe: data.stopBotFromMe, - keepOpen: data.keepOpen, - debounceTime: data.debounceTime, - difyIdFallback: data.difyIdFallback, - ignoreJids: data.ignoreJids, - instanceId: instanceId, - }, - }); - - return { - expire: newSetttings.expire, - keywordFinish: newSetttings.keywordFinish, - delayMessage: newSetttings.delayMessage, - unknownMessage: newSetttings.unknownMessage, - listeningFromMe: newSetttings.listeningFromMe, - stopBotFromMe: newSetttings.stopBotFromMe, - keepOpen: newSetttings.keepOpen, - debounceTime: newSetttings.debounceTime, - difyIdFallback: newSetttings.difyIdFallback, - ignoreJids: newSetttings.ignoreJids, - }; - } catch (error) { - this.logger.error(error); - throw new Error('Error setting default settings'); - } - } - - public async fetchDefaultSettings(instance: InstanceDto) { - try { - const instanceId = await this.prismaRepository.instance - .findFirst({ - where: { - name: instance.instanceName, - }, - }) - .then((instance) => instance.id); - - const settings = await this.prismaRepository.difySetting.findFirst({ - where: { - instanceId: instanceId, - }, - include: { - Fallback: true, - }, - }); - - if (!settings) { - return { - expire: 0, - keywordFinish: '', - delayMessage: 0, - unknownMessage: '', - listeningFromMe: false, - stopBotFromMe: false, - keepOpen: false, - ignoreJids: [], - difyIdFallback: '', - fallback: null, - }; - } - - return { - expire: settings.expire, - keywordFinish: settings.keywordFinish, - delayMessage: settings.delayMessage, - unknownMessage: settings.unknownMessage, - listeningFromMe: settings.listeningFromMe, - stopBotFromMe: settings.stopBotFromMe, - keepOpen: settings.keepOpen, - ignoreJids: settings.ignoreJids, - difyIdFallback: settings.difyIdFallback, - fallback: settings.Fallback, - }; - } catch (error) { - this.logger.error(error); - throw new Error('Error fetching default settings'); - } - } - - public async ignoreJid(instance: InstanceDto, data: DifyIgnoreJidDto) { - try { - const instanceId = await this.prismaRepository.instance - .findFirst({ - where: { - name: instance.instanceName, - }, - }) - .then((instance) => instance.id); - - const settings = await this.prismaRepository.difySetting.findFirst({ - where: { - instanceId: instanceId, - }, - }); - - if (!settings) { - throw new Error('Settings not found'); - } - - let ignoreJids: any = settings?.ignoreJids || []; - - if (data.action === 'add') { - if (ignoreJids.includes(data.remoteJid)) return { ignoreJids: ignoreJids }; - - ignoreJids.push(data.remoteJid); - } else { - ignoreJids = ignoreJids.filter((jid) => jid !== data.remoteJid); - } - - const updateSettings = await this.prismaRepository.difySetting.update({ - where: { - id: settings.id, - }, - data: { - ignoreJids: ignoreJids, - }, - }); - - return { - ignoreJids: updateSettings.ignoreJids, - }; - } catch (error) { - this.logger.error(error); - throw new Error('Error setting default settings'); - } - } - - public async fetchSessions(instance: InstanceDto, difyId?: string, remoteJid?: string) { - try { - const instanceId = await this.prismaRepository.instance - .findFirst({ - where: { - name: instance.instanceName, - }, - }) - .then((instance) => instance.id); - - const dify = await this.prismaRepository.dify.findFirst({ - where: { - id: difyId, - }, - }); - - if (dify && dify.instanceId !== instanceId) { - throw new Error('Dify not found'); - } - - return await this.prismaRepository.integrationSession.findMany({ - where: { - instanceId: instanceId, - remoteJid, - difyId: dify ? difyId : { not: null }, - }, - include: { - DifyBot: true, - }, - }); - } catch (error) { - this.logger.error(error); - throw new Error('Error fetching sessions'); - } - } - - public async changeStatus(instance: InstanceDto, data: any) { - try { - const instanceId = await this.prismaRepository.instance - .findFirst({ - where: { - name: instance.instanceName, - }, - }) - .then((instance) => instance.id); - - const defaultSettingCheck = await this.prismaRepository.difySetting.findFirst({ - where: { - instanceId, - }, - }); - - const remoteJid = data.remoteJid; - const status = data.status; - - if (status === 'delete') { - await this.prismaRepository.integrationSession.deleteMany({ - where: { - remoteJid: remoteJid, - difyId: { not: null }, - }, - }); - - return { dify: { remoteJid: remoteJid, status: status } }; - } - - if (status === 'closed') { - if (defaultSettingCheck?.keepOpen) { - await this.prismaRepository.integrationSession.updateMany({ - where: { - remoteJid: remoteJid, - difyId: { not: null }, - }, - data: { - status: 'closed', - }, - }); - } else { - await this.prismaRepository.integrationSession.deleteMany({ - where: { - remoteJid: remoteJid, - difyId: { not: null }, - }, - }); - } - - return { dify: { ...instance, dify: { remoteJid: remoteJid, status: status } } }; - } else { - const session = await this.prismaRepository.integrationSession.updateMany({ - where: { - instanceId: instanceId, - remoteJid: remoteJid, - difyId: { not: null }, - }, - data: { - status: status, - }, - }); - - const difyData = { - remoteJid: remoteJid, - status: status, - session, - }; - - return { dify: { ...instance, dify: difyData } }; - } - } catch (error) { - this.logger.error(error); - throw new Error('Error changing status'); - } - } - - private getTypeMessage(msg: any) { - let mediaId: string; - - if (this.configService.get('S3').ENABLE) mediaId = msg.message.mediaUrl; - else mediaId = msg.key.id; - - const types = { - conversation: msg?.message?.conversation, - extendedTextMessage: msg?.message?.extendedTextMessage?.text, - contactMessage: msg?.message?.contactMessage?.displayName, - locationMessage: msg?.message?.locationMessage?.degreesLatitude, - viewOnceMessageV2: - msg?.message?.viewOnceMessageV2?.message?.imageMessage?.url || - msg?.message?.viewOnceMessageV2?.message?.videoMessage?.url || - msg?.message?.viewOnceMessageV2?.message?.audioMessage?.url, - listResponseMessage: msg?.message?.listResponseMessage?.singleSelectReply?.selectedRowId, - responseRowId: msg?.message?.listResponseMessage?.singleSelectReply?.selectedRowId, - // Medias - audioMessage: msg?.message?.speechToText - ? msg?.message?.speechToText - : msg?.message?.audioMessage - ? `audioMessage|${mediaId}` - : undefined, - imageMessage: msg?.message?.imageMessage - ? `imageMessage|${mediaId}${ - msg?.message?.imageMessage?.caption ? `|${msg?.message?.imageMessage?.caption}` : '' - }` - : undefined, - videoMessage: msg?.message?.videoMessage - ? `videoMessage|${mediaId}${ - msg?.message?.videoMessage?.caption ? `|${msg?.message?.videoMessage?.caption}` : '' - }` - : undefined, - documentMessage: msg?.message?.documentMessage - ? `documentMessage|${mediaId}${ - msg?.message?.documentMessage?.caption ? `|${msg?.message?.documentMessage?.caption}` : '' - }` - : undefined, - documentWithCaptionMessage: msg?.message?.documentWithCaptionMessage?.message?.documentMessage - ? `documentWithCaptionMessage|${mediaId}${ - msg?.message?.documentWithCaptionMessage?.message?.documentMessage?.caption - ? `|${msg?.message?.documentWithCaptionMessage?.message?.documentMessage?.caption}` - : '' - }` - : undefined, - }; - - const messageType = Object.keys(types).find((key) => types[key] !== undefined) || 'unknown'; - - return { ...types, messageType }; - } - - private getMessageContent(types: any) { - const typeKey = Object.keys(types).find((key) => types[key] !== undefined); - - const result = typeKey ? types[typeKey] : undefined; - - return result; - } - - private getConversationMessage(msg: any) { - const types = this.getTypeMessage(msg); - - const messageContent = this.getMessageContent(types); - - return messageContent; - } - - public async findDifyByTrigger(content: string, instanceId: string) { - // Check for triggerType 'all' - const findTriggerAll = await this.prismaRepository.dify.findFirst({ - where: { - enabled: true, - triggerType: 'all', - instanceId: instanceId, - }, - }); - - if (findTriggerAll) return findTriggerAll; - - const findTriggerAdvanced = await this.prismaRepository.dify.findMany({ - where: { - enabled: true, - triggerType: 'advanced', - instanceId: instanceId, - }, - }); - for (const advanced of findTriggerAdvanced) { - if (advancedOperatorsSearch(content, advanced.triggerValue)) { - return advanced; - } - } - - // Check for exact match - const findTriggerEquals = await this.prismaRepository.dify.findFirst({ - where: { - enabled: true, - triggerType: 'keyword', - triggerOperator: 'equals', - triggerValue: content, - instanceId: instanceId, - }, - }); - - if (findTriggerEquals) return findTriggerEquals; - - // Check for regex match - const findRegex = await this.prismaRepository.dify.findMany({ - where: { - enabled: true, - triggerType: 'keyword', - triggerOperator: 'regex', - instanceId: instanceId, - }, - }); - - let findTriggerRegex = null; - - for (const regex of findRegex) { - const regexValue = new RegExp(regex.triggerValue); - - if (regexValue.test(content)) { - findTriggerRegex = regex; - break; - } - } - - if (findTriggerRegex) return findTriggerRegex; - - // Check for startsWith match - const findStartsWith = await this.prismaRepository.dify.findMany({ - where: { - enabled: true, - triggerType: 'keyword', - triggerOperator: 'startsWith', - instanceId: instanceId, - }, - }); - - let findTriggerStartsWith = null; - - for (const startsWith of findStartsWith) { - if (content.startsWith(startsWith.triggerValue)) { - findTriggerStartsWith = startsWith; - break; - } - } - - if (findTriggerStartsWith) return findTriggerStartsWith; - - // Check for endsWith match - const findEndsWith = await this.prismaRepository.dify.findMany({ - where: { - enabled: true, - triggerType: 'keyword', - triggerOperator: 'endsWith', - instanceId: instanceId, - }, - }); - - let findTriggerEndsWith = null; - - for (const endsWith of findEndsWith) { - if (content.endsWith(endsWith.triggerValue)) { - findTriggerEndsWith = endsWith; - break; - } - } - - if (findTriggerEndsWith) return findTriggerEndsWith; - - // Check for contains match - const findContains = await this.prismaRepository.dify.findMany({ - where: { - enabled: true, - triggerType: 'keyword', - triggerOperator: 'contains', - instanceId: instanceId, - }, - }); - - let findTriggerContains = null; - - for (const contains of findContains) { - if (content.includes(contains.triggerValue)) { - findTriggerContains = contains; - break; - } - } - - if (findTriggerContains) return findTriggerContains; - - const fallback = await this.prismaRepository.difySetting.findFirst({ - where: { - instanceId: instanceId, - }, - }); - - if (fallback?.difyIdFallback) { - const findFallback = await this.prismaRepository.dify.findFirst({ - where: { - id: fallback.difyIdFallback, - }, - }); - - if (findFallback) return findFallback; - } - - return null; - } - - private processDebounce(content: string, remoteJid: string, debounceTime: number, callback: any) { - if (this.userMessageDebounce[remoteJid]) { - this.userMessageDebounce[remoteJid].message += ` ${content}`; - this.logger.log('message debounced: ' + this.userMessageDebounce[remoteJid].message); - clearTimeout(this.userMessageDebounce[remoteJid].timeoutId); - } else { - this.userMessageDebounce[remoteJid] = { - message: content, - timeoutId: null, - }; - } - - this.userMessageDebounce[remoteJid].timeoutId = setTimeout(() => { - const myQuestion = this.userMessageDebounce[remoteJid].message; - this.logger.log('Debounce complete. Processing message: ' + myQuestion); - - delete this.userMessageDebounce[remoteJid]; - callback(myQuestion); - }, debounceTime * 1000); - } - - public async sendDify(instance: InstanceDto, remoteJid: string, msg: Message) { - try { - const settings = await this.prismaRepository.difySetting.findFirst({ - where: { - instanceId: instance.instanceId, - }, - }); - - if (settings?.ignoreJids) { - const ignoreJids: any = settings.ignoreJids; - - let ignoreGroups = false; - let ignoreContacts = false; - - if (ignoreJids.includes('@g.us')) { - ignoreGroups = true; - } - - if (ignoreJids.includes('@s.whatsapp.net')) { - ignoreContacts = true; - } - - if (ignoreGroups && remoteJid.endsWith('@g.us')) { - this.logger.warn('Ignoring message from group: ' + remoteJid); - return; - } - - if (ignoreContacts && remoteJid.endsWith('@s.whatsapp.net')) { - this.logger.warn('Ignoring message from contact: ' + remoteJid); - return; - } - - if (ignoreJids.includes(remoteJid)) { - this.logger.warn('Ignoring message from jid: ' + remoteJid); - return; - } - } - - let session = await this.prismaRepository.integrationSession.findFirst({ - where: { - remoteJid: remoteJid, - instanceId: instance.instanceId, - }, - orderBy: { createdAt: 'desc' }, - }); - - if (session) { - if (session.status !== 'closed' && !session.difyId) { - this.logger.warn('Session is already opened in another integration'); - return; - } else if (!session.difyId) { - session = null; - } - } - - const content = this.getConversationMessage(msg); - - let findDify = null; - - if (!session) { - findDify = await this.findDifyByTrigger(content, instance.instanceId); - - if (!findDify) { - return; - } - } else { - findDify = await this.prismaRepository.dify.findFirst({ - where: { - id: session.difyId, - }, - }); - } - - if (!findDify) return; - - let expire = findDify.expire; - let keywordFinish = findDify.keywordFinish; - let delayMessage = findDify.delayMessage; - let unknownMessage = findDify.unknownMessage; - let listeningFromMe = findDify.listeningFromMe; - let stopBotFromMe = findDify.stopBotFromMe; - let keepOpen = findDify.keepOpen; - let debounceTime = findDify.debounceTime; - - if ( - !expire || - !keywordFinish || - !delayMessage || - !unknownMessage || - !listeningFromMe || - !stopBotFromMe || - !keepOpen || - !debounceTime - ) { - if (!expire) expire = settings.expire; - - if (!keywordFinish) keywordFinish = settings.keywordFinish; - - if (!delayMessage) delayMessage = settings.delayMessage; - - if (!unknownMessage) unknownMessage = settings.unknownMessage; - - if (!listeningFromMe) listeningFromMe = settings.listeningFromMe; - - if (!stopBotFromMe) stopBotFromMe = settings.stopBotFromMe; - - if (!keepOpen) keepOpen = settings.keepOpen; - - if (!debounceTime) debounceTime = settings.debounceTime; - } - - const key = msg.key as { - id: string; - remoteJid: string; - fromMe: boolean; - participant: string; - }; - - if (stopBotFromMe && key.fromMe && session) { - await this.prismaRepository.integrationSession.update({ - where: { - id: session.id, - }, - data: { - status: 'paused', - }, - }); - return; - } - - if (!listeningFromMe && key.fromMe) { - return; - } - - if (debounceTime && debounceTime > 0) { - this.processDebounce(content, remoteJid, debounceTime, async (debouncedContent) => { - await this.processDify( - this.waMonitor.waInstances[instance.instanceName], - remoteJid, - findDify, - session, - settings, - debouncedContent, - msg?.pushName, - ); - }); - } else { - await this.processDify( - this.waMonitor.waInstances[instance.instanceName], - remoteJid, - findDify, - session, - settings, - content, - msg?.pushName, - ); - } - - return; - } catch (error) { - this.logger.error(error); - return; - } - } - public async createNewSession(instance: InstanceDto, data: any) { try { const session = await this.prismaRepository.integrationSession.create({ @@ -1086,7 +25,7 @@ export class DifyService { sessionId: data.remoteJid, status: 'opened', awaitUser: false, - difyId: data.difyId, + botId: data.difyId, instanceId: instance.instanceId, }, }); @@ -1575,7 +514,7 @@ export class DifyService { return; } - private async processDify( + public async processDify( instance: any, remoteJid: string, dify: Dify, @@ -1610,7 +549,7 @@ export class DifyService { } else { await this.prismaRepository.integrationSession.deleteMany({ where: { - difyId: dify.id, + botId: dify.id, remoteJid: remoteJid, }, }); @@ -1665,7 +604,7 @@ export class DifyService { } else { await this.prismaRepository.integrationSession.deleteMany({ where: { - difyId: dify.id, + botId: dify.id, remoteJid: remoteJid, }, }); diff --git a/src/api/integrations/chatbot/openai/controllers/openai.controller.ts b/src/api/integrations/chatbot/openai/controllers/openai.controller.ts index c7cd2315..6e54618a 100644 --- a/src/api/integrations/chatbot/openai/controllers/openai.controller.ts +++ b/src/api/integrations/chatbot/openai/controllers/openai.controller.ts @@ -10,9 +10,9 @@ import { BadRequestException } from '@exceptions'; import { getConversationMessage } from '@utils/getConversationMessage'; import OpenAI from 'openai'; -import { ChatbotController } from '../../chatbot.controller'; +import { ChatbotController, ChatbotControllerInterface, EmitData } from '../../chatbot.controller'; -export class OpenaiController extends ChatbotController { +export class OpenaiController extends ChatbotController implements ChatbotControllerInterface { constructor( private readonly openaiService: OpenaiService, prismaRepository: PrismaRepository, @@ -28,13 +28,13 @@ export class OpenaiController extends ChatbotController { public readonly logger = new Logger(OpenaiController.name); - private integrationEnabled = configService.get('OPENAI').ENABLED; + integrationEnabled = configService.get('OPENAI').ENABLED; + botRepository: any; + settingsRepository: any; + sessionRepository: any; + userMessageDebounce: { [key: string]: { message: string; timeoutId: NodeJS.Timeout } } = {}; private client: OpenAI; - private botRepository: any; - private settingsRepository: any; - private sessionRepository: any; private credsRepository: any; - private userMessageDebounce: { [key: string]: { message: string; timeoutId: NodeJS.Timeout } } = {}; // Credentials public async createOpenaiCreds(instance: InstanceDto, data: OpenaiCredsDto) { @@ -311,7 +311,7 @@ export class OpenaiController extends ChatbotController { } try { - const openaiBot = await this.botRepository.create({ + const bot = await this.botRepository.create({ data: { enabled: data.enabled, description: data.description, @@ -340,7 +340,7 @@ export class OpenaiController extends ChatbotController { }, }); - return openaiBot; + return bot; } catch (error) { this.logger.error(error); throw new Error('Error creating openai bot'); @@ -358,7 +358,7 @@ export class OpenaiController extends ChatbotController { }) .then((instance) => instance.id); - const openaiBots = await this.botRepository.findMany({ + const bots = await this.botRepository.findMany({ where: { instanceId, }, @@ -367,11 +367,11 @@ export class OpenaiController extends ChatbotController { }, }); - if (!openaiBots.length) { + if (!bots.length) { return null; } - return openaiBots; + return bots; } public async fetchBot(instance: InstanceDto, botId: string) { @@ -385,7 +385,7 @@ export class OpenaiController extends ChatbotController { }) .then((instance) => instance.id); - const openaiBot = await this.botRepository.findFirst({ + const bot = await this.botRepository.findFirst({ where: { id: botId, }, @@ -394,15 +394,15 @@ export class OpenaiController extends ChatbotController { }, }); - if (!openaiBot) { + if (!bot) { throw new Error('Openai Bot not found'); } - if (openaiBot.instanceId !== instanceId) { + if (bot.instanceId !== instanceId) { throw new Error('Openai Bot not found'); } - return openaiBot; + return bot; } public async updateBot(instance: InstanceDto, botId: string, data: OpenaiDto) { @@ -416,17 +416,17 @@ export class OpenaiController extends ChatbotController { }) .then((instance) => instance.id); - const openaiBot = await this.botRepository.findFirst({ + const bot = await this.botRepository.findFirst({ where: { id: botId, }, }); - if (!openaiBot) { + if (!bot) { throw new Error('Openai Bot not found'); } - if (openaiBot.instanceId !== instanceId) { + if (bot.instanceId !== instanceId) { throw new Error('Openai Bot not found'); } @@ -522,7 +522,7 @@ export class OpenaiController extends ChatbotController { } try { - const openaiBot = await this.botRepository.update({ + const bot = await this.botRepository.update({ where: { id: botId, }, @@ -553,7 +553,7 @@ export class OpenaiController extends ChatbotController { }, }); - return openaiBot; + return bot; } catch (error) { this.logger.error(error); throw new Error('Error updating openai bot'); @@ -571,17 +571,17 @@ export class OpenaiController extends ChatbotController { }) .then((instance) => instance.id); - const openaiBot = await this.botRepository.findFirst({ + const bot = await this.botRepository.findFirst({ where: { id: botId, }, }); - if (!openaiBot) { + if (!bot) { throw new Error('Openai bot not found'); } - if (openaiBot.instanceId !== instanceId) { + if (bot.instanceId !== instanceId) { throw new Error('Openai bot not found'); } try { @@ -597,7 +597,7 @@ export class OpenaiController extends ChatbotController { }, }); - return { openaiBot: { id: botId } }; + return { bot: { id: botId } }; } catch (error) { this.logger.error(error); throw new Error('Error deleting openai bot'); @@ -925,17 +925,7 @@ export class OpenaiController extends ChatbotController { } // Emit - public async emit({ - instance, - remoteJid, - msg, - pushName, - }: { - instance: InstanceDto; - remoteJid: string; - msg: any; - pushName?: string; - }) { + public async emit({ instance, remoteJid, msg, pushName }: EmitData) { if (!this.integrationEnabled) return; try { diff --git a/src/api/integrations/chatbot/typebot/controllers/typebot.controller.ts b/src/api/integrations/chatbot/typebot/controllers/typebot.controller.ts index 79827cfc..01db49dd 100644 --- a/src/api/integrations/chatbot/typebot/controllers/typebot.controller.ts +++ b/src/api/integrations/chatbot/typebot/controllers/typebot.controller.ts @@ -1,76 +1,971 @@ +import { IgnoreJidDto } from '@api/dto/chatbot.dto'; import { InstanceDto } from '@api/dto/instance.dto'; -import { TypebotDto, TypebotIgnoreJidDto } from '@api/integrations/chatbot/typebot/dto/typebot.dto'; +import { TypebotDto } from '@api/integrations/chatbot/typebot/dto/typebot.dto'; import { TypebotService } from '@api/integrations/chatbot/typebot/services/typebot.service'; -import { configService, Typebot } from '@config/env.config'; +import { PrismaRepository } from '@api/repository/repository.service'; +import { WAMonitoringService } from '@api/services/monitor.service'; +import { Events } from '@api/types/wa.types'; +import { Auth, configService, HttpServer, Typebot } from '@config/env.config'; +import { Logger } from '@config/logger.config'; import { BadRequestException } from '@exceptions'; +import { getConversationMessage } from '@utils/getConversationMessage'; +import axios from 'axios'; -export class TypebotController { - constructor(private readonly typebotService: TypebotService) {} +import { ChatbotController, ChatbotControllerInterface } from '../../chatbot.controller'; - public async createTypebot(instance: InstanceDto, data: TypebotDto) { - if (!configService.get('TYPEBOT').ENABLED) throw new BadRequestException('Typebot is disabled'); +export class TypebotController extends ChatbotController implements ChatbotControllerInterface { + constructor( + private readonly typebotService: TypebotService, + prismaRepository: PrismaRepository, + waMonitor: WAMonitoringService, + ) { + super(prismaRepository, waMonitor); - return this.typebotService.create(instance, data); + this.botRepository = this.prismaRepository.typebot; + this.settingsRepository = this.prismaRepository.typebotSetting; + this.sessionRepository = this.prismaRepository.integrationSession; } - public async findTypebot(instance: InstanceDto) { - if (!configService.get('TYPEBOT').ENABLED) throw new BadRequestException('Typebot is disabled'); + public readonly logger = new Logger(TypebotController.name); - return this.typebotService.find(instance); + integrationEnabled = configService.get('TYPEBOT').ENABLED; + botRepository: any; + settingsRepository: any; + sessionRepository: any; + userMessageDebounce: { [key: string]: { message: string; timeoutId: NodeJS.Timeout } } = {}; + + // Bots + public async createBot(instance: InstanceDto, data: TypebotDto) { + if (!this.integrationEnabled) throw new BadRequestException('Typebot is disabled'); + + const instanceId = await this.prismaRepository.instance + .findFirst({ + where: { + name: instance.instanceName, + }, + }) + .then((instance) => instance.id); + + if ( + !data.expire || + !data.keywordFinish || + !data.delayMessage || + !data.unknownMessage || + !data.listeningFromMe || + !data.stopBotFromMe || + !data.keepOpen || + !data.debounceTime || + !data.ignoreJids + ) { + const defaultSettingCheck = await this.settingsRepository.findFirst({ + where: { + instanceId: instanceId, + }, + }); + + if (!data.expire) data.expire = defaultSettingCheck?.expire || 0; + if (!data.keywordFinish) data.keywordFinish = defaultSettingCheck?.keywordFinish || '#SAIR'; + if (!data.delayMessage) data.delayMessage = defaultSettingCheck?.delayMessage || 1000; + if (!data.unknownMessage) data.unknownMessage = defaultSettingCheck?.unknownMessage || 'Desculpe, não entendi'; + if (!data.listeningFromMe) data.listeningFromMe = defaultSettingCheck?.listeningFromMe || false; + if (!data.stopBotFromMe) data.stopBotFromMe = defaultSettingCheck?.stopBotFromMe || false; + if (!data.keepOpen) data.keepOpen = defaultSettingCheck?.keepOpen || false; + if (!data.debounceTime) data.debounceTime = defaultSettingCheck?.debounceTime || 0; + if (!data.ignoreJids) data.ignoreJids = defaultSettingCheck?.ignoreJids || []; + + if (!defaultSettingCheck) { + await this.settings(instance, { + expire: data.expire, + keywordFinish: data.keywordFinish, + delayMessage: data.delayMessage, + unknownMessage: data.unknownMessage, + listeningFromMe: data.listeningFromMe, + stopBotFromMe: data.stopBotFromMe, + keepOpen: data.keepOpen, + debounceTime: data.debounceTime, + ignoreJids: data.ignoreJids, + }); + } + } + + const checkTriggerAll = await this.botRepository.findFirst({ + where: { + enabled: true, + triggerType: 'all', + instanceId: instanceId, + }, + }); + + if (checkTriggerAll && data.triggerType === 'all') { + throw new Error('You already have a typebot with an "All" trigger, you cannot have more bots while it is active'); + } + + const checkDuplicate = await this.botRepository.findFirst({ + where: { + url: data.url, + typebot: data.typebot, + instanceId: instanceId, + }, + }); + + if (checkDuplicate) { + throw new Error('Typebot already exists'); + } + + if (data.triggerType === 'keyword') { + if (!data.triggerOperator || !data.triggerValue) { + throw new Error('Trigger operator and value are required'); + } + + const checkDuplicate = await this.botRepository.findFirst({ + where: { + triggerOperator: data.triggerOperator, + triggerValue: data.triggerValue, + instanceId: instanceId, + }, + }); + + if (checkDuplicate) { + throw new Error('Trigger already exists'); + } + } + + if (data.triggerType === 'advanced') { + if (!data.triggerValue) { + throw new Error('Trigger value is required'); + } + + const checkDuplicate = await this.botRepository.findFirst({ + where: { + triggerValue: data.triggerValue, + instanceId: instanceId, + }, + }); + + if (checkDuplicate) { + throw new Error('Trigger already exists'); + } + } + + try { + const bot = await this.botRepository.create({ + data: { + enabled: data.enabled, + description: data.description, + url: data.url, + typebot: data.typebot, + expire: data.expire, + keywordFinish: data.keywordFinish, + delayMessage: data.delayMessage, + unknownMessage: data.unknownMessage, + listeningFromMe: data.listeningFromMe, + stopBotFromMe: data.stopBotFromMe, + keepOpen: data.keepOpen, + debounceTime: data.debounceTime, + instanceId: instanceId, + triggerType: data.triggerType, + triggerOperator: data.triggerOperator, + triggerValue: data.triggerValue, + ignoreJids: data.ignoreJids, + }, + }); + + return bot; + } catch (error) { + this.logger.error(error); + throw new Error('Error creating typebot'); + } } - public async fetchTypebot(instance: InstanceDto, typebotId: string) { - if (!configService.get('TYPEBOT').ENABLED) throw new BadRequestException('Typebot is disabled'); + public async findBot(instance: InstanceDto) { + if (!this.integrationEnabled) throw new BadRequestException('Typebot is disabled'); - return this.typebotService.fetch(instance, typebotId); + const instanceId = await this.prismaRepository.instance + .findFirst({ + where: { + name: instance.instanceName, + }, + }) + .then((instance) => instance.id); + + const bots = await this.botRepository.findMany({ + where: { + instanceId: instanceId, + }, + }); + + if (!bots.length) { + return null; + } + + return bots; } - public async updateTypebot(instance: InstanceDto, typebotId: string, data: TypebotDto) { - if (!configService.get('TYPEBOT').ENABLED) throw new BadRequestException('Typebot is disabled'); + public async fetchBot(instance: InstanceDto, botId: string) { + if (!this.integrationEnabled) throw new BadRequestException('Typebot is disabled'); - return this.typebotService.update(instance, typebotId, data); + const instanceId = await this.prismaRepository.instance + .findFirst({ + where: { + name: instance.instanceName, + }, + }) + .then((instance) => instance.id); + + const bot = await this.botRepository.findFirst({ + where: { + id: botId, + }, + }); + + if (!bot) { + throw new Error('Typebot not found'); + } + + if (bot.instanceId !== instanceId) { + throw new Error('Typebot not found'); + } + + return bot; } - public async deleteTypebot(instance: InstanceDto, typebotId: string) { - if (!configService.get('TYPEBOT').ENABLED) throw new BadRequestException('Typebot is disabled'); + public async updateBot(instance: InstanceDto, botId: string, data: TypebotDto) { + if (!this.integrationEnabled) throw new BadRequestException('Typebot is disabled'); - return this.typebotService.delete(instance, typebotId); + const instanceId = await this.prismaRepository.instance + .findFirst({ + where: { + name: instance.instanceName, + }, + }) + .then((instance) => instance.id); + + const typebot = await this.botRepository.findFirst({ + where: { + id: botId, + }, + }); + + if (!typebot) { + throw new Error('Typebot not found'); + } + + if (typebot.instanceId !== instanceId) { + throw new Error('Typebot not found'); + } + + if (data.triggerType === 'all') { + const checkTriggerAll = await this.botRepository.findFirst({ + where: { + enabled: true, + triggerType: 'all', + id: { + not: botId, + }, + instanceId: instanceId, + }, + }); + + if (checkTriggerAll) { + throw new Error( + 'You already have a typebot with an "All" trigger, you cannot have more bots while it is active', + ); + } + } + + const checkDuplicate = await this.botRepository.findFirst({ + where: { + url: data.url, + typebot: data.typebot, + id: { + not: botId, + }, + instanceId: instanceId, + }, + }); + + if (checkDuplicate) { + throw new Error('Typebot already exists'); + } + + if (data.triggerType === 'keyword') { + if (!data.triggerOperator || !data.triggerValue) { + throw new Error('Trigger operator and value are required'); + } + + const checkDuplicate = await this.botRepository.findFirst({ + where: { + triggerOperator: data.triggerOperator, + triggerValue: data.triggerValue, + id: { + not: botId, + }, + instanceId: instanceId, + }, + }); + + if (checkDuplicate) { + throw new Error('Trigger already exists'); + } + } + + if (data.triggerType === 'advanced') { + if (!data.triggerValue) { + throw new Error('Trigger value is required'); + } + + const checkDuplicate = await this.botRepository.findFirst({ + where: { + triggerValue: data.triggerValue, + id: { not: botId }, + instanceId: instanceId, + }, + }); + + if (checkDuplicate) { + throw new Error('Trigger already exists'); + } + } + + try { + const bot = await this.botRepository.update({ + where: { + id: botId, + }, + data: { + enabled: data.enabled, + url: data.url, + typebot: data.typebot, + expire: data.expire, + keywordFinish: data.keywordFinish, + delayMessage: data.delayMessage, + unknownMessage: data.unknownMessage, + listeningFromMe: data.listeningFromMe, + stopBotFromMe: data.stopBotFromMe, + keepOpen: data.keepOpen, + debounceTime: data.debounceTime, + triggerType: data.triggerType, + triggerOperator: data.triggerOperator, + triggerValue: data.triggerValue, + ignoreJids: data.ignoreJids, + }, + }); + + return bot; + } catch (error) { + this.logger.error(error); + throw new Error('Error updating typebot'); + } } - public async startTypebot(instance: InstanceDto, data: any) { - if (!configService.get('TYPEBOT').ENABLED) throw new BadRequestException('Typebot is disabled'); + public async deleteBot(instance: InstanceDto, botId: string) { + if (!this.integrationEnabled) throw new BadRequestException('Typebot is disabled'); - return this.typebotService.startTypebot(instance, data); + const instanceId = await this.prismaRepository.instance + .findFirst({ + where: { + name: instance.instanceName, + }, + }) + .then((instance) => instance.id); + + const typebot = await this.botRepository.findFirst({ + where: { + id: botId, + }, + }); + + if (!typebot) { + throw new Error('Typebot not found'); + } + + if (typebot.instanceId !== instanceId) { + throw new Error('Typebot not found'); + } + try { + await this.prismaRepository.integrationSession.deleteMany({ + where: { + botId: botId, + }, + }); + + await this.botRepository.delete({ + where: { + id: botId, + }, + }); + + return { typebot: { id: botId } }; + } catch (error) { + this.logger.error(error); + throw new Error('Error deleting typebot'); + } } + // Settings public async settings(instance: InstanceDto, data: any) { - if (!configService.get('TYPEBOT').ENABLED) throw new BadRequestException('Typebot is disabled'); + if (!this.integrationEnabled) throw new BadRequestException('Typebot is disabled'); - return this.typebotService.setDefaultSettings(instance, data); + try { + const instanceId = await this.prismaRepository.instance + .findFirst({ + where: { + name: instance.instanceName, + }, + }) + .then((instance) => instance.id); + + const settings = await this.settingsRepository.findFirst({ + where: { + instanceId: instanceId, + }, + }); + + if (settings) { + const updateSettings = await this.settingsRepository.update({ + where: { + id: settings.id, + }, + data: { + expire: data.expire, + keywordFinish: data.keywordFinish, + delayMessage: data.delayMessage, + unknownMessage: data.unknownMessage, + listeningFromMe: data.listeningFromMe, + stopBotFromMe: data.stopBotFromMe, + keepOpen: data.keepOpen, + debounceTime: data.debounceTime, + typebotIdFallback: data.typebotIdFallback, + ignoreJids: data.ignoreJids, + }, + }); + + return { + expire: updateSettings.expire, + keywordFinish: updateSettings.keywordFinish, + delayMessage: updateSettings.delayMessage, + unknownMessage: updateSettings.unknownMessage, + listeningFromMe: updateSettings.listeningFromMe, + stopBotFromMe: updateSettings.stopBotFromMe, + keepOpen: updateSettings.keepOpen, + debounceTime: updateSettings.debounceTime, + typebotIdFallback: updateSettings.typebotIdFallback, + ignoreJids: updateSettings.ignoreJids, + }; + } + + const newSetttings = await this.settingsRepository.create({ + data: { + expire: data.expire, + keywordFinish: data.keywordFinish, + delayMessage: data.delayMessage, + unknownMessage: data.unknownMessage, + listeningFromMe: data.listeningFromMe, + stopBotFromMe: data.stopBotFromMe, + keepOpen: data.keepOpen, + debounceTime: data.debounceTime, + typebotIdFallback: data.typebotIdFallback, + ignoreJids: data.ignoreJids, + instanceId: instanceId, + }, + }); + + return { + expire: newSetttings.expire, + keywordFinish: newSetttings.keywordFinish, + delayMessage: newSetttings.delayMessage, + unknownMessage: newSetttings.unknownMessage, + listeningFromMe: newSetttings.listeningFromMe, + stopBotFromMe: newSetttings.stopBotFromMe, + keepOpen: newSetttings.keepOpen, + debounceTime: newSetttings.debounceTime, + typebotIdFallback: newSetttings.typebotIdFallback, + ignoreJids: newSetttings.ignoreJids, + }; + } catch (error) { + this.logger.error(error); + throw new Error('Error setting default settings'); + } } public async fetchSettings(instance: InstanceDto) { - if (!configService.get('TYPEBOT').ENABLED) throw new BadRequestException('Typebot is disabled'); + if (!this.integrationEnabled) throw new BadRequestException('Typebot is disabled'); - return this.typebotService.fetchDefaultSettings(instance); + try { + const instanceId = await this.prismaRepository.instance + .findFirst({ + where: { + name: instance.instanceName, + }, + }) + .then((instance) => instance.id); + + const settings = await this.settingsRepository.findFirst({ + where: { + instanceId: instanceId, + }, + include: { + Fallback: true, + }, + }); + + if (!settings) { + return { + expire: 0, + keywordFinish: '', + delayMessage: 0, + unknownMessage: '', + listeningFromMe: false, + stopBotFromMe: false, + keepOpen: false, + ignoreJids: [], + typebotIdFallback: null, + fallback: null, + }; + } + + return { + expire: settings.expire, + keywordFinish: settings.keywordFinish, + delayMessage: settings.delayMessage, + unknownMessage: settings.unknownMessage, + listeningFromMe: settings.listeningFromMe, + stopBotFromMe: settings.stopBotFromMe, + keepOpen: settings.keepOpen, + ignoreJids: settings.ignoreJids, + typebotIdFallback: settings.typebotIdFallback, + fallback: settings.Fallback, + }; + } catch (error) { + this.logger.error(error); + throw new Error('Error fetching default settings'); + } + } + + // Sessions + public async startBot(instance: InstanceDto, data: any) { + if (!this.integrationEnabled) throw new BadRequestException('Typebot is disabled'); + + if (data.remoteJid === 'status@broadcast') return; + + const instanceData = await this.prismaRepository.instance.findFirst({ + where: { + name: instance.instanceName, + }, + }); + + if (!instanceData) throw new Error('Instance not found'); + + const remoteJid = data.remoteJid; + const url = data.url; + const typebot = data.typebot; + const startSession = data.startSession; + const variables = data.variables; + let expire = data?.typebot?.expire; + let keywordFinish = data?.typebot?.keywordFinish; + let delayMessage = data?.typebot?.delayMessage; + let unknownMessage = data?.typebot?.unknownMessage; + let listeningFromMe = data?.typebot?.listeningFromMe; + let stopBotFromMe = data?.typebot?.stopBotFromMe; + let keepOpen = data?.typebot?.keepOpen; + + const defaultSettingCheck = await this.settingsRepository.findFirst({ + where: { + instanceId: instanceData.id, + }, + }); + + if (defaultSettingCheck?.ignoreJids) { + const ignoreJids: any = defaultSettingCheck.ignoreJids; + + let ignoreGroups = false; + let ignoreContacts = false; + + if (ignoreJids.includes('@g.us')) { + ignoreGroups = true; + } + + if (ignoreJids.includes('@s.whatsapp.net')) { + ignoreContacts = true; + } + + if (ignoreGroups && remoteJid.includes('@g.us')) { + this.logger.warn('Ignoring message from group: ' + remoteJid); + throw new Error('Group not allowed'); + } + + if (ignoreContacts && remoteJid.includes('@s.whatsapp.net')) { + this.logger.warn('Ignoring message from contact: ' + remoteJid); + throw new Error('Contact not allowed'); + } + + if (ignoreJids.includes(remoteJid)) { + this.logger.warn('Ignoring message from jid: ' + remoteJid); + throw new Error('Jid not allowed'); + } + } + + if ( + !expire || + !keywordFinish || + !delayMessage || + !unknownMessage || + !listeningFromMe || + !stopBotFromMe || + !keepOpen + ) { + if (!expire) expire = defaultSettingCheck?.expire || 0; + if (!keywordFinish) keywordFinish = defaultSettingCheck?.keywordFinish || '#SAIR'; + if (!delayMessage) delayMessage = defaultSettingCheck?.delayMessage || 1000; + if (!unknownMessage) unknownMessage = defaultSettingCheck?.unknownMessage || 'Desculpe, não entendi'; + if (!listeningFromMe) listeningFromMe = defaultSettingCheck?.listeningFromMe || false; + if (!stopBotFromMe) stopBotFromMe = defaultSettingCheck?.stopBotFromMe || false; + if (!keepOpen) keepOpen = defaultSettingCheck?.keepOpen || false; + + if (!defaultSettingCheck) { + await this.settings(instance, { + expire: expire, + keywordFinish: keywordFinish, + delayMessage: delayMessage, + unknownMessage: unknownMessage, + listeningFromMe: listeningFromMe, + stopBotFromMe: stopBotFromMe, + keepOpen: keepOpen, + }); + } + } + + const prefilledVariables = { + remoteJid: remoteJid, + instanceName: instance.instanceName, + serverUrl: configService.get('SERVER').URL, + apiKey: configService.get('AUTHENTICATION').API_KEY.KEY, + ownerJid: instanceData.number, + }; + + if (variables?.length) { + variables.forEach((variable: { name: string | number; value: string }) => { + prefilledVariables[variable.name] = variable.value; + }); + } + + if (startSession) { + let findBot: any = await this.botRepository.findFirst({ + where: { + url: url, + typebot: typebot, + instanceId: instanceData.id, + }, + }); + + if (!findBot) { + findBot = await this.botRepository.create({ + data: { + enabled: true, + url: url, + typebot: typebot, + expire: expire, + triggerType: 'none', + keywordFinish: keywordFinish, + delayMessage: delayMessage, + unknownMessage: unknownMessage, + listeningFromMe: listeningFromMe, + stopBotFromMe: stopBotFromMe, + keepOpen: keepOpen, + instanceId: instanceData.id, + }, + }); + } + + await this.prismaRepository.integrationSession.deleteMany({ + where: { + remoteJid: remoteJid, + instanceId: instanceData.id, + botId: { not: null }, + }, + }); + + const response = await this.typebotService.createNewSession(instanceData, { + enabled: true, + url: url, + typebot: typebot, + remoteJid: remoteJid, + expire: expire, + keywordFinish: keywordFinish, + delayMessage: delayMessage, + unknownMessage: unknownMessage, + listeningFromMe: listeningFromMe, + stopBotFromMe: stopBotFromMe, + keepOpen: keepOpen, + prefilledVariables: prefilledVariables, + typebotId: findBot.id, + }); + + if (response.sessionId) { + await this.typebotService.sendWAMessage( + instanceData, + response.session, + { + expire: expire, + keywordFinish: keywordFinish, + delayMessage: delayMessage, + unknownMessage: unknownMessage, + listeningFromMe: listeningFromMe, + stopBotFromMe: stopBotFromMe, + keepOpen: keepOpen, + }, + remoteJid, + response.messages, + response.input, + response.clientSideActions, + ); + + this.waMonitor.waInstances[instance.instanceName].sendDataWebhook(Events.TYPEBOT_START, { + remoteJid: remoteJid, + url: url, + typebot: typebot, + prefilledVariables: prefilledVariables, + sessionId: `${response.sessionId}`, + }); + } else { + throw new Error('Session ID not found in response'); + } + } else { + const id = Math.floor(Math.random() * 10000000000).toString(); + + try { + const version = configService.get('TYPEBOT').API_VERSION; + let url: string; + let reqData: {}; + if (version === 'latest') { + url = `${data.url}/api/v1/typebots/${data.typebot}/startChat`; + + reqData = { + prefilledVariables: prefilledVariables, + }; + } else { + url = `${data.url}/api/v1/sendMessage`; + + reqData = { + startParams: { + publicId: data.typebot, + prefilledVariables: prefilledVariables, + }, + }; + } + const request = await axios.post(url, reqData); + + await this.typebotService.sendWAMessage( + instanceData, + null, + { + expire: expire, + keywordFinish: keywordFinish, + delayMessage: delayMessage, + unknownMessage: unknownMessage, + listeningFromMe: listeningFromMe, + stopBotFromMe: stopBotFromMe, + keepOpen: keepOpen, + }, + remoteJid, + request.data.messages, + request.data.input, + request.data.clientSideActions, + ); + + this.waMonitor.waInstances[instance.instanceName].sendDataWebhook(Events.TYPEBOT_START, { + remoteJid: remoteJid, + url: url, + typebot: typebot, + variables: variables, + sessionId: id, + }); + } catch (error) { + this.logger.error(error); + return; + } + } + + return { + typebot: { + ...instance, + typebot: { + url: url, + remoteJid: remoteJid, + typebot: typebot, + prefilledVariables: prefilledVariables, + }, + }, + }; } public async changeStatus(instance: InstanceDto, data: any) { - if (!configService.get('TYPEBOT').ENABLED) throw new BadRequestException('Typebot is disabled'); + if (!this.integrationEnabled) throw new BadRequestException('Typebot is disabled'); - return this.typebotService.changeStatus(instance, data); + try { + const instanceId = await this.prismaRepository.instance + .findFirst({ + where: { + name: instance.instanceName, + }, + }) + .then((instance) => instance.id); + + const remoteJid = data.remoteJid; + const status = data.status; + + const defaultSettingCheck = await this.settingsRepository.findFirst({ + where: { + instanceId, + }, + }); + + if (status === 'delete') { + await this.sessionRepository.deleteMany({ + where: { + remoteJid: remoteJid, + instanceId: instanceId, + botId: { not: null }, + }, + }); + + return { typebot: { ...instance, typebot: { remoteJid: remoteJid, status: status } } }; + } + + if (status === 'closed') { + if (defaultSettingCheck?.keepOpen) { + await this.sessionRepository.updateMany({ + where: { + instanceId: instanceId, + remoteJid: remoteJid, + botId: { not: null }, + }, + data: { + status: status, + }, + }); + } else { + await this.sessionRepository.deleteMany({ + where: { + remoteJid: remoteJid, + instanceId: instanceId, + botId: { not: null }, + }, + }); + } + + return { typebot: { ...instance, typebot: { remoteJid: remoteJid, status: status } } }; + } + + const session = await this.sessionRepository.updateMany({ + where: { + instanceId: instanceId, + remoteJid: remoteJid, + botId: { not: null }, + }, + data: { + status: status, + }, + }); + + const typebotData = { + remoteJid: remoteJid, + status: status, + session, + }; + + this.waMonitor.waInstances[instance.instanceName].sendDataWebhook(Events.TYPEBOT_CHANGE_STATUS, typebotData); + + return { typebot: { ...instance, typebot: typebotData } }; + } catch (error) { + this.logger.error(error); + throw new Error('Error changing status'); + } } - public async fetchSessions(instance: InstanceDto, typebotId: string) { - if (!configService.get('TYPEBOT').ENABLED) throw new BadRequestException('Typebot is disabled'); + public async fetchSessions(instance: InstanceDto, botId: string, remoteJid?: string) { + if (!this.integrationEnabled) throw new BadRequestException('Typebot is disabled'); - return this.typebotService.fetchSessions(instance, typebotId); + try { + const instanceId = await this.prismaRepository.instance + .findFirst({ + where: { + name: instance.instanceName, + }, + }) + .then((instance) => instance.id); + + const typebot = await this.botRepository.findFirst({ + where: { + id: botId, + }, + }); + + if (typebot && typebot.instanceId !== instanceId) { + throw new Error('Typebot not found'); + } + + return await this.sessionRepository.findMany({ + where: { + instanceId: instanceId, + remoteJid, + botId: botId ?? { not: null }, + }, + }); + } catch (error) { + this.logger.error(error); + throw new Error('Error fetching sessions'); + } } - public async ignoreJid(instance: InstanceDto, data: TypebotIgnoreJidDto) { - if (!configService.get('TYPEBOT').ENABLED) throw new BadRequestException('Typebot is disabled'); + public async ignoreJid(instance: InstanceDto, data: IgnoreJidDto) { + if (!this.integrationEnabled) throw new BadRequestException('Typebot is disabled'); - return this.typebotService.ignoreJid(instance, data); + try { + const instanceId = await this.prismaRepository.instance + .findFirst({ + where: { + name: instance.instanceName, + }, + }) + .then((instance) => instance.id); + + const settings = await this.settingsRepository.findFirst({ + where: { + instanceId: instanceId, + }, + }); + + if (!settings) { + throw new Error('Settings not found'); + } + + let ignoreJids: any = settings?.ignoreJids || []; + + if (data.action === 'add') { + if (ignoreJids.includes(data.remoteJid)) return { ignoreJids: ignoreJids }; + + ignoreJids.push(data.remoteJid); + } else { + ignoreJids = ignoreJids.filter((jid) => jid !== data.remoteJid); + } + + const updateSettings = await this.settingsRepository.update({ + where: { + id: settings.id, + }, + data: { + ignoreJids: ignoreJids, + }, + }); + + return { + ignoreJids: updateSettings.ignoreJids, + }; + } catch (error) { + this.logger.error(error); + throw new Error('Error setting default settings'); + } } public async emit({ @@ -83,8 +978,143 @@ export class TypebotController { msg: any; pushName?: string; }) { - if (!configService.get('TYPEBOT').ENABLED) return; + if (!this.integrationEnabled) return; - await this.typebotService.sendTypebot(instance, remoteJid, msg); + try { + const instanceData = await this.prismaRepository.instance.findFirst({ + where: { + name: instance.instanceName, + }, + }); + + if (!instanceData) throw new Error('Instance not found'); + + const settings = await this.prismaRepository.typebotSetting.findFirst({ + where: { + instanceId: instance.instanceId, + }, + }); + + if (this.checkIgnoreJids(settings?.ignoreJids, remoteJid)) return; + + const session = await this.getSession(remoteJid, instance); + + const content = getConversationMessage(msg); + + const findBot = await this.findBotTrigger( + this.botRepository, + this.settingsRepository, + content, + instance, + session, + ); + + if (!findBot) return; + + const url = findBot?.url; + const typebot = findBot?.typebot; + let expire = findBot?.expire; + let keywordFinish = findBot?.keywordFinish; + let delayMessage = findBot?.delayMessage; + let unknownMessage = findBot?.unknownMessage; + let listeningFromMe = findBot?.listeningFromMe; + let stopBotFromMe = findBot?.stopBotFromMe; + let keepOpen = findBot?.keepOpen; + let debounceTime = findBot?.debounceTime; + + if ( + !expire || + !keywordFinish || + !delayMessage || + !unknownMessage || + !listeningFromMe || + !stopBotFromMe || + !keepOpen + ) { + if (!expire) expire = settings.expire; + + if (!keywordFinish) keywordFinish = settings.keywordFinish; + + if (!delayMessage) delayMessage = settings.delayMessage; + + if (!unknownMessage) unknownMessage = settings.unknownMessage; + + if (!listeningFromMe) listeningFromMe = settings.listeningFromMe; + + if (!stopBotFromMe) stopBotFromMe = settings.stopBotFromMe; + + if (!keepOpen) keepOpen = settings.keepOpen; + + if (!debounceTime) debounceTime = settings.debounceTime; + } + + const key = msg.key as { + id: string; + remoteJid: string; + fromMe: boolean; + participant: string; + }; + + if (stopBotFromMe && key.fromMe && session) { + await this.sessionRepository.update({ + where: { + id: session.id, + }, + data: { + status: 'paused', + }, + }); + return; + } + + if (!listeningFromMe && key.fromMe) { + return; + } + + if (debounceTime && debounceTime > 0) { + this.processDebounce(this.userMessageDebounce, content, remoteJid, debounceTime, async (debouncedContent) => { + await this.typebotService.processTypebot( + instanceData, + remoteJid, + msg, + session, + findBot, + url, + expire, + typebot, + keywordFinish, + delayMessage, + unknownMessage, + listeningFromMe, + stopBotFromMe, + keepOpen, + debouncedContent, + ); + }); + } else { + await this.typebotService.processTypebot( + instanceData, + remoteJid, + msg, + session, + findBot, + url, + expire, + typebot, + keywordFinish, + delayMessage, + unknownMessage, + listeningFromMe, + stopBotFromMe, + keepOpen, + content, + ); + } + + if (session && !session.awaitUser) return; + } catch (error) { + this.logger.error(error); + return; + } } } diff --git a/src/api/integrations/chatbot/typebot/dto/typebot.dto.ts b/src/api/integrations/chatbot/typebot/dto/typebot.dto.ts index eedb8bc5..a7565236 100644 --- a/src/api/integrations/chatbot/typebot/dto/typebot.dto.ts +++ b/src/api/integrations/chatbot/typebot/dto/typebot.dto.ts @@ -1,14 +1,5 @@ import { TriggerOperator, TriggerType } from '@prisma/client'; -export class Session { - remoteJid?: string; - sessionId?: string; - status?: string; - createdAt?: number; - updateAt?: number; - prefilledVariables?: PrefilledVariables; -} - export class PrefilledVariables { remoteJid?: string; pushName?: string; @@ -47,8 +38,3 @@ export class TypebotSettingDto { typebotIdFallback?: string; ignoreJids?: any; } - -export class TypebotIgnoreJidDto { - remoteJid?: string; - action?: string; -} diff --git a/src/api/integrations/chatbot/typebot/routes/typebot.router.ts b/src/api/integrations/chatbot/typebot/routes/typebot.router.ts index 990cb88c..f556f94f 100644 --- a/src/api/integrations/chatbot/typebot/routes/typebot.router.ts +++ b/src/api/integrations/chatbot/typebot/routes/typebot.router.ts @@ -1,8 +1,9 @@ import { RouterBroker } from '@api/abstract/abstract.router'; +import { IgnoreJidDto } from '@api/dto/chatbot.dto'; import { InstanceDto } from '@api/dto/instance.dto'; -import { TypebotDto, TypebotIgnoreJidDto, TypebotSettingDto } from '@api/integrations/chatbot/typebot/dto/typebot.dto'; -import { typebotController } from '@api/server.module'; +import { TypebotDto, TypebotSettingDto } from '@api/integrations/chatbot/typebot/dto/typebot.dto'; import { HttpStatus } from '@api/routes/index.router'; +import { typebotController } from '@api/server.module'; import { instanceSchema, typebotIgnoreJidSchema, @@ -22,7 +23,7 @@ export class TypebotRouter extends RouterBroker { request: req, schema: typebotSchema, ClassRef: TypebotDto, - execute: (instance, data) => typebotController.createTypebot(instance, data), + execute: (instance, data) => typebotController.createBot(instance, data), }); res.status(HttpStatus.CREATED).json(response); @@ -32,7 +33,7 @@ export class TypebotRouter extends RouterBroker { request: req, schema: instanceSchema, ClassRef: InstanceDto, - execute: (instance) => typebotController.findTypebot(instance), + execute: (instance) => typebotController.findBot(instance), }); res.status(HttpStatus.OK).json(response); @@ -42,7 +43,7 @@ export class TypebotRouter extends RouterBroker { request: req, schema: instanceSchema, ClassRef: InstanceDto, - execute: (instance) => typebotController.fetchTypebot(instance, req.params.typebotId), + execute: (instance) => typebotController.fetchBot(instance, req.params.typebotId), }); res.status(HttpStatus.OK).json(response); @@ -52,7 +53,7 @@ export class TypebotRouter extends RouterBroker { request: req, schema: typebotSchema, ClassRef: TypebotDto, - execute: (instance, data) => typebotController.updateTypebot(instance, req.params.typebotId, data), + execute: (instance, data) => typebotController.updateBot(instance, req.params.typebotId, data), }); res.status(HttpStatus.OK).json(response); @@ -62,7 +63,7 @@ export class TypebotRouter extends RouterBroker { request: req, schema: instanceSchema, ClassRef: InstanceDto, - execute: (instance) => typebotController.deleteTypebot(instance, req.params.typebotId), + execute: (instance) => typebotController.deleteBot(instance, req.params.typebotId), }); res.status(HttpStatus.OK).json(response); @@ -92,7 +93,7 @@ export class TypebotRouter extends RouterBroker { request: req, schema: typebotStartSchema, ClassRef: InstanceDto, - execute: (instance, data) => typebotController.startTypebot(instance, data), + execute: (instance, data) => typebotController.startBot(instance, data), }); res.status(HttpStatus.OK).json(response); @@ -118,10 +119,10 @@ export class TypebotRouter extends RouterBroker { res.status(HttpStatus.OK).json(response); }) .post(this.routerPath('ignoreJid'), ...guards, async (req, res) => { - const response = await this.dataValidate({ + const response = await this.dataValidate({ request: req, schema: typebotIgnoreJidSchema, - ClassRef: TypebotIgnoreJidDto, + ClassRef: IgnoreJidDto, execute: (instance, data) => typebotController.ignoreJid(instance, data), }); diff --git a/src/api/integrations/chatbot/typebot/services/typebot.service.ts b/src/api/integrations/chatbot/typebot/services/typebot.service.ts index a1f6ca50..e425b5d0 100644 --- a/src/api/integrations/chatbot/typebot/services/typebot.service.ts +++ b/src/api/integrations/chatbot/typebot/services/typebot.service.ts @@ -1,12 +1,9 @@ -import { InstanceDto } from '@api/dto/instance.dto'; -import { TypebotDto, TypebotIgnoreJidDto } from '@api/integrations/chatbot/typebot/dto/typebot.dto'; import { PrismaRepository } from '@api/repository/repository.service'; import { WAMonitoringService } from '@api/services/monitor.service'; -import { Events } from '@api/types/wa.types'; -import { Auth, ConfigService, HttpServer, S3, Typebot } from '@config/env.config'; +import { Auth, ConfigService, HttpServer, Typebot } from '@config/env.config'; import { Logger } from '@config/logger.config'; import { Instance, IntegrationSession, Message, Typebot as TypebotModel } from '@prisma/client'; -import { advancedOperatorsSearch } from '@utils/advancedOperatorsSearch'; +import { getConversationMessage } from '@utils/getConversationMessage'; import { sendTelemetry } from '@utils/sendTelemetry'; import axios from 'axios'; @@ -17,980 +14,8 @@ export class TypebotService { private readonly prismaRepository: PrismaRepository, ) {} - private userMessageDebounce: { [key: string]: { message: string; timeoutId: NodeJS.Timeout } } = {}; - private readonly logger = new Logger('TypebotService'); - public async create(instance: InstanceDto, data: TypebotDto) { - const instanceId = await this.prismaRepository.instance - .findFirst({ - where: { - name: instance.instanceName, - }, - }) - .then((instance) => instance.id); - - if ( - !data.expire || - !data.keywordFinish || - !data.delayMessage || - !data.unknownMessage || - !data.listeningFromMe || - !data.stopBotFromMe || - !data.keepOpen || - !data.debounceTime || - !data.ignoreJids - ) { - const defaultSettingCheck = await this.prismaRepository.typebotSetting.findFirst({ - where: { - instanceId: instanceId, - }, - }); - - if (!data.expire) data.expire = defaultSettingCheck?.expire || 0; - if (!data.keywordFinish) data.keywordFinish = defaultSettingCheck?.keywordFinish || '#SAIR'; - if (!data.delayMessage) data.delayMessage = defaultSettingCheck?.delayMessage || 1000; - if (!data.unknownMessage) data.unknownMessage = defaultSettingCheck?.unknownMessage || 'Desculpe, não entendi'; - if (!data.listeningFromMe) data.listeningFromMe = defaultSettingCheck?.listeningFromMe || false; - if (!data.stopBotFromMe) data.stopBotFromMe = defaultSettingCheck?.stopBotFromMe || false; - if (!data.keepOpen) data.keepOpen = defaultSettingCheck?.keepOpen || false; - if (!data.debounceTime) data.debounceTime = defaultSettingCheck?.debounceTime || 0; - if (!data.ignoreJids) data.ignoreJids = defaultSettingCheck?.ignoreJids || []; - - if (!defaultSettingCheck) { - await this.setDefaultSettings(instance, { - expire: data.expire, - keywordFinish: data.keywordFinish, - delayMessage: data.delayMessage, - unknownMessage: data.unknownMessage, - listeningFromMe: data.listeningFromMe, - stopBotFromMe: data.stopBotFromMe, - keepOpen: data.keepOpen, - debounceTime: data.debounceTime, - ignoreJids: data.ignoreJids, - }); - } - } - - const checkTriggerAll = await this.prismaRepository.typebot.findFirst({ - where: { - enabled: true, - triggerType: 'all', - instanceId: instanceId, - }, - }); - - if (checkTriggerAll && data.triggerType === 'all') { - throw new Error('You already have a typebot with an "All" trigger, you cannot have more bots while it is active'); - } - - const checkDuplicate = await this.prismaRepository.typebot.findFirst({ - where: { - url: data.url, - typebot: data.typebot, - instanceId: instanceId, - }, - }); - - if (checkDuplicate) { - throw new Error('Typebot already exists'); - } - - if (data.triggerType === 'keyword') { - if (!data.triggerOperator || !data.triggerValue) { - throw new Error('Trigger operator and value are required'); - } - - const checkDuplicate = await this.prismaRepository.typebot.findFirst({ - where: { - triggerOperator: data.triggerOperator, - triggerValue: data.triggerValue, - instanceId: instanceId, - }, - }); - - if (checkDuplicate) { - throw new Error('Trigger already exists'); - } - } - - if (data.triggerType === 'advanced') { - if (!data.triggerValue) { - throw new Error('Trigger value is required'); - } - - const checkDuplicate = await this.prismaRepository.typebot.findFirst({ - where: { - triggerValue: data.triggerValue, - instanceId: instanceId, - }, - }); - - if (checkDuplicate) { - throw new Error('Trigger already exists'); - } - } - - try { - const typebot = await this.prismaRepository.typebot.create({ - data: { - enabled: data.enabled, - description: data.description, - url: data.url, - typebot: data.typebot, - expire: data.expire, - keywordFinish: data.keywordFinish, - delayMessage: data.delayMessage, - unknownMessage: data.unknownMessage, - listeningFromMe: data.listeningFromMe, - stopBotFromMe: data.stopBotFromMe, - keepOpen: data.keepOpen, - debounceTime: data.debounceTime, - instanceId: instanceId, - triggerType: data.triggerType, - triggerOperator: data.triggerOperator, - triggerValue: data.triggerValue, - ignoreJids: data.ignoreJids, - }, - }); - - return typebot; - } catch (error) { - this.logger.error(error); - throw new Error('Error creating typebot'); - } - } - - public async fetch(instance: InstanceDto, typebotId: string) { - const instanceId = await this.prismaRepository.instance - .findFirst({ - where: { - name: instance.instanceName, - }, - }) - .then((instance) => instance.id); - - const typebot = await this.prismaRepository.typebot.findFirst({ - where: { - id: typebotId, - }, - include: { - sessions: true, - }, - }); - - if (!typebot) { - throw new Error('Typebot not found'); - } - - if (typebot.instanceId !== instanceId) { - throw new Error('Typebot not found'); - } - - return typebot; - } - - public async update(instance: InstanceDto, typebotId: string, data: any) { - const instanceId = await this.prismaRepository.instance - .findFirst({ - where: { - name: instance.instanceName, - }, - }) - .then((instance) => instance.id); - - const typebot = await this.prismaRepository.typebot.findFirst({ - where: { - id: typebotId, - }, - }); - - if (!typebot) { - throw new Error('Typebot not found'); - } - - if (typebot.instanceId !== instanceId) { - throw new Error('Typebot not found'); - } - - if (data.triggerType === 'all') { - const checkTriggerAll = await this.prismaRepository.typebot.findFirst({ - where: { - enabled: true, - triggerType: 'all', - id: { - not: typebotId, - }, - instanceId: instanceId, - }, - }); - - if (checkTriggerAll) { - throw new Error( - 'You already have a typebot with an "All" trigger, you cannot have more bots while it is active', - ); - } - } - - const checkDuplicate = await this.prismaRepository.typebot.findFirst({ - where: { - url: data.url, - typebot: data.typebot, - id: { - not: typebotId, - }, - instanceId: instanceId, - }, - }); - - if (checkDuplicate) { - throw new Error('Typebot already exists'); - } - - if (data.triggerType === 'keyword') { - if (!data.triggerOperator || !data.triggerValue) { - throw new Error('Trigger operator and value are required'); - } - - const checkDuplicate = await this.prismaRepository.typebot.findFirst({ - where: { - triggerOperator: data.triggerOperator, - triggerValue: data.triggerValue, - id: { - not: typebotId, - }, - instanceId: instanceId, - }, - }); - - if (checkDuplicate) { - throw new Error('Trigger already exists'); - } - } - - if (data.triggerType === 'advanced') { - if (!data.triggerValue) { - throw new Error('Trigger value is required'); - } - - const checkDuplicate = await this.prismaRepository.typebot.findFirst({ - where: { - triggerValue: data.triggerValue, - id: { not: typebotId }, - instanceId: instanceId, - }, - }); - - if (checkDuplicate) { - throw new Error('Trigger already exists'); - } - } - - try { - const typebot = await this.prismaRepository.typebot.update({ - where: { - id: typebotId, - }, - data: { - enabled: data.enabled, - url: data.url, - typebot: data.typebot, - expire: data.expire, - keywordFinish: data.keywordFinish, - delayMessage: data.delayMessage, - unknownMessage: data.unknownMessage, - listeningFromMe: data.listeningFromMe, - stopBotFromMe: data.stopBotFromMe, - keepOpen: data.keepOpen, - debounceTime: data.debounceTime, - triggerType: data.triggerType, - triggerOperator: data.triggerOperator, - triggerValue: data.triggerValue, - ignoreJids: data.ignoreJids, - }, - }); - - return typebot; - } catch (error) { - this.logger.error(error); - throw new Error('Error updating typebot'); - } - } - - public async find(instance: InstanceDto): Promise { - const instanceId = await this.prismaRepository.instance - .findFirst({ - where: { - name: instance.instanceName, - }, - }) - .then((instance) => instance.id); - - const typebots = await this.prismaRepository.typebot.findMany({ - where: { - instanceId: instanceId, - }, - include: { - sessions: true, - }, - }); - - if (!typebots.length) { - return null; - } - - return typebots; - } - - public async delete(instance: InstanceDto, typebotId: string) { - const instanceId = await this.prismaRepository.instance - .findFirst({ - where: { - name: instance.instanceName, - }, - }) - .then((instance) => instance.id); - - const typebot = await this.prismaRepository.typebot.findFirst({ - where: { - id: typebotId, - }, - }); - - if (!typebot) { - throw new Error('Typebot not found'); - } - - if (typebot.instanceId !== instanceId) { - throw new Error('Typebot not found'); - } - try { - await this.prismaRepository.integrationSession.deleteMany({ - where: { - typebotId: typebotId, - }, - }); - - await this.prismaRepository.typebot.delete({ - where: { - id: typebotId, - }, - }); - - return { typebot: { id: typebotId } }; - } catch (error) { - this.logger.error(error); - throw new Error('Error deleting typebot'); - } - } - - public async setDefaultSettings(instance: InstanceDto, data: any) { - try { - const instanceId = await this.prismaRepository.instance - .findFirst({ - where: { - name: instance.instanceName, - }, - }) - .then((instance) => instance.id); - - const settings = await this.prismaRepository.typebotSetting.findFirst({ - where: { - instanceId: instanceId, - }, - }); - - if (settings) { - const updateSettings = await this.prismaRepository.typebotSetting.update({ - where: { - id: settings.id, - }, - data: { - expire: data.expire, - keywordFinish: data.keywordFinish, - delayMessage: data.delayMessage, - unknownMessage: data.unknownMessage, - listeningFromMe: data.listeningFromMe, - stopBotFromMe: data.stopBotFromMe, - keepOpen: data.keepOpen, - debounceTime: data.debounceTime, - typebotIdFallback: data.typebotIdFallback, - ignoreJids: data.ignoreJids, - }, - }); - - return { - expire: updateSettings.expire, - keywordFinish: updateSettings.keywordFinish, - delayMessage: updateSettings.delayMessage, - unknownMessage: updateSettings.unknownMessage, - listeningFromMe: updateSettings.listeningFromMe, - stopBotFromMe: updateSettings.stopBotFromMe, - keepOpen: updateSettings.keepOpen, - debounceTime: updateSettings.debounceTime, - typebotIdFallback: updateSettings.typebotIdFallback, - ignoreJids: updateSettings.ignoreJids, - }; - } - - const newSetttings = await this.prismaRepository.typebotSetting.create({ - data: { - expire: data.expire, - keywordFinish: data.keywordFinish, - delayMessage: data.delayMessage, - unknownMessage: data.unknownMessage, - listeningFromMe: data.listeningFromMe, - stopBotFromMe: data.stopBotFromMe, - keepOpen: data.keepOpen, - debounceTime: data.debounceTime, - typebotIdFallback: data.typebotIdFallback, - ignoreJids: data.ignoreJids, - instanceId: instanceId, - }, - }); - - return { - expire: newSetttings.expire, - keywordFinish: newSetttings.keywordFinish, - delayMessage: newSetttings.delayMessage, - unknownMessage: newSetttings.unknownMessage, - listeningFromMe: newSetttings.listeningFromMe, - stopBotFromMe: newSetttings.stopBotFromMe, - keepOpen: newSetttings.keepOpen, - debounceTime: newSetttings.debounceTime, - typebotIdFallback: newSetttings.typebotIdFallback, - ignoreJids: newSetttings.ignoreJids, - }; - } catch (error) { - this.logger.error(error); - throw new Error('Error setting default settings'); - } - } - - public async fetchDefaultSettings(instance: InstanceDto) { - try { - const instanceId = await this.prismaRepository.instance - .findFirst({ - where: { - name: instance.instanceName, - }, - }) - .then((instance) => instance.id); - - const settings = await this.prismaRepository.typebotSetting.findFirst({ - where: { - instanceId: instanceId, - }, - include: { - Fallback: true, - }, - }); - - if (!settings) { - return { - expire: 0, - keywordFinish: '', - delayMessage: 0, - unknownMessage: '', - listeningFromMe: false, - stopBotFromMe: false, - keepOpen: false, - ignoreJids: [], - typebotIdFallback: null, - fallback: null, - }; - } - - return { - expire: settings.expire, - keywordFinish: settings.keywordFinish, - delayMessage: settings.delayMessage, - unknownMessage: settings.unknownMessage, - listeningFromMe: settings.listeningFromMe, - stopBotFromMe: settings.stopBotFromMe, - keepOpen: settings.keepOpen, - ignoreJids: settings.ignoreJids, - typebotIdFallback: settings.typebotIdFallback, - fallback: settings.Fallback, - }; - } catch (error) { - this.logger.error(error); - throw new Error('Error fetching default settings'); - } - } - - public async ignoreJid(instance: InstanceDto, data: TypebotIgnoreJidDto) { - try { - const instanceId = await this.prismaRepository.instance - .findFirst({ - where: { - name: instance.instanceName, - }, - }) - .then((instance) => instance.id); - - const settings = await this.prismaRepository.typebotSetting.findFirst({ - where: { - instanceId: instanceId, - }, - }); - - if (!settings) { - throw new Error('Settings not found'); - } - - let ignoreJids: any = settings?.ignoreJids || []; - - if (data.action === 'add') { - if (ignoreJids.includes(data.remoteJid)) return { ignoreJids: ignoreJids }; - - ignoreJids.push(data.remoteJid); - } else { - ignoreJids = ignoreJids.filter((jid) => jid !== data.remoteJid); - } - - const updateSettings = await this.prismaRepository.typebotSetting.update({ - where: { - id: settings.id, - }, - data: { - ignoreJids: ignoreJids, - }, - }); - - return { - ignoreJids: updateSettings.ignoreJids, - }; - } catch (error) { - this.logger.error(error); - throw new Error('Error setting default settings'); - } - } - - public async fetchSessions(instance: InstanceDto, typebotId?: string, remoteJid?: string) { - try { - const instanceId = await this.prismaRepository.instance - .findFirst({ - where: { - name: instance.instanceName, - }, - }) - .then((instance) => instance.id); - - const typebot = await this.prismaRepository.typebot.findFirst({ - where: { - id: typebotId, - }, - }); - - if (typebot && typebot.instanceId !== instanceId) { - throw new Error('Typebot not found'); - } - - return await this.prismaRepository.integrationSession.findMany({ - where: { - instanceId: instanceId, - remoteJid, - typebotId: typebotId ?? { not: null }, - }, - include: { - Typebot: true, - }, - }); - } catch (error) { - this.logger.error(error); - throw new Error('Error fetching sessions'); - } - } - - public async changeStatus(instance: InstanceDto, data: any) { - try { - const instanceId = await this.prismaRepository.instance - .findFirst({ - where: { - name: instance.instanceName, - }, - }) - .then((instance) => instance.id); - - const remoteJid = data.remoteJid; - const status = data.status; - - const defaultSettingCheck = await this.prismaRepository.typebotSetting.findFirst({ - where: { - instanceId, - }, - }); - - if (status === 'delete') { - await this.prismaRepository.integrationSession.deleteMany({ - where: { - remoteJid: remoteJid, - instanceId: instanceId, - typebotId: { not: null }, - }, - }); - - return { typebot: { ...instance, typebot: { remoteJid: remoteJid, status: status } } }; - } - - if (status === 'closed') { - if (defaultSettingCheck?.keepOpen) { - await this.prismaRepository.integrationSession.updateMany({ - where: { - instanceId: instanceId, - remoteJid: remoteJid, - typebotId: { not: null }, - }, - data: { - status: status, - }, - }); - } else { - await this.prismaRepository.integrationSession.deleteMany({ - where: { - remoteJid: remoteJid, - instanceId: instanceId, - typebotId: { not: null }, - }, - }); - } - - return { typebot: { ...instance, typebot: { remoteJid: remoteJid, status: status } } }; - } - - const session = await this.prismaRepository.integrationSession.updateMany({ - where: { - instanceId: instanceId, - remoteJid: remoteJid, - typebotId: { not: null }, - }, - data: { - status: status, - }, - }); - - const typebotData = { - remoteJid: remoteJid, - status: status, - session, - }; - - this.waMonitor.waInstances[instance.instanceName].sendDataWebhook(Events.TYPEBOT_CHANGE_STATUS, typebotData); - - return { typebot: { ...instance, typebot: typebotData } }; - } catch (error) { - this.logger.error(error); - throw new Error('Error changing status'); - } - } - - public async startTypebot(instance: InstanceDto, data: any) { - if (data.remoteJid === 'status@broadcast') return; - - const instanceData = await this.prismaRepository.instance.findFirst({ - where: { - name: instance.instanceName, - }, - }); - - if (!instanceData) throw new Error('Instance not found'); - - const remoteJid = data.remoteJid; - const url = data.url; - const typebot = data.typebot; - const startSession = data.startSession; - const variables = data.variables; - let expire = data?.typebot?.expire; - let keywordFinish = data?.typebot?.keywordFinish; - let delayMessage = data?.typebot?.delayMessage; - let unknownMessage = data?.typebot?.unknownMessage; - let listeningFromMe = data?.typebot?.listeningFromMe; - let stopBotFromMe = data?.typebot?.stopBotFromMe; - let keepOpen = data?.typebot?.keepOpen; - - const defaultSettingCheck = await this.prismaRepository.typebotSetting.findFirst({ - where: { - instanceId: instanceData.id, - }, - }); - - if (defaultSettingCheck?.ignoreJids) { - const ignoreJids: any = defaultSettingCheck.ignoreJids; - - let ignoreGroups = false; - let ignoreContacts = false; - - if (ignoreJids.includes('@g.us')) { - ignoreGroups = true; - } - - if (ignoreJids.includes('@s.whatsapp.net')) { - ignoreContacts = true; - } - - if (ignoreGroups && remoteJid.includes('@g.us')) { - this.logger.warn('Ignoring message from group: ' + remoteJid); - throw new Error('Group not allowed'); - } - - if (ignoreContacts && remoteJid.includes('@s.whatsapp.net')) { - this.logger.warn('Ignoring message from contact: ' + remoteJid); - throw new Error('Contact not allowed'); - } - - if (ignoreJids.includes(remoteJid)) { - this.logger.warn('Ignoring message from jid: ' + remoteJid); - throw new Error('Jid not allowed'); - } - } - - if ( - !expire || - !keywordFinish || - !delayMessage || - !unknownMessage || - !listeningFromMe || - !stopBotFromMe || - !keepOpen - ) { - if (!expire) expire = defaultSettingCheck?.expire || 0; - if (!keywordFinish) keywordFinish = defaultSettingCheck?.keywordFinish || '#SAIR'; - if (!delayMessage) delayMessage = defaultSettingCheck?.delayMessage || 1000; - if (!unknownMessage) unknownMessage = defaultSettingCheck?.unknownMessage || 'Desculpe, não entendi'; - if (!listeningFromMe) listeningFromMe = defaultSettingCheck?.listeningFromMe || false; - if (!stopBotFromMe) stopBotFromMe = defaultSettingCheck?.stopBotFromMe || false; - if (!keepOpen) keepOpen = defaultSettingCheck?.keepOpen || false; - - if (!defaultSettingCheck) { - await this.setDefaultSettings(instance, { - expire: expire, - keywordFinish: keywordFinish, - delayMessage: delayMessage, - unknownMessage: unknownMessage, - listeningFromMe: listeningFromMe, - stopBotFromMe: stopBotFromMe, - keepOpen: keepOpen, - }); - } - } - - const prefilledVariables = { - remoteJid: remoteJid, - instanceName: instance.instanceName, - serverUrl: this.configService.get('SERVER').URL, - apiKey: this.configService.get('AUTHENTICATION').API_KEY.KEY, - ownerJid: instanceData.number, - }; - - if (variables?.length) { - variables.forEach((variable: { name: string | number; value: string }) => { - prefilledVariables[variable.name] = variable.value; - }); - } - - if (startSession) { - let findTypebot: any = await this.prismaRepository.typebot.findFirst({ - where: { - url: url, - typebot: typebot, - instanceId: instanceData.id, - }, - }); - - if (!findTypebot) { - findTypebot = await this.prismaRepository.typebot.create({ - data: { - enabled: true, - url: url, - typebot: typebot, - expire: expire, - triggerType: 'none', - keywordFinish: keywordFinish, - delayMessage: delayMessage, - unknownMessage: unknownMessage, - listeningFromMe: listeningFromMe, - stopBotFromMe: stopBotFromMe, - keepOpen: keepOpen, - instanceId: instanceData.id, - }, - }); - } - - await this.prismaRepository.integrationSession.deleteMany({ - where: { - remoteJid: remoteJid, - instanceId: instanceData.id, - typebotId: { not: null }, - }, - }); - - const response = await this.createNewSession(instanceData, { - enabled: true, - url: url, - typebot: typebot, - remoteJid: remoteJid, - expire: expire, - keywordFinish: keywordFinish, - delayMessage: delayMessage, - unknownMessage: unknownMessage, - listeningFromMe: listeningFromMe, - stopBotFromMe: stopBotFromMe, - keepOpen: keepOpen, - prefilledVariables: prefilledVariables, - typebotId: findTypebot.id, - }); - - if (response.sessionId) { - await this.sendWAMessage( - instanceData, - response.session, - { - expire: expire, - keywordFinish: keywordFinish, - delayMessage: delayMessage, - unknownMessage: unknownMessage, - listeningFromMe: listeningFromMe, - stopBotFromMe: stopBotFromMe, - keepOpen: keepOpen, - }, - remoteJid, - response.messages, - response.input, - response.clientSideActions, - ); - - this.waMonitor.waInstances[instance.instanceName].sendDataWebhook(Events.TYPEBOT_START, { - remoteJid: remoteJid, - url: url, - typebot: typebot, - prefilledVariables: prefilledVariables, - sessionId: `${response.sessionId}`, - }); - } else { - throw new Error('Session ID not found in response'); - } - } else { - const id = Math.floor(Math.random() * 10000000000).toString(); - - try { - const version = this.configService.get('TYPEBOT').API_VERSION; - let url: string; - let reqData: {}; - if (version === 'latest') { - url = `${data.url}/api/v1/typebots/${data.typebot}/startChat`; - - reqData = { - prefilledVariables: prefilledVariables, - }; - } else { - url = `${data.url}/api/v1/sendMessage`; - - reqData = { - startParams: { - publicId: data.typebot, - prefilledVariables: prefilledVariables, - }, - }; - } - const request = await axios.post(url, reqData); - - await this.sendWAMessage( - instanceData, - null, - { - expire: expire, - keywordFinish: keywordFinish, - delayMessage: delayMessage, - unknownMessage: unknownMessage, - listeningFromMe: listeningFromMe, - stopBotFromMe: stopBotFromMe, - keepOpen: keepOpen, - }, - remoteJid, - request.data.messages, - request.data.input, - request.data.clientSideActions, - ); - - this.waMonitor.waInstances[instance.instanceName].sendDataWebhook(Events.TYPEBOT_START, { - remoteJid: remoteJid, - url: url, - typebot: typebot, - variables: variables, - sessionId: id, - }); - } catch (error) { - this.logger.error(error); - return; - } - } - - return { - typebot: { - ...instance, - typebot: { - url: url, - remoteJid: remoteJid, - typebot: typebot, - prefilledVariables: prefilledVariables, - }, - }, - }; - } - - private getTypeMessage(msg: any) { - let mediaId: string; - - if (this.configService.get('S3').ENABLE) mediaId = msg.message.mediaUrl; - else mediaId = msg.key.id; - - const types = { - conversation: msg?.message?.conversation, - extendedTextMessage: msg?.message?.extendedTextMessage?.text, - contactMessage: msg?.message?.contactMessage?.displayName, - locationMessage: msg?.message?.locationMessage?.degreesLatitude, - viewOnceMessageV2: - msg?.message?.viewOnceMessageV2?.message?.imageMessage?.url || - msg?.message?.viewOnceMessageV2?.message?.videoMessage?.url || - msg?.message?.viewOnceMessageV2?.message?.audioMessage?.url, - listResponseMessage: msg?.message?.listResponseMessage?.singleSelectReply?.selectedRowId, - responseRowId: msg?.message?.listResponseMessage?.singleSelectReply?.selectedRowId, - // Medias - audioMessage: msg?.message?.speechToText - ? msg?.message?.speechToText - : msg?.message?.audioMessage - ? `audioMessage|${mediaId}` - : undefined, - imageMessage: msg?.message?.imageMessage ? `imageMessage|${mediaId}` : undefined, - videoMessage: msg?.message?.videoMessage ? `videoMessage|${mediaId}` : undefined, - documentMessage: msg?.message?.documentMessage ? `documentMessage|${mediaId}` : undefined, - documentWithCaptionMessage: msg?.message?.auddocumentWithCaptionMessageioMessage - ? `documentWithCaptionMessage|${mediaId}` - : undefined, - }; - - const messageType = Object.keys(types).find((key) => types[key] !== undefined) || 'unknown'; - - return { ...types, messageType }; - } - - private getMessageContent(types: any) { - const typeKey = Object.keys(types).find((key) => types[key] !== undefined); - - const result = typeKey ? types[typeKey] : undefined; - - return result; - } - - private getConversationMessage(msg: any) { - const types = this.getTypeMessage(msg); - - const messageContent = this.getMessageContent(types); - - return messageContent; - } - public async createNewSession(instance: Instance, data: any) { if (data.remoteJid === 'status@broadcast') return; const id = Math.floor(Math.random() * 10000000000).toString(); @@ -1051,7 +76,7 @@ export class TypebotService { ownerJid: instance.number, }, awaitUser: false, - typebotId: data.typebotId, + botId: data.botId, instanceId: instance.id, }, }); @@ -1311,359 +336,7 @@ export class TypebotService { } } - public async findTypebotByTrigger(content: string, instanceId: string) { - // Check for triggerType 'all' - const findTriggerAll = await this.prismaRepository.typebot.findFirst({ - where: { - enabled: true, - triggerType: 'all', - instanceId: instanceId, - }, - }); - - if (findTriggerAll) return findTriggerAll; - - const findTriggerAdvanced = await this.prismaRepository.typebot.findMany({ - where: { - enabled: true, - triggerType: 'advanced', - instanceId: instanceId, - }, - }); - for (const advanced of findTriggerAdvanced) { - if (advancedOperatorsSearch(content, advanced.triggerValue)) { - return advanced; - } - } - - // Check for exact match - const findTriggerEquals = await this.prismaRepository.typebot.findFirst({ - where: { - enabled: true, - triggerType: 'keyword', - triggerOperator: 'equals', - triggerValue: content, - instanceId: instanceId, - }, - }); - - if (findTriggerEquals) return findTriggerEquals; - - // Check for regex match - const findRegex = await this.prismaRepository.typebot.findMany({ - where: { - enabled: true, - triggerType: 'keyword', - triggerOperator: 'regex', - instanceId: instanceId, - }, - }); - - let findTriggerRegex = null; - - for (const regex of findRegex) { - const regexValue = new RegExp(regex.triggerValue); - - if (regexValue.test(content)) { - findTriggerRegex = regex; - break; - } - } - - if (findTriggerRegex) return findTriggerRegex; - - // Check for startsWith match - const findStartsWith = await this.prismaRepository.typebot.findMany({ - where: { - enabled: true, - triggerType: 'keyword', - triggerOperator: 'startsWith', - instanceId: instanceId, - }, - }); - - let findTriggerStartsWith = null; - - for (const startsWith of findStartsWith) { - if (content.startsWith(startsWith.triggerValue)) { - findTriggerStartsWith = startsWith; - break; - } - } - - if (findTriggerStartsWith) return findTriggerStartsWith; - - // Check for endsWith match - const findEndsWith = await this.prismaRepository.typebot.findMany({ - where: { - enabled: true, - triggerType: 'keyword', - triggerOperator: 'endsWith', - instanceId: instanceId, - }, - }); - - let findTriggerEndsWith = null; - - for (const endsWith of findEndsWith) { - if (content.endsWith(endsWith.triggerValue)) { - findTriggerEndsWith = endsWith; - break; - } - } - - if (findTriggerEndsWith) return findTriggerEndsWith; - - // Check for contains match - const findContains = await this.prismaRepository.typebot.findMany({ - where: { - enabled: true, - triggerType: 'keyword', - triggerOperator: 'contains', - instanceId: instanceId, - }, - }); - - let findTriggerContains = null; - - for (const contains of findContains) { - if (content.includes(contains.triggerValue)) { - findTriggerContains = contains; - break; - } - } - - if (findTriggerContains) return findTriggerContains; - - const fallback = await this.prismaRepository.typebotSetting.findFirst({ - where: { - instanceId: instanceId, - }, - }); - - if (fallback?.typebotIdFallback) { - const findFallback = await this.prismaRepository.typebot.findFirst({ - where: { - id: fallback.typebotIdFallback, - }, - }); - - if (findFallback) return findFallback; - } - - return null; - } - - private processDebounce(content: string, remoteJid: string, debounceTime: number, callback: any) { - if (this.userMessageDebounce[remoteJid]) { - this.userMessageDebounce[remoteJid].message += ` ${content}`; - this.logger.log('message debounced: ' + this.userMessageDebounce[remoteJid].message); - clearTimeout(this.userMessageDebounce[remoteJid].timeoutId); - } else { - this.userMessageDebounce[remoteJid] = { - message: content, - timeoutId: null, - }; - } - - this.userMessageDebounce[remoteJid].timeoutId = setTimeout(() => { - const myQuestion = this.userMessageDebounce[remoteJid].message; - this.logger.log('Debounce complete. Processing message: ' + myQuestion); - - delete this.userMessageDebounce[remoteJid]; - callback(myQuestion); - }, debounceTime * 1000); - } - - public async sendTypebot(instance: InstanceDto, remoteJid: string, msg: Message) { - try { - const instanceData = await this.prismaRepository.instance.findFirst({ - where: { - name: instance.instanceName, - }, - }); - - if (!instanceData) throw new Error('Instance not found'); - - const settings = await this.prismaRepository.typebotSetting.findFirst({ - where: { - instanceId: instance.instanceId, - }, - }); - - if (settings?.ignoreJids) { - const ignoreJids: any = settings.ignoreJids; - - let ignoreGroups = false; - let ignoreContacts = false; - - if (ignoreJids.includes('@g.us')) { - ignoreGroups = true; - } - - if (ignoreJids.includes('@s.whatsapp.net')) { - ignoreContacts = true; - } - - if (ignoreGroups && remoteJid.endsWith('@g.us')) { - this.logger.warn('Ignoring message from group: ' + remoteJid); - return; - } - - if (ignoreContacts && remoteJid.endsWith('@s.whatsapp.net')) { - this.logger.warn('Ignoring message from contact: ' + remoteJid); - return; - } - - if (ignoreJids.includes(remoteJid)) { - this.logger.warn('Ignoring message from jid: ' + remoteJid); - return; - } - } - - let session = await this.prismaRepository.integrationSession.findFirst({ - where: { - remoteJid: remoteJid, - instanceId: instance.instanceId, - }, - orderBy: { createdAt: 'desc' }, - }); - - if (session) { - if (session.status !== 'closed' && !session.typebotId) { - this.logger.warn('Session is already opened in another integration'); - return; - } else if (!session.typebotId) { - session = null; - } - } - - const content = this.getConversationMessage(msg); - - let findTypebot = null; - - if (!session) { - findTypebot = await this.findTypebotByTrigger(content, instance.instanceId); - - if (!findTypebot) { - return; - } - } else { - findTypebot = await this.prismaRepository.typebot.findFirst({ - where: { - id: session.typebotId, - }, - }); - } - - const url = findTypebot?.url; - const typebot = findTypebot?.typebot; - let expire = findTypebot?.expire; - let keywordFinish = findTypebot?.keywordFinish; - let delayMessage = findTypebot?.delayMessage; - let unknownMessage = findTypebot?.unknownMessage; - let listeningFromMe = findTypebot?.listeningFromMe; - let stopBotFromMe = findTypebot?.stopBotFromMe; - let keepOpen = findTypebot?.keepOpen; - let debounceTime = findTypebot?.debounceTime; - - if ( - !expire || - !keywordFinish || - !delayMessage || - !unknownMessage || - !listeningFromMe || - !stopBotFromMe || - !keepOpen - ) { - if (!expire) expire = settings.expire; - - if (!keywordFinish) keywordFinish = settings.keywordFinish; - - if (!delayMessage) delayMessage = settings.delayMessage; - - if (!unknownMessage) unknownMessage = settings.unknownMessage; - - if (!listeningFromMe) listeningFromMe = settings.listeningFromMe; - - if (!stopBotFromMe) stopBotFromMe = settings.stopBotFromMe; - - if (!keepOpen) keepOpen = settings.keepOpen; - - if (!debounceTime) debounceTime = settings.debounceTime; - } - - const key = msg.key as { - id: string; - remoteJid: string; - fromMe: boolean; - participant: string; - }; - - if (stopBotFromMe && key.fromMe && session) { - await this.prismaRepository.integrationSession.update({ - where: { - id: session.id, - }, - data: { - status: 'paused', - }, - }); - return; - } - - if (!listeningFromMe && key.fromMe) { - return; - } - - if (debounceTime && debounceTime > 0) { - this.processDebounce(content, remoteJid, debounceTime, async (debouncedContent) => { - await this.processTypebot( - instanceData, - remoteJid, - msg, - session, - findTypebot, - url, - expire, - typebot, - keywordFinish, - delayMessage, - unknownMessage, - listeningFromMe, - stopBotFromMe, - keepOpen, - debouncedContent, - ); - }); - } else { - await this.processTypebot( - instanceData, - remoteJid, - msg, - session, - findTypebot, - url, - expire, - typebot, - keywordFinish, - delayMessage, - unknownMessage, - listeningFromMe, - stopBotFromMe, - keepOpen, - content, - ); - } - - if (session && !session.awaitUser) return; - } catch (error) { - this.logger.error(error); - return; - } - } - - private async processTypebot( + public async processTypebot( instance: Instance, remoteJid: string, msg: Message, @@ -1702,7 +375,7 @@ export class TypebotService { } else { await this.prismaRepository.integrationSession.deleteMany({ where: { - typebotId: findTypebot.id, + botId: findTypebot.id, remoteJid: remoteJid, }, }); @@ -1719,7 +392,7 @@ export class TypebotService { listeningFromMe: listeningFromMe, remoteJid: remoteJid, pushName: msg.pushName, - typebotId: findTypebot.id, + botId: findTypebot.id, }); if (data.session) { @@ -1745,7 +418,7 @@ export class TypebotService { ); if (data.messages.length === 0) { - const content = this.getConversationMessage(msg.message); + const content = getConversationMessage(msg.message); if (!content) { if (unknownMessage) { @@ -1776,7 +449,7 @@ export class TypebotService { } else { await this.prismaRepository.integrationSession.deleteMany({ where: { - typebotId: findTypebot.id, + botId: findTypebot.id, remoteJid: remoteJid, }, }); @@ -1901,7 +574,7 @@ export class TypebotService { } else { await this.prismaRepository.integrationSession.deleteMany({ where: { - typebotId: findTypebot.id, + botId: findTypebot.id, remoteJid: remoteJid, }, }); @@ -1993,7 +666,7 @@ export class TypebotService { } else { await this.prismaRepository.integrationSession.deleteMany({ where: { - typebotId: findTypebot.id, + botId: findTypebot.id, remoteJid: remoteJid, }, }); diff --git a/src/api/server.module.ts b/src/api/server.module.ts index 2f18075c..6dc1fa17 100644 --- a/src/api/server.module.ts +++ b/src/api/server.module.ts @@ -108,12 +108,12 @@ export const webhookController = new WebhookController(prismaRepository, waMonit // chatbots const typebotService = new TypebotService(waMonitor, configService, prismaRepository); -export const typebotController = new TypebotController(typebotService); +export const typebotController = new TypebotController(typebotService, prismaRepository, waMonitor); const openaiService = new OpenaiService(waMonitor, configService, prismaRepository); -export const openaiController = new OpenaiController(openaiService); +export const openaiController = new OpenaiController(openaiService, prismaRepository, waMonitor); const difyService = new DifyService(waMonitor, configService, prismaRepository); -export const difyController = new DifyController(difyService); +export const difyController = new DifyController(difyService, prismaRepository, waMonitor); logger.info('Module - ON');