diff --git a/.env.example b/.env.example index 735de258..8bed896a 100644 --- a/.env.example +++ b/.env.example @@ -32,7 +32,7 @@ CLEAN_STORE_CHATS=true DATABASE_ENABLED=false DATABASE_PROVIDER=mongodb # postgresql, mysql, mongodb DATABASE_CONNECTION_URI='mongodb://root:root@mongodb:27017/?authSource=admin&readPreference=primary&ssl=false&directConnection=true' -DATABASE_CONNECTION_DB_PREFIX_NAME=evolution +DATABASE_CONNECTION_CLIENT_NAME=evolution DATABASE_SAVE_DATA_INSTANCE=true DATABASE_SAVE_DATA_NEW_MESSAGE=true DATABASE_SAVE_MESSAGE_UPDATE=true diff --git a/Docker/.env.example b/Docker/.env.example index 4d1c7736..5d957fb9 100644 --- a/Docker/.env.example +++ b/Docker/.env.example @@ -34,7 +34,7 @@ CLEAN_STORE_CHATS=true # Permanent data storage DATABASE_ENABLED=false DATABASE_CONNECTION_URI=mongodb://root:root@mongodb:27017/?authSource=admin&readPreference=primary&ssl=false&directConnection=true -DATABASE_CONNECTION_DB_PREFIX_NAME=evdocker +DATABASE_CONNECTION_CLIENT_NAME=evdocker # Choose the data you want to save in the application's database or store DATABASE_SAVE_DATA_INSTANCE=false diff --git a/Docker/evolution-api-all-services/.env.example b/Docker/evolution-api-all-services/.env.example index 82aaffde..35695cd1 100644 --- a/Docker/evolution-api-all-services/.env.example +++ b/Docker/evolution-api-all-services/.env.example @@ -37,7 +37,7 @@ DATABASE_CONNECTION_URI=mongodb://root:root@mongodb:27017/?authSource=admin & readPreference=primary & ssl=false & directConnection=true -DATABASE_CONNECTION_DB_PREFIX_NAME=evolution +DATABASE_CONNECTION_CLIENT_NAME=evolution # Choose the data you want to save in the application's database or store DATABASE_SAVE_DATA_INSTANCE=false diff --git a/Dockerfile b/Dockerfile index ac814973..dada20d5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -50,7 +50,7 @@ ENV CLEAN_STORE_CHATS=true ENV DATABASE_ENABLED=false ENV DATABASE_CONNECTION_URI=mongodb://root:root@mongodb:27017/?authSource=admin&readPreference=primary&ssl=false&directConnection=true -ENV DATABASE_CONNECTION_DB_PREFIX_NAME=evolution +ENV DATABASE_CONNECTION_CLIENT_NAME=evolution ENV DATABASE_SAVE_DATA_INSTANCE=false ENV DATABASE_SAVE_DATA_NEW_MESSAGE=false diff --git a/prisma/postgresql-schema.prisma b/prisma/postgresql-schema.prisma index 776d1b2d..cabed020 100644 --- a/prisma/postgresql-schema.prisma +++ b/prisma/postgresql-schema.prisma @@ -33,6 +33,18 @@ enum TypebotSessionStatus { paused } +enum TriggerType { + all + keyword +} + +enum TriggerOperator { + contains + equals + startsWith + endsWith +} + model Instance { id String @id @default(cuid()) name String @unique @db.VarChar(255) @@ -42,6 +54,7 @@ model Instance { integration String? @db.VarChar(100) number String? @db.VarChar(100) token String? @unique @db.VarChar(255) + clientName String? @db.VarChar(100) createdAt DateTime? @default(now()) @db.Timestamp updatedAt DateTime? @updatedAt @db.Timestamp Chat Chat[] @@ -55,14 +68,15 @@ model Instance { Rabbitmq Rabbitmq? Sqs Sqs? Websocket Websocket? - Typebot Typebot? + Typebot Typebot[] Session Session? MessageUpdate MessageUpdate[] TypebotSession TypebotSession[] + TypebotSetting TypebotSetting? } model Session { - id Int @id @unique @default(autoincrement()) + id String @id @default(cuid()) sessionId String @unique creds String? @db.Text createdAt DateTime @default(now()) @db.Timestamp @@ -70,7 +84,7 @@ model Session { } model Chat { - id Int @id @default(autoincrement()) + id String @id @default(cuid()) remoteJid String @db.VarChar(100) labels Json? @db.JsonB createdAt DateTime? @default(now()) @db.Timestamp @@ -80,7 +94,7 @@ model Chat { } model Contact { - id Int @id @default(autoincrement()) + id String @id @default(cuid()) remoteJid String @db.VarChar(100) pushName String? @db.VarChar(100) profilePicUrl String? @db.VarChar(500) @@ -91,7 +105,7 @@ model Contact { } model Message { - id Int @id @default(autoincrement()) + id String @id @default(cuid()) key Json @db.JsonB pushName String? @db.VarChar(100) participant String? @db.VarChar(100) @@ -107,13 +121,13 @@ model Message { chatwootIsRead Boolean? @db.Boolean Instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade) instanceId String - typebotSessionId Int? + typebotSessionId String? MessageUpdate MessageUpdate[] TypebotSession TypebotSession? @relation(fields: [typebotSessionId], references: [id]) } model MessageUpdate { - id Int @id @default(autoincrement()) + id String @id @default(cuid()) keyId String @db.VarChar(100) remoteJid String @db.VarChar(100) fromMe Boolean @db.Boolean @@ -121,13 +135,13 @@ model MessageUpdate { pollUpdates Json? @db.JsonB status String @db.VarChar(30) Message Message @relation(fields: [messageId], references: [id], onDelete: Cascade) - messageId Int + messageId String Instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade) instanceId String } model Webhook { - id Int @id @default(autoincrement()) + id String @id @default(cuid()) url String @db.VarChar(500) enabled Boolean? @default(true) @db.Boolean events Json? @db.JsonB @@ -140,7 +154,7 @@ model Webhook { } model Chatwoot { - id Int @id @default(autoincrement()) + id String @id @default(cuid()) enabled Boolean? @default(true) @db.Boolean accountId String? @db.VarChar(100) token String? @db.VarChar(100) @@ -162,7 +176,7 @@ model Chatwoot { } model Label { - id Int @id @default(autoincrement()) + id String @id @default(cuid()) labelId String? @unique @db.VarChar(100) name String @db.VarChar(100) color String @db.VarChar(100) @@ -174,7 +188,7 @@ model Label { } model Proxy { - id Int @id @default(autoincrement()) + id String @id @default(cuid()) enabled Boolean @default(false) @db.Boolean host String @db.VarChar(100) port String @db.VarChar(100) @@ -188,7 +202,7 @@ model Proxy { } model Setting { - id Int @id @default(autoincrement()) + id String @id @default(cuid()) rejectCall Boolean @default(false) @db.Boolean msgCall String? @db.VarChar(100) groupsIgnore Boolean @default(false) @db.Boolean @@ -203,7 +217,7 @@ model Setting { } model Rabbitmq { - id Int @id @default(autoincrement()) + id String @id @default(cuid()) enabled Boolean @default(false) @db.Boolean events Json @db.JsonB createdAt DateTime? @default(now()) @db.Timestamp @@ -213,7 +227,7 @@ model Rabbitmq { } model Sqs { - id Int @id @default(autoincrement()) + id String @id @default(cuid()) enabled Boolean @default(false) @db.Boolean events Json @db.JsonB createdAt DateTime? @default(now()) @db.Timestamp @@ -223,7 +237,7 @@ model Sqs { } model Websocket { - id Int @id @default(autoincrement()) + id String @id @default(cuid()) enabled Boolean @default(false) @db.Boolean events Json @db.JsonB createdAt DateTime? @default(now()) @db.Timestamp @@ -233,24 +247,29 @@ model Websocket { } model Typebot { - id Int @id @default(autoincrement()) + id String @id @default(cuid()) enabled Boolean @default(true) @db.Boolean url String @db.VarChar(500) typebot String @db.VarChar(100) - expire Int @default(0) @db.Integer + expire Int? @default(0) @db.Integer keywordFinish String? @db.VarChar(100) delayMessage Int? @db.Integer unknownMessage String? @db.VarChar(100) - listeningFromMe Boolean @default(false) @db.Boolean + listeningFromMe Boolean? @default(false) @db.Boolean + stopBotFromMe Boolean? @default(false) @db.Boolean + keepOpen Boolean? @default(false) @db.Boolean createdAt DateTime? @default(now()) @db.Timestamp updatedAt DateTime? @updatedAt @db.Timestamp + triggerType TriggerType? + triggerOperator TriggerOperator? + triggerValue String? Instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade) - instanceId String @unique + instanceId String sessions TypebotSession[] } model TypebotSession { - id Int @id @default(autoincrement()) + id String @id @default(cuid()) remoteJid String @db.VarChar(100) pushName String? @db.VarChar(100) sessionId String @db.VarChar(100) @@ -260,8 +279,23 @@ model TypebotSession { createdAt DateTime? @default(now()) @db.Timestamp updatedAt DateTime @updatedAt @db.Timestamp Typebot Typebot @relation(fields: [typebotId], references: [id], onDelete: Cascade) - typebotId Int + typebotId String Message Message[] Instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade) instanceId String } + +model TypebotSetting { + id String @id @default(cuid()) + expire Int? @default(0) @db.Integer + keywordFinish String? @db.VarChar(100) + delayMessage Int? @db.Integer + unknownMessage String? @db.VarChar(100) + listeningFromMe Boolean? @default(false) @db.Boolean + stopBotFromMe Boolean? @default(false) @db.Boolean + keepOpen Boolean? @default(false) @db.Boolean + createdAt DateTime? @default(now()) @db.Timestamp + updatedAt DateTime @updatedAt @db.Timestamp + Instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade) + instanceId String @unique +} diff --git a/src/api/guards/instance.guard.ts b/src/api/guards/instance.guard.ts index b0e6e12d..df0e8bc6 100644 --- a/src/api/guards/instance.guard.ts +++ b/src/api/guards/instance.guard.ts @@ -1,9 +1,6 @@ import { NextFunction, Request, Response } from 'express'; -import { existsSync } from 'fs'; -import { join } from 'path'; import { CacheConf, configService, Database } from '../../config/env.config'; -import { INSTANCE_DIR } from '../../config/path.config'; import { BadRequestException, ForbiddenException, @@ -33,7 +30,7 @@ async function getInstance(instanceName: string) { return exists || (await prisma.instance.findMany({ where: { name: instanceName } })).length > 0; } - return exists || existsSync(join(INSTANCE_DIR, instanceName)); + return false; } catch (error) { throw new InternalServerErrorException(error?.toString()); } @@ -65,6 +62,7 @@ export async function instanceLoggedGuard(req: Request, _: Response, next: NextF if (waMonitor.waInstances[instance.instanceName]) { waMonitor.waInstances[instance.instanceName]?.removeRabbitmqQueues(); + waMonitor.waInstances[instance.instanceName]?.removeSqsQueues(); delete waMonitor.waInstances[instance.instanceName]; } } diff --git a/src/api/integrations/chatwoot/services/chatwoot.service.ts b/src/api/integrations/chatwoot/services/chatwoot.service.ts index 0a89fdaf..49b431e6 100644 --- a/src/api/integrations/chatwoot/services/chatwoot.service.ts +++ b/src/api/integrations/chatwoot/services/chatwoot.service.ts @@ -2198,14 +2198,14 @@ export class ChatwootService { where: { instanceId: instance.instanceId, id: { - in: recentContacts.map((contact) => Number(contact.identifier)), + in: recentContacts.map((contact) => contact.identifier), }, profilePicUrl: { not: null, }, }, }) - ).reduce((acc: Map, contact: ContactModel) => acc.set(contact.id, contact), new Map()); + ).reduce((acc: Map, contact: ContactModel) => acc.set(contact.id, contact), new Map()); recentContacts.forEach(async (contact) => { if (contactsWithProfilePicture.has(contact.identifier)) { diff --git a/src/api/integrations/typebot/controllers/typebot.controller.ts b/src/api/integrations/typebot/controllers/typebot.controller.ts index 78c48d08..707545ac 100644 --- a/src/api/integrations/typebot/controllers/typebot.controller.ts +++ b/src/api/integrations/typebot/controllers/typebot.controller.ts @@ -10,19 +10,6 @@ export class TypebotController { public async createTypebot(instance: InstanceDto, data: TypebotDto) { if (!configService.get('TYPEBOT').ENABLED) throw new BadRequestException('Typebot is disabled'); - if (!data.enabled) { - data.url = ''; - data.typebot = ''; - data.expire = 0; - data.sessions = []; - } else { - const saveData = await this.typebotService.find(instance); - - if (saveData?.typebot?.enabled) { - data.sessions = saveData?.sessions; - } - } - return this.typebotService.create(instance, data); } @@ -32,10 +19,22 @@ export class TypebotController { return this.typebotService.find(instance); } - public async changeStatus(instance: InstanceDto, data: any) { + public async fetchTypebot(instance: InstanceDto, typebotId: string) { if (!configService.get('TYPEBOT').ENABLED) throw new BadRequestException('Typebot is disabled'); - return this.typebotService.changeStatus(instance, data); + return this.typebotService.fetch(instance, typebotId); + } + + public async updateTypebot(instance: InstanceDto, typebotId: string, data: TypebotDto) { + if (!configService.get('TYPEBOT').ENABLED) throw new BadRequestException('Typebot is disabled'); + + return this.typebotService.update(instance, typebotId, data); + } + + public async deleteTypebot(instance: InstanceDto, typebotId: string) { + if (!configService.get('TYPEBOT').ENABLED) throw new BadRequestException('Typebot is disabled'); + + return this.typebotService.delete(instance, typebotId); } public async startTypebot(instance: InstanceDto, data: any) { @@ -43,4 +42,28 @@ export class TypebotController { return this.typebotService.startTypebot(instance, data); } + + public async settings(instance: InstanceDto, data: any) { + if (!configService.get('TYPEBOT').ENABLED) throw new BadRequestException('Typebot is disabled'); + + return this.typebotService.setDefaultSettings(instance, data); + } + + public async fetchSettings(instance: InstanceDto) { + if (!configService.get('TYPEBOT').ENABLED) throw new BadRequestException('Typebot is disabled'); + + return this.typebotService.fetchDefaultSettings(instance); + } + + public async changeStatus(instance: InstanceDto, data: any) { + if (!configService.get('TYPEBOT').ENABLED) throw new BadRequestException('Typebot is disabled'); + + return this.typebotService.changeStatus(instance, data); + } + + public async fetchSessions(instance: InstanceDto, typebotId: string) { + if (!configService.get('TYPEBOT').ENABLED) throw new BadRequestException('Typebot is disabled'); + + return this.typebotService.fetchSessions(instance, typebotId); + } } diff --git a/src/api/integrations/typebot/dto/typebot.dto.ts b/src/api/integrations/typebot/dto/typebot.dto.ts index 0871b107..6957a00c 100644 --- a/src/api/integrations/typebot/dto/typebot.dto.ts +++ b/src/api/integrations/typebot/dto/typebot.dto.ts @@ -1,4 +1,4 @@ -import { TypebotSession } from '@prisma/client'; +import { TriggerOperator, TriggerType } from '@prisma/client'; export class Session { remoteJid?: string; @@ -25,5 +25,19 @@ export class TypebotDto { delayMessage?: number; unknownMessage?: string; listeningFromMe?: boolean; - sessions?: TypebotSession[]; + stopBotFromMe?: boolean; + keepOpen?: boolean; + triggerType?: TriggerType; + triggerOperator?: TriggerOperator; + triggerValue?: string; +} + +export class TypebotSettingDto { + expire?: number; + keywordFinish?: string; + delayMessage?: number; + unknownMessage?: string; + listeningFromMe?: boolean; + stopBotFromMe?: boolean; + keepOpen?: boolean; } diff --git a/src/api/integrations/typebot/routes/typebot.router.ts b/src/api/integrations/typebot/routes/typebot.router.ts index 9e4400d2..9d9042f6 100644 --- a/src/api/integrations/typebot/routes/typebot.router.ts +++ b/src/api/integrations/typebot/routes/typebot.router.ts @@ -3,6 +3,7 @@ import { RequestHandler, Router } from 'express'; import { instanceSchema, typebotSchema, + typebotSettingSchema, typebotStartSchema, typebotStatusSchema, } from '../../../../validate/validate.schema'; @@ -10,13 +11,13 @@ import { RouterBroker } from '../../../abstract/abstract.router'; import { InstanceDto } from '../../../dto/instance.dto'; import { HttpStatus } from '../../../routes/index.router'; import { typebotController } from '../../../server.module'; -import { TypebotDto } from '../dto/typebot.dto'; +import { TypebotDto, TypebotSettingDto } from '../dto/typebot.dto'; export class TypebotRouter extends RouterBroker { constructor(...guards: RequestHandler[]) { super(); this.router - .post(this.routerPath('set'), ...guards, async (req, res) => { + .post(this.routerPath('create'), ...guards, async (req, res) => { const response = await this.dataValidate({ request: req, schema: typebotSchema, @@ -36,12 +37,52 @@ export class TypebotRouter extends RouterBroker { res.status(HttpStatus.OK).json(response); }) - .post(this.routerPath('changeStatus'), ...guards, async (req, res) => { + .get(this.routerPath('fetch/:typebotId'), ...guards, async (req, res) => { const response = await this.dataValidate({ request: req, - schema: typebotStatusSchema, + schema: instanceSchema, ClassRef: InstanceDto, - execute: (instance, data) => typebotController.changeStatus(instance, data), + execute: (instance) => typebotController.fetchTypebot(instance, req.params.typebotId), + }); + + res.status(HttpStatus.OK).json(response); + }) + .put(this.routerPath('update/:typebotId'), ...guards, async (req, res) => { + const response = await this.dataValidate({ + request: req, + schema: typebotSchema, + ClassRef: TypebotDto, + execute: (instance, data) => typebotController.updateTypebot(instance, req.params.typebotId, data), + }); + + res.status(HttpStatus.OK).json(response); + }) + .delete(this.routerPath('delete/:typebotId'), ...guards, async (req, res) => { + const response = await this.dataValidate({ + request: req, + schema: instanceSchema, + ClassRef: InstanceDto, + execute: (instance) => typebotController.deleteTypebot(instance, req.params.typebotId), + }); + + res.status(HttpStatus.OK).json(response); + }) + .post(this.routerPath('settings'), ...guards, async (req, res) => { + const response = await this.dataValidate({ + request: req, + schema: typebotSettingSchema, + ClassRef: TypebotSettingDto, + execute: (instance, data) => typebotController.settings(instance, data), + }); + + res.status(HttpStatus.OK).json(response); + }) + .get(this.routerPath('fetchSettings'), ...guards, async (req, res) => { + const response = await this.dataValidate({ + request: req, + schema: instanceSchema, + ClassRef: InstanceDto, + execute: (instance) => typebotController.fetchSettings(instance), }); res.status(HttpStatus.OK).json(response); @@ -54,6 +95,26 @@ export class TypebotRouter extends RouterBroker { execute: (instance, data) => typebotController.startTypebot(instance, data), }); + res.status(HttpStatus.OK).json(response); + }) + .post(this.routerPath('changeStatus'), ...guards, async (req, res) => { + const response = await this.dataValidate({ + request: req, + schema: typebotStatusSchema, + ClassRef: InstanceDto, + execute: (instance, data) => typebotController.changeStatus(instance, data), + }); + + res.status(HttpStatus.OK).json(response); + }) + .get(this.routerPath('fetchSessions/:typebotId'), ...guards, async (req, res) => { + const response = await this.dataValidate({ + request: req, + schema: instanceSchema, + ClassRef: InstanceDto, + execute: (instance) => typebotController.fetchSessions(instance, req.params.typebotId), + }); + res.status(HttpStatus.OK).json(response); }); } diff --git a/src/api/integrations/typebot/services/typebot.service.ts b/src/api/integrations/typebot/services/typebot.service.ts index 122076c9..12a178b3 100644 --- a/src/api/integrations/typebot/services/typebot.service.ts +++ b/src/api/integrations/typebot/services/typebot.service.ts @@ -1,6 +1,5 @@ import { Message, TypebotSession } from '@prisma/client'; import axios from 'axios'; -import EventEmitter2 from 'eventemitter2'; import { Auth, ConfigService, Typebot } from '../../../../config/env.config'; import { Logger } from '../../../../config/logger.config'; @@ -15,78 +14,499 @@ export class TypebotService { private readonly waMonitor: WAMonitoringService, private readonly configService: ConfigService, private readonly prismaRepository: PrismaRepository, - private readonly eventEmitter: EventEmitter2, - ) { - this.eventEmitter.on('typebot:end', async (data) => { - const keep_open = this.configService.get('TYPEBOT').KEEP_OPEN; - if (keep_open) return; - - await this.prismaRepository.typebotSession.deleteMany({ - where: { - id: data.sessionId, - }, - }); - }); - } + ) {} private readonly logger = new Logger(TypebotService.name); - public create(instance: InstanceDto, data: TypebotDto) { - this.waMonitor.waInstances[instance.instanceName].setTypebot(data); + public async create(instance: InstanceDto, data: TypebotDto) { + const instanceId = await this.prismaRepository.instance + .findFirst({ + where: { + name: instance.instanceName, + }, + }) + .then((instance) => instance.id); - return { typebot: { ...instance, typebot: data } }; + if ( + !data.expire || + !data.keywordFinish || + !data.delayMessage || + !data.unknownMessage || + !data.listeningFromMe || + !data.stopBotFromMe || + !data.keepOpen + ) { + const defaultSettingCheck = await this.prismaRepository.typebotSetting.findFirst({ + where: { + instanceId: instanceId, + }, + }); + + if (!defaultSettingCheck) { + throw new Error('Default settings not found'); + } + + if (!data.expire) data.expire = defaultSettingCheck.expire; + if (!data.keywordFinish) data.keywordFinish = defaultSettingCheck.keywordFinish; + if (!data.delayMessage) data.delayMessage = defaultSettingCheck.delayMessage; + if (!data.unknownMessage) data.unknownMessage = defaultSettingCheck.unknownMessage; + if (!data.listeningFromMe) data.listeningFromMe = defaultSettingCheck.listeningFromMe; + if (!data.stopBotFromMe) data.stopBotFromMe = defaultSettingCheck.stopBotFromMe; + if (!data.keepOpen) data.keepOpen = defaultSettingCheck.keepOpen; + } + + const checkTriggerAll = await this.prismaRepository.typebot.findFirst({ + where: { + enabled: true, + triggerType: 'all', + }, + }); + + 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, + }, + }); + + if (checkDuplicate) { + throw new Error('Typebot already exists'); + } + + if (data.triggerType !== 'all') { + 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, + }, + }); + + if (checkDuplicate) { + throw new Error('Trigger already exists'); + } + } + + try { + const typebot = await this.prismaRepository.typebot.create({ + 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, + instanceId: instanceId, + triggerType: data.triggerType, + triggerOperator: data.triggerOperator, + triggerValue: data.triggerValue, + }, + }); + + 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, + }, + }, + }); + + 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, + }, + }, + }); + + if (checkDuplicate) { + throw new Error('Typebot already exists'); + } + + if (data.triggerType !== 'all') { + 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, + }, + }, + }); + + 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, + triggerType: data.triggerType, + triggerOperator: data.triggerOperator, + triggerValue: data.triggerValue, + }, + }); + + return typebot; + } catch (error) { + this.logger.error(error); + throw new Error('Error updating typebot'); + } + } } public async find(instance: InstanceDto): Promise { - try { - const typebot = await this.waMonitor.waInstances[instance.instanceName].findTypebot(); + const instanceId = await this.prismaRepository.instance + .findFirst({ + where: { + name: instance.instanceName, + }, + }) + .then((instance) => instance.id); - if (Object.keys(typebot).length === 0) { - throw new Error('Typebot not found'); + const typebots = await this.prismaRepository.typebot.findMany({ + where: { + instanceId: instanceId, + }, + include: { + sessions: true, + }, + }); + + if (!typebots.length) { + this.logger.error('Typebot not found'); + 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.typebotSession.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, + }, + }); + + return { + expire: updateSettings.expire, + keywordFinish: updateSettings.keywordFinish, + delayMessage: updateSettings.delayMessage, + unknownMessage: updateSettings.unknownMessage, + listeningFromMe: updateSettings.listeningFromMe, + stopBotFromMe: updateSettings.stopBotFromMe, + keepOpen: updateSettings.keepOpen, + }; } - const sessions = await this.prismaRepository.typebotSession.findMany({ - where: { - typebotId: typebot.id, + 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, + instanceId: instanceId, }, }); return { - typebot, - sessions, + expire: newSetttings.expire, + keywordFinish: newSetttings.keywordFinish, + delayMessage: newSetttings.delayMessage, + unknownMessage: newSetttings.unknownMessage, + listeningFromMe: newSetttings.listeningFromMe, + stopBotFromMe: newSetttings.stopBotFromMe, + keepOpen: newSetttings.keepOpen, }; } catch (error) { - return null; + 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, + }, + }); + + if (!settings) { + throw new Error('Default settings not found'); + } + + return { + expire: settings.expire, + keywordFinish: settings.keywordFinish, + delayMessage: settings.delayMessage, + unknownMessage: settings.unknownMessage, + listeningFromMe: settings.listeningFromMe, + stopBotFromMe: settings.stopBotFromMe, + keepOpen: settings.keepOpen, + }; + } catch (error) { + this.logger.error(error); + throw new Error('Error fetching 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) { + throw new Error('Typebot not found'); + } + + if (typebot.instanceId !== instanceId) { + throw new Error('Typebot not found'); + } + + if (typebotId) { + return await this.prismaRepository.typebotSession.findMany({ + where: { + typebotId: typebotId, + }, + }); + } + + if (remoteJid) { + return await this.prismaRepository.typebotSession.findMany({ + where: { + remoteJid: remoteJid, + }, + }); + } + } catch (error) { + this.logger.error(error); + throw new Error('Error fetching sessions'); } } public async changeStatus(instance: InstanceDto, data: any) { - const remoteJid = data.remoteJid; - const status = data.status; + try { + const instanceId = await this.prismaRepository.instance + .findFirst({ + where: { + name: instance.instanceName, + }, + }) + .then((instance) => instance.id); - const findData = await this.find(instance); + const remoteJid = data.remoteJid; + const status = data.status; - const session = await this.prismaRepository.typebotSession.updateMany({ - where: { - typebotId: findData?.typebot?.id, + const session = await this.prismaRepository.typebotSession.updateMany({ + where: { + instanceId: instanceId, + remoteJid: remoteJid, + }, + data: { + status: status, + }, + }); + + const typebotData = { remoteJid: remoteJid, - }, - data: { status: status, - }, - }); + session, + }; - const typebotData = { - remoteJid: remoteJid, - status: status, - url: findData?.typebot?.url, - typebot: findData?.typebot?.typebot, - session, - }; + this.waMonitor.waInstances[instance.instanceName].sendDataWebhook(Events.TYPEBOT_CHANGE_STATUS, typebotData); - this.waMonitor.waInstances[instance.instanceName].sendDataWebhook(Events.TYPEBOT_CHANGE_STATUS, typebotData); - - return { typebot: { ...instance, typebot: 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) { @@ -97,12 +517,52 @@ export class TypebotService { const typebot = data.typebot; const startSession = data.startSession; const variables = data.variables; - const findTypebot = await this.find(instance); - const expire = findTypebot?.typebot?.expire; - const keywordFinish = findTypebot?.typebot?.keywordFinish; - const delayMessage = findTypebot?.typebot?.delayMessage; - const unknownMessage = findTypebot?.typebot?.unknownMessage; - const listeningFromMe = findTypebot?.typebot?.listeningFromMe; + 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 findTypebot = await this.prismaRepository.typebot.findFirst({ + where: { + url: url, + typebot: typebot, + }, + }); + + if (!findTypebot) { + throw new Error('Typebot not found'); + } + + if ( + !expire || + !keywordFinish || + !delayMessage || + !unknownMessage || + !listeningFromMe || + !stopBotFromMe || + !keepOpen + ) { + const defaultSettingCheck = await this.prismaRepository.typebotSetting.findFirst({ + where: { + instanceId: instance.instanceId, + }, + }); + + if (!defaultSettingCheck) { + throw new Error('Default settings not found'); + } + + if (!expire) expire = defaultSettingCheck.expire; + if (!keywordFinish) keywordFinish = defaultSettingCheck.keywordFinish; + if (!delayMessage) delayMessage = defaultSettingCheck.delayMessage; + if (!unknownMessage) unknownMessage = defaultSettingCheck.unknownMessage; + if (!listeningFromMe) listeningFromMe = defaultSettingCheck.listeningFromMe; + if (!stopBotFromMe) stopBotFromMe = defaultSettingCheck.stopBotFromMe; + if (!keepOpen) keepOpen = defaultSettingCheck.keepOpen; + } const prefilledVariables = { remoteJid: remoteJid, @@ -121,13 +581,12 @@ export class TypebotService { if (startSession) { await this.prismaRepository.typebotSession.deleteMany({ where: { - typebotId: findTypebot.typebot.id, remoteJid: remoteJid, }, }); const response = await this.createNewSession(instance, { - enabled: findTypebot?.typebot?.enabled, + enabled: true, url: url, typebot: typebot, remoteJid: remoteJid, @@ -136,14 +595,25 @@ export class TypebotService { delayMessage: delayMessage, unknownMessage: unknownMessage, listeningFromMe: listeningFromMe, + stopBotFromMe: stopBotFromMe, + keepOpen: keepOpen, prefilledVariables: prefilledVariables, - typebotId: findTypebot.typebot.id, + typebotId: findTypebot.id, }); if (response.sessionId) { await this.sendWAMessage( instance, response.session, + { + expire: expire, + keywordFinish: keywordFinish, + delayMessage: delayMessage, + unknownMessage: unknownMessage, + listeningFromMe: listeningFromMe, + stopBotFromMe: stopBotFromMe, + keepOpen: keepOpen, + }, remoteJid, response.messages, response.input, @@ -188,6 +658,15 @@ export class TypebotService { await this.sendWAMessage( instance, null, + { + expire: expire, + keywordFinish: keywordFinish, + delayMessage: delayMessage, + unknownMessage: unknownMessage, + listeningFromMe: listeningFromMe, + stopBotFromMe: stopBotFromMe, + keepOpen: keepOpen, + }, remoteJid, request.data.messages, request.data.input, @@ -325,18 +804,27 @@ export class TypebotService { public async sendWAMessage( instance: InstanceDto, session: TypebotSession, + settings: { + expire: number; + keywordFinish: string; + delayMessage: number; + unknownMessage: string; + listeningFromMe: boolean; + stopBotFromMe: boolean; + keepOpen: boolean; + }, remoteJid: string, - messages: any[], - input: any[], - clientSideActions: any[], + messages: any, + input: any, + clientSideActions: any, ) { processMessages( this.waMonitor.waInstances[instance.instanceName], session, + settings, messages, input, clientSideActions, - this.eventEmitter, applyFormatting, this.prismaRepository, ).catch((err) => { @@ -415,14 +903,22 @@ export class TypebotService { } async function processMessages( - instance, - session, - messages, - input, - clientSideActions, - eventEmitter, - applyFormatting, - prismaRepository, + instance: any, + session: TypebotSession, + settings: { + expire: number; + keywordFinish: string; + delayMessage: number; + unknownMessage: string; + listeningFromMe: boolean; + stopBotFromMe: boolean; + keepOpen: boolean; + }, + messages: any, + input: any, + clientSideActions: any, + applyFormatting: any, + prismaRepository: PrismaRepository, ) { for (const message of messages) { if (message.type === 'text') { @@ -441,7 +937,7 @@ export class TypebotService { await instance.textMessage({ number: remoteJid.split('@')[0], - delay: instance.localTypebot.delayMessage || 1000, + delay: settings?.delayMessage || 1000, text: formattedText, }); } @@ -449,7 +945,7 @@ export class TypebotService { if (message.type === 'image') { await instance.mediaMessage({ number: remoteJid.split('@')[0], - delay: instance.localTypebot.delayMessage || 1000, + delay: settings?.delayMessage || 1000, mediatype: 'image', media: message.content.url, }); @@ -458,7 +954,7 @@ export class TypebotService { if (message.type === 'video') { await instance.mediaMessage({ number: remoteJid.split('@')[0], - delay: instance.localTypebot.delayMessage || 1000, + delay: settings?.delayMessage || 1000, mediatype: 'video', media: message.content.url, }); @@ -467,7 +963,7 @@ export class TypebotService { if (message.type === 'audio') { await instance.audioWhatsapp({ number: remoteJid.split('@')[0], - delay: instance.localTypebot.delayMessage || 1000, + delay: settings?.delayMessage || 1000, encoding: true, audio: message.content.url, }); @@ -494,7 +990,7 @@ export class TypebotService { await instance.textMessage({ number: remoteJid.split('@')[0], - delay: instance.localTypebot.delayMessage || 1000, + delay: settings?.delayMessage || 1000, text: formattedText, }); } @@ -508,33 +1004,175 @@ export class TypebotService { }, }); } else { - eventEmitter.emit('typebot:end', { - sessionId: session.id, - }); + if (!settings?.keepOpen) { + await prismaRepository.typebotSession.deleteMany({ + where: { + id: session.id, + }, + }); + } } } } - public async sendTypebot(instance: InstanceDto, remoteJid: string, msg: Message) { - const findTypebot = await this.find(instance); - const url = findTypebot.typebot?.url; - const typebot = findTypebot.typebot?.typebot; - const expire = findTypebot.typebot?.expire; - const keywordFinish = findTypebot.typebot?.keywordFinish; - const delayMessage = findTypebot.typebot?.delayMessage; - const unknownMessage = findTypebot.typebot?.unknownMessage; - const listeningFromMe = findTypebot.typebot?.listeningFromMe; + public async findTypebotByTrigger(content: string) { + let typebot = null; - let session = await this.prismaRepository.typebotSession.findFirst({ + const findTriggerAll = await this.prismaRepository.typebot.findFirst({ where: { - typebotId: findTypebot.typebot.id, - remoteJid: remoteJid, + enabled: true, + triggerType: 'all', }, }); - if (session && !session.awaitUser) return; + if (findTriggerAll) { + typebot = findTriggerAll; + } else { + const findTriggerEquals = await this.prismaRepository.typebot.findFirst({ + where: { + enabled: true, + triggerType: 'keyword', + triggerOperator: 'equals', + triggerValue: content, + }, + }); + if (findTriggerEquals) { + typebot = findTriggerEquals; + } else { + const findTriggerStartsWith = await this.prismaRepository.typebot.findFirst({ + where: { + enabled: true, + triggerType: 'keyword', + triggerOperator: 'startsWith', + triggerValue: { startsWith: content }, + }, + }); + + if (findTriggerStartsWith) { + typebot = findTriggerStartsWith; + } else { + const findTriggerEndsWith = await this.prismaRepository.typebot.findFirst({ + where: { + enabled: true, + triggerType: 'keyword', + triggerOperator: 'endsWith', + triggerValue: { endsWith: content }, + }, + }); + + if (findTriggerEndsWith) { + typebot = findTriggerEndsWith; + } else { + const findTriggerContains = await this.prismaRepository.typebot.findFirst({ + where: { + enabled: true, + triggerType: 'keyword', + triggerOperator: 'contains', + triggerValue: { contains: content }, + }, + }); + + if (findTriggerContains) { + typebot = findTriggerContains; + } + } + } + } + } + + return typebot; + } + + public async sendTypebot(instance: InstanceDto, remoteJid: string, msg: Message) { try { + let session = await this.prismaRepository.typebotSession.findFirst({ + where: { + remoteJid: remoteJid, + }, + }); + + const content = this.getConversationMessage(msg.message); + + let findTypebot = null; + + if (!session) { + findTypebot = await this.findTypebotByTrigger(content); + + 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; + + if ( + !expire || + !keywordFinish || + !delayMessage || + !unknownMessage || + !listeningFromMe || + !stopBotFromMe || + !keepOpen + ) { + const settings = await this.prismaRepository.typebotSetting.findFirst({ + where: { + instanceId: instance.instanceId, + }, + }); + + 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; + } + + const key = msg.key as { + id: string; + remoteJid: string; + fromMe: boolean; + participant: string; + }; + + if (!listeningFromMe && key.fromMe) { + return; + } + + if (stopBotFromMe && listeningFromMe && key.fromMe && session) { + await this.prismaRepository.typebotSession.deleteMany({ + where: { + typebotId: findTypebot.id, + remoteJid: remoteJid, + }, + }); + return; + } + + if (session && !session.awaitUser) return; + if (session && expire && expire > 0) { const now = Date.now(); @@ -547,13 +1185,13 @@ export class TypebotService { if (diffInMinutes > expire) { await this.prismaRepository.typebotSession.deleteMany({ where: { - typebotId: findTypebot.typebot.id, + typebotId: findTypebot.id, remoteJid: remoteJid, }, }); const data = await this.createNewSession(instance, { - enabled: findTypebot.typebot.enabled, + enabled: findTypebot.enabled, url: url, typebot: typebot, expire: expire, @@ -563,14 +1201,30 @@ export class TypebotService { listeningFromMe: listeningFromMe, remoteJid: remoteJid, pushName: msg.pushName, - typebotId: findTypebot.typebot.id, + typebotId: findTypebot.id, }); if (data.session) { session = data.session; } - await this.sendWAMessage(instance, session, remoteJid, data.messages, data.input, data.clientSideActions); + await this.sendWAMessage( + instance, + session, + { + expire: expire, + keywordFinish: keywordFinish, + delayMessage: delayMessage, + unknownMessage: unknownMessage, + listeningFromMe: listeningFromMe, + stopBotFromMe: stopBotFromMe, + keepOpen: keepOpen, + }, + remoteJid, + data.messages, + data.input, + data.clientSideActions, + ); if (data.messages.length === 0) { const content = this.getConversationMessage(msg.message); @@ -589,7 +1243,7 @@ export class TypebotService { if (keywordFinish && content.toLowerCase() === keywordFinish.toLowerCase()) { await this.prismaRepository.typebotSession.deleteMany({ where: { - typebotId: findTypebot.typebot.id, + typebotId: findTypebot.id, remoteJid: remoteJid, }, }); @@ -618,6 +1272,15 @@ export class TypebotService { await this.sendWAMessage( instance, session, + { + expire: expire, + keywordFinish: keywordFinish, + delayMessage: delayMessage, + unknownMessage: unknownMessage, + listeningFromMe: listeningFromMe, + stopBotFromMe: stopBotFromMe, + keepOpen: keepOpen, + }, remoteJid, request.data.messages, request.data.input, @@ -639,7 +1302,7 @@ export class TypebotService { if (!session) { const data = await this.createNewSession(instance, { - enabled: findTypebot.typebot?.enabled, + enabled: findTypebot?.enabled, url: url, typebot: typebot, expire: expire, @@ -649,18 +1312,32 @@ export class TypebotService { listeningFromMe: listeningFromMe, remoteJid: remoteJid, pushName: msg.pushName, - typebotId: findTypebot.typebot.id, + typebotId: findTypebot.id, }); if (data.session) { session = data.session; } - await this.sendWAMessage(instance, session, remoteJid, data?.messages, data?.input, data?.clientSideActions); + await this.sendWAMessage( + instance, + session, + { + expire: expire, + keywordFinish: keywordFinish, + delayMessage: delayMessage, + unknownMessage: unknownMessage, + listeningFromMe: listeningFromMe, + stopBotFromMe: stopBotFromMe, + keepOpen: keepOpen, + }, + remoteJid, + data?.messages, + data?.input, + data?.clientSideActions, + ); if (data.messages.length === 0) { - const content = this.getConversationMessage(msg.message); - if (!content) { if (unknownMessage) { this.waMonitor.waInstances[instance.instanceName].textMessage({ @@ -675,7 +1352,7 @@ export class TypebotService { if (keywordFinish && content.toLowerCase() === keywordFinish.toLowerCase()) { await this.prismaRepository.typebotSession.deleteMany({ where: { - typebotId: findTypebot.typebot.id, + typebotId: findTypebot.id, remoteJid: remoteJid, }, }); @@ -705,6 +1382,15 @@ export class TypebotService { await this.sendWAMessage( instance, session, + { + expire: expire, + keywordFinish: keywordFinish, + delayMessage: delayMessage, + unknownMessage: unknownMessage, + listeningFromMe: listeningFromMe, + stopBotFromMe: stopBotFromMe, + keepOpen: keepOpen, + }, remoteJid, request.data.messages, request.data.input, @@ -728,8 +1414,6 @@ export class TypebotService { }, }); - const content = this.getConversationMessage(msg.message); - if (!content) { if (unknownMessage) { this.waMonitor.waInstances[instance.instanceName].textMessage({ @@ -744,7 +1428,7 @@ export class TypebotService { if (keywordFinish && content.toLowerCase() === keywordFinish.toLowerCase()) { await this.prismaRepository.typebotSession.deleteMany({ where: { - typebotId: findTypebot.typebot.id, + typebotId: findTypebot.id, remoteJid: remoteJid, }, }); @@ -771,6 +1455,15 @@ export class TypebotService { await this.sendWAMessage( instance, session, + { + expire: expire, + keywordFinish: keywordFinish, + delayMessage: delayMessage, + unknownMessage: unknownMessage, + listeningFromMe: listeningFromMe, + stopBotFromMe: stopBotFromMe, + keepOpen: keepOpen, + }, remoteJid, request?.data?.messages, request?.data?.input, diff --git a/src/api/integrations/typebot/validate/typebot.schema.ts b/src/api/integrations/typebot/validate/typebot.schema.ts index cc990713..b0f94f8d 100644 --- a/src/api/integrations/typebot/validate/typebot.schema.ts +++ b/src/api/integrations/typebot/validate/typebot.schema.ts @@ -27,13 +27,18 @@ export const typebotSchema: JSONSchema7 = { enabled: { type: 'boolean' }, url: { type: 'string' }, typebot: { type: 'string' }, + triggerType: { type: 'string', enum: ['all', 'keyword'] }, + triggerOperator: { type: 'string', enum: ['equals', 'contains', 'startsWith', 'endsWith'] }, + triggerValue: { type: 'string' }, expire: { type: 'integer' }, + keywordFinish: { type: 'string' }, delayMessage: { type: 'integer' }, unknownMessage: { type: 'string' }, listeningFromMe: { type: 'boolean' }, + stopBotFromMe: { type: 'boolean' }, }, - required: ['enabled', 'url', 'typebot', 'expire', 'delayMessage', 'unknownMessage', 'listeningFromMe'], - ...isNotEmpty('enabled', 'url', 'typebot', 'expire', 'delayMessage', 'unknownMessage', 'listeningFromMe'), + required: ['enabled', 'url', 'typebot', 'triggerType'], + ...isNotEmpty('enabled', 'url', 'typebot', 'triggerType'), }; export const typebotStatusSchema: JSONSchema7 = { @@ -58,3 +63,19 @@ export const typebotStartSchema: JSONSchema7 = { required: ['remoteJid', 'url', 'typebot'], ...isNotEmpty('remoteJid', 'url', 'typebot'), }; + +export const typebotSettingSchema: JSONSchema7 = { + $id: v4(), + type: 'object', + properties: { + expire: { type: 'integer' }, + keywordFinish: { type: 'string' }, + delayMessage: { type: 'integer' }, + unknownMessage: { type: 'string' }, + listeningFromMe: { type: 'boolean' }, + stopBotFromMe: { type: 'boolean' }, + keepOpen: { type: 'boolean' }, + }, + required: ['expire', 'keywordFinish', 'delayMessage', 'unknownMessage', 'listeningFromMe', 'stopBotFromMe'], + ...isNotEmpty('expire', 'keywordFinish', 'delayMessage', 'unknownMessage', 'listeningFromMe', 'stopBotFromMe'), +}; diff --git a/src/api/server.module.ts b/src/api/server.module.ts index 1c881a78..8c59e58f 100644 --- a/src/api/server.module.ts +++ b/src/api/server.module.ts @@ -54,7 +54,7 @@ export const waMonitor = new WAMonitoringService( const authService = new AuthService(prismaRepository); -const typebotService = new TypebotService(waMonitor, configService, prismaRepository, eventEmitter); +const typebotService = new TypebotService(waMonitor, configService, prismaRepository); export const typebotController = new TypebotController(typebotService); const webhookService = new WebhookService(waMonitor); diff --git a/src/api/services/channel.service.ts b/src/api/services/channel.service.ts index 15524d92..85b0d23b 100644 --- a/src/api/services/channel.service.ts +++ b/src/api/services/channel.service.ts @@ -16,7 +16,6 @@ import { Log, Rabbitmq, Sqs, - Typebot, Webhook, Websocket, } from '../../config/env.config'; @@ -33,7 +32,6 @@ import { RabbitmqDto } from '../integrations/rabbitmq/dto/rabbitmq.dto'; import { getAMQP, removeQueues } from '../integrations/rabbitmq/libs/amqp.server'; import { SqsDto } from '../integrations/sqs/dto/sqs.dto'; import { getSQS, removeQueues as removeQueuesSQS } from '../integrations/sqs/libs/sqs.server'; -import { TypebotDto } from '../integrations/typebot/dto/typebot.dto'; import { TypebotService } from '../integrations/typebot/services/typebot.service'; import { WebsocketDto } from '../integrations/websocket/dto/websocket.dto'; import { getIO } from '../integrations/websocket/libs/socket.server'; @@ -59,7 +57,6 @@ export class ChannelStartupService { public readonly localWebsocket: wa.LocalWebsocket = {}; public readonly localRabbitmq: wa.LocalRabbitmq = {}; public readonly localSqs: wa.LocalSqs = {}; - public readonly localTypebot: wa.LocalTypebot = {}; public readonly localProxy: wa.LocalProxy = {}; public readonly localSettings: wa.LocalSettings = {}; public readonly storePath = join(ROOT_DIR, 'store'); @@ -71,7 +68,7 @@ export class ChannelStartupService { this.chatwootCache, ); - public typebotService = new TypebotService(waMonitor, this.configService, this.prismaRepository, this.eventEmitter); + public typebotService = new TypebotService(waMonitor, this.configService, this.prismaRepository); public setInstance(instance: InstanceDto) { this.instance.name = instance.instanceName; @@ -478,78 +475,6 @@ export class ChannelStartupService { } } - public async loadTypebot() { - if (!this.configService.get('TYPEBOT').ENABLED) { - return; - } - const data = await this.prismaRepository.typebot.findUnique({ - where: { - instanceId: this.instanceId, - }, - include: { - sessions: true, - }, - }); - - this.localTypebot.enabled = data?.enabled; - this.localTypebot.url = data?.url; - this.localTypebot.typebot = data?.typebot; - this.localTypebot.expire = data?.expire; - this.localTypebot.keywordFinish = data?.keywordFinish; - this.localTypebot.delayMessage = data?.delayMessage; - this.localTypebot.unknownMessage = data?.unknownMessage; - this.localTypebot.listeningFromMe = data?.listeningFromMe; - this.localTypebot.sessions = data?.sessions; - } - - public async setTypebot(data: TypebotDto) { - if (!this.configService.get('TYPEBOT').ENABLED) { - return; - } - - const typebot = await this.prismaRepository.typebot.create({ - 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, - instanceId: this.instanceId, - }, - }); - - await this.prismaRepository.typebotSession.deleteMany({ - where: { - typebotId: typebot.id, - }, - }); - - Object.assign(this.localTypebot, data); - } - - public async findTypebot() { - if (!this.configService.get('TYPEBOT').ENABLED) { - return; - } - const data = await this.prismaRepository.typebot.findUnique({ - where: { - instanceId: this.instanceId, - }, - include: { - sessions: true, - }, - }); - - if (!data) { - throw new NotFoundException('Typebot not found'); - } - - return data; - } - public async loadProxy() { const data = await this.prismaRepository.proxy.findUnique({ where: { diff --git a/src/api/services/channels/whatsapp.baileys.service.ts b/src/api/services/channels/whatsapp.baileys.service.ts index 0e7362b9..7b2fe625 100644 --- a/src/api/services/channels/whatsapp.baileys.service.ts +++ b/src/api/services/channels/whatsapp.baileys.service.ts @@ -502,7 +502,6 @@ export class BaileysStartupService extends ChannelStartupService { this.loadWebsocket(); this.loadRabbitmq(); this.loadSqs(); - this.loadTypebot(); this.loadProxy(); this.instance.authState = await this.defineAuthState(); @@ -1182,19 +1181,13 @@ export class BaileysStartupService extends ChannelStartupService { } if (this.configService.get('TYPEBOT').ENABLED) { - const typebotSessionRemoteJid = this.localTypebot.sessions?.find( - (session) => session.remoteJid === received.key.remoteJid, - ); - - if ((this.localTypebot.enabled && type === 'notify') || typebotSessionRemoteJid) { - if (!(this.localTypebot.listeningFromMe === false && messageRaw.key.fromMe === true)) { - if (messageRaw.messageType !== 'reactionMessage') - await this.typebotService.sendTypebot( - { instanceName: this.instance.name, instanceId: this.instanceId }, - messageRaw.key.remoteJid, - messageRaw, - ); - } + if (type === 'notify') { + if (messageRaw.messageType !== 'reactionMessage') + await this.typebotService.sendTypebot( + { instanceName: this.instance.name, instanceId: this.instanceId }, + messageRaw.key.remoteJid, + messageRaw, + ); } } diff --git a/src/api/services/channels/whatsapp.business.service.ts b/src/api/services/channels/whatsapp.business.service.ts index df5d107d..ce442696 100644 --- a/src/api/services/channels/whatsapp.business.service.ts +++ b/src/api/services/channels/whatsapp.business.service.ts @@ -133,7 +133,6 @@ export class BusinessStartupService extends ChannelStartupService { this.loadWebsocket(); this.loadRabbitmq(); this.loadSqs(); - this.loadTypebot(); this.eventHandler(content); @@ -399,20 +398,12 @@ export class BusinessStartupService extends ChannelStartupService { } if (this.configService.get('TYPEBOT').ENABLED) { - const typebotSessionRemoteJid = this.localTypebot.sessions?.find( - (session) => session.remoteJid === key.remoteJid, - ); - - if (this.localTypebot.enabled || typebotSessionRemoteJid) { - if (!(this.localTypebot.listeningFromMe === false && key.fromMe === true)) { - if (messageRaw.messageType !== 'reactionMessage') - await this.typebotService.sendTypebot( - { instanceName: this.instance.name }, - messageRaw.key.remoteJid, - messageRaw, - ); - } - } + if (messageRaw.messageType !== 'reactionMessage') + await this.typebotService.sendTypebot( + { instanceName: this.instance.name }, + messageRaw.key.remoteJid, + messageRaw, + ); } await this.prismaRepository.message.create({ diff --git a/src/api/services/monitor.service.ts b/src/api/services/monitor.service.ts index 6cacc2f9..f9a42c8b 100644 --- a/src/api/services/monitor.service.ts +++ b/src/api/services/monitor.service.ts @@ -262,6 +262,7 @@ export class WAMonitoringService { public async saveInstance(data: any) { try { if (this.db.ENABLED) { + const clientName = await this.configService.get('DATABASE').CONNECTION.CLIENT_NAME; await this.prismaRepository.instance.create({ data: { id: data.instanceId, @@ -270,6 +271,7 @@ export class WAMonitoringService { number: data.number, integration: data.integration || Integration.WHATSAPP_BAILEYS, token: data.hash, + clientName: clientName, }, }); } diff --git a/src/api/types/wa.types.ts b/src/api/types/wa.types.ts index 6514c735..05b57b0a 100644 --- a/src/api/types/wa.types.ts +++ b/src/api/types/wa.types.ts @@ -1,5 +1,4 @@ /* eslint-disable @typescript-eslint/no-namespace */ -import { TypebotSession } from '@prisma/client'; import { JsonValue } from '@prisma/client/runtime/library'; import { AuthenticationState, WAConnectionState } from '@whiskeysockets/baileys'; @@ -113,18 +112,6 @@ export declare namespace wa { createdAt?: number; }; - export type LocalTypebot = { - enabled?: boolean; - url?: string; - typebot?: string; - expire?: number; - keywordFinish?: string; - delayMessage?: number; - unknownMessage?: string; - listeningFromMe?: boolean; - sessions?: TypebotSession[]; - }; - export type LocalProxy = { enabled?: boolean; host?: string; diff --git a/src/config/env.config.ts b/src/config/env.config.ts index 310cf672..d1e0ebeb 100644 --- a/src/config/env.config.ts +++ b/src/config/env.config.ts @@ -62,7 +62,7 @@ export type CleanStoreConf = { export type DBConnection = { URI: string; - DB_PREFIX_NAME: string; + CLIENT_NAME: string; }; export type Database = { CONNECTION: DBConnection; @@ -190,7 +190,7 @@ export type SslConf = { PRIVKEY: string; FULLCHAIN: string }; export type Webhook = { GLOBAL?: GlobalWebhook; EVENTS: EventsWebhook }; export type ConfigSessionPhone = { CLIENT: string; NAME: string; VERSION: string }; export type QrCode = { LIMIT: number; COLOR: string }; -export type Typebot = { ENABLED: boolean; API_VERSION: string; KEEP_OPEN: boolean }; +export type Typebot = { ENABLED: boolean; API_VERSION: string }; export type Chatwoot = { ENABLED: boolean; MESSAGE_DELETE: boolean; @@ -299,7 +299,7 @@ export class ConfigService { DATABASE: { CONNECTION: { URI: process.env.DATABASE_CONNECTION_URI || '', - DB_PREFIX_NAME: process.env.DATABASE_CONNECTION_DB_PREFIX_NAME || 'evolution', + CLIENT_NAME: process.env.DATABASE_CONNECTION_CLIENT_NAME || 'evolution', }, ENABLED: process.env?.DATABASE_ENABLED === 'true', PROVIDER: process.env.DATABASE_PROVIDER || 'postgresql', @@ -433,7 +433,6 @@ export class ConfigService { TYPEBOT: { ENABLED: process.env?.TYPEBOT_ENABLED === 'true', API_VERSION: process.env?.TYPEBOT_API_VERSION || 'old', - KEEP_OPEN: process.env.TYPEBOT_KEEP_OPEN === 'true', }, CHATWOOT: { ENABLED: process.env?.CHATWOOT_ENABLED === 'true',