mirror of
https://github.com/EvolutionAPI/evolution-api.git
synced 2025-07-23 04:22:02 -06:00
Merge remote-tracking branch 'upstream/v2.0.0' into v2.0.0
This commit is contained in:
commit
9099adf363
@ -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
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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
381
manager/dist/assets/index-BmAfUzu7.js
vendored
Normal file
File diff suppressed because one or more lines are too long
381
manager/dist/assets/index-CNPbB3iJ.js
vendored
381
manager/dist/assets/index-CNPbB3iJ.js
vendored
File diff suppressed because one or more lines are too long
2
manager/dist/index.html
vendored
2
manager/dist/index.html
vendored
@ -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>
|
||||
|
@ -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`);
|
@ -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;
|
@ -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
|
||||
}
|
||||
|
@ -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;
|
@ -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;
|
@ -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;
|
@ -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
|
||||
}
|
||||
|
@ -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({
|
||||
|
12
src/api/dto/chatbot.dto.ts
Normal file
12
src/api/dto/chatbot.dto.ts
Normal 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;
|
||||
}
|
@ -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,
|
||||
|
@ -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');
|
||||
}
|
||||
}
|
@ -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({
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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';
|
||||
|
@ -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')) {
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
33
src/api/integrations/chatbot/flowise/dto/flowise.dto.ts
Normal file
33
src/api/integrations/chatbot/flowise/dto/flowise.dto.ts
Normal 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;
|
||||
}
|
124
src/api/integrations/chatbot/flowise/routes/flowise.router.ts
Normal file
124
src/api/integrations/chatbot/flowise/routes/flowise.router.ts
Normal 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();
|
||||
}
|
309
src/api/integrations/chatbot/flowise/services/flowise.service.ts
Normal file
309
src/api/integrations/chatbot/flowise/services/flowise.service.ts
Normal 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;
|
||||
}
|
||||
}
|
107
src/api/integrations/chatbot/flowise/validate/flowise.schema.ts
Normal file
107
src/api/integrations/chatbot/flowise/validate/flowise.schema.ts
Normal 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'),
|
||||
};
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
33
src/api/integrations/chatbot/generic/dto/generic.dto.ts
Normal file
33
src/api/integrations/chatbot/generic/dto/generic.dto.ts
Normal 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;
|
||||
}
|
124
src/api/integrations/chatbot/generic/routes/generic.router.ts
Normal file
124
src/api/integrations/chatbot/generic/routes/generic.router.ts
Normal 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();
|
||||
}
|
302
src/api/integrations/chatbot/generic/services/generic.service.ts
Normal file
302
src/api/integrations/chatbot/generic/services/generic.service.ts
Normal 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;
|
||||
}
|
||||
}
|
107
src/api/integrations/chatbot/generic/validate/generic.schema.ts
Normal file
107
src/api/integrations/chatbot/generic/validate/generic.schema.ts
Normal 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'),
|
||||
};
|
File diff suppressed because it is too large
Load Diff
@ -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;
|
||||
}
|
||||
|
@ -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
File diff suppressed because it is too large
Load Diff
@ -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;
|
||||
}
|
||||
|
@ -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),
|
||||
});
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
});
|
||||
|
@ -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');
|
||||
|
@ -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');
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -146,4 +146,5 @@ export const MessageSubtype = [
|
||||
export const Integration = {
|
||||
WHATSAPP_BUSINESS: 'WHATSAPP-BUSINESS',
|
||||
WHATSAPP_BAILEYS: 'WHATSAPP-BAILEYS',
|
||||
EVOLUTION: 'EVOLUTION',
|
||||
};
|
||||
|
@ -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: {
|
||||
|
149
src/utils/findBotByTrigger.ts
Normal file
149
src/utils/findBotByTrigger.ts
Normal 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;
|
||||
};
|
65
src/utils/getConversationMessage.ts
Normal file
65
src/utils/getConversationMessage.ts
Normal 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;
|
||||
};
|
Loading…
Reference in New Issue
Block a user