Merge remote-tracking branch 'upstream/v2.0.0' into v2.0.0

This commit is contained in:
Diego Marino 2024-08-22 21:33:55 -03:00
commit 9099adf363
58 changed files with 8335 additions and 5905 deletions

View File

@ -135,6 +135,10 @@ CONFIG_SESSION_PHONE_CLIENT=Evolution API
# Browser Name = Chrome | Firefox | Edge | Opera | Safari
CONFIG_SESSION_PHONE_NAME=Chrome
# Whatsapp Web version for baileys channel
# https://web.whatsapp.com/check-update?version=0&platform=web
CONFIG_SESSION_PHONE_VERSION=2.3000.1015901307
# Set qrcode display limit
QRCODE_LIMIT=30
# Color of the QRCode on base64
@ -151,6 +155,8 @@ CHATWOOT_ENABLED=false
CHATWOOT_MESSAGE_READ=true
# If you leave this option as true, when sending a message in Chatwoot, the client's last message will be marked as read on WhatsApp.
CHATWOOT_MESSAGE_DELETE=true
# If you leave this option as true, a contact will be created on Chatwoot to provide the QR Code and update messages about the instance.
CHATWOOT_BOT_CONTACT=true
# This db connection is used to import messages from whatsapp to chatwoot database
CHATWOOT_IMPORT_DATABASE_CONNECTION_URI=postgresql://user:passwprd@host:5432/chatwoot?sslmode=disable
CHATWOOT_IMPORT_PLACEHOLDER_MEDIA_MESSAGE=true

View File

@ -4,10 +4,15 @@
* Improved layout manager
* Translation in manager: English, Portuguese, Spanish and French
* Generic Bot Integration
* Option to disable chatwoot bot contact with CHATWOOT_BOT_CONTACT
* Added flowise integration
* Added evolution channel on instance create
### Fixed
* Refactor websocket structure
* Refactor integrations structure for modular system
* Fixed dify agent integration
# 2.0.10 (2024-08-16 16:23)

View File

@ -2,7 +2,7 @@ version: "3.7"
services:
evolution_v2:
image: atendai/evolution-api:v2.0.9
image: atendai/evolution-api:v2.0.10
volumes:
- evolution_instances:/evolution/instances
networks:
@ -92,7 +92,7 @@ services:
- WEBHOOK_EVENTS_ERRORS_WEBHOOK=
- CONFIG_SESSION_PHONE_CLIENT=Evolution API V2
- CONFIG_SESSION_PHONE_NAME=Chrome
- CONFIG_SESSION_PHONE_VERSION=2.2413.51
- CONFIG_SESSION_PHONE_VERSION=2.3000.1015901307
- QRCODE_LIMIT=30
- OPENAI_ENABLED=true
- DIFY_ENABLED=true

381
manager/dist/assets/index-BmAfUzu7.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -5,7 +5,7 @@
<link rel="icon" type="image/png" href="/assets/images/evolution-logo.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Evolution Manager</title>
<script type="module" crossorigin src="/assets/index-CNPbB3iJ.js"></script>
<script type="module" crossorigin src="/assets/index-BmAfUzu7.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-BJ9JMAl_.css">
</head>
<body>

View File

@ -169,17 +169,5 @@ ALTER TABLE `Websocket`
MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
MODIFY `updatedAt` TIMESTAMP NOT NULL;
-- Remove the duplicates
DELETE c1
FROM `Contact` c1
INNER JOIN (
SELECT MIN(id) as id
FROM `Contact`
GROUP BY
`remoteJid`, `instanceId`
) c2 ON c1.`remoteJid` = c2.`remoteJid`
AND c1.`instanceId` = c2.`instanceId`
AND c1.id != c2.id;
-- CreateIndex
CREATE UNIQUE INDEX `Contact_remoteJid_instanceId_key` ON `Contact` (`remoteJid`, `instanceId`);

View File

@ -0,0 +1,269 @@
/*
Warnings:
- You are about to alter the column `createdAt` on the `Chat` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `updatedAt` on the `Chat` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `createdAt` on the `Chatwoot` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `updatedAt` on the `Chatwoot` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `createdAt` on the `Contact` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `updatedAt` on the `Contact` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `createdAt` on the `Dify` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `updatedAt` on the `Dify` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `createdAt` on the `DifySetting` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `updatedAt` on the `DifySetting` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `disconnectionAt` on the `Instance` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `createdAt` on the `Instance` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `updatedAt` on the `Instance` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to drop the column `difyId` on the `IntegrationSession` table. All the data in the column will be lost.
- You are about to drop the column `openaiBotId` on the `IntegrationSession` table. All the data in the column will be lost.
- You are about to drop the column `typebotId` on the `IntegrationSession` table. All the data in the column will be lost.
- You are about to alter the column `createdAt` on the `IntegrationSession` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `updatedAt` on the `IntegrationSession` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `createdAt` on the `Label` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `updatedAt` on the `Label` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `createdAt` on the `Media` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `createdAt` on the `OpenaiBot` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `updatedAt` on the `OpenaiBot` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `createdAt` on the `OpenaiCreds` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `updatedAt` on the `OpenaiCreds` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `createdAt` on the `OpenaiSetting` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `updatedAt` on the `OpenaiSetting` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `createdAt` on the `Proxy` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `updatedAt` on the `Proxy` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `createdAt` on the `Rabbitmq` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `updatedAt` on the `Rabbitmq` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `createdAt` on the `Session` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `createdAt` on the `Setting` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `updatedAt` on the `Setting` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `createdAt` on the `Sqs` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `updatedAt` on the `Sqs` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `createdAt` on the `Template` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `updatedAt` on the `Template` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `createdAt` on the `Typebot` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `updatedAt` on the `Typebot` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `createdAt` on the `TypebotSetting` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `updatedAt` on the `TypebotSetting` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `createdAt` on the `Webhook` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `updatedAt` on the `Webhook` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `createdAt` on the `Websocket` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `updatedAt` on the `Websocket` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
*/
-- DropForeignKey
ALTER TABLE `IntegrationSession` DROP FOREIGN KEY `IntegrationSession_difyId_fkey`;
-- DropForeignKey
ALTER TABLE `IntegrationSession` DROP FOREIGN KEY `IntegrationSession_openaiBotId_fkey`;
-- DropForeignKey
ALTER TABLE `IntegrationSession` DROP FOREIGN KEY `IntegrationSession_typebotId_fkey`;
-- DropIndex
DROP INDEX `Message_typebotSessionId_fkey` ON `Message`;
-- AlterTable
ALTER TABLE `Chat` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
MODIFY `updatedAt` TIMESTAMP NULL;
-- AlterTable
ALTER TABLE `Chatwoot` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
MODIFY `updatedAt` TIMESTAMP NOT NULL;
-- AlterTable
ALTER TABLE `Contact` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
MODIFY `updatedAt` TIMESTAMP NULL;
-- AlterTable
ALTER TABLE `Dify` MODIFY `triggerType` ENUM('all', 'keyword', 'none', 'advanced') NULL,
MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
MODIFY `updatedAt` TIMESTAMP NOT NULL;
-- AlterTable
ALTER TABLE `DifySetting` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
MODIFY `updatedAt` TIMESTAMP NOT NULL;
-- AlterTable
ALTER TABLE `Instance` MODIFY `disconnectionAt` TIMESTAMP NULL,
MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
MODIFY `updatedAt` TIMESTAMP NULL;
-- AlterTable
ALTER TABLE `IntegrationSession` DROP COLUMN `difyId`,
DROP COLUMN `openaiBotId`,
DROP COLUMN `typebotId`,
ADD COLUMN `botId` VARCHAR(191) NULL,
ADD COLUMN `context` JSON NULL,
MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
MODIFY `updatedAt` TIMESTAMP NOT NULL;
-- AlterTable
ALTER TABLE `Label` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
MODIFY `updatedAt` TIMESTAMP NOT NULL;
-- AlterTable
ALTER TABLE `Media` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP;
-- AlterTable
ALTER TABLE `OpenaiBot` MODIFY `triggerType` ENUM('all', 'keyword', 'none', 'advanced') NULL,
MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
MODIFY `updatedAt` TIMESTAMP NOT NULL;
-- AlterTable
ALTER TABLE `OpenaiCreds` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
MODIFY `updatedAt` TIMESTAMP NOT NULL;
-- AlterTable
ALTER TABLE `OpenaiSetting` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
MODIFY `updatedAt` TIMESTAMP NOT NULL;
-- AlterTable
ALTER TABLE `Proxy` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
MODIFY `updatedAt` TIMESTAMP NOT NULL;
-- AlterTable
ALTER TABLE `Rabbitmq` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
MODIFY `updatedAt` TIMESTAMP NOT NULL;
-- AlterTable
ALTER TABLE `Session` MODIFY `createdAt` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP;
-- AlterTable
ALTER TABLE `Setting` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
MODIFY `updatedAt` TIMESTAMP NOT NULL;
-- AlterTable
ALTER TABLE `Sqs` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
MODIFY `updatedAt` TIMESTAMP NOT NULL;
-- AlterTable
ALTER TABLE `Template` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
MODIFY `updatedAt` TIMESTAMP NOT NULL;
-- AlterTable
ALTER TABLE `Typebot` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
MODIFY `updatedAt` TIMESTAMP NULL,
MODIFY `triggerType` ENUM('all', 'keyword', 'none', 'advanced') NULL;
-- AlterTable
ALTER TABLE `TypebotSetting` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
MODIFY `updatedAt` TIMESTAMP NOT NULL;
-- AlterTable
ALTER TABLE `Webhook` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
MODIFY `updatedAt` TIMESTAMP NOT NULL;
-- AlterTable
ALTER TABLE `Websocket` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
MODIFY `updatedAt` TIMESTAMP NOT NULL;
-- CreateTable
CREATE TABLE `GenericBot` (
`id` VARCHAR(191) NOT NULL,
`enabled` BOOLEAN NOT NULL DEFAULT true,
`description` VARCHAR(255) NULL,
`apiUrl` VARCHAR(255) NULL,
`apiKey` VARCHAR(255) NULL,
`expire` INTEGER NULL DEFAULT 0,
`keywordFinish` VARCHAR(100) NULL,
`delayMessage` INTEGER NULL,
`unknownMessage` VARCHAR(100) NULL,
`listeningFromMe` BOOLEAN NULL DEFAULT false,
`stopBotFromMe` BOOLEAN NULL DEFAULT false,
`keepOpen` BOOLEAN NULL DEFAULT false,
`debounceTime` INTEGER NULL,
`ignoreJids` JSON NULL,
`triggerType` ENUM('all', 'keyword', 'none', 'advanced') NULL,
`triggerOperator` ENUM('contains', 'equals', 'startsWith', 'endsWith', 'regex') NULL,
`triggerValue` VARCHAR(191) NULL,
`createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
`updatedAt` TIMESTAMP NOT NULL,
`instanceId` VARCHAR(191) NOT NULL,
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateTable
CREATE TABLE `GenericSetting` (
`id` VARCHAR(191) NOT NULL,
`expire` INTEGER NULL DEFAULT 0,
`keywordFinish` VARCHAR(100) NULL,
`delayMessage` INTEGER NULL,
`unknownMessage` VARCHAR(100) NULL,
`listeningFromMe` BOOLEAN NULL DEFAULT false,
`stopBotFromMe` BOOLEAN NULL DEFAULT false,
`keepOpen` BOOLEAN NULL DEFAULT false,
`debounceTime` INTEGER NULL,
`ignoreJids` JSON NULL,
`createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
`updatedAt` TIMESTAMP NOT NULL,
`botIdFallback` VARCHAR(100) NULL,
`instanceId` VARCHAR(191) NOT NULL,
UNIQUE INDEX `GenericSetting_instanceId_key`(`instanceId`),
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateTable
CREATE TABLE `Flowise` (
`id` VARCHAR(191) NOT NULL,
`enabled` BOOLEAN NOT NULL DEFAULT true,
`description` VARCHAR(255) NULL,
`apiUrl` VARCHAR(255) NULL,
`apiKey` VARCHAR(255) NULL,
`expire` INTEGER NULL DEFAULT 0,
`keywordFinish` VARCHAR(100) NULL,
`delayMessage` INTEGER NULL,
`unknownMessage` VARCHAR(100) NULL,
`listeningFromMe` BOOLEAN NULL DEFAULT false,
`stopBotFromMe` BOOLEAN NULL DEFAULT false,
`keepOpen` BOOLEAN NULL DEFAULT false,
`debounceTime` INTEGER NULL,
`ignoreJids` JSON NULL,
`triggerType` ENUM('all', 'keyword', 'none', 'advanced') NULL,
`triggerOperator` ENUM('contains', 'equals', 'startsWith', 'endsWith', 'regex') NULL,
`triggerValue` VARCHAR(191) NULL,
`createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
`updatedAt` TIMESTAMP NOT NULL,
`instanceId` VARCHAR(191) NOT NULL,
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateTable
CREATE TABLE `FlowiseSetting` (
`id` VARCHAR(191) NOT NULL,
`expire` INTEGER NULL DEFAULT 0,
`keywordFinish` VARCHAR(100) NULL,
`delayMessage` INTEGER NULL,
`unknownMessage` VARCHAR(100) NULL,
`listeningFromMe` BOOLEAN NULL DEFAULT false,
`stopBotFromMe` BOOLEAN NULL DEFAULT false,
`keepOpen` BOOLEAN NULL DEFAULT false,
`debounceTime` INTEGER NULL,
`ignoreJids` JSON NULL,
`createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
`updatedAt` TIMESTAMP NOT NULL,
`flowiseIdFallback` VARCHAR(100) NULL,
`instanceId` VARCHAR(191) NOT NULL,
UNIQUE INDEX `FlowiseSetting_instanceId_key`(`instanceId`),
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- AddForeignKey
ALTER TABLE `GenericBot` ADD CONSTRAINT `GenericBot_instanceId_fkey` FOREIGN KEY (`instanceId`) REFERENCES `Instance`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `GenericSetting` ADD CONSTRAINT `GenericSetting_botIdFallback_fkey` FOREIGN KEY (`botIdFallback`) REFERENCES `GenericBot`(`id`) ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `GenericSetting` ADD CONSTRAINT `GenericSetting_instanceId_fkey` FOREIGN KEY (`instanceId`) REFERENCES `Instance`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `Flowise` ADD CONSTRAINT `Flowise_instanceId_fkey` FOREIGN KEY (`instanceId`) REFERENCES `Instance`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `FlowiseSetting` ADD CONSTRAINT `FlowiseSetting_flowiseIdFallback_fkey` FOREIGN KEY (`flowiseIdFallback`) REFERENCES `Flowise`(`id`) ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `FlowiseSetting` ADD CONSTRAINT `FlowiseSetting_instanceId_fkey` FOREIGN KEY (`instanceId`) REFERENCES `Instance`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;

View File

@ -100,6 +100,10 @@ model Instance {
Dify Dify[]
DifySetting DifySetting?
integrationSessions IntegrationSession[]
GenericBot GenericBot[]
GenericSetting GenericSetting?
Flowise Flowise[]
FlowiseSetting FlowiseSetting?
}
model Session {
@ -284,29 +288,28 @@ model Websocket {
}
model Typebot {
id String @id @default(cuid())
enabled Boolean @default(true)
description String? @db.VarChar(255)
url String @db.VarChar(500)
typebot String @db.VarChar(100)
expire Int? @default(0) @db.Int
keywordFinish String? @db.VarChar(100)
delayMessage Int? @db.Int
unknownMessage String? @db.VarChar(100)
listeningFromMe Boolean? @default(false)
stopBotFromMe Boolean? @default(false)
keepOpen Boolean? @default(false)
debounceTime Int? @db.Int
createdAt DateTime? @default(dbgenerated("CURRENT_TIMESTAMP")) @db.Timestamp
updatedAt DateTime? @updatedAt @db.Timestamp
id String @id @default(cuid())
enabled Boolean @default(true)
description String? @db.VarChar(255)
url String @db.VarChar(500)
typebot String @db.VarChar(100)
expire Int? @default(0) @db.Int
keywordFinish String? @db.VarChar(100)
delayMessage Int? @db.Int
unknownMessage String? @db.VarChar(100)
listeningFromMe Boolean? @default(false)
stopBotFromMe Boolean? @default(false)
keepOpen Boolean? @default(false)
debounceTime Int? @db.Int
createdAt DateTime? @default(dbgenerated("CURRENT_TIMESTAMP")) @db.Timestamp
updatedAt DateTime? @updatedAt @db.Timestamp
ignoreJids Json?
triggerType TriggerType?
triggerOperator TriggerOperator?
triggerValue String?
Instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)
Instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)
instanceId String
TypebotSetting TypebotSetting[]
sessions IntegrationSession[]
}
model TypebotSetting {
@ -335,6 +338,7 @@ model IntegrationSession {
pushName String?
status SessionStatus
awaitUser Boolean @default(false)
context Json?
createdAt DateTime? @default(dbgenerated("CURRENT_TIMESTAMP")) @db.Timestamp
updatedAt DateTime @updatedAt @db.Timestamp
Message Message[]
@ -342,14 +346,7 @@ model IntegrationSession {
instanceId String
parameters Json?
OpenaiBot OpenaiBot? @relation(fields: [openaiBotId], references: [id], onDelete: Cascade)
openaiBotId String?
DifyBot Dify? @relation(fields: [difyId], references: [id], onDelete: Cascade)
difyId String?
Typebot Typebot? @relation(fields: [typebotId], references: [id], onDelete: Cascade)
typebotId String?
botId String?
}
model Media {
@ -377,37 +374,36 @@ model OpenaiCreds {
}
model OpenaiBot {
id String @id @default(cuid())
enabled Boolean @default(true)
description String? @db.VarChar(255)
id String @id @default(cuid())
enabled Boolean @default(true)
description String? @db.VarChar(255)
botType OpenaiBotType
assistantId String? @db.VarChar(255)
functionUrl String? @db.VarChar(500)
model String? @db.VarChar(100)
systemMessages Json? @db.Json
assistantMessages Json? @db.Json
userMessages Json? @db.Json
maxTokens Int? @db.Int
expire Int? @default(0) @db.Int
keywordFinish String? @db.VarChar(100)
delayMessage Int? @db.Int
unknownMessage String? @db.VarChar(100)
listeningFromMe Boolean? @default(false)
stopBotFromMe Boolean? @default(false)
keepOpen Boolean? @default(false)
debounceTime Int? @db.Int
assistantId String? @db.VarChar(255)
functionUrl String? @db.VarChar(500)
model String? @db.VarChar(100)
systemMessages Json? @db.Json
assistantMessages Json? @db.Json
userMessages Json? @db.Json
maxTokens Int? @db.Int
expire Int? @default(0) @db.Int
keywordFinish String? @db.VarChar(100)
delayMessage Int? @db.Int
unknownMessage String? @db.VarChar(100)
listeningFromMe Boolean? @default(false)
stopBotFromMe Boolean? @default(false)
keepOpen Boolean? @default(false)
debounceTime Int? @db.Int
ignoreJids Json?
triggerType TriggerType?
triggerOperator TriggerOperator?
triggerValue String?
createdAt DateTime? @default(dbgenerated("CURRENT_TIMESTAMP")) @db.Timestamp
updatedAt DateTime @updatedAt @db.Timestamp
OpenaiCreds OpenaiCreds @relation(fields: [openaiCredsId], references: [id], onDelete: Cascade)
createdAt DateTime? @default(dbgenerated("CURRENT_TIMESTAMP")) @db.Timestamp
updatedAt DateTime @updatedAt @db.Timestamp
OpenaiCreds OpenaiCreds @relation(fields: [openaiCredsId], references: [id], onDelete: Cascade)
openaiCredsId String
Instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)
Instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)
instanceId String
OpenaiSetting OpenaiSetting[]
sessions IntegrationSession[]
}
model OpenaiSetting {
@ -445,30 +441,29 @@ model Template {
}
model Dify {
id String @id @default(cuid())
enabled Boolean @default(true)
description String? @db.VarChar(255)
id String @id @default(cuid())
enabled Boolean @default(true)
description String? @db.VarChar(255)
botType DifyBotType
apiUrl String? @db.VarChar(255)
apiKey String? @db.VarChar(255)
expire Int? @default(0) @db.Int
keywordFinish String? @db.VarChar(100)
delayMessage Int? @db.Int
unknownMessage String? @db.VarChar(100)
listeningFromMe Boolean? @default(false)
stopBotFromMe Boolean? @default(false)
keepOpen Boolean? @default(false)
debounceTime Int? @db.Int
apiUrl String? @db.VarChar(255)
apiKey String? @db.VarChar(255)
expire Int? @default(0) @db.Int
keywordFinish String? @db.VarChar(100)
delayMessage Int? @db.Int
unknownMessage String? @db.VarChar(100)
listeningFromMe Boolean? @default(false)
stopBotFromMe Boolean? @default(false)
keepOpen Boolean? @default(false)
debounceTime Int? @db.Int
ignoreJids Json?
triggerType TriggerType?
triggerOperator TriggerOperator?
triggerValue String?
createdAt DateTime? @default(dbgenerated("CURRENT_TIMESTAMP")) @db.Timestamp
updatedAt DateTime @updatedAt @db.Timestamp
Instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)
createdAt DateTime? @default(dbgenerated("CURRENT_TIMESTAMP")) @db.Timestamp
updatedAt DateTime @updatedAt @db.Timestamp
Instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)
instanceId String
DifySetting DifySetting[]
sessions IntegrationSession[]
}
model DifySetting {
@ -489,3 +484,91 @@ model DifySetting {
Instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)
instanceId String @unique
}
model GenericBot {
id String @id @default(cuid())
enabled Boolean @default(true)
description String? @db.VarChar(255)
apiUrl String? @db.VarChar(255)
apiKey String? @db.VarChar(255)
expire Int? @default(0) @db.Int
keywordFinish String? @db.VarChar(100)
delayMessage Int? @db.Int
unknownMessage String? @db.VarChar(100)
listeningFromMe Boolean? @default(false)
stopBotFromMe Boolean? @default(false)
keepOpen Boolean? @default(false)
debounceTime Int? @db.Int
ignoreJids Json?
triggerType TriggerType?
triggerOperator TriggerOperator?
triggerValue String?
createdAt DateTime? @default(dbgenerated("CURRENT_TIMESTAMP")) @db.Timestamp
updatedAt DateTime @updatedAt @db.Timestamp
Instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)
instanceId String
GenericSetting GenericSetting[]
}
model GenericSetting {
id String @id @default(cuid())
expire Int? @default(0) @db.Int
keywordFinish String? @db.VarChar(100)
delayMessage Int? @db.Int
unknownMessage String? @db.VarChar(100)
listeningFromMe Boolean? @default(false)
stopBotFromMe Boolean? @default(false)
keepOpen Boolean? @default(false)
debounceTime Int? @db.Int
ignoreJids Json?
createdAt DateTime? @default(dbgenerated("CURRENT_TIMESTAMP")) @db.Timestamp
updatedAt DateTime @updatedAt @db.Timestamp
Fallback GenericBot? @relation(fields: [botIdFallback], references: [id])
botIdFallback String? @db.VarChar(100)
Instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)
instanceId String @unique
}
model Flowise {
id String @id @default(cuid())
enabled Boolean @default(true)
description String? @db.VarChar(255)
apiUrl String? @db.VarChar(255)
apiKey String? @db.VarChar(255)
expire Int? @default(0) @db.Int
keywordFinish String? @db.VarChar(100)
delayMessage Int? @db.Int
unknownMessage String? @db.VarChar(100)
listeningFromMe Boolean? @default(false)
stopBotFromMe Boolean? @default(false)
keepOpen Boolean? @default(false)
debounceTime Int? @db.Int
ignoreJids Json?
triggerType TriggerType?
triggerOperator TriggerOperator?
triggerValue String?
createdAt DateTime? @default(dbgenerated("CURRENT_TIMESTAMP")) @db.Timestamp
updatedAt DateTime @updatedAt @db.Timestamp
Instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)
instanceId String
FlowiseSetting FlowiseSetting[]
}
model FlowiseSetting {
id String @id @default(cuid())
expire Int? @default(0) @db.Int
keywordFinish String? @db.VarChar(100)
delayMessage Int? @db.Int
unknownMessage String? @db.VarChar(100)
listeningFromMe Boolean? @default(false)
stopBotFromMe Boolean? @default(false)
keepOpen Boolean? @default(false)
debounceTime Int? @db.Int
ignoreJids Json?
createdAt DateTime? @default(dbgenerated("CURRENT_TIMESTAMP")) @db.Timestamp
updatedAt DateTime @updatedAt @db.Timestamp
Fallback Flowise? @relation(fields: [flowiseIdFallback], references: [id])
flowiseIdFallback String? @db.VarChar(100)
Instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)
instanceId String @unique
}

