mirror of
https://github.com/EvolutionAPI/evolution-api.git
synced 2025-08-28 10:16:11 -06:00
Merge branch 'release/1.3.1'
Some checks failed
Build Docker image / Build and Deploy (push) Has been cancelled
Some checks failed
Build Docker image / Build and Deploy (push) Has been cancelled
This commit is contained in:
commit
9cdb897a0f
@ -1,7 +1,6 @@
|
|||||||
.git
|
.git
|
||||||
*Dockerfile*
|
*Dockerfile*
|
||||||
*docker-compose*
|
*docker-compose*
|
||||||
package-lock.json
|
|
||||||
.env
|
.env
|
||||||
node_modules
|
node_modules
|
||||||
dist
|
dist
|
10
.env.example
10
.env.example
@ -27,13 +27,17 @@ EVENT_EMITTER_MAX_LISTENERS=50
|
|||||||
# If you don't even want an expiration, enter the value false
|
# If you don't even want an expiration, enter the value false
|
||||||
DEL_INSTANCE=false
|
DEL_INSTANCE=false
|
||||||
|
|
||||||
# Provider: postgresql | mysql
|
# Provider: postgresql | mysql | psql_bouncer
|
||||||
DATABASE_PROVIDER=postgresql
|
DATABASE_PROVIDER=postgresql
|
||||||
DATABASE_CONNECTION_URI='postgresql://user:pass@postgres:5432/evolution?schema=public'
|
DATABASE_CONNECTION_URI='postgresql://user:pass@postgres:5432/evolution_db?schema=evolution_api'
|
||||||
# Client name for the database connection
|
# Client name for the database connection
|
||||||
# It is used to separate an API installation from another that uses the same database.
|
# It is used to separate an API installation from another that uses the same database.
|
||||||
DATABASE_CONNECTION_CLIENT_NAME=evolution_exchange
|
DATABASE_CONNECTION_CLIENT_NAME=evolution_exchange
|
||||||
|
|
||||||
|
# Bouncer connection: used only when the database provider is set to 'psql_bouncer'.
|
||||||
|
# Defines the PostgreSQL URL with pgbouncer enabled (pgbouncer=true).
|
||||||
|
# DATABASE_BOUNCER_CONNECTION_URI=postgresql://user:pass@pgbouncer:5432/evolution_db?pgbouncer=true&schema=evolution_api
|
||||||
|
|
||||||
# Choose the data you want to save in the application's database
|
# Choose the data you want to save in the application's database
|
||||||
DATABASE_SAVE_DATA_INSTANCE=true
|
DATABASE_SAVE_DATA_INSTANCE=true
|
||||||
DATABASE_SAVE_DATA_NEW_MESSAGE=true
|
DATABASE_SAVE_DATA_NEW_MESSAGE=true
|
||||||
@ -196,7 +200,7 @@ CONFIG_SESSION_PHONE_NAME=Chrome
|
|||||||
|
|
||||||
# Whatsapp Web version for baileys channel
|
# Whatsapp Web version for baileys channel
|
||||||
# https://web.whatsapp.com/check-update?version=0&platform=web
|
# https://web.whatsapp.com/check-update?version=0&platform=web
|
||||||
# CONFIG_SESSION_PHONE_VERSION=2.3000.1023204200
|
|
||||||
|
|
||||||
# Set qrcode display limit
|
# Set qrcode display limit
|
||||||
QRCODE_LIMIT=30
|
QRCODE_LIMIT=30
|
||||||
|
19
CHANGELOG.md
19
CHANGELOG.md
@ -1,3 +1,22 @@
|
|||||||
|
# 2.3.1 (develop)
|
||||||
|
|
||||||
|
### Feature
|
||||||
|
|
||||||
|
* Add BaileysMessageProcessor for improved message handling and integrate rxjs for asynchronous processing
|
||||||
|
* Enhance message processing with retry logic for error handling
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
* Update Baileys Version
|
||||||
|
* Update Dockerhub Repository and Delete Config Session Variable
|
||||||
|
* Fixed sending variables in typebot
|
||||||
|
* Add unreadMessages in the response
|
||||||
|
* Phone number as message ID for Evo AI
|
||||||
|
* Fix upload to s3 when media message
|
||||||
|
* Simplify edited message check in BaileysStartupService
|
||||||
|
* Avoid corrupting URLs with query strings
|
||||||
|
* Removed CONFIG_SESSION_PHONE_VERSION environment variable
|
||||||
|
|
||||||
# 2.3.0 (2025-06-17 09:19)
|
# 2.3.0 (2025-06-17 09:19)
|
||||||
|
|
||||||
### Feature
|
### Feature
|
||||||
|
@ -6,7 +6,7 @@ if [ "$DOCKER_ENV" != "true" ]; then
|
|||||||
export_env_vars
|
export_env_vars
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ "$DATABASE_PROVIDER" == "postgresql" || "$DATABASE_PROVIDER" == "mysql" ]]; then
|
if [[ "$DATABASE_PROVIDER" == "postgresql" || "$DATABASE_PROVIDER" == "mysql" || "$DATABASE_PROVIDER" == "psql_bouncer" ]]; then
|
||||||
export DATABASE_URL
|
export DATABASE_URL
|
||||||
echo "Deploying migrations for $DATABASE_PROVIDER"
|
echo "Deploying migrations for $DATABASE_PROVIDER"
|
||||||
echo "Database URL: $DATABASE_URL"
|
echo "Database URL: $DATABASE_URL"
|
||||||
|
@ -6,7 +6,7 @@ if [ "$DOCKER_ENV" != "true" ]; then
|
|||||||
export_env_vars
|
export_env_vars
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ "$DATABASE_PROVIDER" == "postgresql" || "$DATABASE_PROVIDER" == "mysql" ]]; then
|
if [[ "$DATABASE_PROVIDER" == "postgresql" || "$DATABASE_PROVIDER" == "mysql" || "$DATABASE_PROVIDER" == "psql_bouncer" ]]; then
|
||||||
export DATABASE_URL
|
export DATABASE_URL
|
||||||
echo "Generating database for $DATABASE_PROVIDER"
|
echo "Generating database for $DATABASE_PROVIDER"
|
||||||
echo "Database URL: $DATABASE_URL"
|
echo "Database URL: $DATABASE_URL"
|
||||||
|
@ -2,7 +2,7 @@ version: "3.7"
|
|||||||
|
|
||||||
services:
|
services:
|
||||||
evolution_v2:
|
evolution_v2:
|
||||||
image: atendai/evolution-api:v2.2.3
|
image: evoapicloud/evolution-api:v2.3.1
|
||||||
volumes:
|
volumes:
|
||||||
- evolution_instances:/evolution/instances
|
- evolution_instances:/evolution/instances
|
||||||
networks:
|
networks:
|
||||||
@ -94,7 +94,6 @@ services:
|
|||||||
- WEBHOOK_EVENTS_ERRORS_WEBHOOK=
|
- WEBHOOK_EVENTS_ERRORS_WEBHOOK=
|
||||||
- CONFIG_SESSION_PHONE_CLIENT=Evolution API V2
|
- CONFIG_SESSION_PHONE_CLIENT=Evolution API V2
|
||||||
- CONFIG_SESSION_PHONE_NAME=Chrome
|
- CONFIG_SESSION_PHONE_NAME=Chrome
|
||||||
- CONFIG_SESSION_PHONE_VERSION=2.3000.1023204200
|
|
||||||
- QRCODE_LIMIT=30
|
- QRCODE_LIMIT=30
|
||||||
- OPENAI_ENABLED=true
|
- OPENAI_ENABLED=true
|
||||||
- DIFY_ENABLED=true
|
- DIFY_ENABLED=true
|
||||||
|
10
Dockerfile
10
Dockerfile
@ -3,15 +3,17 @@ FROM node:20-alpine AS builder
|
|||||||
RUN apk update && \
|
RUN apk update && \
|
||||||
apk add --no-cache git ffmpeg wget curl bash openssl
|
apk add --no-cache git ffmpeg wget curl bash openssl
|
||||||
|
|
||||||
LABEL version="2.3.0" description="Api to control whatsapp features through http requests."
|
LABEL version="2.3.1" description="Api to control whatsapp features through http requests."
|
||||||
LABEL maintainer="Davidson Gomes" git="https://github.com/DavidsonGomes"
|
LABEL maintainer="Davidson Gomes" git="https://github.com/DavidsonGomes"
|
||||||
LABEL contact="contato@evolution-api.com"
|
LABEL contact="contato@evolution-api.com"
|
||||||
|
|
||||||
WORKDIR /evolution
|
WORKDIR /evolution
|
||||||
|
|
||||||
COPY ./package.json ./tsconfig.json ./
|
COPY ./package*.json ./
|
||||||
|
COPY ./tsconfig.json ./
|
||||||
|
COPY ./tsup.config.ts ./
|
||||||
|
|
||||||
RUN npm install
|
RUN npm ci --silent
|
||||||
|
|
||||||
COPY ./src ./src
|
COPY ./src ./src
|
||||||
COPY ./public ./public
|
COPY ./public ./public
|
||||||
@ -19,7 +21,6 @@ COPY ./prisma ./prisma
|
|||||||
COPY ./manager ./manager
|
COPY ./manager ./manager
|
||||||
COPY ./.env.example ./.env
|
COPY ./.env.example ./.env
|
||||||
COPY ./runWithProvider.js ./
|
COPY ./runWithProvider.js ./
|
||||||
COPY ./tsup.config.ts ./
|
|
||||||
|
|
||||||
COPY ./Docker ./Docker
|
COPY ./Docker ./Docker
|
||||||
|
|
||||||
@ -35,6 +36,7 @@ RUN apk update && \
|
|||||||
apk add tzdata ffmpeg bash openssl
|
apk add tzdata ffmpeg bash openssl
|
||||||
|
|
||||||
ENV TZ=America/Sao_Paulo
|
ENV TZ=America/Sao_Paulo
|
||||||
|
ENV DOCKER_ENV=true
|
||||||
|
|
||||||
WORKDIR /evolution
|
WORKDIR /evolution
|
||||||
|
|
||||||
|
@ -117,4 +117,4 @@ Please contact contato@evolution-api.com to inquire about licensing matters.
|
|||||||
|
|
||||||
Apart from the specific conditions mentioned above, all other rights and restrictions follow the Apache License 2.0. Detailed information about the Apache License 2.0 can be found at [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0).
|
Apart from the specific conditions mentioned above, all other rights and restrictions follow the Apache License 2.0. Detailed information about the Apache License 2.0 can be found at [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0).
|
||||||
|
|
||||||
© 2024 Evolution API
|
© 2025 Evolution API
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
services:
|
services:
|
||||||
|
|
||||||
api:
|
api:
|
||||||
container_name: evolution_api
|
container_name: evolution_api
|
||||||
image: evoapicloud/evolution-api:latest
|
image: evoapicloud/evolution-api:latest
|
||||||
@ -34,19 +35,41 @@ services:
|
|||||||
image: postgres:15
|
image: postgres:15
|
||||||
networks:
|
networks:
|
||||||
- evolution-net
|
- evolution-net
|
||||||
command: ["postgres", "-c", "max_connections=1000", "-c", "listen_addresses=*"]
|
command: [
|
||||||
|
"postgres",
|
||||||
|
"-c", "max_connections=200",
|
||||||
|
"-c", "listen_addresses=*",
|
||||||
|
"-c", "shared_buffers=256MB",
|
||||||
|
"-c", "effective_cache_size=1GB",
|
||||||
|
"-c", "work_mem=4MB"
|
||||||
|
]
|
||||||
restart: always
|
restart: always
|
||||||
ports:
|
ports:
|
||||||
- 5432:5432
|
- 5432:5432
|
||||||
environment:
|
environment:
|
||||||
- POSTGRES_USER=user
|
- POSTGRES_USER=user
|
||||||
- POSTGRES_PASSWORD=pass
|
- POSTGRES_PASSWORD=pass
|
||||||
- POSTGRES_DB=evolution
|
- POSTGRES_DB=evolution_db
|
||||||
- POSTGRES_HOST_AUTH_METHOD=trust
|
- POSTGRES_HOST_AUTH_METHOD=trust
|
||||||
volumes:
|
volumes:
|
||||||
- postgres_data:/var/lib/postgresql/data
|
- postgres_data:/var/lib/postgresql/data
|
||||||
expose:
|
|
||||||
- 5432
|
# pgbouncer:
|
||||||
|
# image: edoburu/pgbouncer:latest
|
||||||
|
# environment:
|
||||||
|
# DB_HOST: postgres
|
||||||
|
# DB_USER: user
|
||||||
|
# DB_PASSWORD: pass
|
||||||
|
# POOL_MODE: transaction
|
||||||
|
# AUTH_TYPE: trust
|
||||||
|
# MAX_CLIENT_CONN: 1000
|
||||||
|
# DEFAULT_POOL_SIZE: 25
|
||||||
|
# depends_on:
|
||||||
|
# - postgres
|
||||||
|
# ports:
|
||||||
|
# - "6543:5432"
|
||||||
|
# networks:
|
||||||
|
# - evolution-net
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
evolution_instances:
|
evolution_instances:
|
||||||
|
4191
package-lock.json
generated
4191
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
19
package.json
19
package.json
@ -1,15 +1,15 @@
|
|||||||
{
|
{
|
||||||
"name": "evolution-api",
|
"name": "evolution-api",
|
||||||
"version": "2.3.0",
|
"version": "2.3.1",
|
||||||
"description": "Rest api for communication with WhatsApp",
|
"description": "Rest api for communication with WhatsApp",
|
||||||
"main": "./dist/main.js",
|
"main": "./dist/main.js",
|
||||||
"type": "commonjs",
|
"type": "commonjs",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "tsc --noEmit && tsup",
|
"build": "tsc --noEmit && tsup",
|
||||||
"start": "tsnd -r tsconfig-paths/register --files --transpile-only ./src/main.ts",
|
"start": "tsx ./src/main.ts",
|
||||||
"start:prod": "node dist/main",
|
"start:prod": "node dist/main",
|
||||||
"dev:server": "tsnd -r tsconfig-paths/register --files --transpile-only --respawn --ignore-watch node_modules ./src/main.ts",
|
"dev:server": "tsx watch ./src/main.ts",
|
||||||
"test": "tsnd -r tsconfig-paths/register --files --transpile-only --respawn --ignore-watch node_modules ./test/all.test.ts",
|
"test": "tsx watch ./test/all.test.ts",
|
||||||
"lint": "eslint --fix --ext .ts src",
|
"lint": "eslint --fix --ext .ts src",
|
||||||
"lint:check": "eslint --ext .ts src",
|
"lint:check": "eslint --ext .ts src",
|
||||||
"db:generate": "node runWithProvider.js \"npx prisma generate --schema ./prisma/DATABASE_PROVIDER-schema.prisma\"",
|
"db:generate": "node runWithProvider.js \"npx prisma generate --schema ./prisma/DATABASE_PROVIDER-schema.prisma\"",
|
||||||
@ -60,12 +60,13 @@
|
|||||||
"amqplib": "^0.10.5",
|
"amqplib": "^0.10.5",
|
||||||
"audio-decode": "^2.2.3",
|
"audio-decode": "^2.2.3",
|
||||||
"axios": "^1.7.9",
|
"axios": "^1.7.9",
|
||||||
"baileys": "github:EvolutionAPI/Baileys",
|
"baileys": "github:WhiskeySockets/Baileys",
|
||||||
"class-validator": "^0.14.1",
|
"class-validator": "^0.14.1",
|
||||||
"compression": "^1.7.5",
|
"compression": "^1.7.5",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"dayjs": "^1.11.13",
|
"dayjs": "^1.11.13",
|
||||||
"dotenv": "^16.4.7",
|
"dotenv": "^16.4.7",
|
||||||
|
"emoji-regex": "^10.4.0",
|
||||||
"eventemitter2": "^6.4.9",
|
"eventemitter2": "^6.4.9",
|
||||||
"express": "^4.21.2",
|
"express": "^4.21.2",
|
||||||
"express-async-errors": "^3.1.1",
|
"express-async-errors": "^3.1.1",
|
||||||
@ -73,7 +74,7 @@
|
|||||||
"form-data": "^4.0.1",
|
"form-data": "^4.0.1",
|
||||||
"https-proxy-agent": "^7.0.6",
|
"https-proxy-agent": "^7.0.6",
|
||||||
"i18next": "^23.7.19",
|
"i18next": "^23.7.19",
|
||||||
"jimp": "^0.16.13",
|
"jimp": "^1.6.0",
|
||||||
"json-schema": "^0.4.0",
|
"json-schema": "^0.4.0",
|
||||||
"jsonschema": "^1.4.1",
|
"jsonschema": "^1.4.1",
|
||||||
"jsonwebtoken": "^9.0.2",
|
"jsonwebtoken": "^9.0.2",
|
||||||
@ -95,9 +96,11 @@
|
|||||||
"qrcode": "^1.5.4",
|
"qrcode": "^1.5.4",
|
||||||
"qrcode-terminal": "^0.12.0",
|
"qrcode-terminal": "^0.12.0",
|
||||||
"redis": "^4.7.0",
|
"redis": "^4.7.0",
|
||||||
"sharp": "^0.32.6",
|
"rxjs": "^7.8.2",
|
||||||
|
"sharp": "^0.34.2",
|
||||||
"socket.io": "^4.8.1",
|
"socket.io": "^4.8.1",
|
||||||
"socket.io-client": "^4.8.1",
|
"socket.io-client": "^4.8.1",
|
||||||
|
"swagger-ui-express": "^5.0.1",
|
||||||
"tsup": "^8.3.5"
|
"tsup": "^8.3.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@ -120,8 +123,8 @@
|
|||||||
"eslint-plugin-prettier": "^5.2.1",
|
"eslint-plugin-prettier": "^5.2.1",
|
||||||
"eslint-plugin-simple-import-sort": "^10.0.0",
|
"eslint-plugin-simple-import-sort": "^10.0.0",
|
||||||
"prettier": "^3.4.2",
|
"prettier": "^3.4.2",
|
||||||
"ts-node-dev": "^2.0.0",
|
|
||||||
"tsconfig-paths": "^4.2.0",
|
"tsconfig-paths": "^4.2.0",
|
||||||
|
"tsx": "^4.20.3",
|
||||||
"typescript": "^5.7.2"
|
"typescript": "^5.7.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,9 +0,0 @@
|
|||||||
/*
|
|
||||||
Warnings:
|
|
||||||
|
|
||||||
- A unique constraint covering the columns `[remoteJid,instanceId]` on the table `Chat` will be added. If there are existing duplicate values, this will fail.
|
|
||||||
*/
|
|
||||||
|
|
||||||
-- AlterTable
|
|
||||||
ALTER TABLE `Setting`
|
|
||||||
ADD COLUMN IF NOT EXISTS `wavoipToken` VARCHAR(100);
|
|
@ -0,0 +1,17 @@
|
|||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE `Nats` (
|
||||||
|
`id` VARCHAR(191) NOT NULL,
|
||||||
|
`enabled` BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
`events` JSON NOT NULL,
|
||||||
|
`createdAt` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
`updatedAt` TIMESTAMP NOT NULL,
|
||||||
|
`instanceId` VARCHAR(191) NOT NULL,
|
||||||
|
|
||||||
|
PRIMARY KEY (`id`)
|
||||||
|
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX `Nats_instanceId_key` ON `Nats`(`instanceId`);
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE `Nats` ADD CONSTRAINT `Nats_instanceId_fkey` FOREIGN KEY (`instanceId`) REFERENCES `Instance`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
|
@ -0,0 +1,62 @@
|
|||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE `N8n` (
|
||||||
|
`id` VARCHAR(191) NOT NULL,
|
||||||
|
`enabled` BOOLEAN NOT NULL DEFAULT true,
|
||||||
|
`description` VARCHAR(255),
|
||||||
|
`webhookUrl` VARCHAR(255),
|
||||||
|
`basicAuthUser` VARCHAR(255),
|
||||||
|
`basicAuthPass` 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` JSON,
|
||||||
|
`splitMessages` BOOLEAN DEFAULT false,
|
||||||
|
`timePerChar` INTEGER DEFAULT 50,
|
||||||
|
`triggerType` ENUM('all', 'keyword', 'none') NULL,
|
||||||
|
`triggerOperator` ENUM('contains', 'equals', 'startsWith', 'endsWith', 'regex') NULL,
|
||||||
|
`triggerValue` VARCHAR(191) NULL,
|
||||||
|
`createdAt` TIMESTAMP 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 `N8nSetting` (
|
||||||
|
`id` VARCHAR(191) 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` JSON,
|
||||||
|
`splitMessages` BOOLEAN DEFAULT false,
|
||||||
|
`timePerChar` INTEGER DEFAULT 50,
|
||||||
|
`createdAt` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
`updatedAt` TIMESTAMP NOT NULL,
|
||||||
|
`n8nIdFallback` VARCHAR(100),
|
||||||
|
`instanceId` VARCHAR(191) NOT NULL,
|
||||||
|
|
||||||
|
PRIMARY KEY (`id`)
|
||||||
|
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX `N8nSetting_instanceId_key` ON `N8nSetting`(`instanceId`);
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE `N8n` ADD CONSTRAINT `N8n_instanceId_fkey` FOREIGN KEY (`instanceId`) REFERENCES `Instance`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE `N8nSetting` ADD CONSTRAINT `N8nSetting_n8nIdFallback_fkey` FOREIGN KEY (`n8nIdFallback`) REFERENCES `N8n`(`id`) ON DELETE SET NULL ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE `N8nSetting` ADD CONSTRAINT `N8nSetting_instanceId_fkey` FOREIGN KEY (`instanceId`) REFERENCES `Instance`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
|
@ -0,0 +1,61 @@
|
|||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE `Evoai` (
|
||||||
|
`id` VARCHAR(191) NOT NULL,
|
||||||
|
`enabled` BOOLEAN NOT NULL DEFAULT true,
|
||||||
|
`description` VARCHAR(255),
|
||||||
|
`agentUrl` 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` JSON,
|
||||||
|
`splitMessages` BOOLEAN DEFAULT false,
|
||||||
|
`timePerChar` INTEGER DEFAULT 50,
|
||||||
|
`triggerType` ENUM('all', 'keyword', 'none') NULL,
|
||||||
|
`triggerOperator` ENUM('contains', 'equals', 'startsWith', 'endsWith', 'regex') NULL,
|
||||||
|
`triggerValue` VARCHAR(191) NULL,
|
||||||
|
`createdAt` TIMESTAMP 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 `EvoaiSetting` (
|
||||||
|
`id` VARCHAR(191) 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` JSON,
|
||||||
|
`splitMessages` BOOLEAN DEFAULT false,
|
||||||
|
`timePerChar` INTEGER DEFAULT 50,
|
||||||
|
`createdAt` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
`updatedAt` TIMESTAMP NOT NULL,
|
||||||
|
`evoaiIdFallback` VARCHAR(100),
|
||||||
|
`instanceId` VARCHAR(191) NOT NULL,
|
||||||
|
|
||||||
|
PRIMARY KEY (`id`)
|
||||||
|
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX `EvoaiSetting_instanceId_key` ON `EvoaiSetting`(`instanceId`);
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE `Evoai` ADD CONSTRAINT `Evoai_instanceId_fkey` FOREIGN KEY (`instanceId`) REFERENCES `Instance`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE `EvoaiSetting` ADD CONSTRAINT `EvoaiSetting_evoaiIdFallback_fkey` FOREIGN KEY (`evoaiIdFallback`) REFERENCES `Evoai`(`id`) ON DELETE SET NULL ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE `EvoaiSetting` ADD CONSTRAINT `EvoaiSetting_instanceId_fkey` FOREIGN KEY (`instanceId`) REFERENCES `Instance`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
|
@ -0,0 +1,2 @@
|
|||||||
|
-- DropIndex
|
||||||
|
ALTER TABLE `Media` DROP INDEX `Media_fileName_key`;
|
@ -0,0 +1,7 @@
|
|||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE `Typebot` ADD COLUMN `splitMessages` BOOLEAN DEFAULT false,
|
||||||
|
ADD COLUMN `timePerChar` INTEGER DEFAULT 50;
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE `TypebotSetting` ADD COLUMN `splitMessages` BOOLEAN DEFAULT false,
|
||||||
|
ADD COLUMN `timePerChar` INTEGER DEFAULT 50;
|
@ -0,0 +1,2 @@
|
|||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE `IsOnWhatsapp` ADD COLUMN `lid` VARCHAR(100);
|
@ -652,16 +652,16 @@ model N8n {
|
|||||||
webhookUrl String? @db.VarChar(255)
|
webhookUrl String? @db.VarChar(255)
|
||||||
basicAuthUser String? @db.VarChar(255)
|
basicAuthUser String? @db.VarChar(255)
|
||||||
basicAuthPass String? @db.VarChar(255)
|
basicAuthPass String? @db.VarChar(255)
|
||||||
expire Int? @default(0) @db.Integer
|
expire Int? @default(0) @db.Int
|
||||||
keywordFinish String? @db.VarChar(100)
|
keywordFinish String? @db.VarChar(100)
|
||||||
delayMessage Int? @db.Integer
|
delayMessage Int? @db.Integer
|
||||||
unknownMessage String? @db.VarChar(100)
|
unknownMessage String? @db.VarChar(100)
|
||||||
listeningFromMe Boolean? @default(false) @db.Boolean
|
listeningFromMe Boolean? @default(false)
|
||||||
stopBotFromMe Boolean? @default(false) @db.Boolean
|
stopBotFromMe Boolean? @default(false)
|
||||||
keepOpen Boolean? @default(false) @db.Boolean
|
keepOpen Boolean? @default(false)
|
||||||
debounceTime Int? @db.Integer
|
debounceTime Int? @db.Integer
|
||||||
ignoreJids Json?
|
ignoreJids Json?
|
||||||
splitMessages Boolean? @default(false) @db.Boolean
|
splitMessages Boolean? @default(false)
|
||||||
timePerChar Int? @default(50) @db.Integer
|
timePerChar Int? @default(50) @db.Integer
|
||||||
triggerType TriggerType?
|
triggerType TriggerType?
|
||||||
triggerOperator TriggerOperator?
|
triggerOperator TriggerOperator?
|
||||||
@ -675,16 +675,16 @@ model N8n {
|
|||||||
|
|
||||||
model N8nSetting {
|
model N8nSetting {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
expire Int? @default(0) @db.Integer
|
expire Int? @default(0) @db.Int
|
||||||
keywordFinish String? @db.VarChar(100)
|
keywordFinish String? @db.VarChar(100)
|
||||||
delayMessage Int? @db.Integer
|
delayMessage Int? @db.Integer
|
||||||
unknownMessage String? @db.VarChar(100)
|
unknownMessage String? @db.VarChar(100)
|
||||||
listeningFromMe Boolean? @default(false) @db.Boolean
|
listeningFromMe Boolean? @default(false)
|
||||||
stopBotFromMe Boolean? @default(false) @db.Boolean
|
stopBotFromMe Boolean? @default(false)
|
||||||
keepOpen Boolean? @default(false) @db.Boolean
|
keepOpen Boolean? @default(false)
|
||||||
debounceTime Int? @db.Integer
|
debounceTime Int? @db.Integer
|
||||||
ignoreJids Json?
|
ignoreJids Json?
|
||||||
splitMessages Boolean? @default(false) @db.Boolean
|
splitMessages Boolean? @default(false)
|
||||||
timePerChar Int? @default(50) @db.Integer
|
timePerChar Int? @default(50) @db.Integer
|
||||||
createdAt DateTime? @default(now()) @db.Timestamp
|
createdAt DateTime? @default(now()) @db.Timestamp
|
||||||
updatedAt DateTime @updatedAt @db.Timestamp
|
updatedAt DateTime @updatedAt @db.Timestamp
|
||||||
@ -700,16 +700,16 @@ model Evoai {
|
|||||||
description String? @db.VarChar(255)
|
description String? @db.VarChar(255)
|
||||||
agentUrl String? @db.VarChar(255)
|
agentUrl String? @db.VarChar(255)
|
||||||
apiKey String? @db.VarChar(255)
|
apiKey String? @db.VarChar(255)
|
||||||
expire Int? @default(0) @db.Integer
|
expire Int? @default(0) @db.Int
|
||||||
keywordFinish String? @db.VarChar(100)
|
keywordFinish String? @db.VarChar(100)
|
||||||
delayMessage Int? @db.Integer
|
delayMessage Int? @db.Integer
|
||||||
unknownMessage String? @db.VarChar(100)
|
unknownMessage String? @db.VarChar(100)
|
||||||
listeningFromMe Boolean? @default(false) @db.Boolean
|
listeningFromMe Boolean? @default(false)
|
||||||
stopBotFromMe Boolean? @default(false) @db.Boolean
|
stopBotFromMe Boolean? @default(false)
|
||||||
keepOpen Boolean? @default(false) @db.Boolean
|
keepOpen Boolean? @default(false)
|
||||||
debounceTime Int? @db.Integer
|
debounceTime Int? @db.Integer
|
||||||
ignoreJids Json?
|
ignoreJids Json?
|
||||||
splitMessages Boolean? @default(false) @db.Boolean
|
splitMessages Boolean? @default(false)
|
||||||
timePerChar Int? @default(50) @db.Integer
|
timePerChar Int? @default(50) @db.Integer
|
||||||
triggerType TriggerType?
|
triggerType TriggerType?
|
||||||
triggerOperator TriggerOperator?
|
triggerOperator TriggerOperator?
|
||||||
@ -723,16 +723,16 @@ model Evoai {
|
|||||||
|
|
||||||
model EvoaiSetting {
|
model EvoaiSetting {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
expire Int? @default(0) @db.Integer
|
expire Int? @default(0) @db.Int
|
||||||
keywordFinish String? @db.VarChar(100)
|
keywordFinish String? @db.VarChar(100)
|
||||||
delayMessage Int? @db.Integer
|
delayMessage Int? @db.Integer
|
||||||
unknownMessage String? @db.VarChar(100)
|
unknownMessage String? @db.VarChar(100)
|
||||||
listeningFromMe Boolean? @default(false) @db.Boolean
|
listeningFromMe Boolean? @default(false)
|
||||||
stopBotFromMe Boolean? @default(false) @db.Boolean
|
stopBotFromMe Boolean? @default(false)
|
||||||
keepOpen Boolean? @default(false) @db.Boolean
|
keepOpen Boolean? @default(false)
|
||||||
debounceTime Int? @db.Integer
|
debounceTime Int? @db.Integer
|
||||||
ignoreJids Json?
|
ignoreJids Json?
|
||||||
splitMessages Boolean? @default(false) @db.Boolean
|
splitMessages Boolean? @default(false)
|
||||||
timePerChar Int? @default(50) @db.Integer
|
timePerChar Int? @default(50) @db.Integer
|
||||||
createdAt DateTime? @default(now()) @db.Timestamp
|
createdAt DateTime? @default(now()) @db.Timestamp
|
||||||
updatedAt DateTime @updatedAt @db.Timestamp
|
updatedAt DateTime @updatedAt @db.Timestamp
|
||||||
|
752
prisma/psql_bouncer-schema.prisma
Normal file
752
prisma/psql_bouncer-schema.prisma
Normal file
@ -0,0 +1,752 @@
|
|||||||
|
// This is your Prisma schema file,
|
||||||
|
// learn more about it in the docs: https://pris.ly/d/prisma-schema
|
||||||
|
|
||||||
|
// Looking for ways to speed up your queries, or scale easily with your serverless or edge functions?
|
||||||
|
// Try Prisma Accelerate: https://pris.ly/cli/accelerate-init
|
||||||
|
|
||||||
|
generator client {
|
||||||
|
provider = "prisma-client-js"
|
||||||
|
}
|
||||||
|
|
||||||
|
datasource db {
|
||||||
|
provider = "postgresql"
|
||||||
|
url = env("DATABASE_BOUNCER_CONNECTION_URI")
|
||||||
|
directUrl = env("DATABASE_CONNECTION_URI")
|
||||||
|
}
|
||||||
|
|
||||||
|
enum InstanceConnectionStatus {
|
||||||
|
open
|
||||||
|
close
|
||||||
|
connecting
|
||||||
|
}
|
||||||
|
|
||||||
|
enum DeviceMessage {
|
||||||
|
ios
|
||||||
|
android
|
||||||
|
web
|
||||||
|
unknown
|
||||||
|
desktop
|
||||||
|
}
|
||||||
|
|
||||||
|
enum SessionStatus {
|
||||||
|
opened
|
||||||
|
closed
|
||||||
|
paused
|
||||||
|
}
|
||||||
|
|
||||||
|
enum TriggerType {
|
||||||
|
all
|
||||||
|
keyword
|
||||||
|
none
|
||||||
|
advanced
|
||||||
|
}
|
||||||
|
|
||||||
|
enum TriggerOperator {
|
||||||
|
contains
|
||||||
|
equals
|
||||||
|
startsWith
|
||||||
|
endsWith
|
||||||
|
regex
|
||||||
|
}
|
||||||
|
|
||||||
|
enum OpenaiBotType {
|
||||||
|
assistant
|
||||||
|
chatCompletion
|
||||||
|
}
|
||||||
|
|
||||||
|
enum DifyBotType {
|
||||||
|
chatBot
|
||||||
|
textGenerator
|
||||||
|
agent
|
||||||
|
workflow
|
||||||
|
}
|
||||||
|
|
||||||
|
model Instance {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
name String @unique @db.VarChar(255)
|
||||||
|
connectionStatus InstanceConnectionStatus @default(open)
|
||||||
|
ownerJid String? @db.VarChar(100)
|
||||||
|
profileName String? @db.VarChar(100)
|
||||||
|
profilePicUrl String? @db.VarChar(500)
|
||||||
|
integration String? @db.VarChar(100)
|
||||||
|
number String? @db.VarChar(100)
|
||||||
|
businessId String? @db.VarChar(100)
|
||||||
|
token String? @db.VarChar(255)
|
||||||
|
clientName String? @db.VarChar(100)
|
||||||
|
disconnectionReasonCode Int? @db.Integer
|
||||||
|
disconnectionObject Json? @db.JsonB
|
||||||
|
disconnectionAt DateTime? @db.Timestamp
|
||||||
|
createdAt DateTime? @default(now()) @db.Timestamp
|
||||||
|
updatedAt DateTime? @updatedAt @db.Timestamp
|
||||||
|
Chat Chat[]
|
||||||
|
Contact Contact[]
|
||||||
|
Message Message[]
|
||||||
|
Webhook Webhook?
|
||||||
|
Chatwoot Chatwoot?
|
||||||
|
Label Label[]
|
||||||
|
Proxy Proxy?
|
||||||
|
Setting Setting?
|
||||||
|
Rabbitmq Rabbitmq?
|
||||||
|
Nats Nats?
|
||||||
|
Sqs Sqs?
|
||||||
|
Websocket Websocket?
|
||||||
|
Typebot Typebot[]
|
||||||
|
Session Session?
|
||||||
|
MessageUpdate MessageUpdate[]
|
||||||
|
TypebotSetting TypebotSetting?
|
||||||
|
Media Media[]
|
||||||
|
OpenaiCreds OpenaiCreds[]
|
||||||
|
OpenaiBot OpenaiBot[]
|
||||||
|
OpenaiSetting OpenaiSetting?
|
||||||
|
Template Template[]
|
||||||
|
Dify Dify[]
|
||||||
|
DifySetting DifySetting?
|
||||||
|
IntegrationSession IntegrationSession[]
|
||||||
|
EvolutionBot EvolutionBot[]
|
||||||
|
EvolutionBotSetting EvolutionBotSetting?
|
||||||
|
Flowise Flowise[]
|
||||||
|
FlowiseSetting FlowiseSetting?
|
||||||
|
Pusher Pusher?
|
||||||
|
N8n N8n[]
|
||||||
|
N8nSetting N8nSetting[]
|
||||||
|
Evoai Evoai[]
|
||||||
|
EvoaiSetting EvoaiSetting?
|
||||||
|
}
|
||||||
|
|
||||||
|
model Session {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
sessionId String @unique
|
||||||
|
creds String? @db.Text
|
||||||
|
createdAt DateTime @default(now()) @db.Timestamp
|
||||||
|
Instance Instance @relation(fields: [sessionId], references: [id], onDelete: Cascade)
|
||||||
|
}
|
||||||
|
|
||||||
|
model Chat {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
remoteJid String @db.VarChar(100)
|
||||||
|
name String? @db.VarChar(100)
|
||||||
|
labels Json? @db.JsonB
|
||||||
|
createdAt DateTime? @default(now()) @db.Timestamp
|
||||||
|
updatedAt DateTime? @updatedAt @db.Timestamp
|
||||||
|
Instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)
|
||||||
|
instanceId String
|
||||||
|
unreadMessages Int @default(0)
|
||||||
|
|
||||||
|
@@index([instanceId])
|
||||||
|
@@index([remoteJid])
|
||||||
|
}
|
||||||
|
|
||||||
|
model Contact {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
remoteJid String @db.VarChar(100)
|
||||||
|
pushName String? @db.VarChar(100)
|
||||||
|
profilePicUrl String? @db.VarChar(500)
|
||||||
|
createdAt DateTime? @default(now()) @db.Timestamp
|
||||||
|
updatedAt DateTime? @updatedAt @db.Timestamp
|
||||||
|
Instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)
|
||||||
|
instanceId String
|
||||||
|
|
||||||
|
@@unique([remoteJid, instanceId])
|
||||||
|
@@index([remoteJid])
|
||||||
|
@@index([instanceId])
|
||||||
|
}
|
||||||
|
|
||||||
|
model Message {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
key Json @db.JsonB
|
||||||
|
pushName String? @db.VarChar(100)
|
||||||
|
participant String? @db.VarChar(100)
|
||||||
|
messageType String @db.VarChar(100)
|
||||||
|
message Json @db.JsonB
|
||||||
|
contextInfo Json? @db.JsonB
|
||||||
|
source DeviceMessage
|
||||||
|
messageTimestamp Int @db.Integer
|
||||||
|
chatwootMessageId Int? @db.Integer
|
||||||
|
chatwootInboxId Int? @db.Integer
|
||||||
|
chatwootConversationId Int? @db.Integer
|
||||||
|
chatwootContactInboxSourceId String? @db.VarChar(100)
|
||||||
|
chatwootIsRead Boolean? @db.Boolean
|
||||||
|
Instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)
|
||||||
|
instanceId String
|
||||||
|
MessageUpdate MessageUpdate[]
|
||||||
|
Media Media?
|
||||||
|
webhookUrl String? @db.VarChar(500)
|
||||||
|
status String? @db.VarChar(30)
|
||||||
|
|
||||||
|
sessionId String?
|
||||||
|
session IntegrationSession? @relation(fields: [sessionId], references: [id])
|
||||||
|
|
||||||
|
@@index([instanceId])
|
||||||
|
}
|
||||||
|
|
||||||
|
model MessageUpdate {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
keyId String @db.VarChar(100)
|
||||||
|
remoteJid String @db.VarChar(100)
|
||||||
|
fromMe Boolean @db.Boolean
|
||||||
|
participant String? @db.VarChar(100)
|
||||||
|
pollUpdates Json? @db.JsonB
|
||||||
|
status String @db.VarChar(30)
|
||||||
|
Message Message @relation(fields: [messageId], references: [id], onDelete: Cascade)
|
||||||
|
messageId String
|
||||||
|
Instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)
|
||||||
|
instanceId String
|
||||||
|
|
||||||
|
@@index([instanceId])
|
||||||
|
@@index([messageId])
|
||||||
|
}
|
||||||
|
|
||||||
|
model Webhook {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
url String @db.VarChar(500)
|
||||||
|
headers Json? @db.JsonB
|
||||||
|
enabled Boolean? @default(true) @db.Boolean
|
||||||
|
events Json? @db.JsonB
|
||||||
|
webhookByEvents Boolean? @default(false) @db.Boolean
|
||||||
|
webhookBase64 Boolean? @default(false) @db.Boolean
|
||||||
|
createdAt DateTime? @default(now()) @db.Timestamp
|
||||||
|
updatedAt DateTime @updatedAt @db.Timestamp
|
||||||
|
Instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)
|
||||||
|
instanceId String @unique
|
||||||
|
|
||||||
|
@@index([instanceId])
|
||||||
|
}
|
||||||
|
|
||||||
|
model Chatwoot {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
enabled Boolean? @default(true) @db.Boolean
|
||||||
|
accountId String? @db.VarChar(100)
|
||||||
|
token String? @db.VarChar(100)
|
||||||
|
url String? @db.VarChar(500)
|
||||||
|
nameInbox String? @db.VarChar(100)
|
||||||
|
signMsg Boolean? @default(false) @db.Boolean
|
||||||
|
signDelimiter String? @db.VarChar(100)
|
||||||
|
number String? @db.VarChar(100)
|
||||||
|
reopenConversation Boolean? @default(false) @db.Boolean
|
||||||
|
conversationPending Boolean? @default(false) @db.Boolean
|
||||||
|
mergeBrazilContacts Boolean? @default(false) @db.Boolean
|
||||||
|
importContacts Boolean? @default(false) @db.Boolean
|
||||||
|
importMessages Boolean? @default(false) @db.Boolean
|
||||||
|
daysLimitImportMessages Int? @db.Integer
|
||||||
|
organization String? @db.VarChar(100)
|
||||||
|
logo String? @db.VarChar(500)
|
||||||
|
ignoreJids Json?
|
||||||
|
createdAt DateTime? @default(now()) @db.Timestamp
|
||||||
|
updatedAt DateTime @updatedAt @db.Timestamp
|
||||||
|
Instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)
|
||||||
|
instanceId String @unique
|
||||||
|
}
|
||||||
|
|
||||||
|
model Label {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
labelId String? @db.VarChar(100)
|
||||||
|
name String @db.VarChar(100)
|
||||||
|
color String @db.VarChar(100)
|
||||||
|
predefinedId String? @db.VarChar(100)
|
||||||
|
createdAt DateTime? @default(now()) @db.Timestamp
|
||||||
|
updatedAt DateTime @updatedAt @db.Timestamp
|
||||||
|
Instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)
|
||||||
|
instanceId String
|
||||||
|
|
||||||
|
@@unique([labelId, instanceId])
|
||||||
|
}
|
||||||
|
|
||||||
|
model Proxy {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
enabled Boolean @default(false) @db.Boolean
|
||||||
|
host String @db.VarChar(100)
|
||||||
|
port String @db.VarChar(100)
|
||||||
|
protocol String @db.VarChar(100)
|
||||||
|
username String @db.VarChar(100)
|
||||||
|
password String @db.VarChar(100)
|
||||||
|
createdAt DateTime? @default(now()) @db.Timestamp
|
||||||
|
updatedAt DateTime @updatedAt @db.Timestamp
|
||||||
|
Instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)
|
||||||
|
instanceId String @unique
|
||||||
|
}
|
||||||
|
|
||||||
|
model Setting {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
rejectCall Boolean @default(false) @db.Boolean
|
||||||
|
msgCall String? @db.VarChar(100)
|
||||||
|
groupsIgnore Boolean @default(false) @db.Boolean
|
||||||
|
alwaysOnline Boolean @default(false) @db.Boolean
|
||||||
|
readMessages Boolean @default(false) @db.Boolean
|
||||||
|
readStatus Boolean @default(false) @db.Boolean
|
||||||
|
syncFullHistory Boolean @default(false) @db.Boolean
|
||||||
|
wavoipToken String? @db.VarChar(100)
|
||||||
|
createdAt DateTime? @default(now()) @db.Timestamp
|
||||||
|
updatedAt DateTime @updatedAt @db.Timestamp
|
||||||
|
Instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)
|
||||||
|
instanceId String @unique
|
||||||
|
|
||||||
|
@@index([instanceId])
|
||||||
|
}
|
||||||
|
|
||||||
|
model Rabbitmq {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
enabled Boolean @default(false) @db.Boolean
|
||||||
|
events Json @db.JsonB
|
||||||
|
createdAt DateTime? @default(now()) @db.Timestamp
|
||||||
|
updatedAt DateTime @updatedAt @db.Timestamp
|
||||||
|
Instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)
|
||||||
|
instanceId String @unique
|
||||||
|
}
|
||||||
|
|
||||||
|
model Nats {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
enabled Boolean @default(false) @db.Boolean
|
||||||
|
events Json @db.JsonB
|
||||||
|
createdAt DateTime? @default(now()) @db.Timestamp
|
||||||
|
updatedAt DateTime @updatedAt @db.Timestamp
|
||||||
|
Instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)
|
||||||
|
instanceId String @unique
|
||||||
|
}
|
||||||
|
|
||||||
|
model Sqs {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
enabled Boolean @default(false) @db.Boolean
|
||||||
|
events Json @db.JsonB
|
||||||
|
createdAt DateTime? @default(now()) @db.Timestamp
|
||||||
|
updatedAt DateTime @updatedAt @db.Timestamp
|
||||||
|
Instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)
|
||||||
|
instanceId String @unique
|
||||||
|
}
|
||||||
|
|
||||||
|
model Websocket {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
enabled Boolean @default(false) @db.Boolean
|
||||||
|
events Json @db.JsonB
|
||||||
|
createdAt DateTime? @default(now()) @db.Timestamp
|
||||||
|
updatedAt DateTime @updatedAt @db.Timestamp
|
||||||
|
Instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)
|
||||||
|
instanceId String @unique
|
||||||
|
}
|
||||||
|
|
||||||
|
model Pusher {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
enabled Boolean @default(false) @db.Boolean
|
||||||
|
appId String @db.VarChar(100)
|
||||||
|
key String @db.VarChar(100)
|
||||||
|
secret String @db.VarChar(100)
|
||||||
|
cluster String @db.VarChar(100)
|
||||||
|
useTLS Boolean @default(false) @db.Boolean
|
||||||
|
events Json @db.JsonB
|
||||||
|
createdAt DateTime? @default(now()) @db.Timestamp
|
||||||
|
updatedAt DateTime @updatedAt @db.Timestamp
|
||||||
|
Instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)
|
||||||
|
instanceId String @unique
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
ignoreJids Json?
|
||||||
|
triggerType TriggerType?
|
||||||
|
triggerOperator TriggerOperator?
|
||||||
|
triggerValue String?
|
||||||
|
splitMessages Boolean? @default(false) @db.Boolean
|
||||||
|
timePerChar Int? @default(50) @db.Integer
|
||||||
|
Instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)
|
||||||
|
instanceId String
|
||||||
|
TypebotSetting TypebotSetting[]
|
||||||
|
}
|
||||||
|
|
||||||
|
model TypebotSetting {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
expire Int? @default(0) @db.Integer
|
||||||
|
keywordFinish String? @db.VarChar(100)
|
||||||
|
delayMessage Int? @db.Integer
|
||||||
|
unknownMessage String? @db.VarChar(100)
|
||||||
|
listeningFromMe Boolean? @default(false) @db.Boolean
|
||||||
|
stopBotFromMe Boolean? @default(false) @db.Boolean
|
||||||
|
keepOpen Boolean? @default(false) @db.Boolean
|
||||||
|
debounceTime Int? @db.Integer
|
||||||
|
typebotIdFallback String? @db.VarChar(100)
|
||||||
|
ignoreJids Json?
|
||||||
|
splitMessages Boolean? @default(false) @db.Boolean
|
||||||
|
timePerChar Int? @default(50) @db.Integer
|
||||||
|
createdAt DateTime? @default(now()) @db.Timestamp
|
||||||
|
updatedAt DateTime @updatedAt @db.Timestamp
|
||||||
|
Fallback Typebot? @relation(fields: [typebotIdFallback], references: [id])
|
||||||
|
Instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)
|
||||||
|
instanceId String @unique
|
||||||
|
}
|
||||||
|
|
||||||
|
model Media {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
fileName String @db.VarChar(500)
|
||||||
|
type String @db.VarChar(100)
|
||||||
|
mimetype String @db.VarChar(100)
|
||||||
|
createdAt DateTime? @default(now()) @db.Date
|
||||||
|
Message Message @relation(fields: [messageId], references: [id], onDelete: Cascade)
|
||||||
|
messageId String @unique
|
||||||
|
Instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)
|
||||||
|
instanceId String
|
||||||
|
}
|
||||||
|
|
||||||
|
model OpenaiCreds {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
name String? @unique @db.VarChar(255)
|
||||||
|
apiKey String? @unique @db.VarChar(255)
|
||||||
|
createdAt DateTime? @default(now()) @db.Timestamp
|
||||||
|
updatedAt DateTime @updatedAt @db.Timestamp
|
||||||
|
Instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)
|
||||||
|
instanceId String
|
||||||
|
OpenaiAssistant OpenaiBot[]
|
||||||
|
OpenaiSetting OpenaiSetting?
|
||||||
|
}
|
||||||
|
|
||||||
|
model OpenaiBot {
|
||||||
|
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
|
||||||
|
splitMessages Boolean? @default(false) @db.Boolean
|
||||||
|
timePerChar Int? @default(50) @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)
|
||||||
|
openaiCredsId String
|
||||||
|
Instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)
|
||||||
|
instanceId String
|
||||||
|
OpenaiSetting OpenaiSetting[]
|
||||||
|
}
|
||||||
|
|
||||||
|
model IntegrationSession {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
sessionId String @db.VarChar(255)
|
||||||
|
remoteJid String @db.VarChar(100)
|
||||||
|
pushName String?
|
||||||
|
status SessionStatus
|
||||||
|
awaitUser Boolean @default(false) @db.Boolean
|
||||||
|
context Json?
|
||||||
|
type String? @db.VarChar(100)
|
||||||
|
createdAt DateTime? @default(now()) @db.Timestamp
|
||||||
|
updatedAt DateTime @updatedAt @db.Timestamp
|
||||||
|
Message Message[]
|
||||||
|
Instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)
|
||||||
|
instanceId String
|
||||||
|
parameters Json? @db.JsonB
|
||||||
|
|
||||||
|
botId String?
|
||||||
|
}
|
||||||
|
|
||||||
|
model OpenaiSetting {
|
||||||
|
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?
|
||||||
|
splitMessages Boolean? @default(false) @db.Boolean
|
||||||
|
timePerChar Int? @default(50) @db.Integer
|
||||||
|
speechToText Boolean? @default(false) @db.Boolean
|
||||||
|
createdAt DateTime? @default(now()) @db.Timestamp
|
||||||
|
updatedAt DateTime @updatedAt @db.Timestamp
|
||||||
|
OpenaiCreds OpenaiCreds? @relation(fields: [openaiCredsId], references: [id])
|
||||||
|
openaiCredsId String @unique
|
||||||
|
Fallback OpenaiBot? @relation(fields: [openaiIdFallback], references: [id])
|
||||||
|
openaiIdFallback String? @db.VarChar(100)
|
||||||
|
Instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)
|
||||||
|
instanceId String @unique
|
||||||
|
}
|
||||||
|
|
||||||
|
model Template {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
templateId String @unique @db.VarChar(255)
|
||||||
|
name String @unique @db.VarChar(255)
|
||||||
|
template Json @db.JsonB
|
||||||
|
webhookUrl String? @db.VarChar(500)
|
||||||
|
createdAt DateTime? @default(now()) @db.Timestamp
|
||||||
|
updatedAt DateTime @updatedAt @db.Timestamp
|
||||||
|
Instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)
|
||||||
|
instanceId String
|
||||||
|
}
|
||||||
|
|
||||||
|
model Dify {
|
||||||
|
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
|
||||||
|
ignoreJids Json?
|
||||||
|
splitMessages Boolean? @default(false) @db.Boolean
|
||||||
|
timePerChar Int? @default(50) @db.Integer
|
||||||
|
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
|
||||||
|
DifySetting DifySetting[]
|
||||||
|
}
|
||||||
|
|
||||||
|
model DifySetting {
|
||||||
|
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?
|
||||||
|
splitMessages Boolean? @default(false) @db.Boolean
|
||||||
|
timePerChar Int? @default(50) @db.Integer
|
||||||
|
createdAt DateTime? @default(now()) @db.Timestamp
|
||||||
|
updatedAt DateTime @updatedAt @db.Timestamp
|
||||||
|
Fallback Dify? @relation(fields: [difyIdFallback], references: [id])
|
||||||
|
difyIdFallback String? @db.VarChar(100)
|
||||||
|
Instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)
|
||||||
|
instanceId String @unique
|
||||||
|
}
|
||||||
|
|
||||||
|
model EvolutionBot {
|
||||||
|
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?
|
||||||
|
splitMessages Boolean? @default(false) @db.Boolean
|
||||||
|
timePerChar Int? @default(50) @db.Integer
|
||||||
|
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
|
||||||
|
EvolutionBotSetting EvolutionBotSetting[]
|
||||||
|
}
|
||||||
|
|
||||||
|
model EvolutionBotSetting {
|
||||||
|
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?
|
||||||
|
splitMessages Boolean? @default(false) @db.Boolean
|
||||||
|
timePerChar Int? @default(50) @db.Integer
|
||||||
|
createdAt DateTime? @default(now()) @db.Timestamp
|
||||||
|
updatedAt DateTime @updatedAt @db.Timestamp
|
||||||
|
Fallback EvolutionBot? @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?
|
||||||
|
splitMessages Boolean? @default(false) @db.Boolean
|
||||||
|
timePerChar Int? @default(50) @db.Integer
|
||||||
|
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?
|
||||||
|
splitMessages Boolean? @default(false) @db.Boolean
|
||||||
|
timePerChar Int? @default(50) @db.Integer
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
model IsOnWhatsapp {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
remoteJid String @unique @db.VarChar(100)
|
||||||
|
jidOptions String
|
||||||
|
lid String? @db.VarChar(100)
|
||||||
|
createdAt DateTime @default(now()) @db.Timestamp
|
||||||
|
updatedAt DateTime @updatedAt @db.Timestamp
|
||||||
|
}
|
||||||
|
|
||||||
|
model N8n {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
enabled Boolean @default(true) @db.Boolean
|
||||||
|
description String? @db.VarChar(255)
|
||||||
|
webhookUrl String? @db.VarChar(255)
|
||||||
|
basicAuthUser String? @db.VarChar(255)
|
||||||
|
basicAuthPass 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?
|
||||||
|
splitMessages Boolean? @default(false) @db.Boolean
|
||||||
|
timePerChar Int? @default(50) @db.Integer
|
||||||
|
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
|
||||||
|
N8nSetting N8nSetting[]
|
||||||
|
}
|
||||||
|
|
||||||
|
model N8nSetting {
|
||||||
|
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?
|
||||||
|
splitMessages Boolean? @default(false) @db.Boolean
|
||||||
|
timePerChar Int? @default(50) @db.Integer
|
||||||
|
createdAt DateTime? @default(now()) @db.Timestamp
|
||||||
|
updatedAt DateTime @updatedAt @db.Timestamp
|
||||||
|
Fallback N8n? @relation(fields: [n8nIdFallback], references: [id])
|
||||||
|
n8nIdFallback String? @db.VarChar(100)
|
||||||
|
Instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)
|
||||||
|
instanceId String @unique
|
||||||
|
}
|
||||||
|
|
||||||
|
model Evoai {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
enabled Boolean @default(true) @db.Boolean
|
||||||
|
description String? @db.VarChar(255)
|
||||||
|
agentUrl 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?
|
||||||
|
splitMessages Boolean? @default(false) @db.Boolean
|
||||||
|
timePerChar Int? @default(50) @db.Integer
|
||||||
|
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
|
||||||
|
EvoaiSetting EvoaiSetting[]
|
||||||
|
}
|
||||||
|
|
||||||
|
model EvoaiSetting {
|
||||||
|
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?
|
||||||
|
splitMessages Boolean? @default(false) @db.Boolean
|
||||||
|
timePerChar Int? @default(50) @db.Integer
|
||||||
|
createdAt DateTime? @default(now()) @db.Timestamp
|
||||||
|
updatedAt DateTime @updatedAt @db.Timestamp
|
||||||
|
Fallback Evoai? @relation(fields: [evoaiIdFallback], references: [id])
|
||||||
|
evoaiIdFallback String? @db.VarChar(100)
|
||||||
|
Instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)
|
||||||
|
instanceId String @unique
|
||||||
|
}
|
@ -11,11 +11,28 @@ if (!DATABASE_PROVIDER) {
|
|||||||
console.warn(`DATABASE_PROVIDER is not set in the .env file, using default: ${databaseProviderDefault}`);
|
console.warn(`DATABASE_PROVIDER is not set in the .env file, using default: ${databaseProviderDefault}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Função para determinar qual pasta de migrations usar
|
||||||
|
// Função para determinar qual pasta de migrations usar
|
||||||
|
function getMigrationsFolder(provider) {
|
||||||
|
switch (provider) {
|
||||||
|
case 'psql_bouncer':
|
||||||
|
return 'postgresql-migrations'; // psql_bouncer usa as migrations do postgresql
|
||||||
|
default:
|
||||||
|
return `${provider}-migrations`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const migrationsFolder = getMigrationsFolder(databaseProviderDefault);
|
||||||
|
|
||||||
let command = process.argv
|
let command = process.argv
|
||||||
.slice(2)
|
.slice(2)
|
||||||
.join(' ')
|
.join(' ')
|
||||||
.replace(/DATABASE_PROVIDER/g, databaseProviderDefault);
|
.replace(/DATABASE_PROVIDER/g, databaseProviderDefault);
|
||||||
|
|
||||||
|
// Substituir referências à pasta de migrations pela pasta correta
|
||||||
|
const migrationsPattern = new RegExp(`${databaseProviderDefault}-migrations`, 'g');
|
||||||
|
command = command.replace(migrationsPattern, migrationsFolder);
|
||||||
|
|
||||||
if (command.includes('rmdir') && existsSync('prisma\\migrations')) {
|
if (command.includes('rmdir') && existsSync('prisma\\migrations')) {
|
||||||
try {
|
try {
|
||||||
execSync('rmdir /S /Q prisma\\migrations', { stdio: 'inherit' });
|
execSync('rmdir /S /Q prisma\\migrations', { stdio: 'inherit' });
|
||||||
|
@ -70,6 +70,10 @@ export class ChatController {
|
|||||||
return await this.waMonitor.waInstances[instanceName].fetchChats(query);
|
return await this.waMonitor.waInstances[instanceName].fetchChats(query);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async findChatByRemoteJid({ instanceName }: InstanceDto, remoteJid: string) {
|
||||||
|
return await this.waMonitor.waInstances[instanceName].findChatByRemoteJid(remoteJid);
|
||||||
|
}
|
||||||
|
|
||||||
public async sendPresence({ instanceName }: InstanceDto, data: SendPresenceDto) {
|
public async sendPresence({ instanceName }: InstanceDto, data: SendPresenceDto) {
|
||||||
return await this.waMonitor.waInstances[instanceName].sendPresence(data);
|
return await this.waMonitor.waInstances[instanceName].sendPresence(data);
|
||||||
}
|
}
|
||||||
|
@ -17,13 +17,15 @@ import {
|
|||||||
import { WAMonitoringService } from '@api/services/monitor.service';
|
import { WAMonitoringService } from '@api/services/monitor.service';
|
||||||
import { BadRequestException } from '@exceptions';
|
import { BadRequestException } from '@exceptions';
|
||||||
import { isBase64, isURL } from 'class-validator';
|
import { isBase64, isURL } from 'class-validator';
|
||||||
|
import emojiRegex from 'emoji-regex';
|
||||||
|
|
||||||
|
const regex = emojiRegex();
|
||||||
|
|
||||||
function isEmoji(str: string) {
|
function isEmoji(str: string) {
|
||||||
if (str === '') return true;
|
if (str === '') return true;
|
||||||
|
|
||||||
const emojiRegex =
|
const match = str.match(regex);
|
||||||
/^[\u{1F300}-\u{1F9FF}\u{2600}-\u{26FF}\u{2700}-\u{27BF}\u{1F000}-\u{1F02F}\u{1F0A0}-\u{1F0FF}\u{1F100}-\u{1F64F}\u{1F680}-\u{1F6FF}]$/u;
|
return match?.length === 1 && match[0] === str;
|
||||||
return emojiRegex.test(str);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class SendMessageController {
|
export class SendMessageController {
|
||||||
|
@ -455,6 +455,12 @@ export class EvolutionStartupService extends ChannelStartupService {
|
|||||||
if (base64 || file || audioFile) {
|
if (base64 || file || audioFile) {
|
||||||
if (this.configService.get<S3>('S3').ENABLE) {
|
if (this.configService.get<S3>('S3').ENABLE) {
|
||||||
try {
|
try {
|
||||||
|
// Verificação adicional para garantir que há conteúdo de mídia real
|
||||||
|
const hasRealMedia = this.hasValidMediaContent(messageRaw);
|
||||||
|
|
||||||
|
if (!hasRealMedia) {
|
||||||
|
this.logger.warn('Message detected as media but contains no valid media content');
|
||||||
|
} else {
|
||||||
const fileBuffer = audioFile?.buffer || file?.buffer;
|
const fileBuffer = audioFile?.buffer || file?.buffer;
|
||||||
const buffer = base64 ? Buffer.from(base64, 'base64') : fileBuffer;
|
const buffer = base64 ? Buffer.from(base64, 'base64') : fileBuffer;
|
||||||
|
|
||||||
@ -488,6 +494,7 @@ export class EvolutionStartupService extends ChannelStartupService {
|
|||||||
const mediaUrl = await s3Service.getObjectUrl(fullName);
|
const mediaUrl = await s3Service.getObjectUrl(fullName);
|
||||||
|
|
||||||
messageRaw.message.mediaUrl = mediaUrl;
|
messageRaw.message.mediaUrl = mediaUrl;
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logger.error(['Error on upload file to minio', error?.message, error?.stack]);
|
this.logger.error(['Error on upload file to minio', error?.message, error?.stack]);
|
||||||
}
|
}
|
||||||
|
@ -429,6 +429,12 @@ export class BusinessStartupService extends ChannelStartupService {
|
|||||||
try {
|
try {
|
||||||
const message: any = received;
|
const message: any = received;
|
||||||
|
|
||||||
|
// Verificação adicional para garantir que há conteúdo de mídia real
|
||||||
|
const hasRealMedia = this.hasValidMediaContent(messageRaw);
|
||||||
|
|
||||||
|
if (!hasRealMedia) {
|
||||||
|
this.logger.warn('Message detected as media but contains no valid media content');
|
||||||
|
} else {
|
||||||
const id = message.messages[0][message.messages[0].type].id;
|
const id = message.messages[0][message.messages[0].type].id;
|
||||||
let urlServer = this.configService.get<WaBusiness>('WA_BUSINESS').URL;
|
let urlServer = this.configService.get<WaBusiness>('WA_BUSINESS').URL;
|
||||||
const version = this.configService.get<WaBusiness>('WA_BUSINESS').VERSION;
|
const version = this.configService.get<WaBusiness>('WA_BUSINESS').VERSION;
|
||||||
@ -533,6 +539,7 @@ export class BusinessStartupService extends ChannelStartupService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logger.error(['Error on upload file to minio', error?.message, error?.stack]);
|
this.logger.error(['Error on upload file to minio', error?.message, error?.stack]);
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,59 @@
|
|||||||
|
import { Logger } from '@config/logger.config';
|
||||||
|
import { BaileysEventMap, MessageUpsertType, proto } from 'baileys';
|
||||||
|
import { catchError, concatMap, delay, EMPTY, from, retryWhen, Subject, Subscription, take, tap } from 'rxjs';
|
||||||
|
|
||||||
|
type MessageUpsertPayload = BaileysEventMap['messages.upsert'];
|
||||||
|
type MountProps = {
|
||||||
|
onMessageReceive: (payload: MessageUpsertPayload, settings: any) => Promise<void>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export class BaileysMessageProcessor {
|
||||||
|
private processorLogs = new Logger('BaileysMessageProcessor');
|
||||||
|
private subscription?: Subscription;
|
||||||
|
|
||||||
|
protected messageSubject = new Subject<{
|
||||||
|
messages: proto.IWebMessageInfo[];
|
||||||
|
type: MessageUpsertType;
|
||||||
|
requestId?: string;
|
||||||
|
settings: any;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
mount({ onMessageReceive }: MountProps) {
|
||||||
|
this.subscription = this.messageSubject
|
||||||
|
.pipe(
|
||||||
|
tap(({ messages }) => {
|
||||||
|
this.processorLogs.log(`Processing batch of ${messages.length} messages`);
|
||||||
|
}),
|
||||||
|
concatMap(({ messages, type, requestId, settings }) =>
|
||||||
|
from(onMessageReceive({ messages, type, requestId }, settings)).pipe(
|
||||||
|
retryWhen((errors) =>
|
||||||
|
errors.pipe(
|
||||||
|
tap((error) => this.processorLogs.warn(`Retrying message batch due to error: ${error.message}`)),
|
||||||
|
delay(1000), // 1 segundo de delay
|
||||||
|
take(3), // Máximo 3 tentativas
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
catchError((error) => {
|
||||||
|
this.processorLogs.error(`Error processing message batch: ${error}`);
|
||||||
|
return EMPTY;
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.subscribe({
|
||||||
|
error: (error) => {
|
||||||
|
this.processorLogs.error(`Message stream error: ${error}`);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
processMessage(payload: MessageUpsertPayload, settings: any) {
|
||||||
|
const { messages, type, requestId } = payload;
|
||||||
|
this.messageSubject.next({ messages, type, requestId, settings });
|
||||||
|
}
|
||||||
|
|
||||||
|
onDestroy() {
|
||||||
|
this.subscription?.unsubscribe();
|
||||||
|
this.messageSubject.complete();
|
||||||
|
}
|
||||||
|
}
|
@ -99,6 +99,7 @@ import makeWASocket, {
|
|||||||
Contact,
|
Contact,
|
||||||
delay,
|
delay,
|
||||||
DisconnectReason,
|
DisconnectReason,
|
||||||
|
downloadContentFromMessage,
|
||||||
downloadMediaMessage,
|
downloadMediaMessage,
|
||||||
generateWAMessageFromContent,
|
generateWAMessageFromContent,
|
||||||
getAggregateVotesInPollMessage,
|
getAggregateVotesInPollMessage,
|
||||||
@ -122,7 +123,7 @@ import makeWASocket, {
|
|||||||
WABrowserDescription,
|
WABrowserDescription,
|
||||||
WAMediaUpload,
|
WAMediaUpload,
|
||||||
WAMessage,
|
WAMessage,
|
||||||
WAMessageUpdate,
|
WAMessageKey,
|
||||||
WAPresence,
|
WAPresence,
|
||||||
WASocket,
|
WASocket,
|
||||||
} from 'baileys';
|
} from 'baileys';
|
||||||
@ -147,6 +148,7 @@ import sharp from 'sharp';
|
|||||||
import { PassThrough, Readable } from 'stream';
|
import { PassThrough, Readable } from 'stream';
|
||||||
import { v4 } from 'uuid';
|
import { v4 } from 'uuid';
|
||||||
|
|
||||||
|
import { BaileysMessageProcessor } from './baileysMessage.processor';
|
||||||
import { useVoiceCallsBaileys } from './voiceCalls/useVoiceCallsBaileys';
|
import { useVoiceCallsBaileys } from './voiceCalls/useVoiceCallsBaileys';
|
||||||
|
|
||||||
const groupMetadataCache = new CacheService(new CacheEngine(configService, 'groups').getEngine());
|
const groupMetadataCache = new CacheService(new CacheEngine(configService, 'groups').getEngine());
|
||||||
@ -212,6 +214,8 @@ async function getVideoDuration(input: Buffer | string | Readable): Promise<numb
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class BaileysStartupService extends ChannelStartupService {
|
export class BaileysStartupService extends ChannelStartupService {
|
||||||
|
private messageProcessor = new BaileysMessageProcessor();
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public readonly configService: ConfigService,
|
public readonly configService: ConfigService,
|
||||||
public readonly eventEmitter: EventEmitter2,
|
public readonly eventEmitter: EventEmitter2,
|
||||||
@ -223,6 +227,9 @@ export class BaileysStartupService extends ChannelStartupService {
|
|||||||
) {
|
) {
|
||||||
super(configService, eventEmitter, prismaRepository, chatwootCache);
|
super(configService, eventEmitter, prismaRepository, chatwootCache);
|
||||||
this.instance.qrcode = { count: 0 };
|
this.instance.qrcode = { count: 0 };
|
||||||
|
this.messageProcessor.mount({
|
||||||
|
onMessageReceive: this.messageHandle['messages.upsert'].bind(this), // Bind the method to the current context
|
||||||
|
});
|
||||||
|
|
||||||
this.authStateProvider = new AuthStateProvider(this.providerFiles);
|
this.authStateProvider = new AuthStateProvider(this.providerFiles);
|
||||||
}
|
}
|
||||||
@ -242,6 +249,7 @@ export class BaileysStartupService extends ChannelStartupService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async logoutInstance() {
|
public async logoutInstance() {
|
||||||
|
this.messageProcessor.onDestroy();
|
||||||
await this.client?.logout('Log out instance: ' + this.instanceName);
|
await this.client?.logout('Log out instance: ' + this.instanceName);
|
||||||
|
|
||||||
this.client?.ws?.close();
|
this.client?.ws?.close();
|
||||||
@ -541,17 +549,18 @@ export class BaileysStartupService extends ChannelStartupService {
|
|||||||
this.logger.info(`Browser: ${browser}`);
|
this.logger.info(`Browser: ${browser}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
let version;
|
|
||||||
let log;
|
|
||||||
|
|
||||||
if (session.VERSION) {
|
|
||||||
version = session.VERSION.split('.');
|
|
||||||
log = `Baileys version env: ${version}`;
|
|
||||||
} else {
|
|
||||||
const baileysVersion = await fetchLatestWaWebVersion({});
|
const baileysVersion = await fetchLatestWaWebVersion({});
|
||||||
version = baileysVersion.version;
|
const version = baileysVersion.version;
|
||||||
log = `Baileys version: ${version}`;
|
const log = `Baileys version: ${version.join('.')}`;
|
||||||
}
|
|
||||||
|
// if (session.VERSION) {
|
||||||
|
// version = session.VERSION.split('.');
|
||||||
|
// log = `Baileys version env: ${version}`;
|
||||||
|
// } else {
|
||||||
|
// const baileysVersion = await fetchLatestWaWebVersion({});
|
||||||
|
// version = baileysVersion.version;
|
||||||
|
// log = `Baileys version: ${version}`;
|
||||||
|
// }
|
||||||
|
|
||||||
this.logger.info(log);
|
this.logger.info(log);
|
||||||
|
|
||||||
@ -887,7 +896,7 @@ export class BaileysStartupService extends ChannelStartupService {
|
|||||||
}: {
|
}: {
|
||||||
chats: Chat[];
|
chats: Chat[];
|
||||||
contacts: Contact[];
|
contacts: Contact[];
|
||||||
messages: proto.IWebMessageInfo[];
|
messages: WAMessage[];
|
||||||
isLatest?: boolean;
|
isLatest?: boolean;
|
||||||
progress?: number;
|
progress?: number;
|
||||||
syncType?: proto.HistorySync.HistorySyncType;
|
syncType?: proto.HistorySync.HistorySyncType;
|
||||||
@ -973,6 +982,10 @@ export class BaileysStartupService extends ChannelStartupService {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (m.key.remoteJid?.includes('@lid') && m.key.senderPn) {
|
||||||
|
m.key.remoteJid = m.key.senderPn;
|
||||||
|
}
|
||||||
|
|
||||||
if (Long.isLong(m?.messageTimestamp)) {
|
if (Long.isLong(m?.messageTimestamp)) {
|
||||||
m.messageTimestamp = m.messageTimestamp?.toNumber();
|
m.messageTimestamp = m.messageTimestamp?.toNumber();
|
||||||
}
|
}
|
||||||
@ -1030,11 +1043,31 @@ export class BaileysStartupService extends ChannelStartupService {
|
|||||||
},
|
},
|
||||||
|
|
||||||
'messages.upsert': async (
|
'messages.upsert': async (
|
||||||
{ messages, type, requestId }: { messages: proto.IWebMessageInfo[]; type: MessageUpsertType; requestId?: string },
|
{ messages, type, requestId }: { messages: WAMessage[]; type: MessageUpsertType; requestId?: string },
|
||||||
settings: any,
|
settings: any,
|
||||||
) => {
|
) => {
|
||||||
try {
|
try {
|
||||||
for (const received of messages) {
|
for (const received of messages) {
|
||||||
|
if (received.key.remoteJid?.includes('@lid') && received.key.senderPn) {
|
||||||
|
(received.key as { previousRemoteJid?: string | null }).previousRemoteJid = received.key.remoteJid;
|
||||||
|
received.key.remoteJid = received.key.senderPn;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
received?.messageStubParameters?.some?.((param) =>
|
||||||
|
[
|
||||||
|
'No matching sessions found for message',
|
||||||
|
'Bad MAC',
|
||||||
|
'failed to decrypt message',
|
||||||
|
'SessionError',
|
||||||
|
'Invalid PreKey ID',
|
||||||
|
'No session record',
|
||||||
|
'No session found to decrypt message',
|
||||||
|
].some((err) => param?.includes?.(err)),
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
this.logger.warn(`Message ignored with messageStubParameters: ${JSON.stringify(received, null, 2)}`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if (received.message?.conversation || received.message?.extendedTextMessage?.text) {
|
if (received.message?.conversation || received.message?.extendedTextMessage?.text) {
|
||||||
const text = received.message?.conversation || received.message?.extendedTextMessage?.text;
|
const text = received.message?.conversation || received.message?.extendedTextMessage?.text;
|
||||||
|
|
||||||
@ -1055,7 +1088,7 @@ export class BaileysStartupService extends ChannelStartupService {
|
|||||||
const editedMessage =
|
const editedMessage =
|
||||||
received?.message?.protocolMessage || received?.message?.editedMessage?.message?.protocolMessage;
|
received?.message?.protocolMessage || received?.message?.editedMessage?.message?.protocolMessage;
|
||||||
|
|
||||||
if (received.message?.protocolMessage?.editedMessage && editedMessage) {
|
if (editedMessage) {
|
||||||
if (this.configService.get<Chatwoot>('CHATWOOT').ENABLED && this.localChatwoot?.enabled)
|
if (this.configService.get<Chatwoot>('CHATWOOT').ENABLED && this.localChatwoot?.enabled)
|
||||||
this.chatwootService.eventWhatsapp(
|
this.chatwootService.eventWhatsapp(
|
||||||
'messages.edit',
|
'messages.edit',
|
||||||
@ -1103,7 +1136,7 @@ export class BaileysStartupService extends ChannelStartupService {
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
(type !== 'notify' && type !== 'append') ||
|
(type !== 'notify' && type !== 'append') ||
|
||||||
received.message?.protocolMessage ||
|
editedMessage ||
|
||||||
received.message?.pollUpdateMessage ||
|
received.message?.pollUpdateMessage ||
|
||||||
!received?.message
|
!received?.message
|
||||||
) {
|
) {
|
||||||
@ -1226,6 +1259,13 @@ export class BaileysStartupService extends ChannelStartupService {
|
|||||||
if (this.configService.get<S3>('S3').ENABLE) {
|
if (this.configService.get<S3>('S3').ENABLE) {
|
||||||
try {
|
try {
|
||||||
const message: any = received;
|
const message: any = received;
|
||||||
|
|
||||||
|
// Verificação adicional para garantir que há conteúdo de mídia real
|
||||||
|
const hasRealMedia = this.hasValidMediaContent(message);
|
||||||
|
|
||||||
|
if (!hasRealMedia) {
|
||||||
|
this.logger.warn('Message detected as media but contains no valid media content');
|
||||||
|
} else {
|
||||||
const media = await this.getBase64FromMediaMessage({ message }, true);
|
const media = await this.getBase64FromMediaMessage({ message }, true);
|
||||||
|
|
||||||
const { buffer, mediaType, fileName, size } = media;
|
const { buffer, mediaType, fileName, size } = media;
|
||||||
@ -1253,6 +1293,7 @@ export class BaileysStartupService extends ChannelStartupService {
|
|||||||
messageRaw.message.mediaUrl = mediaUrl;
|
messageRaw.message.mediaUrl = mediaUrl;
|
||||||
|
|
||||||
await this.prismaRepository.message.update({ where: { id: msg.id }, data: messageRaw });
|
await this.prismaRepository.message.update({ where: { id: msg.id }, data: messageRaw });
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logger.error(['Error on upload file to minio', error?.message, error?.stack]);
|
this.logger.error(['Error on upload file to minio', error?.message, error?.stack]);
|
||||||
}
|
}
|
||||||
@ -1356,7 +1397,7 @@ export class BaileysStartupService extends ChannelStartupService {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
'messages.update': async (args: WAMessageUpdate[], settings: any) => {
|
'messages.update': async (args: { update: Partial<WAMessage>; key: WAMessageKey }[], settings: any) => {
|
||||||
this.logger.log(`Update messages ${JSON.stringify(args, undefined, 2)}`);
|
this.logger.log(`Update messages ${JSON.stringify(args, undefined, 2)}`);
|
||||||
|
|
||||||
const readChatToUpdate: Record<string, true> = {}; // {remoteJid: true}
|
const readChatToUpdate: Record<string, true> = {}; // {remoteJid: true}
|
||||||
@ -1366,6 +1407,10 @@ export class BaileysStartupService extends ChannelStartupService {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (key.remoteJid?.includes('@lid') && key.senderPn) {
|
||||||
|
key.remoteJid = key.senderPn;
|
||||||
|
}
|
||||||
|
|
||||||
const updateKey = `${this.instance.id}_${key.id}_${update.status}`;
|
const updateKey = `${this.instance.id}_${key.id}_${update.status}`;
|
||||||
|
|
||||||
const cached = await this.baileysCache.get(updateKey);
|
const cached = await this.baileysCache.get(updateKey);
|
||||||
@ -1618,7 +1663,9 @@ export class BaileysStartupService extends ChannelStartupService {
|
|||||||
|
|
||||||
if (events['messages.upsert']) {
|
if (events['messages.upsert']) {
|
||||||
const payload = events['messages.upsert'];
|
const payload = events['messages.upsert'];
|
||||||
this.messageHandle['messages.upsert'](payload, settings);
|
|
||||||
|
this.messageProcessor.processMessage(payload, settings);
|
||||||
|
// this.messageHandle['messages.upsert'](payload, settings);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (events['messages.update']) {
|
if (events['messages.update']) {
|
||||||
@ -2121,6 +2168,13 @@ export class BaileysStartupService extends ChannelStartupService {
|
|||||||
if (isMedia && this.configService.get<S3>('S3').ENABLE) {
|
if (isMedia && this.configService.get<S3>('S3').ENABLE) {
|
||||||
try {
|
try {
|
||||||
const message: any = messageRaw;
|
const message: any = messageRaw;
|
||||||
|
|
||||||
|
// Verificação adicional para garantir que há conteúdo de mídia real
|
||||||
|
const hasRealMedia = this.hasValidMediaContent(message);
|
||||||
|
|
||||||
|
if (!hasRealMedia) {
|
||||||
|
this.logger.warn('Message detected as media but contains no valid media content');
|
||||||
|
} else {
|
||||||
const media = await this.getBase64FromMediaMessage({ message }, true);
|
const media = await this.getBase64FromMediaMessage({ message }, true);
|
||||||
|
|
||||||
const { buffer, mediaType, fileName, size } = media;
|
const { buffer, mediaType, fileName, size } = media;
|
||||||
@ -2146,6 +2200,7 @@ export class BaileysStartupService extends ChannelStartupService {
|
|||||||
messageRaw.message.mediaUrl = mediaUrl;
|
messageRaw.message.mediaUrl = mediaUrl;
|
||||||
|
|
||||||
await this.prismaRepository.message.update({ where: { id: msg.id }, data: messageRaw });
|
await this.prismaRepository.message.update({ where: { id: msg.id }, data: messageRaw });
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logger.error(['Error on upload file to minio', error?.message, error?.stack]);
|
this.logger.error(['Error on upload file to minio', error?.message, error?.stack]);
|
||||||
}
|
}
|
||||||
@ -2504,7 +2559,9 @@ export class BaileysStartupService extends ChannelStartupService {
|
|||||||
imageBuffer = Buffer.from(base64Data, 'base64');
|
imageBuffer = Buffer.from(base64Data, 'base64');
|
||||||
} else {
|
} else {
|
||||||
const timestamp = new Date().getTime();
|
const timestamp = new Date().getTime();
|
||||||
const url = `${image}?timestamp=${timestamp}`;
|
const parsedURL = new URL(image);
|
||||||
|
parsedURL.searchParams.set('timestamp', timestamp.toString());
|
||||||
|
const url = parsedURL.toString();
|
||||||
|
|
||||||
let config: any = { responseType: 'arraybuffer' };
|
let config: any = { responseType: 'arraybuffer' };
|
||||||
|
|
||||||
@ -2725,7 +2782,9 @@ export class BaileysStartupService extends ChannelStartupService {
|
|||||||
|
|
||||||
if (isURL(audio)) {
|
if (isURL(audio)) {
|
||||||
const timestamp = new Date().getTime();
|
const timestamp = new Date().getTime();
|
||||||
const url = `${audio}?timestamp=${timestamp}`;
|
const parsedURL = new URL(audio);
|
||||||
|
parsedURL.searchParams.set('timestamp', timestamp.toString());
|
||||||
|
const url = parsedURL.toString();
|
||||||
|
|
||||||
const config: any = { responseType: 'stream' };
|
const config: any = { responseType: 'stream' };
|
||||||
|
|
||||||
@ -3413,6 +3472,18 @@ export class BaileysStartupService extends ChannelStartupService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async mapMediaType(mediaType) {
|
||||||
|
const map = {
|
||||||
|
imageMessage: 'image',
|
||||||
|
videoMessage: 'video',
|
||||||
|
documentMessage: 'document',
|
||||||
|
stickerMessage: 'sticker',
|
||||||
|
audioMessage: 'audio',
|
||||||
|
ptvMessage: 'video',
|
||||||
|
};
|
||||||
|
return map[mediaType] || null;
|
||||||
|
}
|
||||||
|
|
||||||
public async getBase64FromMediaMessage(data: getBase64FromMediaMessageDto, getBuffer = false) {
|
public async getBase64FromMediaMessage(data: getBase64FromMediaMessageDto, getBuffer = false) {
|
||||||
try {
|
try {
|
||||||
const m = data?.message;
|
const m = data?.message;
|
||||||
@ -3437,6 +3508,23 @@ export class BaileysStartupService extends ChannelStartupService {
|
|||||||
let mediaMessage: any;
|
let mediaMessage: any;
|
||||||
let mediaType: string;
|
let mediaType: string;
|
||||||
|
|
||||||
|
if (msg.message?.templateMessage) {
|
||||||
|
const template =
|
||||||
|
msg.message.templateMessage.hydratedTemplate || msg.message.templateMessage.hydratedFourRowTemplate;
|
||||||
|
|
||||||
|
for (const type of TypeMediaMessage) {
|
||||||
|
if (template[type]) {
|
||||||
|
mediaMessage = template[type];
|
||||||
|
mediaType = type;
|
||||||
|
msg.message = { [type]: { ...template[type], url: template[type].staticUrl } };
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!mediaMessage) {
|
||||||
|
throw 'Template message does not contain a supported media type';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
for (const type of TypeMediaMessage) {
|
for (const type of TypeMediaMessage) {
|
||||||
mediaMessage = msg.message[type];
|
mediaMessage = msg.message[type];
|
||||||
if (mediaMessage) {
|
if (mediaMessage) {
|
||||||
@ -3448,17 +3536,48 @@ export class BaileysStartupService extends ChannelStartupService {
|
|||||||
if (!mediaMessage) {
|
if (!mediaMessage) {
|
||||||
throw 'The message is not of the media type';
|
throw 'The message is not of the media type';
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (typeof mediaMessage['mediaKey'] === 'object') {
|
if (typeof mediaMessage['mediaKey'] === 'object') {
|
||||||
msg.message = JSON.parse(JSON.stringify(msg.message));
|
msg.message = JSON.parse(JSON.stringify(msg.message));
|
||||||
}
|
}
|
||||||
|
|
||||||
const buffer = await downloadMediaMessage(
|
let buffer: Buffer;
|
||||||
|
|
||||||
|
try {
|
||||||
|
buffer = await downloadMediaMessage(
|
||||||
{ key: msg?.key, message: msg?.message },
|
{ key: msg?.key, message: msg?.message },
|
||||||
'buffer',
|
'buffer',
|
||||||
{},
|
{},
|
||||||
{ logger: P({ level: 'error' }) as any, reuploadRequest: this.client.updateMediaMessage },
|
{ logger: P({ level: 'error' }) as any, reuploadRequest: this.client.updateMediaMessage },
|
||||||
);
|
);
|
||||||
|
} catch (err) {
|
||||||
|
this.logger.error('Download Media failed, trying to retry in 5 seconds...');
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 5000));
|
||||||
|
const mediaType = Object.keys(msg.message).find((key) => key.endsWith('Message'));
|
||||||
|
if (!mediaType) throw new Error('Could not determine mediaType for fallback');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const media = await downloadContentFromMessage(
|
||||||
|
{
|
||||||
|
mediaKey: msg.message?.[mediaType]?.mediaKey,
|
||||||
|
directPath: msg.message?.[mediaType]?.directPath,
|
||||||
|
url: `https://mmg.whatsapp.net${msg?.message?.[mediaType]?.directPath}`,
|
||||||
|
},
|
||||||
|
await this.mapMediaType(mediaType),
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
const chunks = [];
|
||||||
|
for await (const chunk of media) {
|
||||||
|
chunks.push(chunk);
|
||||||
|
}
|
||||||
|
buffer = Buffer.concat(chunks);
|
||||||
|
this.logger.info('Download Media with downloadContentFromMessage was successful!');
|
||||||
|
} catch (fallbackErr) {
|
||||||
|
this.logger.error('Download Media with downloadContentFromMessage also failed!');
|
||||||
|
throw fallbackErr;
|
||||||
|
}
|
||||||
|
}
|
||||||
const typeMessage = getContentType(msg.message);
|
const typeMessage = getContentType(msg.message);
|
||||||
|
|
||||||
const ext = mimeTypes.extension(mediaMessage?.['mimetype']);
|
const ext = mimeTypes.extension(mediaMessage?.['mimetype']);
|
||||||
@ -3591,7 +3710,9 @@ export class BaileysStartupService extends ChannelStartupService {
|
|||||||
let pic: WAMediaUpload;
|
let pic: WAMediaUpload;
|
||||||
if (isURL(picture)) {
|
if (isURL(picture)) {
|
||||||
const timestamp = new Date().getTime();
|
const timestamp = new Date().getTime();
|
||||||
const url = `${picture}?timestamp=${timestamp}`;
|
const parsedURL = new URL(picture);
|
||||||
|
parsedURL.searchParams.set('timestamp', timestamp.toString());
|
||||||
|
const url = parsedURL.toString();
|
||||||
|
|
||||||
let config: any = { responseType: 'arraybuffer' };
|
let config: any = { responseType: 'arraybuffer' };
|
||||||
|
|
||||||
@ -3873,7 +3994,9 @@ export class BaileysStartupService extends ChannelStartupService {
|
|||||||
let pic: WAMediaUpload;
|
let pic: WAMediaUpload;
|
||||||
if (isURL(picture.image)) {
|
if (isURL(picture.image)) {
|
||||||
const timestamp = new Date().getTime();
|
const timestamp = new Date().getTime();
|
||||||
const url = `${picture.image}?timestamp=${timestamp}`;
|
const parsedURL = new URL(picture.image);
|
||||||
|
parsedURL.searchParams.set('timestamp', timestamp.toString());
|
||||||
|
const url = parsedURL.toString();
|
||||||
|
|
||||||
let config: any = { responseType: 'arraybuffer' };
|
let config: any = { responseType: 'arraybuffer' };
|
||||||
|
|
||||||
|
@ -26,7 +26,7 @@ import axios from 'axios';
|
|||||||
import { proto } from 'baileys';
|
import { proto } from 'baileys';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import FormData from 'form-data';
|
import FormData from 'form-data';
|
||||||
import Jimp from 'jimp';
|
import { Jimp, JimpMime } from 'jimp';
|
||||||
import Long from 'long';
|
import Long from 'long';
|
||||||
import mimeTypes from 'mime-types';
|
import mimeTypes from 'mime-types';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
@ -457,6 +457,24 @@ export class ChatwootService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async mergeContacts(baseId: number, mergeId: number) {
|
||||||
|
try {
|
||||||
|
const contact = await chatwootRequest(this.getClientCwConfig(), {
|
||||||
|
method: 'POST',
|
||||||
|
url: `/api/v1/accounts/${this.provider.accountId}/actions/contact_merge`,
|
||||||
|
body: {
|
||||||
|
base_contact_id: baseId,
|
||||||
|
mergee_contact_id: mergeId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return contact;
|
||||||
|
} catch {
|
||||||
|
this.logger.error('Error merging contacts');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async mergeBrazilianContacts(contacts: any[]) {
|
private async mergeBrazilianContacts(contacts: any[]) {
|
||||||
try {
|
try {
|
||||||
const contact = await chatwootRequest(this.getClientCwConfig(), {
|
const contact = await chatwootRequest(this.getClientCwConfig(), {
|
||||||
@ -549,24 +567,41 @@ export class ChatwootService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async createConversation(instance: InstanceDto, body: any) {
|
public async createConversation(instance: InstanceDto, body: any) {
|
||||||
const isLid = body.key.remoteJid.includes('@lid') && body.key.senderPn;
|
if (!body?.key) {
|
||||||
const remoteJid = isLid ? body.key.senderPn : body.key.remoteJid;
|
this.logger.warn(
|
||||||
|
`body.key is null or undefined in createConversation. Full body object: ${JSON.stringify(body)}`,
|
||||||
|
);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isLid = body.key.previousRemoteJid?.includes('@lid') && body.key.senderPn;
|
||||||
|
const remoteJid = body.key.remoteJid;
|
||||||
const cacheKey = `${instance.instanceName}:createConversation-${remoteJid}`;
|
const cacheKey = `${instance.instanceName}:createConversation-${remoteJid}`;
|
||||||
const lockKey = `${instance.instanceName}:lock:createConversation-${remoteJid}`;
|
const lockKey = `${instance.instanceName}:lock:createConversation-${remoteJid}`;
|
||||||
const maxWaitTime = 5000; // 5 secounds
|
const maxWaitTime = 5000; // 5 secounds
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Processa atualização de contatos já criados @lid
|
// Processa atualização de contatos já criados @lid
|
||||||
if (body.key.remoteJid.includes('@lid') && body.key.senderPn && body.key.senderPn !== body.key.remoteJid) {
|
if (isLid && body.key.senderPn !== body.key.previousRemoteJid) {
|
||||||
const contact = await this.findContact(instance, body.key.remoteJid.split('@')[0]);
|
const contact = await this.findContact(instance, body.key.remoteJid.split('@')[0]);
|
||||||
if (contact && contact.identifier !== body.key.senderPn) {
|
if (contact && contact.identifier !== body.key.senderPn) {
|
||||||
this.logger.verbose(
|
this.logger.verbose(
|
||||||
`Identifier needs update: (contact.identifier: ${contact.identifier}, body.key.remoteJid: ${body.key.remoteJid}, body.key.senderPn: ${body.key.senderPn})`,
|
`Identifier needs update: (contact.identifier: ${contact.identifier}, body.key.remoteJid: ${body.key.remoteJid}, body.key.senderPn: ${body.key.senderPn}`,
|
||||||
);
|
);
|
||||||
await this.updateContact(instance, contact.id, {
|
const updateContact = await this.updateContact(instance, contact.id, {
|
||||||
identifier: body.key.senderPn,
|
identifier: body.key.senderPn,
|
||||||
phone_number: `+${body.key.senderPn.split('@')[0]}`,
|
phone_number: `+${body.key.senderPn.split('@')[0]}`,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (updateContact === null) {
|
||||||
|
const baseContact = await this.findContact(instance, body.key.senderPn.split('@')[0]);
|
||||||
|
if (baseContact) {
|
||||||
|
await this.mergeContacts(baseContact.id, contact.id);
|
||||||
|
this.logger.verbose(
|
||||||
|
`Merge contacts: (${baseContact.id}) ${baseContact.phone_number} and (${contact.id}) ${contact.phone_number}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.logger.verbose(`--- Start createConversation ---`);
|
this.logger.verbose(`--- Start createConversation ---`);
|
||||||
@ -646,7 +681,7 @@ export class ChatwootService {
|
|||||||
instance,
|
instance,
|
||||||
body.key.participant.split('@')[0],
|
body.key.participant.split('@')[0],
|
||||||
filterInbox.id,
|
filterInbox.id,
|
||||||
isGroup,
|
false,
|
||||||
body.pushName,
|
body.pushName,
|
||||||
picture_url.profilePictureUrl || null,
|
picture_url.profilePictureUrl || null,
|
||||||
body.key.participant,
|
body.key.participant,
|
||||||
@ -685,7 +720,6 @@ export class ChatwootService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const jid = isLid && body?.key?.senderPn ? body.key.senderPn : body.key.remoteJid;
|
|
||||||
contact = await this.createContact(
|
contact = await this.createContact(
|
||||||
instance,
|
instance,
|
||||||
chatId,
|
chatId,
|
||||||
@ -693,7 +727,7 @@ export class ChatwootService {
|
|||||||
isGroup,
|
isGroup,
|
||||||
nameContact,
|
nameContact,
|
||||||
picture_url.profilePictureUrl || null,
|
picture_url.profilePictureUrl || null,
|
||||||
jid,
|
remoteJid,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1866,6 +1900,12 @@ export class ChatwootService {
|
|||||||
|
|
||||||
public async eventWhatsapp(event: string, instance: InstanceDto, body: any) {
|
public async eventWhatsapp(event: string, instance: InstanceDto, body: any) {
|
||||||
try {
|
try {
|
||||||
|
// Ignore events that are not messages (like EPHEMERAL_SYNC_RESPONSE)
|
||||||
|
if (body?.type && body.type !== 'message' && body.type !== 'conversation') {
|
||||||
|
this.logger.verbose(`Ignoring non-message event type: ${body.type}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const waInstance = this.waMonitor.waInstances[instance.instanceName];
|
const waInstance = this.waMonitor.waInstances[instance.instanceName];
|
||||||
|
|
||||||
if (!waInstance) {
|
if (!waInstance) {
|
||||||
@ -1911,6 +1951,11 @@ export class ChatwootService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (event === 'messages.upsert' || event === 'send.message') {
|
if (event === 'messages.upsert' || event === 'send.message') {
|
||||||
|
if (!body?.key) {
|
||||||
|
this.logger.warn(`body.key is null or undefined. Full body object: ${JSON.stringify(body)}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (body.key.remoteJid === 'status@broadcast') {
|
if (body.key.remoteJid === 'status@broadcast') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -2101,9 +2146,11 @@ export class ChatwootService {
|
|||||||
const fileData = Buffer.from(imgBuffer.data, 'binary');
|
const fileData = Buffer.from(imgBuffer.data, 'binary');
|
||||||
|
|
||||||
const img = await Jimp.read(fileData);
|
const img = await Jimp.read(fileData);
|
||||||
await img.cover(320, 180);
|
await img.cover({
|
||||||
|
w: 320,
|
||||||
const processedBuffer = await img.getBufferAsync(Jimp.MIME_PNG);
|
h: 180,
|
||||||
|
});
|
||||||
|
const processedBuffer = await img.getBuffer(JimpMime.png);
|
||||||
|
|
||||||
const fileStream = new Readable();
|
const fileStream = new Readable();
|
||||||
fileStream._read = () => {}; // _read is required but you can noop it
|
fileStream._read = () => {}; // _read is required but you can noop it
|
||||||
@ -2231,10 +2278,23 @@ export class ChatwootService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (event === 'messages.edit' || event === 'send.message.update') {
|
if (event === 'messages.edit' || event === 'send.message.update') {
|
||||||
|
// Ignore events that are not messages (like EPHEMERAL_SYNC_RESPONSE)
|
||||||
|
if (body?.type && body.type !== 'message') {
|
||||||
|
this.logger.verbose(`Ignoring non-message event type: ${body.type}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!body?.key?.id) {
|
||||||
|
this.logger.warn(
|
||||||
|
`body.key.id is null or undefined in messages.edit. Full body object: ${JSON.stringify(body)}`,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const editedText = `${
|
const editedText = `${
|
||||||
body?.editedMessage?.conversation || body?.editedMessage?.extendedTextMessage?.text
|
body?.editedMessage?.conversation || body?.editedMessage?.extendedTextMessage?.text
|
||||||
}\n\n_\`${i18next.t('cw.message.edited')}.\`_`;
|
}\n\n_\`${i18next.t('cw.message.edited')}.\`_`;
|
||||||
const message = await this.getMessageByKeyId(instance, body?.key?.id);
|
const message = await this.getMessageByKeyId(instance, body.key.id);
|
||||||
const key = message.key as {
|
const key = message.key as {
|
||||||
id: string;
|
id: string;
|
||||||
fromMe: boolean;
|
fromMe: boolean;
|
||||||
|
@ -70,7 +70,7 @@ export class EvoaiService extends BaseChatbotService<Evoai, EvoaiSetting> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const callId = `req-${uuidv4().substring(0, 8)}`;
|
const callId = `req-${uuidv4().substring(0, 8)}`;
|
||||||
const messageId = msg?.key?.id || uuidv4();
|
const messageId = remoteJid.split('@')[0] || uuidv4(); // Use phone number as messageId
|
||||||
|
|
||||||
// Prepare message parts
|
// Prepare message parts
|
||||||
const parts = [
|
const parts = [
|
||||||
|
@ -119,7 +119,7 @@ export class TypebotController extends BaseChatbotController<TypebotModel, Typeb
|
|||||||
|
|
||||||
const instanceData = await this.prismaRepository.instance.findFirst({
|
const instanceData = await this.prismaRepository.instance.findFirst({
|
||||||
where: {
|
where: {
|
||||||
id: instance.instanceId,
|
name: instance.instanceName,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -290,7 +290,7 @@ export class TypebotController extends BaseChatbotController<TypebotModel, Typeb
|
|||||||
request.data.clientSideActions,
|
request.data.clientSideActions,
|
||||||
);
|
);
|
||||||
|
|
||||||
this.waMonitor.waInstances[instance.instanceId].sendDataWebhook(Events.TYPEBOT_START, {
|
this.waMonitor.waInstances[instance.instanceName].sendDataWebhook(Events.TYPEBOT_START, {
|
||||||
remoteJid: remoteJid,
|
remoteJid: remoteJid,
|
||||||
url: url,
|
url: url,
|
||||||
typebot: typebot,
|
typebot: typebot,
|
||||||
|
@ -186,7 +186,7 @@ export class TypebotService extends BaseChatbotService<TypebotModel, any> {
|
|||||||
messages,
|
messages,
|
||||||
input,
|
input,
|
||||||
clientSideActions,
|
clientSideActions,
|
||||||
this.applyFormatting,
|
this.applyFormatting.bind(this),
|
||||||
this.prismaRepository,
|
this.prismaRepository,
|
||||||
).catch((err) => {
|
).catch((err) => {
|
||||||
console.error('Erro ao processar mensagens:', err);
|
console.error('Erro ao processar mensagens:', err);
|
||||||
|
@ -8,7 +8,12 @@ import { EmitData, EventController, EventControllerInterface } from '../event.co
|
|||||||
|
|
||||||
export class RabbitmqController extends EventController implements EventControllerInterface {
|
export class RabbitmqController extends EventController implements EventControllerInterface {
|
||||||
public amqpChannel: amqp.Channel | null = null;
|
public amqpChannel: amqp.Channel | null = null;
|
||||||
|
private amqpConnection: amqp.Connection | null = null;
|
||||||
private readonly logger = new Logger('RabbitmqController');
|
private readonly logger = new Logger('RabbitmqController');
|
||||||
|
private reconnectAttempts = 0;
|
||||||
|
private maxReconnectAttempts = 10;
|
||||||
|
private reconnectDelay = 5000; // 5 seconds
|
||||||
|
private isReconnecting = false;
|
||||||
|
|
||||||
constructor(prismaRepository: PrismaRepository, waMonitor: WAMonitoringService) {
|
constructor(prismaRepository: PrismaRepository, waMonitor: WAMonitoringService) {
|
||||||
super(prismaRepository, waMonitor, configService.get<Rabbitmq>('RABBITMQ')?.ENABLED, 'rabbitmq');
|
super(prismaRepository, waMonitor, configService.get<Rabbitmq>('RABBITMQ')?.ENABLED, 'rabbitmq');
|
||||||
@ -19,7 +24,11 @@ export class RabbitmqController extends EventController implements EventControll
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await new Promise<void>((resolve, reject) => {
|
await this.connect();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async connect(): Promise<void> {
|
||||||
|
return new Promise<void>((resolve, reject) => {
|
||||||
const uri = configService.get<Rabbitmq>('RABBITMQ').URI;
|
const uri = configService.get<Rabbitmq>('RABBITMQ').URI;
|
||||||
const frameMax = configService.get<Rabbitmq>('RABBITMQ').FRAME_MAX;
|
const frameMax = configService.get<Rabbitmq>('RABBITMQ').FRAME_MAX;
|
||||||
const rabbitmqExchangeName = configService.get<Rabbitmq>('RABBITMQ').EXCHANGE_NAME;
|
const rabbitmqExchangeName = configService.get<Rabbitmq>('RABBITMQ').EXCHANGE_NAME;
|
||||||
@ -33,22 +42,61 @@ export class RabbitmqController extends EventController implements EventControll
|
|||||||
password: url.password || 'guest',
|
password: url.password || 'guest',
|
||||||
vhost: url.pathname.slice(1) || '/',
|
vhost: url.pathname.slice(1) || '/',
|
||||||
frameMax: frameMax,
|
frameMax: frameMax,
|
||||||
|
heartbeat: 30, // Add heartbeat of 30 seconds
|
||||||
};
|
};
|
||||||
|
|
||||||
amqp.connect(connectionOptions, (error, connection) => {
|
amqp.connect(connectionOptions, (error, connection) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
|
this.logger.error({
|
||||||
|
local: 'RabbitmqController.connect',
|
||||||
|
message: 'Failed to connect to RabbitMQ',
|
||||||
|
error: error.message || error,
|
||||||
|
});
|
||||||
reject(error);
|
reject(error);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Connection event handlers
|
||||||
|
connection.on('error', (err) => {
|
||||||
|
this.logger.error({
|
||||||
|
local: 'RabbitmqController.connectionError',
|
||||||
|
message: 'RabbitMQ connection error',
|
||||||
|
error: err.message || err,
|
||||||
|
});
|
||||||
|
this.handleConnectionLoss();
|
||||||
|
});
|
||||||
|
|
||||||
|
connection.on('close', () => {
|
||||||
|
this.logger.warn('RabbitMQ connection closed');
|
||||||
|
this.handleConnectionLoss();
|
||||||
|
});
|
||||||
|
|
||||||
connection.createChannel((channelError, channel) => {
|
connection.createChannel((channelError, channel) => {
|
||||||
if (channelError) {
|
if (channelError) {
|
||||||
|
this.logger.error({
|
||||||
|
local: 'RabbitmqController.createChannel',
|
||||||
|
message: 'Failed to create RabbitMQ channel',
|
||||||
|
error: channelError.message || channelError,
|
||||||
|
});
|
||||||
reject(channelError);
|
reject(channelError);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Channel event handlers
|
||||||
|
channel.on('error', (err) => {
|
||||||
|
this.logger.error({
|
||||||
|
local: 'RabbitmqController.channelError',
|
||||||
|
message: 'RabbitMQ channel error',
|
||||||
|
error: err.message || err,
|
||||||
|
});
|
||||||
|
this.handleConnectionLoss();
|
||||||
|
});
|
||||||
|
|
||||||
|
channel.on('close', () => {
|
||||||
|
this.logger.warn('RabbitMQ channel closed');
|
||||||
|
this.handleConnectionLoss();
|
||||||
|
});
|
||||||
|
|
||||||
const exchangeName = rabbitmqExchangeName;
|
const exchangeName = rabbitmqExchangeName;
|
||||||
|
|
||||||
channel.assertExchange(exchangeName, 'topic', {
|
channel.assertExchange(exchangeName, 'topic', {
|
||||||
@ -56,16 +104,81 @@ export class RabbitmqController extends EventController implements EventControll
|
|||||||
autoDelete: false,
|
autoDelete: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.amqpConnection = connection;
|
||||||
this.amqpChannel = channel;
|
this.amqpChannel = channel;
|
||||||
|
this.reconnectAttempts = 0; // Reset reconnect attempts on successful connection
|
||||||
|
this.isReconnecting = false;
|
||||||
|
|
||||||
this.logger.info('AMQP initialized');
|
this.logger.info('AMQP initialized successfully');
|
||||||
|
|
||||||
resolve();
|
resolve();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}).then(() => {
|
})
|
||||||
if (configService.get<Rabbitmq>('RABBITMQ')?.GLOBAL_ENABLED) this.initGlobalQueues();
|
.then(() => {
|
||||||
|
if (configService.get<Rabbitmq>('RABBITMQ')?.GLOBAL_ENABLED) {
|
||||||
|
this.initGlobalQueues();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
this.logger.error({
|
||||||
|
local: 'RabbitmqController.init',
|
||||||
|
message: 'Failed to initialize AMQP',
|
||||||
|
error: error.message || error,
|
||||||
});
|
});
|
||||||
|
this.scheduleReconnect();
|
||||||
|
throw error;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleConnectionLoss(): void {
|
||||||
|
if (this.isReconnecting) {
|
||||||
|
return; // Already attempting to reconnect
|
||||||
|
}
|
||||||
|
|
||||||
|
this.amqpChannel = null;
|
||||||
|
this.amqpConnection = null;
|
||||||
|
this.scheduleReconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
private scheduleReconnect(): void {
|
||||||
|
if (this.reconnectAttempts >= this.maxReconnectAttempts) {
|
||||||
|
this.logger.error(
|
||||||
|
`Maximum reconnect attempts (${this.maxReconnectAttempts}) reached. Stopping reconnection attempts.`,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.isReconnecting) {
|
||||||
|
return; // Already scheduled
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isReconnecting = true;
|
||||||
|
this.reconnectAttempts++;
|
||||||
|
|
||||||
|
const delay = this.reconnectDelay * Math.pow(2, Math.min(this.reconnectAttempts - 1, 5)); // Exponential backoff with max delay
|
||||||
|
|
||||||
|
this.logger.info(
|
||||||
|
`Scheduling RabbitMQ reconnection attempt ${this.reconnectAttempts}/${this.maxReconnectAttempts} in ${delay}ms`,
|
||||||
|
);
|
||||||
|
|
||||||
|
setTimeout(async () => {
|
||||||
|
try {
|
||||||
|
this.logger.info(
|
||||||
|
`Attempting to reconnect to RabbitMQ (attempt ${this.reconnectAttempts}/${this.maxReconnectAttempts})`,
|
||||||
|
);
|
||||||
|
await this.connect();
|
||||||
|
this.logger.info('Successfully reconnected to RabbitMQ');
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.error({
|
||||||
|
local: 'RabbitmqController.scheduleReconnect',
|
||||||
|
message: `Reconnection attempt ${this.reconnectAttempts} failed`,
|
||||||
|
error: error.message || error,
|
||||||
|
});
|
||||||
|
this.isReconnecting = false;
|
||||||
|
this.scheduleReconnect();
|
||||||
|
}
|
||||||
|
}, delay);
|
||||||
}
|
}
|
||||||
|
|
||||||
private set channel(channel: amqp.Channel) {
|
private set channel(channel: amqp.Channel) {
|
||||||
@ -76,6 +189,17 @@ export class RabbitmqController extends EventController implements EventControll
|
|||||||
return this.amqpChannel;
|
return this.amqpChannel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async ensureConnection(): Promise<boolean> {
|
||||||
|
if (!this.amqpChannel) {
|
||||||
|
this.logger.warn('AMQP channel is not available, attempting to reconnect...');
|
||||||
|
if (!this.isReconnecting) {
|
||||||
|
this.scheduleReconnect();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
public async emit({
|
public async emit({
|
||||||
instanceName,
|
instanceName,
|
||||||
origin,
|
origin,
|
||||||
@ -95,6 +219,11 @@ export class RabbitmqController extends EventController implements EventControll
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!(await this.ensureConnection())) {
|
||||||
|
this.logger.warn(`Failed to emit event ${event} for instance ${instanceName}: No AMQP connection`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const instanceRabbitmq = await this.get(instanceName);
|
const instanceRabbitmq = await this.get(instanceName);
|
||||||
const rabbitmqLocal = instanceRabbitmq?.events;
|
const rabbitmqLocal = instanceRabbitmq?.events;
|
||||||
const rabbitmqGlobal = configService.get<Rabbitmq>('RABBITMQ').GLOBAL_ENABLED;
|
const rabbitmqGlobal = configService.get<Rabbitmq>('RABBITMQ').GLOBAL_ENABLED;
|
||||||
@ -154,7 +283,15 @@ export class RabbitmqController extends EventController implements EventControll
|
|||||||
|
|
||||||
break;
|
break;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
this.logger.error({
|
||||||
|
local: 'RabbitmqController.emit',
|
||||||
|
message: `Error publishing local RabbitMQ message (attempt ${retry + 1}/3)`,
|
||||||
|
error: error.message || error,
|
||||||
|
});
|
||||||
retry++;
|
retry++;
|
||||||
|
if (retry >= 3) {
|
||||||
|
this.handleConnectionLoss();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -199,7 +336,15 @@ export class RabbitmqController extends EventController implements EventControll
|
|||||||
|
|
||||||
break;
|
break;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
this.logger.error({
|
||||||
|
local: 'RabbitmqController.emit',
|
||||||
|
message: `Error publishing global RabbitMQ message (attempt ${retry + 1}/3)`,
|
||||||
|
error: error.message || error,
|
||||||
|
});
|
||||||
retry++;
|
retry++;
|
||||||
|
if (retry >= 3) {
|
||||||
|
this.handleConnectionLoss();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -208,33 +353,38 @@ export class RabbitmqController extends EventController implements EventControll
|
|||||||
private async initGlobalQueues(): Promise<void> {
|
private async initGlobalQueues(): Promise<void> {
|
||||||
this.logger.info('Initializing global queues');
|
this.logger.info('Initializing global queues');
|
||||||
|
|
||||||
|
if (!(await this.ensureConnection())) {
|
||||||
|
this.logger.error('Cannot initialize global queues: No AMQP connection');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const rabbitmqExchangeName = configService.get<Rabbitmq>('RABBITMQ').EXCHANGE_NAME;
|
const rabbitmqExchangeName = configService.get<Rabbitmq>('RABBITMQ').EXCHANGE_NAME;
|
||||||
const events = configService.get<Rabbitmq>('RABBITMQ').EVENTS;
|
const events = configService.get<Rabbitmq>('RABBITMQ').EVENTS;
|
||||||
const prefixKey = configService.get<Rabbitmq>('RABBITMQ').PREFIX_KEY;
|
const prefixKey = configService.get<Rabbitmq>('RABBITMQ').PREFIX_KEY;
|
||||||
|
|
||||||
if (!events) {
|
if (!events) {
|
||||||
this.logger.warn('No events to initialize on AMQP');
|
this.logger.warn('No events to initialize on AMQP');
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const eventKeys = Object.keys(events);
|
const eventKeys = Object.keys(events);
|
||||||
|
|
||||||
eventKeys.forEach((event) => {
|
for (const event of eventKeys) {
|
||||||
if (events[event] === false) return;
|
if (events[event] === false) continue;
|
||||||
|
|
||||||
|
try {
|
||||||
const queueName =
|
const queueName =
|
||||||
prefixKey !== ''
|
prefixKey !== ''
|
||||||
? `${prefixKey}.${event.replace(/_/g, '.').toLowerCase()}`
|
? `${prefixKey}.${event.replace(/_/g, '.').toLowerCase()}`
|
||||||
: `${event.replace(/_/g, '.').toLowerCase()}`;
|
: `${event.replace(/_/g, '.').toLowerCase()}`;
|
||||||
const exchangeName = rabbitmqExchangeName;
|
const exchangeName = rabbitmqExchangeName;
|
||||||
|
|
||||||
this.amqpChannel.assertExchange(exchangeName, 'topic', {
|
await this.amqpChannel.assertExchange(exchangeName, 'topic', {
|
||||||
durable: true,
|
durable: true,
|
||||||
autoDelete: false,
|
autoDelete: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.amqpChannel.assertQueue(queueName, {
|
await this.amqpChannel.assertQueue(queueName, {
|
||||||
durable: true,
|
durable: true,
|
||||||
autoDelete: false,
|
autoDelete: false,
|
||||||
arguments: {
|
arguments: {
|
||||||
@ -242,7 +392,18 @@ export class RabbitmqController extends EventController implements EventControll
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
this.amqpChannel.bindQueue(queueName, exchangeName, event);
|
await this.amqpChannel.bindQueue(queueName, exchangeName, event);
|
||||||
|
|
||||||
|
this.logger.info(`Global queue initialized: ${queueName}`);
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.error({
|
||||||
|
local: 'RabbitmqController.initGlobalQueues',
|
||||||
|
message: `Failed to initialize global queue for event ${event}`,
|
||||||
|
error: error.message || error,
|
||||||
});
|
});
|
||||||
|
this.handleConnectionLoss();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -191,6 +191,16 @@ export class ChatRouter extends RouterBroker {
|
|||||||
|
|
||||||
return res.status(HttpStatus.OK).json(response);
|
return res.status(HttpStatus.OK).json(response);
|
||||||
})
|
})
|
||||||
|
.get(this.routerPath('findChatByRemoteJid'), ...guards, async (req, res) => {
|
||||||
|
const instance = req.params as unknown as InstanceDto;
|
||||||
|
const { remoteJid } = req.query as unknown as { remoteJid: string };
|
||||||
|
if (!remoteJid) {
|
||||||
|
return res.status(HttpStatus.BAD_REQUEST).json({ error: 'remoteJid is a required query parameter' });
|
||||||
|
}
|
||||||
|
const response = await chatController.findChatByRemoteJid(instance, remoteJid);
|
||||||
|
|
||||||
|
return res.status(HttpStatus.OK).json(response);
|
||||||
|
})
|
||||||
// Profile routes
|
// Profile routes
|
||||||
.post(this.routerPath('fetchBusinessProfile'), ...guards, async (req, res) => {
|
.post(this.routerPath('fetchBusinessProfile'), ...guards, async (req, res) => {
|
||||||
const response = await this.dataValidate<ProfilePictureDto>({
|
const response = await this.dataValidate<ProfilePictureDto>({
|
||||||
|
@ -69,8 +69,7 @@ router
|
|||||||
clientName: process.env.DATABASE_CONNECTION_CLIENT_NAME,
|
clientName: process.env.DATABASE_CONNECTION_CLIENT_NAME,
|
||||||
manager: !serverConfig.DISABLE_MANAGER ? `${req.protocol}://${req.get('host')}/manager` : undefined,
|
manager: !serverConfig.DISABLE_MANAGER ? `${req.protocol}://${req.get('host')}/manager` : undefined,
|
||||||
documentation: `https://doc.evolution-api.com`,
|
documentation: `https://doc.evolution-api.com`,
|
||||||
whatsappWebVersion:
|
whatsappWebVersion: (await fetchLatestWaWebVersion({})).version.join('.'),
|
||||||
process.env.CONFIG_SESSION_PHONE_VERSION || (await fetchLatestWaWebVersion({})).version.join('.'),
|
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.post('/verify-creds', authGuard['apikey'], async (req, res) => {
|
.post('/verify-creds', authGuard['apikey'], async (req, res) => {
|
||||||
|
@ -696,6 +696,16 @@ export class ChannelStartupService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async findChatByRemoteJid(remoteJid: string) {
|
||||||
|
if (!remoteJid) return null;
|
||||||
|
return await this.prismaRepository.chat.findFirst({
|
||||||
|
where: {
|
||||||
|
instanceId: this.instanceId,
|
||||||
|
remoteJid: remoteJid,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
public async fetchChats(query: any) {
|
public async fetchChats(query: any) {
|
||||||
const remoteJid = query?.where?.remoteJid
|
const remoteJid = query?.where?.remoteJid
|
||||||
? query?.where?.remoteJid.includes('@')
|
? query?.where?.remoteJid.includes('@')
|
||||||
@ -738,22 +748,23 @@ export class ChannelStartupService {
|
|||||||
"Chat"."name" as "pushName",
|
"Chat"."name" as "pushName",
|
||||||
"Chat"."createdAt" as "windowStart",
|
"Chat"."createdAt" as "windowStart",
|
||||||
"Chat"."createdAt" + INTERVAL '24 hours' as "windowExpires",
|
"Chat"."createdAt" + INTERVAL '24 hours' as "windowExpires",
|
||||||
|
"Chat"."unreadMessages" as "unreadMessages",
|
||||||
CASE WHEN "Chat"."createdAt" + INTERVAL '24 hours' > NOW() THEN true ELSE false END as "windowActive",
|
CASE WHEN "Chat"."createdAt" + INTERVAL '24 hours' > NOW() THEN true ELSE false END as "windowActive",
|
||||||
"Message"."id" AS lastMessageId,
|
"Message"."id" AS "lastMessageId",
|
||||||
"Message"."key" AS lastMessage_key,
|
"Message"."key" AS "lastMessage_key",
|
||||||
CASE
|
CASE
|
||||||
WHEN "Message"."key"->>'fromMe' = 'true' THEN 'Você'
|
WHEN "Message"."key"->>'fromMe' = 'true' THEN 'Você'
|
||||||
ELSE "Message"."pushName"
|
ELSE "Message"."pushName"
|
||||||
END AS lastMessagePushName,
|
END AS "lastMessagePushName",
|
||||||
"Message"."participant" AS lastMessageParticipant,
|
"Message"."participant" AS "lastMessageParticipant",
|
||||||
"Message"."messageType" AS lastMessageMessageType,
|
"Message"."messageType" AS "lastMessageMessageType",
|
||||||
"Message"."message" AS lastMessageMessage,
|
"Message"."message" AS "lastMessageMessage",
|
||||||
"Message"."contextInfo" AS lastMessageContextInfo,
|
"Message"."contextInfo" AS "lastMessageContextInfo",
|
||||||
"Message"."source" AS lastMessageSource,
|
"Message"."source" AS "lastMessageSource",
|
||||||
"Message"."messageTimestamp" AS lastMessageMessageTimestamp,
|
"Message"."messageTimestamp" AS "lastMessageMessageTimestamp",
|
||||||
"Message"."instanceId" AS lastMessageInstanceId,
|
"Message"."instanceId" AS "lastMessageInstanceId",
|
||||||
"Message"."sessionId" AS lastMessageSessionId,
|
"Message"."sessionId" AS "lastMessageSessionId",
|
||||||
"Message"."status" AS lastMessageStatus
|
"Message"."status" AS "lastMessageStatus"
|
||||||
FROM "Message"
|
FROM "Message"
|
||||||
LEFT JOIN "Contact" ON "Contact"."remoteJid" = "Message"."key"->>'remoteJid' AND "Contact"."instanceId" = "Message"."instanceId"
|
LEFT JOIN "Contact" ON "Contact"."remoteJid" = "Message"."key"->>'remoteJid' AND "Contact"."instanceId" = "Message"."instanceId"
|
||||||
LEFT JOIN "Chat" ON "Chat"."remoteJid" = "Message"."key"->>'remoteJid' AND "Chat"."instanceId" = "Message"."instanceId"
|
LEFT JOIN "Chat" ON "Chat"."remoteJid" = "Message"."key"->>'remoteJid' AND "Chat"."instanceId" = "Message"."instanceId"
|
||||||
@ -770,47 +781,65 @@ export class ChannelStartupService {
|
|||||||
|
|
||||||
if (results && isArray(results) && results.length > 0) {
|
if (results && isArray(results) && results.length > 0) {
|
||||||
const mappedResults = results.map((contact) => {
|
const mappedResults = results.map((contact) => {
|
||||||
const lastMessage = contact.lastmessageid
|
const lastMessage = contact.lastMessageId
|
||||||
? {
|
? {
|
||||||
id: contact.lastmessageid,
|
id: contact.lastMessageId,
|
||||||
key: contact.lastmessage_key,
|
key: contact.lastMessage_key,
|
||||||
pushName: contact.lastmessagepushname,
|
pushName: contact.lastMessagePushName,
|
||||||
participant: contact.lastmessageparticipant,
|
participant: contact.lastMessageParticipant,
|
||||||
messageType: contact.lastmessagemessagetype,
|
messageType: contact.lastMessageMessageType,
|
||||||
message: contact.lastmessagemessage,
|
message: contact.lastMessageMessage,
|
||||||
contextInfo: contact.lastmessagecontextinfo,
|
contextInfo: contact.lastMessageContextInfo,
|
||||||
source: contact.lastmessagesource,
|
source: contact.lastMessageSource,
|
||||||
messageTimestamp: contact.lastmessagemessagetimestamp,
|
messageTimestamp: contact.lastMessageMessageTimestamp,
|
||||||
instanceId: contact.lastmessageinstanceid,
|
instanceId: contact.lastMessageInstanceId,
|
||||||
sessionId: contact.lastmessagesessionid,
|
sessionId: contact.lastMessageSessionId,
|
||||||
status: contact.lastmessagestatus,
|
status: contact.lastMessageStatus,
|
||||||
}
|
}
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: contact.contactid || null,
|
id: contact.contactId || null,
|
||||||
remoteJid: contact.remotejid,
|
remoteJid: contact.remoteJid,
|
||||||
pushName: contact.pushname,
|
pushName: contact.pushName,
|
||||||
profilePicUrl: contact.profilepicurl,
|
profilePicUrl: contact.profilePicUrl,
|
||||||
updatedAt: contact.updatedat,
|
updatedAt: contact.updatedAt,
|
||||||
windowStart: contact.windowstart,
|
windowStart: contact.windowStart,
|
||||||
windowExpires: contact.windowexpires,
|
windowExpires: contact.windowExpires,
|
||||||
windowActive: contact.windowactive,
|
windowActive: contact.windowActive,
|
||||||
lastMessage: lastMessage ? this.cleanMessageData(lastMessage) : undefined,
|
lastMessage: lastMessage ? this.cleanMessageData(lastMessage) : undefined,
|
||||||
unreadCount: 0,
|
unreadCount: contact.unreadMessages,
|
||||||
isSaved: !!contact.contactid,
|
isSaved: !!contact.contactId,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
if (query?.take && query?.skip) {
|
|
||||||
const skip = query.skip || 0;
|
|
||||||
const take = query.take || 20;
|
|
||||||
return mappedResults.slice(skip, skip + take);
|
|
||||||
}
|
|
||||||
|
|
||||||
return mappedResults;
|
return mappedResults;
|
||||||
}
|
}
|
||||||
|
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public hasValidMediaContent(message: any): boolean {
|
||||||
|
if (!message?.message) return false;
|
||||||
|
|
||||||
|
const msg = message.message;
|
||||||
|
|
||||||
|
// Se só tem messageContextInfo, não é mídia válida
|
||||||
|
if (Object.keys(msg).length === 1 && 'messageContextInfo' in msg) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verifica se tem pelo menos um tipo de mídia válido
|
||||||
|
const mediaTypes = [
|
||||||
|
'imageMessage',
|
||||||
|
'videoMessage',
|
||||||
|
'stickerMessage',
|
||||||
|
'documentMessage',
|
||||||
|
'documentWithCaptionMessage',
|
||||||
|
'ptvMessage',
|
||||||
|
'audioMessage',
|
||||||
|
];
|
||||||
|
|
||||||
|
return mediaTypes.some((type) => msg[type] && Object.keys(msg[type]).length > 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -249,7 +249,7 @@ export type Webhook = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
export type Pusher = { ENABLED: boolean; GLOBAL?: GlobalPusher; EVENTS: EventsPusher };
|
export type Pusher = { ENABLED: boolean; GLOBAL?: GlobalPusher; EVENTS: EventsPusher };
|
||||||
export type ConfigSessionPhone = { CLIENT: string; NAME: string; VERSION: string };
|
export type ConfigSessionPhone = { CLIENT: string; NAME: string };
|
||||||
export type QrCode = { LIMIT: number; COLOR: string };
|
export type QrCode = { LIMIT: number; COLOR: string };
|
||||||
export type Typebot = { ENABLED: boolean; API_VERSION: string; SEND_MEDIA_BASE64: boolean };
|
export type Typebot = { ENABLED: boolean; API_VERSION: string; SEND_MEDIA_BASE64: boolean };
|
||||||
export type Chatwoot = {
|
export type Chatwoot = {
|
||||||
@ -590,7 +590,6 @@ export class ConfigService {
|
|||||||
CONFIG_SESSION_PHONE: {
|
CONFIG_SESSION_PHONE: {
|
||||||
CLIENT: process.env?.CONFIG_SESSION_PHONE_CLIENT || 'Evolution API',
|
CLIENT: process.env?.CONFIG_SESSION_PHONE_CLIENT || 'Evolution API',
|
||||||
NAME: process.env?.CONFIG_SESSION_PHONE_NAME || 'Chrome',
|
NAME: process.env?.CONFIG_SESSION_PHONE_NAME || 'Chrome',
|
||||||
VERSION: process.env?.CONFIG_SESSION_PHONE_VERSION || null,
|
|
||||||
},
|
},
|
||||||
QRCODE: {
|
QRCODE: {
|
||||||
LIMIT: Number.parseInt(process.env.QRCODE_LIMIT) || 30,
|
LIMIT: Number.parseInt(process.env.QRCODE_LIMIT) || 30,
|
||||||
|
19
src/railway.json
Normal file
19
src/railway.json
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://railway.com/railway.schema.json",
|
||||||
|
"build": {
|
||||||
|
"builder": "DOCKERFILE",
|
||||||
|
"dockerfilePath": "Dockerfile"
|
||||||
|
},
|
||||||
|
"deploy": {
|
||||||
|
"runtime": "V2",
|
||||||
|
"numReplicas": 1,
|
||||||
|
"sleepApplication": false,
|
||||||
|
"multiRegionConfig": {
|
||||||
|
"us-east4-eqdc4a": {
|
||||||
|
"numReplicas": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"restartPolicyType": "ON_FAILURE",
|
||||||
|
"restartPolicyMaxRetries": 10
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user