View File

@ -0,0 +1,22 @@
/*
Warnings:
- You are about to drop the column `difyId` on the `IntegrationSession` table. All the data in the column will be lost.
- You are about to drop the column `openaiBotId` on the `IntegrationSession` table. All the data in the column will be lost.
- You are about to drop the column `typebotId` on the `IntegrationSession` table. All the data in the column will be lost.
*/
-- DropForeignKey
ALTER TABLE "IntegrationSession" DROP CONSTRAINT "IntegrationSession_difyId_fkey";
-- DropForeignKey
ALTER TABLE "IntegrationSession" DROP CONSTRAINT "IntegrationSession_openaiBotId_fkey";
-- DropForeignKey
ALTER TABLE "IntegrationSession" DROP CONSTRAINT "IntegrationSession_typebotId_fkey";
-- AlterTable
ALTER TABLE "IntegrationSession" DROP COLUMN "difyId",
DROP COLUMN "openaiBotId",
DROP COLUMN "typebotId",
ADD COLUMN "botId" TEXT;

View File

@ -0,0 +1,57 @@
-- CreateTable
CREATE TABLE "GenericBot" (
"id" TEXT NOT NULL,
"enabled" BOOLEAN NOT NULL DEFAULT true,
"description" VARCHAR(255),
"apiUrl" VARCHAR(255),
"apiKey" VARCHAR(255),
"expire" INTEGER DEFAULT 0,
"keywordFinish" VARCHAR(100),
"delayMessage" INTEGER,
"unknownMessage" VARCHAR(100),
"listeningFromMe" BOOLEAN DEFAULT false,
"stopBotFromMe" BOOLEAN DEFAULT false,
"keepOpen" BOOLEAN DEFAULT false,
"debounceTime" INTEGER,
"ignoreJids" JSONB,
"triggerType" "TriggerType",
"triggerOperator" "TriggerOperator",
"triggerValue" TEXT,
"createdAt" TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP NOT NULL,
"instanceId" TEXT NOT NULL,
CONSTRAINT "GenericBot_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "GenericSetting" (
"id" TEXT NOT NULL,
"expire" INTEGER DEFAULT 0,
"keywordFinish" VARCHAR(100),
"delayMessage" INTEGER,
"unknownMessage" VARCHAR(100),
"listeningFromMe" BOOLEAN DEFAULT false,
"stopBotFromMe" BOOLEAN DEFAULT false,
"keepOpen" BOOLEAN DEFAULT false,
"debounceTime" INTEGER,
"ignoreJids" JSONB,
"createdAt" TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP NOT NULL,
"botIdFallback" VARCHAR(100),
"instanceId" TEXT NOT NULL,
CONSTRAINT "GenericSetting_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "GenericSetting_instanceId_key" ON "GenericSetting"("instanceId");
-- AddForeignKey
ALTER TABLE "GenericBot" ADD CONSTRAINT "GenericBot_instanceId_fkey" FOREIGN KEY ("instanceId") REFERENCES "Instance"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "GenericSetting" ADD CONSTRAINT "GenericSetting_botIdFallback_fkey" FOREIGN KEY ("botIdFallback") REFERENCES "GenericBot"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "GenericSetting" ADD CONSTRAINT "GenericSetting_instanceId_fkey" FOREIGN KEY ("instanceId") REFERENCES "Instance"("id") ON DELETE CASCADE ON UPDATE CASCADE;

View File

@ -0,0 +1,57 @@
-- CreateTable
CREATE TABLE "Flowise" (
"id" TEXT NOT NULL,
"enabled" BOOLEAN NOT NULL DEFAULT true,
"description" VARCHAR(255),
"apiUrl" VARCHAR(255),
"apiKey" VARCHAR(255),
"expire" INTEGER DEFAULT 0,
"keywordFinish" VARCHAR(100),
"delayMessage" INTEGER,
"unknownMessage" VARCHAR(100),
"listeningFromMe" BOOLEAN DEFAULT false,
"stopBotFromMe" BOOLEAN DEFAULT false,
"keepOpen" BOOLEAN DEFAULT false,
"debounceTime" INTEGER,
"ignoreJids" JSONB,
"triggerType" "TriggerType",
"triggerOperator" "TriggerOperator",
"triggerValue" TEXT,
"createdAt" TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP NOT NULL,
"instanceId" TEXT NOT NULL,
CONSTRAINT "Flowise_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "FlowiseSetting" (
"id" TEXT NOT NULL,
"expire" INTEGER DEFAULT 0,
"keywordFinish" VARCHAR(100),
"delayMessage" INTEGER,
"unknownMessage" VARCHAR(100),
"listeningFromMe" BOOLEAN DEFAULT false,
"stopBotFromMe" BOOLEAN DEFAULT false,
"keepOpen" BOOLEAN DEFAULT false,
"debounceTime" INTEGER,
"ignoreJids" JSONB,
"createdAt" TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP NOT NULL,
"flowiseIdFallback" VARCHAR(100),
"instanceId" TEXT NOT NULL,
CONSTRAINT "FlowiseSetting_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "FlowiseSetting_instanceId_key" ON "FlowiseSetting"("instanceId");
-- AddForeignKey
ALTER TABLE "Flowise" ADD CONSTRAINT "Flowise_instanceId_fkey" FOREIGN KEY ("instanceId") REFERENCES "Instance"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "FlowiseSetting" ADD CONSTRAINT "FlowiseSetting_flowiseIdFallback_fkey" FOREIGN KEY ("flowiseIdFallback") REFERENCES "Flowise"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "FlowiseSetting" ADD CONSTRAINT "FlowiseSetting_instanceId_fkey" FOREIGN KEY ("instanceId") REFERENCES "Instance"("id") ON DELETE CASCADE ON UPDATE CASCADE;

View File

@ -100,6 +100,10 @@ model Instance {
Dify Dify[]
DifySetting DifySetting?
integrationSessions IntegrationSession[]
GenericBot GenericBot[]
GenericSetting GenericSetting?
Flowise Flowise[]
FlowiseSetting FlowiseSetting?
}
model Session {
@ -285,29 +289,28 @@ model Websocket {
}
model Typebot {
id String @id @default(cuid())
enabled Boolean @default(true) @db.Boolean
description String? @db.VarChar(255)
url String @db.VarChar(500)
typebot String @db.VarChar(100)
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
debounceTime Int? @db.Integer
createdAt DateTime? @default(now()) @db.Timestamp
updatedAt DateTime? @updatedAt @db.Timestamp
id String @id @default(cuid())
enabled Boolean @default(true) @db.Boolean
description String? @db.VarChar(255)
url String @db.VarChar(500)
typebot String @db.VarChar(100)
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
debounceTime Int? @db.Integer
createdAt DateTime? @default(now()) @db.Timestamp
updatedAt DateTime? @updatedAt @db.Timestamp
ignoreJids Json?
triggerType TriggerType?
triggerOperator TriggerOperator?
triggerValue String?
Instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)
Instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)
instanceId String
TypebotSetting TypebotSetting[]
sessions IntegrationSession[]
}
model TypebotSetting {
@ -354,37 +357,36 @@ model OpenaiCreds {
}
model OpenaiBot {
id String @id @default(cuid())
enabled Boolean @default(true) @db.Boolean
description String? @db.VarChar(255)
id String @id @default(cuid())
enabled Boolean @default(true) @db.Boolean
description String? @db.VarChar(255)
botType OpenaiBotType
assistantId String? @db.VarChar(255)
functionUrl String? @db.VarChar(500)
model String? @db.VarChar(100)
systemMessages Json? @db.JsonB
assistantMessages Json? @db.JsonB
userMessages Json? @db.JsonB
maxTokens Int? @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
stopBotFromMe Boolean? @default(false) @db.Boolean
keepOpen Boolean? @default(false) @db.Boolean
debounceTime Int? @db.Integer
assistantId String? @db.VarChar(255)
functionUrl String? @db.VarChar(500)
model String? @db.VarChar(100)
systemMessages Json? @db.JsonB
assistantMessages Json? @db.JsonB
userMessages Json? @db.JsonB
maxTokens Int? @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
stopBotFromMe Boolean? @default(false) @db.Boolean
keepOpen Boolean? @default(false) @db.Boolean
debounceTime Int? @db.Integer
ignoreJids Json?
triggerType TriggerType?
triggerOperator TriggerOperator?
triggerValue String?
createdAt DateTime? @default(now()) @db.Timestamp
updatedAt DateTime @updatedAt @db.Timestamp
OpenaiCreds OpenaiCreds @relation(fields: [openaiCredsId], references: [id], onDelete: Cascade)
createdAt DateTime? @default(now()) @db.Timestamp
updatedAt DateTime @updatedAt @db.Timestamp
OpenaiCreds OpenaiCreds @relation(fields: [openaiCredsId], references: [id], onDelete: Cascade)
openaiCredsId String
Instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)
Instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)
instanceId String
OpenaiSetting OpenaiSetting[]
sessions IntegrationSession[]
}
model IntegrationSession {
@ -402,14 +404,7 @@ model IntegrationSession {
instanceId String
parameters Json? @db.JsonB
OpenaiBot OpenaiBot? @relation(fields: [openaiBotId], references: [id], onDelete: Cascade)
openaiBotId String?
DifyBot Dify? @relation(fields: [difyId], references: [id], onDelete: Cascade)
difyId String?
Typebot Typebot? @relation(fields: [typebotId], references: [id], onDelete: Cascade)
typebotId String?
botId String?
}
model OpenaiSetting {
@ -447,30 +442,29 @@ model Template {
}
model Dify {
id String @id @default(cuid())
enabled Boolean @default(true) @db.Boolean
description String? @db.VarChar(255)
id String @id @default(cuid())
enabled Boolean @default(true) @db.Boolean
description String? @db.VarChar(255)
botType DifyBotType
apiUrl String? @db.VarChar(255)
apiKey String? @db.VarChar(255)
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
debounceTime Int? @db.Integer
apiUrl String? @db.VarChar(255)
apiKey String? @db.VarChar(255)
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
debounceTime Int? @db.Integer
ignoreJids Json?
triggerType TriggerType?
triggerOperator TriggerOperator?
triggerValue String?
createdAt DateTime? @default(now()) @db.Timestamp
updatedAt DateTime @updatedAt @db.Timestamp
Instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)
createdAt DateTime? @default(now()) @db.Timestamp
updatedAt DateTime @updatedAt @db.Timestamp
Instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)
instanceId String
DifySetting DifySetting[]
sessions IntegrationSession[]
}
model DifySetting {
@ -491,3 +485,91 @@ model DifySetting {
Instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)
instanceId String @unique
}
model GenericBot {
id String @id @default(cuid())
enabled Boolean @default(true) @db.Boolean
description String? @db.VarChar(255)
apiUrl String? @db.VarChar(255)
apiKey String? @db.VarChar(255)
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
debounceTime Int? @db.Integer
ignoreJids Json?
triggerType TriggerType?
triggerOperator TriggerOperator?
triggerValue String?
createdAt DateTime? @default(now()) @db.Timestamp
updatedAt DateTime @updatedAt @db.Timestamp
Instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)
instanceId String
GenericSetting GenericSetting[]
}
model GenericSetting {
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
debounceTime Int? @db.Integer
ignoreJids Json?
createdAt DateTime? @default(now()) @db.Timestamp
updatedAt DateTime @updatedAt @db.Timestamp
Fallback GenericBot? @relation(fields: [botIdFallback], references: [id])
botIdFallback String? @db.VarChar(100)
Instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)
instanceId String @unique
}
model Flowise {
id String @id @default(cuid())
enabled Boolean @default(true) @db.Boolean
description String? @db.VarChar(255)
apiUrl String? @db.VarChar(255)
apiKey String? @db.VarChar(255)
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
debounceTime Int? @db.Integer
ignoreJids Json?
triggerType TriggerType?
triggerOperator TriggerOperator?
triggerValue String?
createdAt DateTime? @default(now()) @db.Timestamp
updatedAt DateTime @updatedAt @db.Timestamp
Instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)
instanceId String
FlowiseSetting FlowiseSetting[]
}
model FlowiseSetting {
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
debounceTime Int? @db.Integer
ignoreJids Json?
createdAt DateTime? @default(now()) @db.Timestamp
updatedAt DateTime @updatedAt @db.Timestamp
Fallback Flowise? @relation(fields: [flowiseIdFallback], references: [id])
flowiseIdFallback String? @db.VarChar(100)
Instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)
instanceId String @unique
}

View File

@ -36,11 +36,7 @@ export class InstanceController {
public async createInstance(instanceData: InstanceDto) {
try {
if (!instanceData.token && instanceData.integration === Integration.WHATSAPP_BUSINESS) {
throw new BadRequestException('token is required');
}
const instance = channelController.init(instanceData.integration, {
const instance = channelController.init(instanceData, {
configService: this.configService,
eventEmitter: this.eventEmitter,
prismaRepository: this.prismaRepository,
@ -52,6 +48,8 @@ export class InstanceController {
const instanceId = v4();
instanceData.instanceId = instanceId;
let hash: string;
if (!instanceData.token) hash = v4().toUpperCase();
@ -75,16 +73,16 @@ export class InstanceController {
businessId: instanceData.businessId,
});
instance.sendDataWebhook(Events.INSTANCE_CREATE, {
instanceName: instanceData.instanceName,
instanceId: instanceId,
});
this.waMonitor.waInstances[instance.instanceName] = instance;
this.waMonitor.delInstanceTime(instance.instanceName);
// set events
eventController.setInstance(instance.instanceName, instanceData);
await eventController.setInstance(instance.instanceName, instanceData);
instance.sendDataWebhook(Events.INSTANCE_CREATE, {
instanceName: instanceData.instanceName,
instanceId: instanceId,
});
if (instanceData.proxyHost && instanceData.proxyPort && instanceData.proxyProtocol) {
const testProxy = await this.proxyService.testProxy({

View File

@ -0,0 +1,12 @@
export class Session {
remoteJid?: string;
sessionId?: string;
status?: string;
createdAt?: number;
updateAt?: number;
}
export class IgnoreJidDto {
remoteJid?: string;
action?: string;
}

View File

@ -1,10 +1,13 @@
import { InstanceDto } from '@api/dto/instance.dto';
import { ProviderFiles } from '@api/provider/sessions';
import { PrismaRepository } from '@api/repository/repository.service';
import { CacheService } from '@api/services/cache.service';
import { Integration } from '@api/types/wa.types';
import { ConfigService } from '@config/env.config';
import { BadRequestException } from '@exceptions';
import EventEmitter2 from 'eventemitter2';
import { EvolutionStartupService } from './evolution/evolution.channel.service';
import { BaileysStartupService } from './whatsapp/baileys/whatsapp.baileys.service';
import { BusinessStartupService } from './whatsapp/business/whatsapp.business.service';
@ -19,8 +22,12 @@ type ChannelDataType = {
};
export class ChannelController {
public init(integration: string, data: ChannelDataType) {
if (integration === Integration.WHATSAPP_BUSINESS) {
public init(instanceData: InstanceDto, data: ChannelDataType) {
if (!instanceData.token && instanceData.integration === Integration.WHATSAPP_BUSINESS) {
throw new BadRequestException('token is required');
}
if (instanceData.integration === Integration.WHATSAPP_BUSINESS) {
return new BusinessStartupService(
data.configService,
data.eventEmitter,
@ -32,6 +39,18 @@ export class ChannelController {
);
}
if (instanceData.integration === Integration.EVOLUTION) {
return new EvolutionStartupService(
data.configService,
data.eventEmitter,
data.prismaRepository,
data.cache,
data.chatwootCache,
data.baileysCache,
data.providerFiles,
);
}
return new BaileysStartupService(
data.configService,
data.eventEmitter,

View File

@ -0,0 +1,475 @@
import { Options, SendAudioDto, SendMediaDto, SendTextDto } from '@api/dto/sendMessage.dto';
import { ProviderFiles } from '@api/provider/sessions';
import { PrismaRepository } from '@api/repository/repository.service';
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, Openai } from '@config/env.config';
import { BadRequestException, InternalServerErrorException } from '@exceptions';
import EventEmitter2 from 'eventemitter2';
export class EvolutionStartupService extends ChannelStartupService {
constructor(
public readonly configService: ConfigService,
public readonly eventEmitter: EventEmitter2,
public readonly prismaRepository: PrismaRepository,
public readonly cache: CacheService,
public readonly chatwootCache: CacheService,
public readonly baileysCache: CacheService,
private readonly providerFiles: ProviderFiles,
) {
super(configService, eventEmitter, prismaRepository, chatwootCache);
this.client = null;
}
public client: any;
public stateConnection: wa.StateConnection = { state: 'open' };
public phoneNumber: string;
public mobile: boolean;
public get connectionStatus() {
return this.stateConnection;
}
public async closeClient() {
this.stateConnection = { state: 'close' };
}
public get qrCode(): wa.QrCode {
return {
pairingCode: this.instance.qrcode?.pairingCode,
code: this.instance.qrcode?.code,
base64: this.instance.qrcode?.base64,
count: this.instance.qrcode?.count,
};
}
public async logoutInstance() {
await this.closeClient();
}
public async profilePicture(number: string) {
const jid = this.createJid(number);
return {
wuid: jid,
profilePictureUrl: null,
};
}
public async getProfileName() {
return null;
}
public async profilePictureUrl() {
return null;
}
public async getProfileStatus() {
return null;
}
public async connectToWhatsapp(data?: any): Promise<any> {
if (!data) return;
try {
this.loadChatwoot();
this.eventHandler(data);
} catch (error) {
this.logger.error(error);
throw new InternalServerErrorException(error?.toString());
}
}
protected async eventHandler(received: any) {
try {
let messageRaw: any;
if (received.message) {
const key = {
id: received.key.id,
remoteJid: received.key.remoteJid,
fromMe: received.key.fromMe,
};
messageRaw = {
key,
pushName: received.pushName,
message: received.message,
messageType: received.messageType,
messageTimestamp: Math.round(new Date().getTime() / 1000),
source: 'unknown',
instanceId: this.instanceId,
};
if (this.configService.get<Openai>('OPENAI').ENABLED) {
const openAiDefaultSettings = await this.prismaRepository.openaiSetting.findFirst({
where: {
instanceId: this.instanceId,
},
include: {
OpenaiCreds: true,
},
});
if (
openAiDefaultSettings &&
openAiDefaultSettings.openaiCredsId &&
openAiDefaultSettings.speechToText &&
received?.message?.audioMessage
) {
messageRaw.message.speechToText = await this.openaiService.speechToText(
openAiDefaultSettings.OpenaiCreds,
received,
this.client.updateMediaMessage,
);
}
}
this.logger.log(messageRaw);
this.sendDataWebhook(Events.MESSAGES_UPSERT, 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<Chatwoot>('CHATWOOT').ENABLED && this.localChatwoot.enabled) {
const chatwootSentMessage = await this.chatwootService.eventWhatsapp(
Events.MESSAGES_UPSERT,
{ instanceName: this.instance.name, instanceId: this.instanceId },
messageRaw,
);
if (chatwootSentMessage?.id) {
messageRaw.chatwootMessageId = chatwootSentMessage.id;
messageRaw.chatwootInboxId = chatwootSentMessage.id;
messageRaw.chatwootConversationId = chatwootSentMessage.id;
}
}
await this.prismaRepository.message.create({
data: messageRaw,
});
const contact = await this.prismaRepository.contact.findFirst({
where: { instanceId: this.instanceId, remoteJid: key.remoteJid },
});
const contactRaw: any = {
remoteJid: messageRaw.key.remoteJid,
pushName: received.pushName,
instanceId: this.instanceId,
};
if (contactRaw.remoteJid === 'status@broadcast') {
return;
}
if (contact) {
const contactRaw: any = {
remoteJid: messageRaw.key.remoteJid,
pushName: received.pushName,
instanceId: this.instanceId,
};
this.sendDataWebhook(Events.CONTACTS_UPDATE, contactRaw);
if (this.configService.get<Chatwoot>('CHATWOOT').ENABLED && this.localChatwoot.enabled) {
await this.chatwootService.eventWhatsapp(
Events.CONTACTS_UPDATE,
{ instanceName: this.instance.name, instanceId: this.instanceId },
contactRaw,
);
}
await this.prismaRepository.contact.updateMany({
where: { remoteJid: contact.remoteJid },
data: contactRaw,
});
return;
}
this.sendDataWebhook(Events.CONTACTS_UPSERT, contactRaw);
this.prismaRepository.contact.create({
data: contactRaw,
});
}
} catch (error) {
this.logger.error(error);
}
}
protected async sendMessageWithTyping(number: string, message: any, options?: Options, isIntegration = false) {
try {
let quoted: any;
let webhookUrl: any;
if (options?.quoted) {
const m = options?.quoted;
const msg = m?.key;
if (!msg) {
throw 'Message not found';
}
quoted = msg;
}
if (options?.webhookUrl) {
webhookUrl = options.webhookUrl;
}
const messageRaw: any = {
key: { fromMe: true, id: 'ID', remoteJid: this.createJid(number) },
message: {
...message,
quoted,
},
messageType: 'conversation',
messageTimestamp: Math.round(new Date().getTime() / 1000),
webhookUrl,
source: 'unknown',
instanceId: this.instanceId,
};
this.logger.log(messageRaw);
this.sendDataWebhook(Events.SEND_MESSAGE, messageRaw);
if (this.configService.get<Chatwoot>('CHATWOOT').ENABLED && this.localChatwoot.enabled && !isIntegration) {
this.chatwootService.eventWhatsapp(
Events.SEND_MESSAGE,
{ instanceName: this.instance.name, instanceId: this.instanceId },
messageRaw,
);
}
if (this.configService.get<Chatwoot>('CHATWOOT').ENABLED && this.localChatwoot.enabled && isIntegration)
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,
});
return messageRaw;
} catch (error) {
this.logger.error(error);
throw new BadRequestException(error.toString());
}
}
public async textMessage(data: SendTextDto, isIntegration = false) {
const res = await this.sendMessageWithTyping(
data.number,
{
conversation: data.text,
},
{
delay: data?.delay,
presence: 'composing',
quoted: data?.quoted,
linkPreview: data?.linkPreview,
mentionsEveryOne: data?.mentionsEveryOne,
mentioned: data?.mentioned,
},
isIntegration,
);
return res;
}
public async mediaMessage(data: SendMediaDto, isIntegration = false) {
const message = data;
return await this.sendMessageWithTyping(
data.number,
{ ...message },
{
delay: data?.delay,
presence: 'composing',
quoted: data?.quoted,
linkPreview: data?.linkPreview,
mentionsEveryOne: data?.mentionsEveryOne,
mentioned: data?.mentioned,
},
isIntegration,
);
}
public async audioWhatsapp(data: SendAudioDto, isIntegration = false) {
const message = data;
return await this.sendMessageWithTyping(
data.number,
{ ...message },
{
delay: data?.delay,
presence: 'composing',
quoted: data?.quoted,
linkPreview: data?.linkPreview,
mentionsEveryOne: data?.mentionsEveryOne,
mentioned: data?.mentioned,
},
isIntegration,
);
}
public async buttonMessage() {
throw new BadRequestException('Method not available on Evolution Channel');
}
public async locationMessage() {
throw new BadRequestException('Method not available on Evolution Channel');
}
public async listMessage() {
throw new BadRequestException('Method not available on Evolution Channel');
}
public async templateMessage() {
throw new BadRequestException('Method not available on Evolution Channel');
}
public async contactMessage() {
throw new BadRequestException('Method not available on Evolution Channel');
}
public async reactionMessage() {
throw new BadRequestException('Method not available on Evolution Channel');
}
public async getBase64FromMediaMessage() {
throw new BadRequestException('Method not available on Evolution Channel');
}
public async deleteMessage() {
throw new BadRequestException('Method not available on Evolution Channel');
}
public async mediaSticker() {
throw new BadRequestException('Method not available on Evolution Channel');
}
public async pollMessage() {
throw new BadRequestException('Method not available on Evolution Channel');
}
public async statusMessage() {
throw new BadRequestException('Method not available on Evolution Channel');
}
public async reloadConnection() {
throw new BadRequestException('Method not available on Evolution Channel');
}
public async whatsappNumber() {
throw new BadRequestException('Method not available on Evolution Channel');
}
public async markMessageAsRead() {
throw new BadRequestException('Method not available on Evolution Channel');
}
public async archiveChat() {
throw new BadRequestException('Method not available on Evolution Channel');
}
public async markChatUnread() {
throw new BadRequestException('Method not available on Evolution Channel');
}
public async fetchProfile() {
throw new BadRequestException('Method not available on Evolution Channel');
}
public async sendPresence() {
throw new BadRequestException('Method not available on Evolution Channel');
}
public async setPresence() {
throw new BadRequestException('Method not available on Evolution Channel');
}
public async fetchPrivacySettings() {
throw new BadRequestException('Method not available on Evolution Channel');
}
public async updatePrivacySettings() {
throw new BadRequestException('Method not available on Evolution Channel');
}
public async fetchBusinessProfile() {
throw new BadRequestException('Method not available on Evolution Channel');
}
public async updateProfileName() {
throw new BadRequestException('Method not available on Evolution Channel');
}
public async updateProfileStatus() {
throw new BadRequestException('Method not available on Evolution Channel');
}
public async updateProfilePicture() {
throw new BadRequestException('Method not available on Evolution Channel');
}
public async removeProfilePicture() {
throw new BadRequestException('Method not available on Evolution Channel');
}
public async blockUser() {
throw new BadRequestException('Method not available on Evolution Channel');
}
public async updateMessage() {
throw new BadRequestException('Method not available on Evolution Channel');
}
public async createGroup() {
throw new BadRequestException('Method not available on Evolution Channel');
}
public async updateGroupPicture() {
throw new BadRequestException('Method not available on Evolution Channel');
}
public async updateGroupSubject() {
throw new BadRequestException('Method not available on Evolution Channel');
}
public async updateGroupDescription() {
throw new BadRequestException('Method not available on Evolution Channel');
}
public async findGroup() {
throw new BadRequestException('Method not available on Evolution Channel');
}
public async fetchAllGroups() {
throw new BadRequestException('Method not available on Evolution Channel');
}
public async inviteCode() {
throw new BadRequestException('Method not available on Evolution Channel');
}
public async inviteInfo() {
throw new BadRequestException('Method not available on Evolution Channel');
}
public async sendInvite() {
throw new BadRequestException('Method not available on Evolution Channel');
}
public async acceptInviteCode() {
throw new BadRequestException('Method not available on Evolution Channel');
}
public async revokeInviteCode() {
throw new BadRequestException('Method not available on Evolution Channel');
}
public async findParticipants() {
throw new BadRequestException('Method not available on Evolution Channel');
}
public async updateGParticipant() {
throw new BadRequestException('Method not available on Evolution Channel');
}
public async updateGSetting() {
throw new BadRequestException('Method not available on Evolution Channel');
}
public async toggleEphemeral() {
throw new BadRequestException('Method not available on Evolution Channel');
}
public async leaveGroup() {
throw new BadRequestException('Method not available on Evolution Channel');
}
public async fetchLabels() {
throw new BadRequestException('Method not available on Evolution Channel');
}
public async handleLabel() {
throw new BadRequestException('Method not available on Evolution Channel');
}
public async receiveMobileCode() {
throw new BadRequestException('Method not available on Evolution Channel');
}
public async fakeCall() {
throw new BadRequestException('Method not available on Evolution Channel');
}
}

View File

@ -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,14 @@ export class BaileysStartupService extends ChannelStartupService {
);
}
if (this.configService.get<Chatwoot>('CHATWOOT').ENABLED && this.localChatwoot.enabled && isIntegration) {
if (this.configService.get<Typebot>('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>('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>('DIFY').ENABLED) {
if (messageRaw.messageType !== 'reactionMessage')
await this.difyService.sendDify(
{ instanceName: this.instance.name, instanceId: this.instanceId },
messageRaw.key.remoteJid,
messageRaw,
);
}
}
if (this.configService.get<Chatwoot>('CHATWOOT').ENABLED && this.localChatwoot.enabled && isIntegration)
await chatbotController.emit({
instance: { instanceName: this.instance.name, instanceId: this.instanceId },
remoteJid: messageRaw.key.remoteJid,
msg: messageRaw,
pushName: messageRaw.pushName,
isIntegration,
});
if (this.configService.get<Database>('DATABASE').SAVE_DATA.NEW_MESSAGE)
await this.prismaRepository.message.create({

View File

@ -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';
@ -85,17 +85,10 @@ export class BusinessStartupService extends ChannelStartupService {
public async profilePicture(number: string) {
const jid = this.createJid(number);
try {
return {
wuid: jid,
profilePictureUrl: await this.client.profilePictureUrl(jid, 'image'),
};
} catch (error) {
return {
wuid: jid,
profilePictureUrl: null,
};
}
return {
wuid: jid,
profilePictureUrl: null,
};
}
public async getProfileName() {
@ -923,35 +916,13 @@ export class BusinessStartupService extends ChannelStartupService {
);
}
if (this.configService.get<Chatwoot>('CHATWOOT').ENABLED && this.localChatwoot.enabled && isIntegration) {
if (this.configService.get<Typebot>('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>('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>('DIFY').ENABLED) {
if (messageRaw.messageType !== 'reactionMessage')
await this.difyService.sendDify(
{ instanceName: this.instance.name, instanceId: this.instanceId },
messageRaw.key.remoteJid,
messageRaw,
);
}
}
if (this.configService.get<Chatwoot>('CHATWOOT').ENABLED && this.localChatwoot.enabled && isIntegration)
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,

View File

@ -1,12 +1,54 @@
import { InstanceDto } from '@api/dto/instance.dto';
import { PrismaRepository } from '@api/repository/repository.service';
import { difyController, openaiController, typebotController, websocketController } from '@api/server.module';
import {
difyController,
flowiseController,
genericController,
openaiController,
typebotController,
websocketController,
} from '@api/server.module';
import { WAMonitoringService } from '@api/services/monitor.service';
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<any>;
findBot(instance: InstanceDto): Promise<any>;
fetchBot(instance: InstanceDto, botId: string): Promise<any>;
updateBot(instance: InstanceDto, botId: string, data: any): Promise<any>;
deleteBot(instance: InstanceDto, botId: string): Promise<any>;
settings(instance: InstanceDto, data: any): Promise<any>;
fetchSettings(instance: InstanceDto): Promise<any>;
changeStatus(instance: InstanceDto, botId: string, status: string): Promise<any>;
fetchSessions(instance: InstanceDto, botId: string, remoteJid?: string): Promise<any>;
ignoreJid(instance: InstanceDto, data: any): Promise<any>;
emit(data: EmitData): Promise<void>;
}
export class ChatbotController {
public prismaRepository: PrismaRepository;
public waMonitor: WAMonitoringService;
public readonly logger = new Logger(ChatbotController.name);
constructor(prismaRepository: PrismaRepository, waMonitor: WAMonitoringService) {
this.prisma = prismaRepository;
this.monitor = waMonitor;
@ -33,18 +75,24 @@ export class ChatbotController {
remoteJid,
msg,
pushName,
isIntegration = false,
}: {
instance: InstanceDto;
remoteJid: string;
msg: any;
pushName?: string;
isIntegration?: boolean;
}): Promise<void> {
const emitData = {
instance,
remoteJid,
msg,
pushName,
isIntegration,
};
// generic
await genericController.emit(emitData);
// typebot
await typebotController.emit(emitData);
@ -53,6 +101,9 @@ export class ChatbotController {
// dify
await difyController.emit(emitData);
// flowise
await flowiseController.emit(emitData);
}
public async setInstance(instanceName: string, data: any): Promise<any> {
@ -63,4 +114,112 @@ export class ChatbotController {
events: data.websocketEvents,
});
}
public processDebounce(
userMessageDebounce: any,
content: string,
remoteJid: string,
debounceTime: number,
callback: any,
) {
if (userMessageDebounce[remoteJid]) {
userMessageDebounce[remoteJid].message += ` ${content}`;
this.logger.log('message debounced: ' + userMessageDebounce[remoteJid].message);
clearTimeout(userMessageDebounce[remoteJid].timeoutId);
} else {
userMessageDebounce[remoteJid] = {
message: content,
timeoutId: null,
};
}
userMessageDebounce[remoteJid].timeoutId = setTimeout(() => {
const myQuestion = userMessageDebounce[remoteJid].message;
this.logger.log('Debounce complete. Processing message: ' + myQuestion);
delete userMessageDebounce[remoteJid];
callback(myQuestion);
}, debounceTime * 1000);
}
public checkIgnoreJids(ignoreJids: any, remoteJid: string) {
if (ignoreJids && ignoreJids.length > 0) {
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 true;
}
if (ignoreContacts && remoteJid.endsWith('@s.whatsapp.net')) {
this.logger.warn('Ignoring message from contact: ' + remoteJid);
return true;
}
if (ignoreJids.includes(remoteJid)) {
this.logger.warn('Ignoring message from jid: ' + remoteJid);
return true;
}
return false;
}
return false;
}
public async getSession(remoteJid: string, instance: InstanceDto) {
let session = await this.prismaRepository.integrationSession.findFirst({
where: {
remoteJid: remoteJid,
instanceId: instance.instanceId,
},
orderBy: { createdAt: 'desc' },
});
if (session) {
if (session.status !== 'closed' && !session.botId) {
this.logger.warn('Session is already opened in another integration');
return;
} else if (!session.botId) {
session = null;
}
}
return session;
}
public async findBotTrigger(
botRepository: any,
settingsRepository: any,
content: string,
instance: InstanceDto,
session?: IntegrationSession,
) {
let findBot = null;
if (!session) {
findBot = await findBotByTrigger(botRepository, settingsRepository, content, instance.instanceId);
if (!findBot) {
return;
}
} else {
findBot = await botRepository.findFirst({
where: {
id: session.botId,
},
});
}
return findBot;
}
}

View File

@ -4,15 +4,20 @@ import { OpenaiRouter } from '@api/integrations/chatbot/openai/routes/openai.rou
import { TypebotRouter } from '@api/integrations/chatbot/typebot/routes/typebot.router';
import { Router } from 'express';
import { FlowiseRouter } from './flowise/routes/flowise.router';
import { GenericRouter } from './generic/routes/generic.router';
export class ChatbotRouter {
public readonly router: Router;
constructor(...guards: any[]) {
this.router = Router();
this.router.use('/generic', new GenericRouter(...guards).router);
this.router.use('/chatwoot', new ChatwootRouter(...guards).router);
this.router.use('/typebot', new TypebotRouter(...guards).router);
this.router.use('/openai', new OpenaiRouter(...guards).router);
this.router.use('/dify', new DifyRouter(...guards).router);
this.router.use('/flowise', new FlowiseRouter(...guards).router);
}
}

View File

@ -1,4 +1,6 @@
export * from '@api/integrations/chatbot/chatwoot/validate/chatwoot.schema';
export * from '@api/integrations/chatbot/dify/validate/dify.schema';
export * from '@api/integrations/chatbot/flowise/validate/flowise.schema';
export * from '@api/integrations/chatbot/generic/validate/generic.schema';
export * from '@api/integrations/chatbot/openai/validate/openai.schema';
export * from '@api/integrations/chatbot/typebot/validate/typebot.schema';

View File

@ -215,7 +215,13 @@ export class ChatwootService {
inboxId = inbox.id;
}
this.logger.log(`Inox created - inboxId: ${inboxId}`);
this.logger.log(`Inbox created - inboxId: ${inboxId}`);
if (!this.configService.get<Chatwoot>('CHATWOOT').BOT_CONTACT) {
this.logger.log('Chatwoot bot contact is disabled');
return true;
}
this.logger.log('Creating chatwoot bot contact');
const contact =
@ -826,6 +832,12 @@ export class ChatwootService {
return null;
}
if (!this.configService.get<Chatwoot>('CHATWOOT').BOT_CONTACT) {
this.logger.log('Chatwoot bot contact is disabled');
return true;
}
const contact = await this.findContact(instance, '123456');
if (!contact) {
@ -940,6 +952,12 @@ export class ChatwootService {
return null;
}
if (!this.configService.get<Chatwoot>('CHATWOOT').BOT_CONTACT) {
this.logger.log('Chatwoot bot contact is disabled');
return true;
}
const contact = await this.findContact(instance, '123456');
if (!contact) {
@ -1159,7 +1177,9 @@ export class ChatwootService {
return { message: 'bot' };
}
if (chatId === '123456' && body.message_type === 'outgoing') {
const cwBotContact = this.configService.get<Chatwoot>('CHATWOOT').BOT_CONTACT;
if (cwBotContact && chatId === '123456' && body.message_type === 'outgoing') {
const command = messageReceived.replace('/', '');
if (command.includes('init') || command.includes('iniciar')) {

View File

@ -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>('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>('DIFY').ENABLED) throw new BadRequestException('Dify is disabled');
public readonly logger = new Logger(DifyController.name);
return this.difyService.find(instance);
integrationEnabled = configService.get<Dify>('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>('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>('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>('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>('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>('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>('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>('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>('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>('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.settingsRepository.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;
}
}
}

View File

@ -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;
}

View File

@ -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<DifyIgnoreJidDto>({
const response = await this.dataValidate<IgnoreJidDto>({
request: req,
schema: difyIgnoreJidSchema,
ClassRef: DifyIgnoreJidDto,
ClassRef: IgnoreJidDto,
execute: (instance, data) => difyController.ignoreJid(instance, data),
});

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,797 @@
import { IgnoreJidDto } from '@api/dto/chatbot.dto';
import { InstanceDto } from '@api/dto/instance.dto';
import { PrismaRepository } from '@api/repository/repository.service';
import { WAMonitoringService } from '@api/services/monitor.service';
import { Logger } from '@config/logger.config';
import { getConversationMessage } from '@utils/getConversationMessage';
import { ChatbotController, ChatbotControllerInterface, EmitData } from '../../chatbot.controller';
import { FlowiseDto } from '../dto/flowise.dto';
import { FlowiseService } from '../services/flowise.service';
export class FlowiseController extends ChatbotController implements ChatbotControllerInterface {
constructor(
private readonly flowiseService: FlowiseService,
prismaRepository: PrismaRepository,
waMonitor: WAMonitoringService,
) {
super(prismaRepository, waMonitor);
this.botRepository = this.prismaRepository.flowise;
this.settingsRepository = this.prismaRepository.flowiseSetting;
this.sessionRepository = this.prismaRepository.integrationSession;
}
public readonly logger = new Logger(FlowiseController.name);
integrationEnabled: boolean;
botRepository: any;
settingsRepository: any;
sessionRepository: any;
userMessageDebounce: { [key: string]: { message: string; timeoutId: NodeJS.Timeout } } = {};
// Bots
public async createBot(instance: InstanceDto, data: FlowiseDto) {
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 Flowise with an "All" trigger, you cannot have more bots while it is active');
}
const checkDuplicate = await this.botRepository.findFirst({
where: {
instanceId: instanceId,
apiUrl: data.apiUrl,
apiKey: data.apiKey,
},
});
if (checkDuplicate) {
throw new Error('Flowise 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,
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 bot');
}
}
public async findBot(instance: InstanceDto) {
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 fetchBot(instance: InstanceDto, botId: string) {
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('Bot not found');
}
if (bot.instanceId !== instanceId) {
throw new Error('Bot not found');
}
return bot;
}
public async updateBot(instance: InstanceDto, botId: string, data: FlowiseDto) {
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('Bot not found');
}
if (bot.instanceId !== instanceId) {
throw new Error('Bot 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 bot 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,
apiUrl: data.apiUrl,
apiKey: data.apiKey,
},
});
if (checkDuplicate) {
throw new Error('Bot 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,
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 bot');
}
}
public async deleteBot(instance: InstanceDto, botId: string) {
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('Bot not found');
}
if (bot.instanceId !== instanceId) {
throw new Error('Bot 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 bot');
}
}
// Settings
public async settings(instance: InstanceDto, data: any) {
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,
flowiseIdFallback: data.flowiseIdFallback,
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,
flowiseIdFallback: updateSettings.flowiseIdFallback,
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,
flowiseIdFallback: data.flowiseIdFallback,
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,
flowiseIdFallback: newSetttings.flowiseIdFallback,
ignoreJids: newSetttings.ignoreJids,
};
} catch (error) {
this.logger.error(error);
throw new Error('Error setting default settings');
}
}
public async fetchSettings(instance: InstanceDto) {
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: [],
flowiseIdFallback: '',
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,
flowiseIdFallback: settings.flowiseIdFallback,
fallback: settings.Fallback,
};
} catch (error) {
this.logger.error(error);
throw new Error('Error fetching default settings');
}
}
// 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.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, botId: string, remoteJid?: string) {
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: IgnoreJidDto) {
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');
}
}
// Emit
public async emit({ instance, remoteJid, msg }: EmitData) {
try {
const settings = await this.settingsRepository.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.flowiseService.processBot(
this.waMonitor.waInstances[instance.instanceName],
remoteJid,
findBot,
session,
settings,
debouncedContent,
msg?.pushName,
);
});
} else {
await this.flowiseService.processBot(
this.waMonitor.waInstances[instance.instanceName],
remoteJid,
findBot,
session,
settings,
content,
msg?.pushName,
);
}
return;
} catch (error) {
this.logger.error(error);
return;
}
}
}

View File

@ -0,0 +1,33 @@
import { TriggerOperator, TriggerType } from '@prisma/client';
export class FlowiseDto {
enabled?: boolean;
description?: string;
apiUrl?: string;
apiKey?: string;
expire?: number;
keywordFinish?: string;
delayMessage?: number;
unknownMessage?: string;
listeningFromMe?: boolean;
stopBotFromMe?: boolean;
keepOpen?: boolean;
debounceTime?: number;
triggerType?: TriggerType;
triggerOperator?: TriggerOperator;
triggerValue?: string;
ignoreJids?: any;
}
export class FlowiseSettingDto {
expire?: number;
keywordFinish?: string;
delayMessage?: number;
unknownMessage?: string;
listeningFromMe?: boolean;
stopBotFromMe?: boolean;
keepOpen?: boolean;
debounceTime?: number;
flowiseIdFallback?: string;
ignoreJids?: any;
}

View File

@ -0,0 +1,124 @@
import { RouterBroker } from '@api/abstract/abstract.router';
import { IgnoreJidDto } from '@api/dto/chatbot.dto';
import { InstanceDto } from '@api/dto/instance.dto';
import { HttpStatus } from '@api/routes/index.router';
import { flowiseController } from '@api/server.module';
import { instanceSchema } from '@validate/instance.schema';
import { RequestHandler, Router } from 'express';
import { FlowiseDto, FlowiseSettingDto } from '../dto/flowise.dto';
import {
flowiseIgnoreJidSchema,
flowiseSchema,
flowiseSettingSchema,
flowiseStatusSchema,
} from '../validate/flowise.schema';
export class FlowiseRouter extends RouterBroker {
constructor(...guards: RequestHandler[]) {
super();
this.router
.post(this.routerPath('create'), ...guards, async (req, res) => {
const response = await this.dataValidate<FlowiseDto>({
request: req,
schema: flowiseSchema,
ClassRef: FlowiseDto,
execute: (instance, data) => flowiseController.createBot(instance, data),
});
res.status(HttpStatus.CREATED).json(response);
})
.get(this.routerPath('find'), ...guards, async (req, res) => {
const response = await this.dataValidate<InstanceDto>({
request: req,
schema: instanceSchema,
ClassRef: InstanceDto,
execute: (instance) => flowiseController.findBot(instance),
});
res.status(HttpStatus.OK).json(response);
})
.get(this.routerPath('fetch/:flowiseId'), ...guards, async (req, res) => {
const response = await this.dataValidate<InstanceDto>({
request: req,
schema: instanceSchema,
ClassRef: InstanceDto,
execute: (instance) => flowiseController.fetchBot(instance, req.params.flowiseId),
});
res.status(HttpStatus.OK).json(response);
})
.put(this.routerPath('update/:flowiseId'), ...guards, async (req, res) => {
const response = await this.dataValidate<FlowiseDto>({
request: req,
schema: flowiseSchema,
ClassRef: FlowiseDto,
execute: (instance, data) => flowiseController.updateBot(instance, req.params.flowiseId, data),
});
res.status(HttpStatus.OK).json(response);
})
.delete(this.routerPath('delete/:flowiseId'), ...guards, async (req, res) => {
const response = await this.dataValidate<InstanceDto>({
request: req,
schema: instanceSchema,
ClassRef: InstanceDto,
execute: (instance) => flowiseController.deleteBot(instance, req.params.flowiseId),
});
res.status(HttpStatus.OK).json(response);
})
.post(this.routerPath('settings'), ...guards, async (req, res) => {
const response = await this.dataValidate<FlowiseSettingDto>({
request: req,
schema: flowiseSettingSchema,
ClassRef: FlowiseSettingDto,
execute: (instance, data) => flowiseController.settings(instance, data),
});
res.status(HttpStatus.OK).json(response);
})
.get(this.routerPath('fetchSettings'), ...guards, async (req, res) => {
const response = await this.dataValidate<InstanceDto>({
request: req,
schema: instanceSchema,
ClassRef: InstanceDto,
execute: (instance) => flowiseController.fetchSettings(instance),
});
res.status(HttpStatus.OK).json(response);
})
.post(this.routerPath('changeStatus'), ...guards, async (req, res) => {
const response = await this.dataValidate<InstanceDto>({
request: req,
schema: flowiseStatusSchema,
ClassRef: InstanceDto,
execute: (instance, data) => flowiseController.changeStatus(instance, data),
});
res.status(HttpStatus.OK).json(response);
})
.get(this.routerPath('fetchSessions/:flowiseId'), ...guards, async (req, res) => {
const response = await this.dataValidate<InstanceDto>({
request: req,
schema: instanceSchema,
ClassRef: InstanceDto,
execute: (instance) => flowiseController.fetchSessions(instance, req.params.flowiseId),
});
res.status(HttpStatus.OK).json(response);
})
.post(this.routerPath('ignoreJid'), ...guards, async (req, res) => {
const response = await this.dataValidate<IgnoreJidDto>({
request: req,
schema: flowiseIgnoreJidSchema,
ClassRef: IgnoreJidDto,
execute: (instance, data) => flowiseController.ignoreJid(instance, data),
});
res.status(HttpStatus.OK).json(response);
});
}
public readonly router: Router = Router();
}

View File

@ -0,0 +1,309 @@
import { InstanceDto } from '@api/dto/instance.dto';
import { PrismaRepository } from '@api/repository/repository.service';
import { WAMonitoringService } from '@api/services/monitor.service';
import { Integration } from '@api/types/wa.types';
import { Auth, ConfigService, HttpServer } from '@config/env.config';
import { Logger } from '@config/logger.config';
import { Flowise, FlowiseSetting, IntegrationSession } from '@prisma/client';
import { sendTelemetry } from '@utils/sendTelemetry';
import axios from 'axios';
export class FlowiseService {
constructor(
private readonly waMonitor: WAMonitoringService,
private readonly configService: ConfigService,
private readonly prismaRepository: PrismaRepository,
) {}
private readonly logger = new Logger('FlowiseService');
public async createNewSession(instance: InstanceDto, data: any) {
try {
const session = await this.prismaRepository.integrationSession.create({
data: {
remoteJid: data.remoteJid,
sessionId: data.remoteJid,
status: 'opened',
awaitUser: false,
botId: data.botId,
instanceId: instance.instanceId,
},
});
return { session };
} catch (error) {
this.logger.error(error);
return;
}
}
private isImageMessage(content: string) {
return content.includes('imageMessage');
}
private async sendMessageToBot(
instance: any,
session: IntegrationSession,
bot: Flowise,
remoteJid: string,
pushName: string,
content: string,
) {
const payload: any = {
question: content,
overrideConfig: {
sessionId: remoteJid,
vars: {
remoteJid: remoteJid,
pushName: pushName,
instanceName: instance.instanceName,
serverUrl: this.configService.get<HttpServer>('SERVER').URL,
apiKey: this.configService.get<Auth>('AUTHENTICATION').API_KEY.KEY,
},
},
};
if (this.isImageMessage(content)) {
const contentSplit = content.split('|');
payload.uploads = [
{
data: contentSplit[1].split('?')[0],
type: 'url',
name: 'Flowise.png',
mime: 'image/png',
},
];
payload.question = contentSplit[2] || content;
}
if (instance.integration === Integration.WHATSAPP_BAILEYS) {
await instance.client.presenceSubscribe(remoteJid);
await instance.client.sendPresenceUpdate('composing', remoteJid);
}
let headers: any = {
'Content-Type': 'application/json',
};
if (bot.apiKey) {
headers = {
...headers,
Authorization: `Bearer ${bot.apiKey}`,
};
}
const endpoint = bot.apiUrl;
if (!endpoint) return null;
const response = await axios.post(endpoint, payload, {
headers,
});
if (instance.integration === Integration.WHATSAPP_BAILEYS)
await instance.client.sendPresenceUpdate('paused', remoteJid);
const message = response?.data?.text;
return message;
}
private async sendMessageWhatsApp(
instance: any,
remoteJid: string,
session: IntegrationSession,
settings: FlowiseSetting,
message: string,
) {
const regex = /!?\[(.*?)\]\((.*?)\)/g;
const result = [];
let lastIndex = 0;
let match;
while ((match = regex.exec(message)) !== null) {
if (match.index > lastIndex) {
result.push({ text: message.slice(lastIndex, match.index).trim() });
}
result.push({ caption: match[1], url: match[2] });
lastIndex = regex.lastIndex;
}
if (lastIndex < message.length) {
result.push({ text: message.slice(lastIndex).trim() });
}
for (const item of result) {
if (item.text) {
await instance.textMessage(
{
number: remoteJid.split('@')[0],
delay: settings?.delayMessage || 1000,
text: item.text,
},
false,
);
}
if (item.url) {
await instance.mediaMessage(
{
number: remoteJid.split('@')[0],
delay: settings?.delayMessage || 1000,
mediatype: 'image',
media: item.url,
caption: item.caption,
},
false,
);
}
}
await this.prismaRepository.integrationSession.update({
where: {
id: session.id,
},
data: {
status: 'opened',
awaitUser: true,
},
});
sendTelemetry('/message/sendText');
return;
}
private async initNewSession(
instance: any,
remoteJid: string,
bot: Flowise,
settings: FlowiseSetting,
session: IntegrationSession,
content: string,
pushName?: string,
) {
const data = await this.createNewSession(instance, {
remoteJid,
botId: bot.id,
});
if (data.session) {
session = data.session;
}
const message = await this.sendMessageToBot(instance, session, bot, remoteJid, pushName, content);
await this.sendMessageWhatsApp(instance, remoteJid, session, settings, message);
return;
}
public async processBot(
instance: any,
remoteJid: string,
bot: Flowise,
session: IntegrationSession,
settings: FlowiseSetting,
content: string,
pushName?: string,
) {
if (session && session.status !== 'opened') {
return;
}
if (session && settings.expire && settings.expire > 0) {
const now = Date.now();
const sessionUpdatedAt = new Date(session.updatedAt).getTime();
const diff = now - sessionUpdatedAt;
const diffInMinutes = Math.floor(diff / 1000 / 60);
if (diffInMinutes > settings.expire) {
if (settings.keepOpen) {
await this.prismaRepository.integrationSession.update({
where: {
id: session.id,
},
data: {
status: 'closed',
},
});
} else {
await this.prismaRepository.integrationSession.deleteMany({
where: {
botId: bot.id,
remoteJid: remoteJid,
},
});
}
await this.initNewSession(instance, remoteJid, bot, settings, session, content, pushName);
return;
}
}
if (!session) {
await this.initNewSession(instance, remoteJid, bot, settings, session, content, pushName);
return;
}
await this.prismaRepository.integrationSession.update({
where: {
id: session.id,
},
data: {
status: 'opened',
awaitUser: false,
},
});
if (!content) {
if (settings.unknownMessage) {
this.waMonitor.waInstances[instance.instanceName].textMessage(
{
number: remoteJid.split('@')[0],
delay: settings.delayMessage || 1000,
text: settings.unknownMessage,
},
false,
);
sendTelemetry('/message/sendText');
}
return;
}
if (settings.keywordFinish && content.toLowerCase() === settings.keywordFinish.toLowerCase()) {
if (settings.keepOpen) {
await this.prismaRepository.integrationSession.update({
where: {
id: session.id,
},
data: {
status: 'closed',
},
});
} else {
await this.prismaRepository.integrationSession.deleteMany({
where: {
botId: bot.id,
remoteJid: remoteJid,
},
});
}
return;
}
const message = await this.sendMessageToBot(instance, session, bot, remoteJid, pushName, content);
await this.sendMessageWhatsApp(instance, remoteJid, session, settings, message);
return;
}
}

View File

@ -0,0 +1,107 @@
import { JSONSchema7 } from 'json-schema';
import { v4 } from 'uuid';
const isNotEmpty = (...propertyNames: string[]): JSONSchema7 => {
const properties = {};
propertyNames.forEach(
(property) =>
(properties[property] = {
minLength: 1,
description: `The "${property}" cannot be empty`,
}),
);
return {
if: {
propertyNames: {
enum: [...propertyNames],
},
},
then: { properties },
};
};
export const flowiseSchema: JSONSchema7 = {
$id: v4(),
type: 'object',
properties: {
enabled: { type: 'boolean' },
description: { type: 'string' },
apiUrl: { type: 'string' },
apiKey: { type: 'string' },
triggerType: { type: 'string', enum: ['all', 'keyword', 'none', 'advanced'] },
triggerOperator: { type: 'string', enum: ['equals', 'contains', 'startsWith', 'endsWith', 'regex'] },
triggerValue: { type: 'string' },
expire: { type: 'integer' },
keywordFinish: { type: 'string' },
delayMessage: { type: 'integer' },
unknownMessage: { type: 'string' },
listeningFromMe: { type: 'boolean' },
stopBotFromMe: { type: 'boolean' },
keepOpen: { type: 'boolean' },
debounceTime: { type: 'integer' },
ignoreJids: { type: 'array', items: { type: 'string' } },
},
required: ['enabled', 'apiUrl', 'triggerType'],
...isNotEmpty('enabled', 'apiUrl', 'triggerType'),
};
export const flowiseStatusSchema: JSONSchema7 = {
$id: v4(),
type: 'object',
properties: {
remoteJid: { type: 'string' },
status: { type: 'string', enum: ['opened', 'closed', 'paused', 'delete'] },
},
required: ['remoteJid', 'status'],
...isNotEmpty('remoteJid', 'status'),
};
export const flowiseSettingSchema: 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' },
debounceTime: { type: 'integer' },
ignoreJids: { type: 'array', items: { type: 'string' } },
botIdFallback: { type: 'string' },
},
required: [
'expire',
'keywordFinish',
'delayMessage',
'unknownMessage',
'listeningFromMe',
'stopBotFromMe',
'keepOpen',
'debounceTime',
'ignoreJids',
],
...isNotEmpty(
'expire',
'keywordFinish',
'delayMessage',
'unknownMessage',
'listeningFromMe',
'stopBotFromMe',
'keepOpen',
'debounceTime',
'ignoreJids',
),
};
export const flowiseIgnoreJidSchema: JSONSchema7 = {
$id: v4(),
type: 'object',
properties: {
remoteJid: { type: 'string' },
action: { type: 'string', enum: ['add', 'remove'] },
},
required: ['remoteJid', 'action'],
...isNotEmpty('remoteJid', 'action'),
};

View File

@ -0,0 +1,797 @@
import { IgnoreJidDto } from '@api/dto/chatbot.dto';
import { InstanceDto } from '@api/dto/instance.dto';
import { PrismaRepository } from '@api/repository/repository.service';
import { WAMonitoringService } from '@api/services/monitor.service';
import { Logger } from '@config/logger.config';
import { getConversationMessage } from '@utils/getConversationMessage';
import { ChatbotController, ChatbotControllerInterface, EmitData } from '../../chatbot.controller';
import { GenericBotDto } from '../dto/generic.dto';
import { GenericService } from '../services/generic.service';
export class GenericController extends ChatbotController implements ChatbotControllerInterface {
constructor(
private readonly genericService: GenericService,
prismaRepository: PrismaRepository,
waMonitor: WAMonitoringService,
) {
super(prismaRepository, waMonitor);
this.botRepository = this.prismaRepository.genericBot;
this.settingsRepository = this.prismaRepository.genericSetting;
this.sessionRepository = this.prismaRepository.integrationSession;
}
public readonly logger = new Logger(GenericController.name);
integrationEnabled: boolean;
botRepository: any;
settingsRepository: any;
sessionRepository: any;
userMessageDebounce: { [key: string]: { message: string; timeoutId: NodeJS.Timeout } } = {};
// Bots
public async createBot(instance: InstanceDto, data: GenericBotDto) {
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,
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,
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 bot');
}
}
public async findBot(instance: InstanceDto) {
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 fetchBot(instance: InstanceDto, botId: string) {
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('Bot not found');
}
if (bot.instanceId !== instanceId) {
throw new Error('Bot not found');
}
return bot;
}
public async updateBot(instance: InstanceDto, botId: string, data: GenericBotDto) {
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('Bot not found');
}
if (bot.instanceId !== instanceId) {
throw new Error('Bot 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 bot 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,
apiUrl: data.apiUrl,
apiKey: data.apiKey,
},
});
if (checkDuplicate) {
throw new Error('Bot 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,
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 bot');
}
}
public async deleteBot(instance: InstanceDto, botId: string) {
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('Bot not found');
}
if (bot.instanceId !== instanceId) {
throw new Error('Bot 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 bot');
}
}
// Settings
public async settings(instance: InstanceDto, data: any) {
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,
botIdFallback: data.botIdFallback,
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,
botIdFallback: updateSettings.botIdFallback,
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,
botIdFallback: data.botIdFallback,
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,
botIdFallback: newSetttings.botIdFallback,
ignoreJids: newSetttings.ignoreJids,
};
} catch (error) {
this.logger.error(error);
throw new Error('Error setting default settings');
}
}
public async fetchSettings(instance: InstanceDto) {
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: [],
botIdFallback: '',
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,
botIdFallback: settings.botIdFallback,
fallback: settings.Fallback,
};
} catch (error) {
this.logger.error(error);
throw new Error('Error fetching default settings');
}
}
// 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.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, botId: string, remoteJid?: string) {
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: IgnoreJidDto) {
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');
}
}
// Emit
public async emit({ instance, remoteJid, msg }: EmitData) {
try {
const settings = await this.settingsRepository.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.genericService.processBot(
this.waMonitor.waInstances[instance.instanceName],
remoteJid,
findBot,
session,
settings,
debouncedContent,
msg?.pushName,
);
});
} else {
await this.genericService.processBot(
this.waMonitor.waInstances[instance.instanceName],
remoteJid,
findBot,
session,
settings,
content,
msg?.pushName,
);
}
return;
} catch (error) {
this.logger.error(error);
return;
}
}
}

View File

@ -0,0 +1,33 @@
import { TriggerOperator, TriggerType } from '@prisma/client';
export class GenericBotDto {
enabled?: boolean;
description?: string;
apiUrl?: string;
apiKey?: string;
expire?: number;
keywordFinish?: string;
delayMessage?: number;
unknownMessage?: string;
listeningFromMe?: boolean;
stopBotFromMe?: boolean;
keepOpen?: boolean;
debounceTime?: number;
triggerType?: TriggerType;
triggerOperator?: TriggerOperator;
triggerValue?: string;
ignoreJids?: any;
}
export class GenericBotSettingDto {
expire?: number;
keywordFinish?: string;
delayMessage?: number;
unknownMessage?: string;
listeningFromMe?: boolean;
stopBotFromMe?: boolean;
keepOpen?: boolean;
debounceTime?: number;
botIdFallback?: string;
ignoreJids?: any;
}

View File

@ -0,0 +1,124 @@
import { RouterBroker } from '@api/abstract/abstract.router';
import { IgnoreJidDto } from '@api/dto/chatbot.dto';
import { InstanceDto } from '@api/dto/instance.dto';
import { HttpStatus } from '@api/routes/index.router';
import { genericController } from '@api/server.module';
import { instanceSchema } from '@validate/instance.schema';
import { RequestHandler, Router } from 'express';
import { GenericBotDto, GenericBotSettingDto } from '../dto/generic.dto';
import {
genericIgnoreJidSchema,
genericSchema,
genericSettingSchema,
genericStatusSchema,
} from '../validate/generic.schema';
export class GenericRouter extends RouterBroker {
constructor(...guards: RequestHandler[]) {
super();
this.router
.post(this.routerPath('create'), ...guards, async (req, res) => {
const response = await this.dataValidate<GenericBotDto>({
request: req,
schema: genericSchema,
ClassRef: GenericBotDto,
execute: (instance, data) => genericController.createBot(instance, data),
});
res.status(HttpStatus.CREATED).json(response);
})
.get(this.routerPath('find'), ...guards, async (req, res) => {
const response = await this.dataValidate<InstanceDto>({
request: req,
schema: instanceSchema,
ClassRef: InstanceDto,
execute: (instance) => genericController.findBot(instance),
});
res.status(HttpStatus.OK).json(response);
})
.get(this.routerPath('fetch/:genericId'), ...guards, async (req, res) => {
const response = await this.dataValidate<InstanceDto>({
request: req,
schema: instanceSchema,
ClassRef: InstanceDto,
execute: (instance) => genericController.fetchBot(instance, req.params.genericId),
});
res.status(HttpStatus.OK).json(response);
})
.put(this.routerPath('update/:genericId'), ...guards, async (req, res) => {
const response = await this.dataValidate<GenericBotDto>({
request: req,
schema: genericSchema,
ClassRef: GenericBotDto,
execute: (instance, data) => genericController.updateBot(instance, req.params.genericId, data),
});
res.status(HttpStatus.OK).json(response);
})
.delete(this.routerPath('delete/:genericId'), ...guards, async (req, res) => {
const response = await this.dataValidate<InstanceDto>({
request: req,
schema: instanceSchema,
ClassRef: InstanceDto,
execute: (instance) => genericController.deleteBot(instance, req.params.genericId),
});
res.status(HttpStatus.OK).json(response);
})
.post(this.routerPath('settings'), ...guards, async (req, res) => {
const response = await this.dataValidate<GenericBotSettingDto>({
request: req,
schema: genericSettingSchema,
ClassRef: GenericBotSettingDto,
execute: (instance, data) => genericController.settings(instance, data),
});
res.status(HttpStatus.OK).json(response);
})
.get(this.routerPath('fetchSettings'), ...guards, async (req, res) => {
const response = await this.dataValidate<InstanceDto>({
request: req,
schema: instanceSchema,
ClassRef: InstanceDto,
execute: (instance) => genericController.fetchSettings(instance),
});
res.status(HttpStatus.OK).json(response);
})
.post(this.routerPath('changeStatus'), ...guards, async (req, res) => {
const response = await this.dataValidate<InstanceDto>({
request: req,
schema: genericStatusSchema,
ClassRef: InstanceDto,
execute: (instance, data) => genericController.changeStatus(instance, data),
});
res.status(HttpStatus.OK).json(response);
})
.get(this.routerPath('fetchSessions/:genericId'), ...guards, async (req, res) => {
const response = await this.dataValidate<InstanceDto>({
request: req,
schema: instanceSchema,
ClassRef: InstanceDto,
execute: (instance) => genericController.fetchSessions(instance, req.params.genericId),
});
res.status(HttpStatus.OK).json(response);
})
.post(this.routerPath('ignoreJid'), ...guards, async (req, res) => {
const response = await this.dataValidate<IgnoreJidDto>({
request: req,
schema: genericIgnoreJidSchema,
ClassRef: IgnoreJidDto,
execute: (instance, data) => genericController.ignoreJid(instance, data),
});
res.status(HttpStatus.OK).json(response);
});
}
public readonly router: Router = Router();
}

View File

@ -0,0 +1,302 @@
import { InstanceDto } from '@api/dto/instance.dto';
import { PrismaRepository } from '@api/repository/repository.service';
import { WAMonitoringService } from '@api/services/monitor.service';
import { Integration } from '@api/types/wa.types';
import { Auth, ConfigService, HttpServer } from '@config/env.config';
import { Logger } from '@config/logger.config';
import { GenericBot, GenericSetting, IntegrationSession } from '@prisma/client';
import { sendTelemetry } from '@utils/sendTelemetry';
import axios from 'axios';
export class GenericService {
constructor(
private readonly waMonitor: WAMonitoringService,
private readonly configService: ConfigService,
private readonly prismaRepository: PrismaRepository,
) {}
private readonly logger = new Logger('GenericService');
public async createNewSession(instance: InstanceDto, data: any) {
try {
const session = await this.prismaRepository.integrationSession.create({
data: {
remoteJid: data.remoteJid,
sessionId: data.remoteJid,
status: 'opened',
awaitUser: false,
botId: data.botId,
instanceId: instance.instanceId,
},
});
return { session };
} catch (error) {
this.logger.error(error);
return;
}
}
private isImageMessage(content: string) {
return content.includes('imageMessage');
}
private async sendMessageToBot(
instance: any,
session: IntegrationSession,
bot: GenericBot,
remoteJid: string,
pushName: string,
content: string,
) {
const payload: any = {
inputs: {
remoteJid: remoteJid,
pushName: pushName,
instanceName: instance.instanceName,
serverUrl: this.configService.get<HttpServer>('SERVER').URL,
apiKey: this.configService.get<Auth>('AUTHENTICATION').API_KEY.KEY,
},
query: content,
conversation_id: session.sessionId === remoteJid ? undefined : session.sessionId,
user: remoteJid,
};
if (this.isImageMessage(content)) {
const contentSplit = content.split('|');
payload.files = [
{
type: 'image',
url: contentSplit[1].split('?')[0],
},
];
payload.query = contentSplit[2] || content;
}
if (instance.integration === Integration.WHATSAPP_BAILEYS) {
await instance.client.presenceSubscribe(remoteJid);
await instance.client.sendPresenceUpdate('composing', remoteJid);
}
let headers: any = {
'Content-Type': 'application/json',
};
if (bot.apiKey) {
headers = {
...headers,
Authorization: `Bearer ${bot.apiKey}`,
};
}
const response = await axios.post(bot.apiUrl, payload, {
headers,
});
if (instance.integration === Integration.WHATSAPP_BAILEYS)
await instance.client.sendPresenceUpdate('paused', remoteJid);
const message = response?.data?.answer;
return message;
}
private async sendMessageWhatsApp(
instance: any,
remoteJid: string,
session: IntegrationSession,
settings: GenericSetting,
message: string,
) {
const regex = /!?\[(.*?)\]\((.*?)\)/g;
const result = [];
let lastIndex = 0;
let match;
while ((match = regex.exec(message)) !== null) {
if (match.index > lastIndex) {
result.push({ text: message.slice(lastIndex, match.index).trim() });
}
result.push({ caption: match[1], url: match[2] });
lastIndex = regex.lastIndex;
}
if (lastIndex < message.length) {
result.push({ text: message.slice(lastIndex).trim() });
}
for (const item of result) {
if (item.text) {
await instance.textMessage(
{
number: remoteJid.split('@')[0],
delay: settings?.delayMessage || 1000,
text: item.text,
},
false,
);
}
if (item.url) {
await instance.mediaMessage(
{
number: remoteJid.split('@')[0],
delay: settings?.delayMessage || 1000,
mediatype: 'image',
media: item.url,
caption: item.caption,
},
false,
);
}
}
await this.prismaRepository.integrationSession.update({
where: {
id: session.id,
},
data: {
status: 'opened',
awaitUser: true,
},
});
sendTelemetry('/message/sendText');
return;
}
private async initNewSession(
instance: any,
remoteJid: string,
bot: GenericBot,
settings: GenericSetting,
session: IntegrationSession,
content: string,
pushName?: string,
) {
const data = await this.createNewSession(instance, {
remoteJid,
botId: bot.id,
});
if (data.session) {
session = data.session;
}
const message = await this.sendMessageToBot(instance, session, bot, remoteJid, pushName, content);
await this.sendMessageWhatsApp(instance, remoteJid, session, settings, message);
return;
}
public async processBot(
instance: any,
remoteJid: string,
bot: GenericBot,
session: IntegrationSession,
settings: GenericSetting,
content: string,
pushName?: string,
) {
if (session && session.status !== 'opened') {
return;
}
if (session && settings.expire && settings.expire > 0) {
const now = Date.now();
const sessionUpdatedAt = new Date(session.updatedAt).getTime();
const diff = now - sessionUpdatedAt;
const diffInMinutes = Math.floor(diff / 1000 / 60);
if (diffInMinutes > settings.expire) {
if (settings.keepOpen) {
await this.prismaRepository.integrationSession.update({
where: {
id: session.id,
},
data: {
status: 'closed',
},
});
} else {
await this.prismaRepository.integrationSession.deleteMany({
where: {
botId: bot.id,
remoteJid: remoteJid,
},
});
}
await this.initNewSession(instance, remoteJid, bot, settings, session, content, pushName);
return;
}
}
if (!session) {
await this.initNewSession(instance, remoteJid, bot, settings, session, content, pushName);
return;
}
await this.prismaRepository.integrationSession.update({
where: {
id: session.id,
},
data: {
status: 'opened',
awaitUser: false,
},
});
if (!content) {
if (settings.unknownMessage) {
this.waMonitor.waInstances[instance.instanceName].textMessage(
{
number: remoteJid.split('@')[0],
delay: settings.delayMessage || 1000,
text: settings.unknownMessage,
},
false,
);
sendTelemetry('/message/sendText');
}
return;
}
if (settings.keywordFinish && content.toLowerCase() === settings.keywordFinish.toLowerCase()) {
if (settings.keepOpen) {
await this.prismaRepository.integrationSession.update({
where: {
id: session.id,
},
data: {
status: 'closed',
},
});
} else {
await this.prismaRepository.integrationSession.deleteMany({
where: {
botId: bot.id,
remoteJid: remoteJid,
},
});
}
return;
}
const message = await this.sendMessageToBot(instance, session, bot, remoteJid, pushName, content);
await this.sendMessageWhatsApp(instance, remoteJid, session, settings, message);
return;
}
}

View File

@ -0,0 +1,107 @@
import { JSONSchema7 } from 'json-schema';
import { v4 } from 'uuid';
const isNotEmpty = (...propertyNames: string[]): JSONSchema7 => {
const properties = {};
propertyNames.forEach(
(property) =>
(properties[property] = {
minLength: 1,
description: `The "${property}" cannot be empty`,
}),
);
return {
if: {
propertyNames: {
enum: [...propertyNames],
},
},
then: { properties },
};
};
export const genericSchema: JSONSchema7 = {
$id: v4(),
type: 'object',
properties: {
enabled: { type: 'boolean' },
description: { type: 'string' },
apiUrl: { type: 'string' },
apiKey: { type: 'string' },
triggerType: { type: 'string', enum: ['all', 'keyword', 'none', 'advanced'] },
triggerOperator: { type: 'string', enum: ['equals', 'contains', 'startsWith', 'endsWith', 'regex'] },
triggerValue: { type: 'string' },
expire: { type: 'integer' },
keywordFinish: { type: 'string' },
delayMessage: { type: 'integer' },
unknownMessage: { type: 'string' },
listeningFromMe: { type: 'boolean' },
stopBotFromMe: { type: 'boolean' },
keepOpen: { type: 'boolean' },
debounceTime: { type: 'integer' },
ignoreJids: { type: 'array', items: { type: 'string' } },
},
required: ['enabled', 'apiUrl', 'triggerType'],
...isNotEmpty('enabled', 'apiUrl', 'triggerType'),
};
export const genericStatusSchema: JSONSchema7 = {
$id: v4(),
type: 'object',
properties: {
remoteJid: { type: 'string' },
status: { type: 'string', enum: ['opened', 'closed', 'paused', 'delete'] },
},
required: ['remoteJid', 'status'],
...isNotEmpty('remoteJid', 'status'),
};
export const genericSettingSchema: 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' },
debounceTime: { type: 'integer' },
ignoreJids: { type: 'array', items: { type: 'string' } },
botIdFallback: { type: 'string' },
},
required: [
'expire',
'keywordFinish',
'delayMessage',
'unknownMessage',
'listeningFromMe',
'stopBotFromMe',
'keepOpen',
'debounceTime',
'ignoreJids',
],
...isNotEmpty(
'expire',
'keywordFinish',
'delayMessage',
'unknownMessage',
'listeningFromMe',
'stopBotFromMe',
'keepOpen',
'debounceTime',
'ignoreJids',
),
};
export const genericIgnoreJidSchema: JSONSchema7 = {
$id: v4(),
type: 'object',
properties: {
remoteJid: { type: 'string' },
action: { type: 'string', enum: ['add', 'remove'] },
},
required: ['remoteJid', 'action'],
...isNotEmpty('remoteJid', 'action'),
};

View File

@ -1,13 +1,5 @@
import { TriggerOperator, TriggerType } from '@prisma/client';
export class Session {
remoteJid?: string;
sessionId?: string;
status?: string;
createdAt?: number;
updateAt?: number;
}
export class OpenaiCredsDto {
name: string;
apiKey: string;
@ -53,8 +45,3 @@ export class OpenaiSettingDto {
ignoreJids?: any;
speechToText?: boolean;
}
export class OpenaiIgnoreJidDto {
remoteJid?: string;
action?: string;
}

View File

@ -1,11 +1,7 @@
import { RouterBroker } from '@api/abstract/abstract.router';
import { IgnoreJidDto } from '@api/dto/chatbot.dto';
import { InstanceDto } from '@api/dto/instance.dto';
import {
OpenaiCredsDto,
OpenaiDto,
OpenaiIgnoreJidDto,
OpenaiSettingDto,
} from '@api/integrations/chatbot/openai/dto/openai.dto';
import { OpenaiCredsDto, OpenaiDto, OpenaiSettingDto } from '@api/integrations/chatbot/openai/dto/openai.dto';
import { HttpStatus } from '@api/routes/index.router';
import { openaiController } from '@api/server.module';
import {
@ -57,7 +53,7 @@ export class OpenaiRouter extends RouterBroker {
request: req,
schema: openaiSchema,
ClassRef: OpenaiDto,
execute: (instance, data) => openaiController.createOpenai(instance, data),
execute: (instance, data) => openaiController.createBot(instance, data),
});
res.status(HttpStatus.CREATED).json(response);
@ -67,7 +63,7 @@ export class OpenaiRouter extends RouterBroker {
request: req,
schema: instanceSchema,
ClassRef: InstanceDto,
execute: (instance) => openaiController.findOpenai(instance),
execute: (instance) => openaiController.findBot(instance),
});
res.status(HttpStatus.OK).json(response);
@ -77,7 +73,7 @@ export class OpenaiRouter extends RouterBroker {
request: req,
schema: instanceSchema,
ClassRef: InstanceDto,
execute: (instance) => openaiController.fetchOpenai(instance, req.params.openaiBotId),
execute: (instance) => openaiController.fetchBot(instance, req.params.openaiBotId),
});
res.status(HttpStatus.OK).json(response);
@ -87,7 +83,7 @@ export class OpenaiRouter extends RouterBroker {
request: req,
schema: openaiSchema,
ClassRef: OpenaiDto,
execute: (instance, data) => openaiController.updateOpenai(instance, req.params.openaiBotId, data),
execute: (instance, data) => openaiController.updateBot(instance, req.params.openaiBotId, data),
});
res.status(HttpStatus.OK).json(response);
@ -97,7 +93,7 @@ export class OpenaiRouter extends RouterBroker {
request: req,
schema: instanceSchema,
ClassRef: InstanceDto,
execute: (instance) => openaiController.deleteOpenai(instance, req.params.openaiBotId),
execute: (instance) => openaiController.deleteBot(instance, req.params.openaiBotId),
});
res.status(HttpStatus.OK).json(response);
@ -143,10 +139,10 @@ export class OpenaiRouter extends RouterBroker {
res.status(HttpStatus.OK).json(response);
})
.post(this.routerPath('ignoreJid'), ...guards, async (req, res) => {
const response = await this.dataValidate<OpenaiIgnoreJidDto>({
const response = await this.dataValidate<IgnoreJidDto>({
request: req,
schema: openaiIgnoreJidSchema,
ClassRef: OpenaiIgnoreJidDto,
ClassRef: IgnoreJidDto,
execute: (instance, data) => openaiController.ignoreJid(instance, data),
});

File diff suppressed because it is too large Load Diff

View File

@ -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;
}

View File

@ -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<TypebotIgnoreJidDto>({
const response = await this.dataValidate<IgnoreJidDto>({
request: req,
schema: typebotIgnoreJidSchema,
ClassRef: TypebotIgnoreJidDto,
ClassRef: IgnoreJidDto,
execute: (instance, data) => typebotController.ignoreJid(instance, data),
});

View File

@ -3,6 +3,25 @@ import { rabbitmqController, sqsController, webhookController, websocketControll
import { WAMonitoringService } from '@api/services/monitor.service';
import { Server } from 'http';
export type EmitData = {
instanceName: string;
origin: string;
event: string;
data: Object;
serverUrl: string;
dateTime: string;
sender: string;
apiKey?: string;
local?: boolean;
};
export interface EventControllerInterface {
integrationEnabled: boolean;
set(instanceName: string, data: any): Promise<any>;
get(instanceName: string): Promise<any>;
emit({ instanceName, origin, event, data, serverUrl, dateTime, sender, apiKey, local }: EmitData): Promise<void>;
}
export class EventController {
public prismaRepository: PrismaRepository;
public waMonitor: WAMonitoringService;

View File

@ -7,19 +7,19 @@ import { Logger } from '@config/logger.config';
import { NotFoundException } from '@exceptions';
import * as amqp from 'amqplib/callback_api';
import { EventController } from '../../event.controller';
import { EmitData, EventController, EventControllerInterface } from '../../event.controller';
export class RabbitmqController extends EventController {
export class RabbitmqController extends EventController implements EventControllerInterface {
public amqpChannel: amqp.Channel | null = null;
private readonly logger = new Logger(RabbitmqController.name);
integrationEnabled = configService.get<Rabbitmq>('RABBITMQ')?.ENABLED;
constructor(prismaRepository: PrismaRepository, waMonitor: WAMonitoringService) {
super(prismaRepository, waMonitor);
}
public async init(): Promise<void> {
if (!configService.get<Rabbitmq>('RABBITMQ')?.ENABLED) {
return;
}
if (!this.integrationEnabled) return;
await new Promise<void>((resolve, reject) => {
const uri = configService.get<Rabbitmq>('RABBITMQ').URI;
@ -62,6 +62,8 @@ export class RabbitmqController extends EventController {
}
public async set(instanceName: string, data: RabbitmqDto): Promise<wa.LocalRabbitmq> {
if (!this.integrationEnabled) return;
if (!data.enabled) {
data.events = [];
} else {
@ -91,6 +93,8 @@ export class RabbitmqController extends EventController {
}
public async get(instanceName: string): Promise<wa.LocalWebsocket> {
if (!this.integrationEnabled) return;
if (undefined === this.monitor.waInstances[instanceName]) {
throw new NotFoundException('Instance not found');
}
@ -117,19 +121,8 @@ export class RabbitmqController extends EventController {
dateTime,
sender,
apiKey,
}: {
instanceName: string;
origin: string;
event: string;
data: Object;
serverUrl: string;
dateTime: string;
sender: string;
apiKey?: string;
}): Promise<void> {
if (!configService.get<Rabbitmq>('RABBITMQ')?.ENABLED) {
return;
}
}: EmitData): Promise<void> {
if (!this.integrationEnabled) return;
const instanceRabbitmq = await this.get(instanceName);
const rabbitmqLocal = instanceRabbitmq?.events;

View File

@ -7,20 +7,19 @@ import { configService, Log, Sqs } from '@config/env.config';
import { Logger } from '@config/logger.config';
import { NotFoundException } from '@exceptions';
import { EventController } from '../../event.controller';
import { EmitData, EventController, EventControllerInterface } from '../../event.controller';
export class SqsController extends EventController {
export class SqsController extends EventController implements EventControllerInterface {
private sqs: SQS;
private readonly logger = new Logger(SqsController.name);
integrationEnabled = configService.get<Sqs>('SQS')?.ENABLED;
constructor(prismaRepository: PrismaRepository, waMonitor: WAMonitoringService) {
super(prismaRepository, waMonitor);
}
public init(): void {
if (!configService.get<Sqs>('SQS')?.ENABLED) {
return;
}
if (!this.integrationEnabled) return;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
new Promise<void>((resolve, reject) => {
@ -48,6 +47,8 @@ export class SqsController extends EventController {
}
public async set(instanceName: string, data: SqsDto): Promise<wa.LocalSqs> {
if (!this.integrationEnabled) return;
if (!data.enabled) {
data.events = [];
} else {
@ -77,6 +78,8 @@ export class SqsController extends EventController {
}
public async get(instanceName: string): Promise<wa.LocalSqs> {
if (!this.integrationEnabled) return;
if (undefined === this.monitor.waInstances[instanceName]) {
throw new NotFoundException('Instance not found');
}
@ -103,19 +106,8 @@ export class SqsController extends EventController {
dateTime,
sender,
apiKey,
}: {
instanceName: string;
origin: string;
event: string;
data: Object;
serverUrl: string;
dateTime: string;
sender: string;
apiKey?: string;
}): Promise<void> {
if (!configService.get<Sqs>('SQS')?.ENABLED) {
return;
}
}: EmitData): Promise<void> {
if (!this.integrationEnabled) return;
const instanceSqs = await this.get(instanceName);
const sqsLocal = instanceSqs?.events;

View File

@ -1,21 +1,22 @@
import { PrismaRepository } from '@api/repository/repository.service';
import { WAMonitoringService } from '@api/services/monitor.service';
import { wa } from '@api/types/wa.types';
import { configService, Log, Webhook, Websocket } from '@config/env.config';
import { configService, Log, Webhook } from '@config/env.config';
import { Logger } from '@config/logger.config';
import { BadRequestException, NotFoundException } from '@exceptions';
import axios from 'axios';
import { isURL } from 'class-validator';
import { EventController } from '../../event.controller';
import { EmitData, EventController, EventControllerInterface } from '../../event.controller';
import { WebhookDto } from '../dto/webhook.dto';
export class WebhookController extends EventController {
export class WebhookController extends EventController implements EventControllerInterface {
private readonly logger = new Logger(WebhookController.name);
constructor(prismaRepository: PrismaRepository, waMonitor: WAMonitoringService) {
super(prismaRepository, waMonitor);
}
integrationEnabled: boolean;
public async set(instanceName: string, data: WebhookDto): Promise<wa.LocalWebHook> {
if (!isURL(data.url, { require_tld: false })) {
@ -30,27 +31,24 @@ export class WebhookController extends EventController {
}
}
try {
await this.get(instanceName);
await this.get(instanceName);
return this.prisma.webhook.update({
where: {
instanceId: this.monitor.waInstances[instanceName].instanceId,
},
data,
});
} catch (err) {
return this.prisma.webhook.create({
data: {
enabled: data.enabled,
events: data.events,
instanceId: this.monitor.waInstances[instanceName].instanceId,
url: data.url,
webhookBase64: data.webhookBase64,
webhookByEvents: data.webhookByEvents,
},
});
}
return this.prisma.webhook.upsert({
where: {
instanceId: this.monitor.waInstances[instanceName].instanceId,
},
update: {
...data,
},
create: {
enabled: data.enabled,
events: data.events,
instanceId: this.monitor.waInstances[instanceName].instanceId,
url: data.url,
webhookBase64: data.webhookBase64,
webhookByEvents: data.webhookByEvents,
},
});
}
public async get(instanceName: string): Promise<wa.LocalWebHook> {
@ -81,23 +79,13 @@ export class WebhookController extends EventController {
sender,
apiKey,
local,
}: {
instanceName: string;
origin: string;
event: string;
data: Object;
serverUrl: string;
dateTime: string;
sender: string;
apiKey?: string;
local?: boolean;
}): Promise<void> {
if (!configService.get<Websocket>('WEBSOCKET')?.ENABLED) {
}: EmitData): Promise<void> {
const instanceWebhook = await this.get(instanceName);
if (!instanceWebhook || !instanceWebhook.enabled) {
return;
}
const instanceWebhook = await this.get(instanceName);
const webhookGlobal = configService.get<Webhook>('WEBHOOK');
const webhookConfig = configService.get<Webhook>('WEBHOOK');
const webhookLocal = instanceWebhook?.events;
const we = event.replace(/[.-]/gm, '_').toUpperCase();
const transformedWe = we.replace(/_/gm, '-').toLowerCase();
@ -113,6 +101,7 @@ export class WebhookController extends EventController {
server_url: serverUrl,
apikey: apiKey,
};
if (local) {
if (Array.isArray(webhookLocal) && webhookLocal.includes(we)) {
let baseURL: string;
@ -156,16 +145,12 @@ export class WebhookController extends EventController {
}
}
if (webhookGlobal.GLOBAL?.ENABLED) {
if (webhookGlobal.EVENTS[we]) {
const globalWebhook = configService.get<Webhook>('WEBHOOK').GLOBAL;
if (webhookConfig.GLOBAL?.ENABLED) {
if (webhookConfig.EVENTS[we]) {
let globalURL = webhookConfig.GLOBAL.URL;
let globalURL;
if (webhookGlobal.GLOBAL.WEBHOOK_BY_EVENTS) {
globalURL = `${globalWebhook.URL}/${transformedWe}`;
} else {
globalURL = globalWebhook.URL;
if (webhookConfig.GLOBAL.WEBHOOK_BY_EVENTS) {
globalURL = `${globalURL}/${transformedWe}`;
}
if (enabledLog) {
@ -179,7 +164,7 @@ export class WebhookController extends EventController {
}
try {
if (globalWebhook && globalWebhook?.ENABLED && isURL(globalURL)) {
if (isURL(globalURL)) {
const httpService = axios.create({ baseURL: globalURL });
await httpService.post('', webhookData);
@ -202,7 +187,7 @@ export class WebhookController extends EventController {
}
}
public async receiveWebhook(data: any) {
public async receiveWebhookMeta(data: any) {
if (data.object === 'whatsapp_business_account') {
if (data.entry[0]?.changes[0]?.field === 'message_template_status_update') {
const template = await this.prismaRepository.template.findFirst({
@ -228,7 +213,7 @@ export class WebhookController extends EventController {
const numberId = entry.changes[0].value.metadata.phone_number_id;
if (!numberId) {
this.logger.error('WebhookService -> receiveWebhook -> numberId not found');
this.logger.error('WebhookService -> receiveWebhookMeta -> numberId not found');
return;
}
@ -237,7 +222,7 @@ export class WebhookController extends EventController {
});
if (!instance) {
this.logger.error('WebhookService -> receiveWebhook -> instance not found');
this.logger.error('WebhookService -> receiveWebhookMeta -> instance not found');
return;
}
@ -249,4 +234,28 @@ export class WebhookController extends EventController {
return;
}
public async receiveWebhookEvolution(data: any) {
const numberId = data.numberId;
if (!numberId) {
this.logger.error('WebhookService -> receiveWebhookEvolution -> numberId not found');
return;
}
const instance = await this.prismaRepository.instance.findFirst({
where: { number: numberId },
});
if (!instance) {
this.logger.error('WebhookService -> receiveWebhook -> instance not found');
return;
}
await this.waMonitor.waInstances[instance.name].connectToWhatsapp(data);
return {
status: 'success',
};
}
}

View File

@ -1,7 +1,7 @@
import { RouterBroker } from '@api/abstract/abstract.router';
import { InstanceDto } from '@api/dto/instance.dto';
import { webhookController } from '@api/server.module';
import { HttpStatus } from '@api/routes/index.router';
import { webhookController } from '@api/server.module';
import { ConfigService, WaBusiness } from '@config/env.config';
import { instanceSchema, webhookSchema } from '@validate/validate.schema';
import { RequestHandler, Router } from 'express';
@ -32,14 +32,20 @@ export class WebhookRouter extends RouterBroker {
res.status(HttpStatus.OK).json(response);
})
.get('meta', async (req, res) => {
.get(this.routerPath('meta', false), async (req, res) => {
if (req.query['hub.verify_token'] === configService.get<WaBusiness>('WA_BUSINESS').TOKEN_WEBHOOK)
res.send(req.query['hub.challenge']);
else res.send('Error, wrong validation token');
})
.post('meta', async (req, res) => {
.post(this.routerPath('meta', false), async (req, res) => {
const { body } = req;
const response = await webhookController.receiveWebhook(body);
const response = await webhookController.receiveWebhookMeta(body);
return res.status(200).json(response);
})
.post(this.routerPath('evolution', false), async (req, res) => {
const { body } = req;
const response = await webhookController.receiveWebhookEvolution(body);
return res.status(200).json(response);
});

View File

@ -8,12 +8,13 @@ import { NotFoundException } from '@exceptions';
import { Server } from 'http';
import { Server as SocketIO } from 'socket.io';
import { EventController } from '../../event.controller';
import { EmitData, EventController, EventControllerInterface } from '../../event.controller';
export class WebsocketController extends EventController {
export class WebsocketController extends EventController implements EventControllerInterface {
private io: SocketIO;
private corsConfig: Array<any>;
private readonly logger = new Logger(WebsocketController.name);
integrationEnabled = configService.get<Websocket>('WEBSOCKET')?.ENABLED;
constructor(prismaRepository: PrismaRepository, waMonitor: WAMonitoringService) {
super(prismaRepository, waMonitor);
@ -21,9 +22,7 @@ export class WebsocketController extends EventController {
}
public init(httpServer: Server): void {
if (!configService.get<Websocket>('WEBSOCKET')?.ENABLED) {
return;
}
if (!this.integrationEnabled) return;
this.socket = new SocketIO(httpServer, {
cors: {
@ -59,6 +58,8 @@ export class WebsocketController extends EventController {
}
public async set(instanceName: string, data: WebsocketDto): Promise<wa.LocalWebsocket> {
if (!this.integrationEnabled) return;
if (!data.enabled) {
data.events = [];
} else {
@ -88,6 +89,8 @@ export class WebsocketController extends EventController {
}
public async get(instanceName: string): Promise<wa.LocalWebsocket> {
if (!this.integrationEnabled) return;
if (undefined === this.monitor.waInstances[instanceName]) {
throw new NotFoundException('Instance not found');
}
@ -114,19 +117,8 @@ export class WebsocketController extends EventController {
dateTime,
sender,
apiKey,
}: {
instanceName: string;
origin: string;
event: string;
data: Object;
serverUrl: string;
dateTime: string;
sender: string;
apiKey?: string;
}): Promise<void> {
if (!configService.get<Websocket>('WEBSOCKET')?.ENABLED) {
return;
}
}: EmitData): Promise<void> {
if (!this.integrationEnabled) return;
const configEv = event.replace(/[.-]/gm, '_').toUpperCase();
const logEnabled = configService.get<Log>('LOG').LEVEL.includes('WEBSOCKET');

View File

@ -17,6 +17,10 @@ import { ChatwootController } from './integrations/chatbot/chatwoot/controllers/
import { ChatwootService } from './integrations/chatbot/chatwoot/services/chatwoot.service';
import { DifyController } from './integrations/chatbot/dify/controllers/dify.controller';
import { DifyService } from './integrations/chatbot/dify/services/dify.service';
import { FlowiseController } from './integrations/chatbot/flowise/controllers/flowise.controller';
import { FlowiseService } from './integrations/chatbot/flowise/services/flowise.service';
import { GenericController } from './integrations/chatbot/generic/controllers/generic.controller';
import { GenericService } from './integrations/chatbot/generic/services/generic.service';
import { OpenaiController } from './integrations/chatbot/openai/controllers/openai.controller';
import { OpenaiService } from './integrations/chatbot/openai/services/openai.service';
import { TypebotController } from './integrations/chatbot/typebot/controllers/typebot.controller';
@ -108,12 +112,18 @@ 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);
const genericService = new GenericService(waMonitor, configService, prismaRepository);
export const genericController = new GenericController(genericService, prismaRepository, waMonitor);
const flowiseService = new FlowiseService(waMonitor, configService, prismaRepository);
export const flowiseController = new FlowiseController(flowiseService, prismaRepository, waMonitor);
logger.info('Module - ON');

View File

@ -58,11 +58,6 @@ export class ChannelStartupService {
this.instance.token = instance.token;
this.instance.businessId = instance.businessId;
this.sendDataWebhook(Events.STATUS_INSTANCE, {
instance: this.instance.name,
status: 'created',
});
if (this.configService.get<Chatwoot>('CHATWOOT').ENABLED && this.localChatwoot.enabled) {
this.chatwootService.eventWhatsapp(
Events.STATUS_INSTANCE,

View File

@ -1,6 +1,4 @@
import { InstanceDto } from '@api/dto/instance.dto';
import { BaileysStartupService } from '@api/integrations/channel/whatsapp/baileys/whatsapp.baileys.service';
import { BusinessStartupService } from '@api/integrations/channel/whatsapp/business/whatsapp.business.service';
import { ProviderFiles } from '@api/provider/sessions';
import { PrismaRepository } from '@api/repository/repository.service';
import { channelController } from '@api/server.module';
@ -37,7 +35,7 @@ export class WAMonitoringService {
private readonly redis: Partial<CacheConf> = {};
private readonly logger = new Logger('WAMonitoringService');
public readonly waInstances: Record<string, BaileysStartupService | BusinessStartupService> = {};
public readonly waInstances: Record<string, any> = {};
private readonly providerSession = Object.freeze(this.configService.get<ProviderSession>('PROVIDER'));
@ -196,7 +194,7 @@ export class WAMonitoringService {
data: {
id: data.instanceId,
name: data.instanceName,
connectionStatus: data.integration && data.integration === Integration.WHATSAPP_BUSINESS ? 'open' : 'close',
connectionStatus: data.integration && data.integration === Integration.WHATSAPP_BAILEYS ? 'close' : 'open',
number: data.number,
integration: data.integration || Integration.WHATSAPP_BAILEYS,
token: data.hash,
@ -210,7 +208,7 @@ export class WAMonitoringService {
}
private async setInstance(instanceData: InstanceDto) {
const instance = channelController.init(instanceData.integration, {
const instance = channelController.init(instanceData, {
configService: this.configService,
eventEmitter: this.eventEmitter,
prismaRepository: this.prismaRepository,

View File

@ -146,4 +146,5 @@ export const MessageSubtype = [
export const Integration = {
WHATSAPP_BUSINESS: 'WHATSAPP-BUSINESS',
WHATSAPP_BAILEYS: 'WHATSAPP-BAILEYS',
EVOLUTION: 'EVOLUTION',
};

View File

@ -181,6 +181,7 @@ export type Chatwoot = {
ENABLED: boolean;
MESSAGE_DELETE: boolean;
MESSAGE_READ: boolean;
BOT_CONTACT: boolean;
IMPORT: {
DATABASE: {
CONNECTION: {
@ -426,6 +427,7 @@ export class ConfigService {
ENABLED: process.env?.CHATWOOT_ENABLED === 'true',
MESSAGE_DELETE: process.env.CHATWOOT_MESSAGE_DELETE === 'true',
MESSAGE_READ: process.env.CHATWOOT_MESSAGE_READ === 'true',
BOT_CONTACT: process.env.CHATWOOT_BOT_CONTACT === 'true',
IMPORT: {
DATABASE: {
CONNECTION: {

View File

@ -0,0 +1,149 @@
import { advancedOperatorsSearch } from './advancedOperatorsSearch';
export const findBotByTrigger = async (
botRepository: any,
settingsRepository: any,
content: string,
instanceId: string,
) => {
// Check for triggerType 'all'
const findTriggerAll = await botRepository.findFirst({
where: {
enabled: true,
triggerType: 'all',
instanceId: instanceId,
},
});
if (findTriggerAll) return findTriggerAll;
const findTriggerAdvanced = await botRepository.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 botRepository.findFirst({
where: {
enabled: true,
triggerType: 'keyword',
triggerOperator: 'equals',
triggerValue: content,
instanceId: instanceId,
},
});
if (findTriggerEquals) return findTriggerEquals;
// Check for regex match
const findRegex = await botRepository.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 botRepository.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 botRepository.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 botRepository.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 settingsRepository.findFirst({
where: {
instanceId: instanceId,
},
});
if (fallback?.openaiIdFallback) {
const findFallback = await botRepository.findFirst({
where: {
id: fallback.openaiIdFallback,
},
});
if (findFallback) return findFallback;
}
return null;
};

View File

@ -0,0 +1,65 @@
import { configService, S3 } from '@config/env.config';
const getTypeMessage = (msg: any) => {
let mediaId: string;
if (configService.get<S3>('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 };
};
const getMessageContent = (types: any) => {
const typeKey = Object.keys(types).find((key) => types[key] !== undefined);
const result = typeKey ? types[typeKey] : undefined;
return result;
};
export const getConversationMessage = (msg: any) => {
const types = getTypeMessage(msg);
const messageContent = getMessageContent(types);
return messageContent;
};