mirror of
https://github.com/EvolutionAPI/evolution-api.git
synced 2025-07-13 15:14:49 -06:00
Merge branch 'develop' into victoreduardos/jwt-webhook
This commit is contained in:
commit
b064e512e2
@ -1,5 +1,7 @@
|
||||
.git
|
||||
*Dockerfile*
|
||||
*docker-compose*
|
||||
package-lock.json
|
||||
.env
|
||||
node_modules
|
||||
dist
|
@ -62,6 +62,7 @@ RABBITMQ_EVENTS_MESSAGES_EDITED=false
|
||||
RABBITMQ_EVENTS_MESSAGES_UPDATE=false
|
||||
RABBITMQ_EVENTS_MESSAGES_DELETE=false
|
||||
RABBITMQ_EVENTS_SEND_MESSAGE=false
|
||||
RABBITMQ_EVENTS_SEND_MESSAGE_UPDATE=false
|
||||
RABBITMQ_EVENTS_CONTACTS_SET=false
|
||||
RABBITMQ_EVENTS_CONTACTS_UPSERT=false
|
||||
RABBITMQ_EVENTS_CONTACTS_UPDATE=false
|
||||
@ -108,6 +109,7 @@ PUSHER_EVENTS_MESSAGES_EDITED=true
|
||||
PUSHER_EVENTS_MESSAGES_UPDATE=true
|
||||
PUSHER_EVENTS_MESSAGES_DELETE=true
|
||||
PUSHER_EVENTS_SEND_MESSAGE=true
|
||||
PUSHER_EVENTS_SEND_MESSAGE_UPDATE=true
|
||||
PUSHER_EVENTS_CONTACTS_SET=true
|
||||
PUSHER_EVENTS_CONTACTS_UPSERT=true
|
||||
PUSHER_EVENTS_CONTACTS_UPDATE=true
|
||||
@ -149,6 +151,7 @@ WEBHOOK_EVENTS_MESSAGES_EDITED=true
|
||||
WEBHOOK_EVENTS_MESSAGES_UPDATE=true
|
||||
WEBHOOK_EVENTS_MESSAGES_DELETE=true
|
||||
WEBHOOK_EVENTS_SEND_MESSAGE=true
|
||||
WEBHOOK_EVENTS_SEND_MESSAGE_UPDATE=true
|
||||
WEBHOOK_EVENTS_CONTACTS_SET=true
|
||||
WEBHOOK_EVENTS_CONTACTS_UPSERT=true
|
||||
WEBHOOK_EVENTS_CONTACTS_UPDATE=true
|
||||
|
2
.github/workflows/publish_docker_image.yml
vendored
2
.github/workflows/publish_docker_image.yml
vendored
@ -20,7 +20,7 @@ jobs:
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: atendai/evolution-api
|
||||
images: evoapicloud/evolution-api
|
||||
tags: type=semver,pattern=v{{version}}
|
||||
|
||||
- name: Set up QEMU
|
||||
|
@ -20,7 +20,7 @@ jobs:
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: atendai/evolution-api
|
||||
images: evoapicloud/evolution-api
|
||||
tags: homolog
|
||||
|
||||
- name: Set up QEMU
|
||||
|
@ -20,7 +20,7 @@ jobs:
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: atendai/evolution-api
|
||||
images: evoapicloud/evolution-api
|
||||
tags: latest
|
||||
|
||||
- name: Set up QEMU
|
||||
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -2,6 +2,8 @@
|
||||
/dist
|
||||
/node_modules
|
||||
|
||||
.cursor*
|
||||
|
||||
/Docker/.env
|
||||
|
||||
.vscode
|
||||
|
@ -1,3 +1,9 @@
|
||||
# 2.2.4 (hotfix)
|
||||
|
||||
### Fixed
|
||||
|
||||
* Shell injection vulnerability
|
||||
|
||||
# 2.2.3 (2025-02-03 11:52)
|
||||
|
||||
### Fixed
|
||||
|
@ -2,7 +2,7 @@ version: "3.7"
|
||||
|
||||
services:
|
||||
evolution_v2:
|
||||
image: atendai/evolution-api:v2.1.2
|
||||
image: evoapicloud/evolution-api:latest
|
||||
volumes:
|
||||
- evolution_instances:/evolution/instances
|
||||
networks:
|
||||
@ -34,6 +34,7 @@ services:
|
||||
- RABBITMQ_EVENTS_MESSAGES_UPDATE=false
|
||||
- RABBITMQ_EVENTS_MESSAGES_DELETE=false
|
||||
- RABBITMQ_EVENTS_SEND_MESSAGE=false
|
||||
- RABBITMQ_EVENTS_SEND_MESSAGE_UPDATE=false
|
||||
- RABBITMQ_EVENTS_CONTACTS_SET=false
|
||||
- RABBITMQ_EVENTS_CONTACTS_UPSERT=false
|
||||
- RABBITMQ_EVENTS_CONTACTS_UPDATE=false
|
||||
@ -71,6 +72,7 @@ services:
|
||||
- WEBHOOK_EVENTS_MESSAGES_UPDATE=true
|
||||
- WEBHOOK_EVENTS_MESSAGES_DELETE=true
|
||||
- WEBHOOK_EVENTS_SEND_MESSAGE=true
|
||||
- WEBHOOK_EVENTS_SEND_MESSAGE_UPDATE=true
|
||||
- WEBHOOK_EVENTS_CONTACTS_SET=true
|
||||
- WEBHOOK_EVENTS_CONTACTS_UPSERT=true
|
||||
- WEBHOOK_EVENTS_CONTACTS_UPDATE=true
|
||||
|
@ -1,11 +1,11 @@
|
||||
FROM node:20-alpine AS builder
|
||||
|
||||
RUN apk update && \
|
||||
apk add git ffmpeg wget curl bash openssl
|
||||
apk add --no-cache git ffmpeg wget curl bash openssl
|
||||
|
||||
LABEL version="2.2.3" description="Api to control whatsapp features through http requests."
|
||||
LABEL maintainer="Davidson Gomes" git="https://github.com/DavidsonGomes"
|
||||
LABEL contact="contato@atendai.com"
|
||||
LABEL contact="contato@evolution-api.com"
|
||||
|
||||
WORKDIR /evolution
|
||||
|
||||
|
2
LICENSE
2
LICENSE
@ -8,7 +8,7 @@ a. LOGO and copyright information: In the process of using Evolution API's front
|
||||
|
||||
b. Usage Notification Requirement: If Evolution API is used as part of any project, including closed-source systems (e.g., proprietary software), the user is required to display a clear notification within the system that Evolution API is being utilized. This notification should be visible to system administrators and accessible from the system's documentation or settings page. Failure to comply with this requirement may result in the necessity for a commercial license, as determined by the producer.
|
||||
|
||||
Please contact contato@atendai.com to inquire about licensing matters.
|
||||
Please contact contato@evolution-api.com to inquire about licensing matters.
|
||||
|
||||
2. As a contributor, you should agree that:
|
||||
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
<div align="center">
|
||||
|
||||
[]
|
||||
[](https://evolution-api.com/whatsapp)
|
||||
[](https://evolution-api.com/discord)
|
||||
[](https://evolution-api.com/postman)
|
||||
@ -87,6 +88,7 @@ https://github.com/sponsors/EvolutionAPI
|
||||
We are proud to collaborate with the following content creators who have contributed valuable insights and tutorials about Evolution API:
|
||||
|
||||
- [Promovaweb](https://www.youtube.com/@promovaweb)
|
||||
- [Sandeco](https://www.youtube.com/@canalsandeco)
|
||||
- [Comunidade ZDG](https://www.youtube.com/@ComunidadeZDG)
|
||||
- [Francis MNO](https://www.youtube.com/@FrancisMNO)
|
||||
- [Pablo Cabral](https://youtube.com/@pablocabral)
|
||||
@ -111,7 +113,7 @@ Evolution API is licensed under the Apache License 2.0, with the following addit
|
||||
|
||||
2. **Usage Notification Requirement**: If Evolution API is used as part of any project, including closed-source systems (e.g., proprietary software), the user is required to display a clear notification within the system that Evolution API is being utilized. This notification should be visible to system administrators and accessible from the system's documentation or settings page. Failure to comply with this requirement may result in the necessity for a commercial license, as determined by the producer.
|
||||
|
||||
Please contact contato@atendai.com to inquire about licensing matters.
|
||||
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).
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
services:
|
||||
api:
|
||||
container_name: evolution_api
|
||||
image: atendai/evolution-api:homolog
|
||||
image: evoapicloud/evolution-api:latest
|
||||
restart: always
|
||||
depends_on:
|
||||
- redis
|
||||
|
1688
package-lock.json
generated
1688
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -41,7 +41,7 @@
|
||||
],
|
||||
"author": {
|
||||
"name": "Davidson Gomes",
|
||||
"email": "contato@atendai.com"
|
||||
"email": "contato@evolution-api.com"
|
||||
},
|
||||
"license": "Apache-2.0",
|
||||
"bugs": {
|
||||
@ -83,6 +83,7 @@
|
||||
"mime-types": "^2.1.35",
|
||||
"minio": "^8.0.3",
|
||||
"multer": "^1.4.5-lts.1",
|
||||
"nats": "^2.29.1",
|
||||
"node-cache": "^5.1.2",
|
||||
"node-cron": "^3.0.3",
|
||||
"openai": "^4.77.3",
|
||||
|
@ -0,0 +1,9 @@
|
||||
/*
|
||||
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,175 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- You are about to alter the column `createdAt` on the `Chat` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
|
||||
- You are about to alter the column `updatedAt` on the `Chat` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
|
||||
- You are about to alter the column `createdAt` on the `Chatwoot` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
|
||||
- You are about to alter the column `updatedAt` on the `Chatwoot` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
|
||||
- You are about to alter the column `createdAt` on the `Contact` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
|
||||
- You are about to alter the column `updatedAt` on the `Contact` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
|
||||
- You are about to alter the column `createdAt` on the `Dify` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
|
||||
- You are about to alter the column `updatedAt` on the `Dify` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
|
||||
- You are about to alter the column `createdAt` on the `DifySetting` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
|
||||
- You are about to alter the column `updatedAt` on the `DifySetting` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
|
||||
- You are about to alter the column `createdAt` on the `EvolutionBot` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
|
||||
- You are about to alter the column `updatedAt` on the `EvolutionBot` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
|
||||
- You are about to alter the column `createdAt` on the `EvolutionBotSetting` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
|
||||
- You are about to alter the column `updatedAt` on the `EvolutionBotSetting` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
|
||||
- You are about to alter the column `createdAt` on the `Flowise` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
|
||||
- You are about to alter the column `updatedAt` on the `Flowise` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
|
||||
- You are about to alter the column `createdAt` on the `FlowiseSetting` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
|
||||
- You are about to alter the column `updatedAt` on the `FlowiseSetting` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
|
||||
- You are about to alter the column `disconnectionAt` on the `Instance` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
|
||||
- You are about to alter the column `createdAt` on the `Instance` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
|
||||
- You are about to alter the column `updatedAt` on the `Instance` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
|
||||
- You are about to alter the column `createdAt` on the `IntegrationSession` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
|
||||
- You are about to alter the column `updatedAt` on the `IntegrationSession` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
|
||||
- You are about to alter the column `createdAt` on the `IsOnWhatsapp` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
|
||||
- You are about to alter the column `updatedAt` on the `IsOnWhatsapp` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
|
||||
- You are about to alter the column `createdAt` on the `Label` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
|
||||
- You are about to alter the column `updatedAt` on the `Label` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
|
||||
- You are about to alter the column `createdAt` on the `Media` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
|
||||
- You are about to alter the column `createdAt` on the `OpenaiBot` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
|
||||
- You are about to alter the column `updatedAt` on the `OpenaiBot` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
|
||||
- You are about to alter the column `createdAt` on the `OpenaiCreds` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
|
||||
- You are about to alter the column `updatedAt` on the `OpenaiCreds` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
|
||||
- You are about to alter the column `createdAt` on the `OpenaiSetting` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
|
||||
- You are about to alter the column `updatedAt` on the `OpenaiSetting` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
|
||||
- You are about to alter the column `createdAt` on the `Proxy` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
|
||||
- You are about to alter the column `updatedAt` on the `Proxy` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
|
||||
- You are about to alter the column `createdAt` on the `Pusher` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
|
||||
- You are about to alter the column `updatedAt` on the `Pusher` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
|
||||
- You are about to alter the column `createdAt` on the `Rabbitmq` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
|
||||
- You are about to alter the column `updatedAt` on the `Rabbitmq` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
|
||||
- You are about to alter the column `createdAt` on the `Session` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
|
||||
- You are about to alter the column `createdAt` on the `Setting` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
|
||||
- You are about to alter the column `updatedAt` on the `Setting` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
|
||||
- You are about to alter the column `createdAt` on the `Sqs` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
|
||||
- You are about to alter the column `updatedAt` on the `Sqs` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
|
||||
- You are about to alter the column `createdAt` on the `Template` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
|
||||
- You are about to alter the column `updatedAt` on the `Template` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
|
||||
- You are about to alter the column `createdAt` on the `Typebot` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
|
||||
- You are about to alter the column `updatedAt` on the `Typebot` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
|
||||
- You are about to alter the column `createdAt` on the `TypebotSetting` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
|
||||
- You are about to alter the column `updatedAt` on the `TypebotSetting` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
|
||||
- You are about to alter the column `createdAt` on the `Webhook` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
|
||||
- You are about to alter the column `updatedAt` on the `Webhook` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
|
||||
- You are about to alter the column `createdAt` on the `Websocket` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
|
||||
- You are about to alter the column `updatedAt` on the `Websocket` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
|
||||
- A unique constraint covering the columns `[instanceId,remoteJid]` on the table `Chat` will be added. If there are existing duplicate values, this will fail.
|
||||
|
||||
*/
|
||||
-- AlterTable
|
||||
ALTER TABLE `Chat` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
MODIFY `updatedAt` TIMESTAMP NULL;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE `Chatwoot` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
MODIFY `updatedAt` TIMESTAMP NOT NULL;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE `Contact` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
MODIFY `updatedAt` TIMESTAMP NULL;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE `Dify` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
MODIFY `updatedAt` TIMESTAMP NOT NULL;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE `DifySetting` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
MODIFY `updatedAt` TIMESTAMP NOT NULL;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE `EvolutionBot` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
MODIFY `updatedAt` TIMESTAMP NOT NULL;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE `EvolutionBotSetting` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
MODIFY `updatedAt` TIMESTAMP NOT NULL;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE `Flowise` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
MODIFY `updatedAt` TIMESTAMP NOT NULL;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE `FlowiseSetting` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
MODIFY `updatedAt` TIMESTAMP NOT NULL;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE `Instance` MODIFY `disconnectionAt` TIMESTAMP NULL,
|
||||
MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
MODIFY `updatedAt` TIMESTAMP NULL;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE `IntegrationSession` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
MODIFY `updatedAt` TIMESTAMP NOT NULL;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE `IsOnWhatsapp` MODIFY `createdAt` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
MODIFY `updatedAt` TIMESTAMP NOT NULL;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE `Label` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
MODIFY `updatedAt` TIMESTAMP NOT NULL;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE `Media` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE `OpenaiBot` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
MODIFY `updatedAt` TIMESTAMP NOT NULL;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE `OpenaiCreds` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
MODIFY `updatedAt` TIMESTAMP NOT NULL;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE `OpenaiSetting` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
MODIFY `updatedAt` TIMESTAMP NOT NULL;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE `Proxy` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
MODIFY `updatedAt` TIMESTAMP NOT NULL;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE `Pusher` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
MODIFY `updatedAt` TIMESTAMP NOT NULL;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE `Rabbitmq` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
MODIFY `updatedAt` TIMESTAMP NOT NULL;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE `Session` MODIFY `createdAt` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE `Setting` ADD COLUMN `wavoipToken` VARCHAR(100) NULL,
|
||||
MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
MODIFY `updatedAt` TIMESTAMP NOT NULL;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE `Sqs` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
MODIFY `updatedAt` TIMESTAMP NOT NULL;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE `Template` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
MODIFY `updatedAt` TIMESTAMP NOT NULL;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE `Typebot` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
MODIFY `updatedAt` TIMESTAMP NULL;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE `TypebotSetting` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
MODIFY `updatedAt` TIMESTAMP NOT NULL;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE `Webhook` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
MODIFY `updatedAt` TIMESTAMP NOT NULL;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE `Websocket` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
MODIFY `updatedAt` TIMESTAMP NOT NULL;
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX `Chat_instanceId_remoteJid_key` ON `Chat`(`instanceId`, `remoteJid`);
|
@ -1,3 +1,3 @@
|
||||
# Please do not edit this file manually
|
||||
# It should be added in your version-control system (i.e. Git)
|
||||
# It should be added in your version-control system (e.g., Git)
|
||||
provider = "mysql"
|
@ -86,6 +86,7 @@ model Instance {
|
||||
Proxy Proxy?
|
||||
Setting Setting?
|
||||
Rabbitmq Rabbitmq?
|
||||
Nats Nats?
|
||||
Sqs Sqs?
|
||||
Websocket Websocket?
|
||||
Typebot Typebot[]
|
||||
@ -99,7 +100,7 @@ model Instance {
|
||||
Template Template[]
|
||||
Dify Dify[]
|
||||
DifySetting DifySetting?
|
||||
integrationSessions IntegrationSession[]
|
||||
IntegrationSession IntegrationSession[]
|
||||
EvolutionBot EvolutionBot[]
|
||||
EvolutionBotSetting EvolutionBotSetting?
|
||||
Flowise Flowise[]
|
||||
@ -116,18 +117,19 @@ model Session {
|
||||
}
|
||||
|
||||
model Chat {
|
||||
id String @id @default(cuid())
|
||||
remoteJid String @db.VarChar(100)
|
||||
name String? @db.VarChar(100)
|
||||
labels Json? @db.Json
|
||||
createdAt DateTime? @default(dbgenerated("CURRENT_TIMESTAMP")) @db.Timestamp
|
||||
updatedAt DateTime? @updatedAt @db.Timestamp
|
||||
Instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)
|
||||
instanceId String
|
||||
id String @id @default(cuid())
|
||||
remoteJid String @db.VarChar(100)
|
||||
name String? @db.VarChar(100)
|
||||
labels Json? @db.Json
|
||||
createdAt DateTime? @default(dbgenerated("CURRENT_TIMESTAMP")) @db.Timestamp
|
||||
updatedAt DateTime? @updatedAt @db.Timestamp
|
||||
Instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)
|
||||
instanceId String
|
||||
unreadMessages Int @default(0)
|
||||
|
||||
@@unique([instanceId, remoteJid])
|
||||
@@index([instanceId])
|
||||
@@index([remoteJid])
|
||||
@@unique([instanceId, remoteJid])
|
||||
}
|
||||
|
||||
model Contact {
|
||||
@ -170,6 +172,7 @@ model Message {
|
||||
|
||||
sessionId String?
|
||||
session IntegrationSession? @relation(fields: [sessionId], references: [id])
|
||||
|
||||
@@index([instanceId])
|
||||
}
|
||||
|
||||
@ -185,6 +188,7 @@ model MessageUpdate {
|
||||
messageId String
|
||||
Instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)
|
||||
instanceId String
|
||||
|
||||
@@index([instanceId])
|
||||
@@index([messageId])
|
||||
}
|
||||
@ -201,6 +205,7 @@ model Webhook {
|
||||
updatedAt DateTime @updatedAt @db.Timestamp
|
||||
Instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)
|
||||
instanceId String @unique
|
||||
|
||||
@@index([instanceId])
|
||||
}
|
||||
|
||||
@ -269,6 +274,7 @@ model Setting {
|
||||
updatedAt DateTime @updatedAt @db.Timestamp
|
||||
Instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)
|
||||
instanceId String @unique
|
||||
|
||||
@@index([instanceId])
|
||||
}
|
||||
|
||||
@ -282,6 +288,16 @@ model Rabbitmq {
|
||||
instanceId String @unique
|
||||
}
|
||||
|
||||
model Nats {
|
||||
id String @id @default(cuid())
|
||||
enabled Boolean @default(false)
|
||||
events Json @db.Json
|
||||
createdAt DateTime? @default(dbgenerated("CURRENT_TIMESTAMP")) @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)
|
||||
|
@ -0,0 +1,17 @@
|
||||
-- CreateTable
|
||||
CREATE TABLE "Nats" (
|
||||
"id" TEXT NOT NULL,
|
||||
"enabled" BOOLEAN NOT NULL DEFAULT false,
|
||||
"events" JSONB NOT NULL,
|
||||
"createdAt" TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP NOT NULL,
|
||||
"instanceId" TEXT NOT NULL,
|
||||
|
||||
CONSTRAINT "Nats_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- 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;
|
@ -86,6 +86,7 @@ model Instance {
|
||||
Proxy Proxy?
|
||||
Setting Setting?
|
||||
Rabbitmq Rabbitmq?
|
||||
Nats Nats?
|
||||
Sqs Sqs?
|
||||
Websocket Websocket?
|
||||
Typebot Typebot[]
|
||||
@ -99,7 +100,7 @@ model Instance {
|
||||
Template Template[]
|
||||
Dify Dify[]
|
||||
DifySetting DifySetting?
|
||||
integrationSessions IntegrationSession[]
|
||||
IntegrationSession IntegrationSession[]
|
||||
EvolutionBot EvolutionBot[]
|
||||
EvolutionBotSetting EvolutionBotSetting?
|
||||
Flowise Flowise[]
|
||||
@ -125,6 +126,7 @@ model Chat {
|
||||
Instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)
|
||||
instanceId String
|
||||
unreadMessages Int @default(0)
|
||||
|
||||
@@index([instanceId])
|
||||
@@index([remoteJid])
|
||||
}
|
||||
@ -168,6 +170,7 @@ model Message {
|
||||
|
||||
sessionId String?
|
||||
session IntegrationSession? @relation(fields: [sessionId], references: [id])
|
||||
|
||||
@@index([instanceId])
|
||||
}
|
||||
|
||||
@ -183,6 +186,7 @@ model MessageUpdate {
|
||||
messageId String
|
||||
Instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)
|
||||
instanceId String
|
||||
|
||||
@@index([instanceId])
|
||||
@@index([messageId])
|
||||
}
|
||||
@ -199,6 +203,7 @@ model Webhook {
|
||||
updatedAt DateTime @updatedAt @db.Timestamp
|
||||
Instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)
|
||||
instanceId String @unique
|
||||
|
||||
@@index([instanceId])
|
||||
}
|
||||
|
||||
@ -269,6 +274,7 @@ model Setting {
|
||||
updatedAt DateTime @updatedAt @db.Timestamp
|
||||
Instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)
|
||||
instanceId String @unique
|
||||
|
||||
@@index([instanceId])
|
||||
}
|
||||
|
||||
@ -282,6 +288,16 @@ model Rabbitmq {
|
||||
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
|
||||
|
15
src/api/controllers/business.controller.ts
Normal file
15
src/api/controllers/business.controller.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import { getCatalogDto, getCollectionsDto } from '@api/dto/business.dto';
|
||||
import { InstanceDto } from '@api/dto/instance.dto';
|
||||
import { WAMonitoringService } from '@api/services/monitor.service';
|
||||
|
||||
export class BusinessController {
|
||||
constructor(private readonly waMonitor: WAMonitoringService) {}
|
||||
|
||||
public async fetchCatalog({ instanceName }: InstanceDto, data: getCatalogDto) {
|
||||
return await this.waMonitor.waInstances[instanceName].fetchCatalog(instanceName, data);
|
||||
}
|
||||
|
||||
public async fetchCollections({ instanceName }: InstanceDto, data: getCollectionsDto) {
|
||||
return await this.waMonitor.waInstances[instanceName].fetchCollections(instanceName, data);
|
||||
}
|
||||
}
|
@ -170,6 +170,9 @@ export class InstanceController {
|
||||
rabbitmq: {
|
||||
enabled: instanceData?.rabbitmq?.enabled,
|
||||
},
|
||||
nats: {
|
||||
enabled: instanceData?.nats?.enabled,
|
||||
},
|
||||
sqs: {
|
||||
enabled: instanceData?.sqs?.enabled,
|
||||
},
|
||||
@ -258,6 +261,9 @@ export class InstanceController {
|
||||
rabbitmq: {
|
||||
enabled: instanceData?.rabbitmq?.enabled,
|
||||
},
|
||||
nats: {
|
||||
enabled: instanceData?.nats?.enabled,
|
||||
},
|
||||
sqs: {
|
||||
enabled: instanceData?.sqs?.enabled,
|
||||
},
|
||||
|
@ -18,6 +18,14 @@ import { WAMonitoringService } from '@api/services/monitor.service';
|
||||
import { BadRequestException } from '@exceptions';
|
||||
import { isBase64, isURL } from 'class-validator';
|
||||
|
||||
function isEmoji(str: string) {
|
||||
if (str === '') return true;
|
||||
|
||||
const emojiRegex =
|
||||
/^[\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 emojiRegex.test(str);
|
||||
}
|
||||
|
||||
export class SendMessageController {
|
||||
constructor(private readonly waMonitor: WAMonitoringService) {}
|
||||
|
||||
@ -81,8 +89,8 @@ export class SendMessageController {
|
||||
}
|
||||
|
||||
public async sendReaction({ instanceName }: InstanceDto, data: SendReactionDto) {
|
||||
if (!data.reaction.match(/[^()\w\sà-ú"-+]+/)) {
|
||||
throw new BadRequestException('"reaction" must be an emoji');
|
||||
if (!isEmoji(data.reaction)) {
|
||||
throw new BadRequestException('Reaction must be a single emoji or empty string');
|
||||
}
|
||||
return await this.waMonitor.waInstances[instanceName].reactionMessage(data);
|
||||
}
|
||||
|
14
src/api/dto/business.dto.ts
Normal file
14
src/api/dto/business.dto.ts
Normal file
@ -0,0 +1,14 @@
|
||||
export class NumberDto {
|
||||
number: string;
|
||||
}
|
||||
|
||||
export class getCatalogDto {
|
||||
number?: string;
|
||||
limit?: number;
|
||||
cursor?: string;
|
||||
}
|
||||
|
||||
export class getCollectionsDto {
|
||||
number?: string;
|
||||
limit?: number;
|
||||
}
|
@ -44,6 +44,7 @@ export class Metadata {
|
||||
mentionsEveryOne?: boolean;
|
||||
mentioned?: string[];
|
||||
encoding?: boolean;
|
||||
notConvertSticker?: boolean;
|
||||
}
|
||||
|
||||
export class SendTextDto extends Metadata {
|
||||
|
@ -206,6 +206,20 @@ export class BusinessStartupService extends ChannelStartupService {
|
||||
return content;
|
||||
}
|
||||
|
||||
private messageLocationJson(received: any) {
|
||||
const message = received.messages[0];
|
||||
let content: any = {
|
||||
locationMessage: {
|
||||
degreesLatitude: message.location.latitude,
|
||||
degreesLongitude: message.location.longitude,
|
||||
name: message.location?.name,
|
||||
address: message.location?.address,
|
||||
},
|
||||
};
|
||||
message.context ? (content = { ...content, contextInfo: { stanzaId: message.context.id } }) : content;
|
||||
return content;
|
||||
}
|
||||
|
||||
private messageContactsJson(received: any) {
|
||||
const message = received.messages[0];
|
||||
let content: any = {};
|
||||
@ -283,6 +297,9 @@ export class BusinessStartupService extends ChannelStartupService {
|
||||
case 'template':
|
||||
messageType = 'conversation';
|
||||
break;
|
||||
case 'location':
|
||||
messageType = 'locationMessage';
|
||||
break;
|
||||
default:
|
||||
messageType = 'conversation';
|
||||
break;
|
||||
@ -438,6 +455,17 @@ export class BusinessStartupService extends ChannelStartupService {
|
||||
source: 'unknown',
|
||||
instanceId: this.instanceId,
|
||||
};
|
||||
} else if (received?.messages[0].location) {
|
||||
messageRaw = {
|
||||
key,
|
||||
pushName,
|
||||
message: this.messageLocationJson(received),
|
||||
contextInfo: this.messageLocationJson(received)?.contextInfo,
|
||||
messageType: this.renderMessageType(received.messages[0].type),
|
||||
messageTimestamp: parseInt(received.messages[0].timestamp) as number,
|
||||
source: 'unknown',
|
||||
instanceId: this.instanceId,
|
||||
};
|
||||
} else {
|
||||
messageRaw = {
|
||||
key,
|
||||
@ -800,6 +828,7 @@ export class BusinessStartupService extends ChannelStartupService {
|
||||
}
|
||||
if (message['media']) {
|
||||
const isImage = message['mimetype']?.startsWith('image/');
|
||||
const isVideo = message['mimetype']?.startsWith('video/');
|
||||
|
||||
content = {
|
||||
messaging_product: 'whatsapp',
|
||||
@ -809,7 +838,7 @@ export class BusinessStartupService extends ChannelStartupService {
|
||||
[message['mediaType']]: {
|
||||
[message['type']]: message['id'],
|
||||
preview_url: linkPreview,
|
||||
...(message['fileName'] && !isImage && { filename: message['fileName'] }),
|
||||
...(message['fileName'] && !isImage && !isVideo && { filename: message['fileName'] }),
|
||||
caption: message['caption'],
|
||||
},
|
||||
};
|
||||
@ -977,8 +1006,10 @@ export class BusinessStartupService extends ChannelStartupService {
|
||||
|
||||
private async getIdMedia(mediaMessage: any) {
|
||||
const formData = new FormData();
|
||||
const media = mediaMessage.media || mediaMessage.audio;
|
||||
if (!media) throw new Error('Media or audio not found');
|
||||
|
||||
const fileStream = createReadStream(mediaMessage.media);
|
||||
const fileStream = createReadStream(media);
|
||||
|
||||
formData.append('file', fileStream, { filename: 'media', contentType: mediaMessage.mimetype });
|
||||
formData.append('typeFile', mediaMessage.mimetype);
|
||||
@ -1079,7 +1110,7 @@ export class BusinessStartupService extends ChannelStartupService {
|
||||
const prepareMedia: any = {
|
||||
fileName: `${hash}.mp3`,
|
||||
mediaType: 'audio',
|
||||
media: audio,
|
||||
audio,
|
||||
};
|
||||
|
||||
if (isURL(audio)) {
|
||||
@ -1101,15 +1132,7 @@ export class BusinessStartupService extends ChannelStartupService {
|
||||
public async audioWhatsapp(data: SendAudioDto, file?: any, isIntegration = false) {
|
||||
const mediaData: SendAudioDto = { ...data };
|
||||
|
||||
if (file?.buffer) {
|
||||
mediaData.audio = file.buffer.toString('base64');
|
||||
} else if (isURL(mediaData.audio)) {
|
||||
// DO NOTHING
|
||||
// mediaData.audio = mediaData.audio;
|
||||
} else {
|
||||
console.error('El archivo no tiene buffer o file es undefined');
|
||||
throw new Error('File or buffer is undefined');
|
||||
}
|
||||
if (file) mediaData.audio = file.buffer.toString('base64');
|
||||
|
||||
const message = await this.processAudio(mediaData.audio, data.number);
|
||||
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { getCollectionsDto } from '@api/dto/business.dto';
|
||||
import { OfferCallDto } from '@api/dto/call.dto';
|
||||
import {
|
||||
ArchiveChatDto,
|
||||
@ -91,6 +92,7 @@ import makeWASocket, {
|
||||
BufferedEventData,
|
||||
BufferJSON,
|
||||
CacheStore,
|
||||
CatalogCollection,
|
||||
Chat,
|
||||
ConnectionState,
|
||||
Contact,
|
||||
@ -100,6 +102,7 @@ import makeWASocket, {
|
||||
fetchLatestBaileysVersion,
|
||||
generateWAMessageFromContent,
|
||||
getAggregateVotesInPollMessage,
|
||||
GetCatalogOptions,
|
||||
getContentType,
|
||||
getDevice,
|
||||
GroupMetadata,
|
||||
@ -113,6 +116,7 @@ import makeWASocket, {
|
||||
MiscMessageGenerationOptions,
|
||||
ParticipantAction,
|
||||
prepareWAMessageMedia,
|
||||
Product,
|
||||
proto,
|
||||
UserFacingSocketConfig,
|
||||
WABrowserDescription,
|
||||
@ -1128,38 +1132,73 @@ export class BaileysStartupService extends ChannelStartupService {
|
||||
}
|
||||
}
|
||||
|
||||
if (received.message?.protocolMessage?.editedMessage || received.message?.editedMessage?.message) {
|
||||
const editedMessage =
|
||||
received.message?.protocolMessage || received.message?.editedMessage?.message?.protocolMessage;
|
||||
if (editedMessage) {
|
||||
if (this.configService.get<Chatwoot>('CHATWOOT').ENABLED && this.localChatwoot?.enabled)
|
||||
this.chatwootService.eventWhatsapp(
|
||||
'messages.edit',
|
||||
{ instanceName: this.instance.name, instanceId: this.instance.id },
|
||||
editedMessage,
|
||||
);
|
||||
const editedMessage =
|
||||
received?.message?.protocolMessage || received?.message?.editedMessage?.message?.protocolMessage;
|
||||
|
||||
await this.sendDataWebhook(Events.MESSAGES_EDITED, editedMessage);
|
||||
if (received.message?.protocolMessage?.editedMessage && editedMessage) {
|
||||
if (this.configService.get<Chatwoot>('CHATWOOT').ENABLED && this.localChatwoot?.enabled)
|
||||
this.chatwootService.eventWhatsapp(
|
||||
'messages.edit',
|
||||
{ instanceName: this.instance.name, instanceId: this.instance.id },
|
||||
editedMessage,
|
||||
);
|
||||
|
||||
await this.sendDataWebhook(Events.MESSAGES_EDITED, editedMessage);
|
||||
const oldMessage = await this.getMessage(editedMessage.key, true);
|
||||
if ((oldMessage as any)?.id) {
|
||||
const editedMessageTimestamp = Long.isLong(editedMessage?.timestampMs)
|
||||
? editedMessage.timestampMs?.toNumber()
|
||||
: (editedMessage.timestampMs as number);
|
||||
|
||||
await this.prismaRepository.message.update({
|
||||
where: { id: (oldMessage as any).id },
|
||||
data: {
|
||||
message: editedMessage.editedMessage as any,
|
||||
messageTimestamp: editedMessageTimestamp,
|
||||
status: 'EDITED',
|
||||
},
|
||||
});
|
||||
await this.prismaRepository.messageUpdate.create({
|
||||
data: {
|
||||
fromMe: editedMessage.key.fromMe,
|
||||
keyId: editedMessage.key.id,
|
||||
remoteJid: editedMessage.key.remoteJid,
|
||||
status: 'EDITED',
|
||||
instanceId: this.instanceId,
|
||||
messageId: (oldMessage as any).id,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (received.messageStubParameters && received.messageStubParameters[0] === 'Message absent from node') {
|
||||
this.logger.info(`Recovering message lost messageId: ${received.key.id}`);
|
||||
// if (received.messageStubParameters && received.messageStubParameters[0] === 'Message absent from node') {
|
||||
// this.logger.info(`Recovering message lost messageId: ${received.key.id}`);
|
||||
|
||||
await this.baileysCache.set(received.key.id, {
|
||||
message: received,
|
||||
retry: 0,
|
||||
});
|
||||
// await this.baileysCache.set(received.key.id, {
|
||||
// message: received,
|
||||
// retry: 0,
|
||||
// });
|
||||
|
||||
// continue;
|
||||
// }
|
||||
|
||||
// const retryCache = (await this.baileysCache.get(received.key.id)) || null;
|
||||
|
||||
// if (retryCache) {
|
||||
// this.logger.info('Recovered message lost');
|
||||
// await this.baileysCache.delete(received.key.id);
|
||||
// }
|
||||
|
||||
// Cache to avoid duplicate messages
|
||||
const messageKey = `${this.instance.id}_${received.key.id}`;
|
||||
const cached = await this.baileysCache.get(messageKey);
|
||||
|
||||
if (cached && !editedMessage) {
|
||||
this.logger.info(`Message duplicated ignored: ${received.key.id}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const retryCache = (await this.baileysCache.get(received.key.id)) || null;
|
||||
|
||||
if (retryCache) {
|
||||
this.logger.info('Recovered message lost');
|
||||
await this.baileysCache.delete(received.key.id);
|
||||
}
|
||||
await this.baileysCache.set(messageKey, true, 30 * 60);
|
||||
|
||||
if (
|
||||
(type !== 'notify' && type !== 'append') ||
|
||||
@ -1186,7 +1225,9 @@ export class BaileysStartupService extends ChannelStartupService {
|
||||
existingChat &&
|
||||
received.pushName &&
|
||||
existingChat.name !== received.pushName &&
|
||||
received.pushName.trim().length > 0
|
||||
received.pushName.trim().length > 0 &&
|
||||
!received.key.fromMe &&
|
||||
!received.key.remoteJid.includes('@g.us')
|
||||
) {
|
||||
this.sendDataWebhook(Events.CHATS_UPSERT, [{ ...existingChat, name: received.pushName }]);
|
||||
if (this.configService.get<Database>('DATABASE').SAVE_DATA.CHATS) {
|
||||
@ -1292,7 +1333,12 @@ export class BaileysStartupService extends ChannelStartupService {
|
||||
|
||||
const { buffer, mediaType, fileName, size } = media;
|
||||
const mimetype = mimeTypes.lookup(fileName).toString();
|
||||
const fullName = join(`${this.instance.id}`, received.key.remoteJid, mediaType, fileName);
|
||||
const fullName = join(
|
||||
`${this.instance.id}`,
|
||||
received.key.remoteJid,
|
||||
mediaType,
|
||||
`${Date.now()}_${fileName}`,
|
||||
);
|
||||
await s3Service.uploadFile(fullName, buffer, size.fileLength?.low, {
|
||||
'Content-Type': mimetype,
|
||||
});
|
||||
@ -1422,6 +1468,17 @@ export class BaileysStartupService extends ChannelStartupService {
|
||||
continue;
|
||||
}
|
||||
|
||||
const updateKey = `${this.instance.id}_${key.id}_${update.status}`;
|
||||
|
||||
const cached = await this.baileysCache.get(updateKey);
|
||||
|
||||
if (cached) {
|
||||
this.logger.info(`Message duplicated ignored: ${key.id}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
await this.baileysCache.set(updateKey, true, 30 * 60);
|
||||
|
||||
if (status[update.status] === 'READ' && key.fromMe) {
|
||||
if (this.configService.get<Chatwoot>('CHATWOOT').ENABLED && this.localChatwoot?.enabled) {
|
||||
this.chatwootService.eventWhatsapp(
|
||||
@ -1529,7 +1586,6 @@ export class BaileysStartupService extends ChannelStartupService {
|
||||
const chatToInsert = {
|
||||
remoteJid: message.remoteJid,
|
||||
instanceId: this.instanceId,
|
||||
name: message.pushName || '',
|
||||
unreadMessages: 0,
|
||||
};
|
||||
|
||||
@ -2703,21 +2759,43 @@ export class BaileysStartupService extends ChannelStartupService {
|
||||
imageBuffer = Buffer.from(response.data, 'binary');
|
||||
}
|
||||
|
||||
const webpBuffer = await sharp(imageBuffer).webp().toBuffer();
|
||||
const isAnimated = this.isAnimated(image, imageBuffer);
|
||||
|
||||
return webpBuffer;
|
||||
if (isAnimated) {
|
||||
return await sharp(imageBuffer, { animated: true }).webp({ quality: 80 }).toBuffer();
|
||||
} else {
|
||||
return await sharp(imageBuffer).webp().toBuffer();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Erro ao converter a imagem para WebP:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
private isAnimatedWebp(buffer: Buffer): boolean {
|
||||
if (buffer.length < 12) return false;
|
||||
|
||||
return buffer.indexOf(Buffer.from('ANIM')) !== -1;
|
||||
}
|
||||
|
||||
private isAnimated(image: string, buffer: Buffer): boolean {
|
||||
const lowerCaseImage = image.toLowerCase();
|
||||
|
||||
if (lowerCaseImage.includes('.gif')) return true;
|
||||
|
||||
if (lowerCaseImage.includes('.webp')) return this.isAnimatedWebp(buffer);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public async mediaSticker(data: SendStickerDto, file?: any) {
|
||||
const mediaData: SendStickerDto = { ...data };
|
||||
|
||||
if (file) mediaData.sticker = file.buffer.toString('base64');
|
||||
|
||||
const convert = await this.convertToWebP(data.sticker);
|
||||
const convert = data?.notConvertSticker
|
||||
? Buffer.from(data.sticker, 'base64')
|
||||
: await this.convertToWebP(data.sticker);
|
||||
const gifPlayback = data.sticker.includes('.gif');
|
||||
const result = await this.sendMessageWithTyping(
|
||||
data.number,
|
||||
@ -3573,6 +3651,18 @@ export class BaileysStartupService extends ChannelStartupService {
|
||||
status: 'DELETED',
|
||||
},
|
||||
});
|
||||
const messageUpdate: any = {
|
||||
messageId: message.id,
|
||||
keyId: messageId,
|
||||
remoteJid: response.key.remoteJid,
|
||||
fromMe: response.key.fromMe,
|
||||
participant: response.key?.remoteJid,
|
||||
status: 'DELETED',
|
||||
instanceId: this.instanceId,
|
||||
};
|
||||
await this.prismaRepository.messageUpdate.create({
|
||||
data: messageUpdate,
|
||||
});
|
||||
} else {
|
||||
await this.prismaRepository.message.deleteMany({
|
||||
where: {
|
||||
@ -3899,13 +3989,84 @@ export class BaileysStartupService extends ChannelStartupService {
|
||||
}
|
||||
|
||||
try {
|
||||
return await this.client.sendMessage(jid, {
|
||||
const oldMessage: any = await this.getMessage(data.key, true);
|
||||
if (!oldMessage) throw new NotFoundException('Message not found');
|
||||
if (oldMessage?.key?.remoteJid !== jid) {
|
||||
throw new BadRequestException('RemoteJid does not match');
|
||||
}
|
||||
if (oldMessage?.messageTimestamp > Date.now() + 900000) {
|
||||
// 15 minutes in milliseconds
|
||||
throw new BadRequestException('Message is older than 15 minutes');
|
||||
}
|
||||
|
||||
const messageSent = await this.client.sendMessage(jid, {
|
||||
...(options as any),
|
||||
edit: data.key,
|
||||
});
|
||||
if (messageSent) {
|
||||
const editedMessage =
|
||||
messageSent?.message?.protocolMessage || messageSent?.message?.editedMessage?.message?.protocolMessage;
|
||||
|
||||
if (editedMessage) {
|
||||
this.sendDataWebhook(Events.SEND_MESSAGE_UPDATE, editedMessage);
|
||||
if (this.configService.get<Chatwoot>('CHATWOOT').ENABLED && this.localChatwoot?.enabled)
|
||||
this.chatwootService.eventWhatsapp(
|
||||
'send.message.update',
|
||||
{ instanceName: this.instance.name, instanceId: this.instance.id },
|
||||
editedMessage,
|
||||
);
|
||||
|
||||
const messageId = messageSent.message?.protocolMessage?.key?.id;
|
||||
if (messageId) {
|
||||
let message = await this.prismaRepository.message.findFirst({
|
||||
where: {
|
||||
key: {
|
||||
path: ['id'],
|
||||
equals: messageId,
|
||||
},
|
||||
},
|
||||
});
|
||||
if (!message) throw new NotFoundException('Message not found');
|
||||
|
||||
if (!(message.key.valueOf() as any).fromMe) {
|
||||
new BadRequestException('You cannot edit others messages');
|
||||
}
|
||||
if ((message.key.valueOf() as any)?.deleted) {
|
||||
new BadRequestException('You cannot edit deleted messages');
|
||||
}
|
||||
if (oldMessage.messageType === 'conversation' || oldMessage.messageType === 'extendedTextMessage') {
|
||||
oldMessage.message.conversation = data.text;
|
||||
} else {
|
||||
oldMessage.message[oldMessage.messageType].caption = data.text;
|
||||
}
|
||||
message = await this.prismaRepository.message.update({
|
||||
where: { id: message.id },
|
||||
data: {
|
||||
message: oldMessage.message,
|
||||
status: 'EDITED',
|
||||
messageTimestamp: Math.floor(Date.now() / 1000), // Convert to int32 by dividing by 1000 to get seconds
|
||||
},
|
||||
});
|
||||
const messageUpdate: any = {
|
||||
messageId: message.id,
|
||||
keyId: messageId,
|
||||
remoteJid: messageSent.key.remoteJid,
|
||||
fromMe: messageSent.key.fromMe,
|
||||
participant: messageSent.key?.remoteJid,
|
||||
status: 'EDITED',
|
||||
instanceId: this.instanceId,
|
||||
};
|
||||
await this.prismaRepository.messageUpdate.create({
|
||||
data: messageUpdate,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return messageSent;
|
||||
} catch (error) {
|
||||
this.logger.error(error);
|
||||
throw new BadRequestException(error.toString());
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
@ -4534,4 +4695,137 @@ export class BaileysStartupService extends ChannelStartupService {
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
//Business Controller
|
||||
public async fetchCatalog(instanceName: string, data: getCollectionsDto) {
|
||||
const jid = data.number ? createJid(data.number) : this.client?.user?.id;
|
||||
const limit = data.limit || 10;
|
||||
const cursor = null;
|
||||
|
||||
const onWhatsapp = (await this.whatsappNumber({ numbers: [jid] }))?.shift();
|
||||
|
||||
if (!onWhatsapp.exists) {
|
||||
throw new BadRequestException(onWhatsapp);
|
||||
}
|
||||
|
||||
try {
|
||||
const info = (await this.whatsappNumber({ numbers: [jid] }))?.shift();
|
||||
const business = await this.fetchBusinessProfile(info?.jid);
|
||||
|
||||
let catalog = await this.getCatalog({ jid: info?.jid, limit, cursor });
|
||||
let nextPageCursor = catalog.nextPageCursor;
|
||||
let nextPageCursorJson = nextPageCursor ? JSON.parse(atob(nextPageCursor)) : null;
|
||||
let pagination = nextPageCursorJson?.pagination_cursor
|
||||
? JSON.parse(atob(nextPageCursorJson.pagination_cursor))
|
||||
: null;
|
||||
let fetcherHasMore = pagination?.fetcher_has_more === true ? true : false;
|
||||
|
||||
let productsCatalog = catalog.products || [];
|
||||
let countLoops = 0;
|
||||
while (fetcherHasMore && countLoops < 4) {
|
||||
catalog = await this.getCatalog({ jid: info?.jid, limit, cursor: nextPageCursor });
|
||||
nextPageCursor = catalog.nextPageCursor;
|
||||
nextPageCursorJson = nextPageCursor ? JSON.parse(atob(nextPageCursor)) : null;
|
||||
pagination = nextPageCursorJson?.pagination_cursor
|
||||
? JSON.parse(atob(nextPageCursorJson.pagination_cursor))
|
||||
: null;
|
||||
fetcherHasMore = pagination?.fetcher_has_more === true ? true : false;
|
||||
productsCatalog = [...productsCatalog, ...catalog.products];
|
||||
countLoops++;
|
||||
}
|
||||
|
||||
return {
|
||||
wuid: info?.jid || jid,
|
||||
numberExists: info?.exists,
|
||||
isBusiness: business.isBusiness,
|
||||
catalogLength: productsCatalog.length,
|
||||
catalog: productsCatalog,
|
||||
};
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
return {
|
||||
wuid: jid,
|
||||
name: null,
|
||||
isBusiness: false,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public async getCatalog({
|
||||
jid,
|
||||
limit,
|
||||
cursor,
|
||||
}: GetCatalogOptions): Promise<{ products: Product[]; nextPageCursor: string | undefined }> {
|
||||
try {
|
||||
jid = jid ? createJid(jid) : this.instance.wuid;
|
||||
|
||||
const catalog = await this.client.getCatalog({ jid, limit: limit, cursor: cursor });
|
||||
|
||||
if (!catalog) {
|
||||
return {
|
||||
products: undefined,
|
||||
nextPageCursor: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
return catalog;
|
||||
} catch (error) {
|
||||
throw new InternalServerErrorException('Error getCatalog', error.toString());
|
||||
}
|
||||
}
|
||||
|
||||
public async fetchCollections(instanceName: string, data: getCollectionsDto) {
|
||||
const jid = data.number ? createJid(data.number) : this.client?.user?.id;
|
||||
const limit = data.limit <= 20 ? data.limit : 20; //(tem esse limite, não sei porque)
|
||||
|
||||
const onWhatsapp = (await this.whatsappNumber({ numbers: [jid] }))?.shift();
|
||||
|
||||
if (!onWhatsapp.exists) {
|
||||
throw new BadRequestException(onWhatsapp);
|
||||
}
|
||||
|
||||
try {
|
||||
const info = (await this.whatsappNumber({ numbers: [jid] }))?.shift();
|
||||
const business = await this.fetchBusinessProfile(info?.jid);
|
||||
const collections = await this.getCollections(info?.jid, limit);
|
||||
|
||||
return {
|
||||
wuid: info?.jid || jid,
|
||||
name: info?.name,
|
||||
numberExists: info?.exists,
|
||||
isBusiness: business.isBusiness,
|
||||
collectionsLength: collections?.length,
|
||||
collections: collections,
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
wuid: jid,
|
||||
name: null,
|
||||
isBusiness: false,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public async getCollections(jid?: string | undefined, limit?: number): Promise<CatalogCollection[]> {
|
||||
try {
|
||||
jid = jid ? createJid(jid) : this.instance.wuid;
|
||||
|
||||
const result = await this.client.getCollections(jid, limit);
|
||||
|
||||
if (!result) {
|
||||
return [
|
||||
{
|
||||
id: undefined,
|
||||
name: undefined,
|
||||
products: [],
|
||||
status: undefined,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
return result.collections;
|
||||
} catch (error) {
|
||||
throw new InternalServerErrorException('Error getCatalog', error.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1106,7 +1106,7 @@ export class ChatwootService {
|
||||
|
||||
sendTelemetry('/message/sendWhatsAppAudio');
|
||||
|
||||
const messageSent = await waInstance?.audioWhatsapp(data, true);
|
||||
const messageSent = await waInstance?.audioWhatsapp(data, null, true);
|
||||
|
||||
return messageSent;
|
||||
}
|
||||
@ -1898,7 +1898,7 @@ export class ChatwootService {
|
||||
.replaceAll(/~((?!\s)([^\n~]+?)(?<!\s))~/g, '~~$1~~')
|
||||
: originalMessage;
|
||||
|
||||
if (bodyMessage && bodyMessage.includes('Por favor, classifique esta conversa, http')) {
|
||||
if (bodyMessage && bodyMessage.includes('/survey/responses/') && bodyMessage.includes('http')) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -2199,7 +2199,7 @@ export class ChatwootService {
|
||||
}
|
||||
}
|
||||
|
||||
if (event === 'messages.edit') {
|
||||
if (event === 'messages.edit' || event === 'send.message.update') {
|
||||
const editedText = `${
|
||||
body?.editedMessage?.conversation || body?.editedMessage?.extendedTextMessage?.text
|
||||
}\n\n_\`${i18next.t('cw.message.edited')}.\`_`;
|
||||
|
@ -1018,10 +1018,6 @@ export class TypebotController extends ChatbotController implements ChatbotContr
|
||||
return;
|
||||
}
|
||||
|
||||
if (session && !session.awaitUser) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (debounceTime && debounceTime > 0) {
|
||||
this.processDebounce(this.userMessageDebounce, content, remoteJid, debounceTime, async (debouncedContent) => {
|
||||
await this.typebotService.processTypebot(
|
||||
|
@ -741,6 +741,10 @@ export class TypebotService {
|
||||
}
|
||||
}
|
||||
|
||||
if (session && !session.awaitUser) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (session && session.status !== 'opened') {
|
||||
return;
|
||||
}
|
||||
|
@ -132,6 +132,7 @@ export class EventController {
|
||||
'MESSAGES_UPDATE',
|
||||
'MESSAGES_DELETE',
|
||||
'SEND_MESSAGE',
|
||||
'SEND_MESSAGE_UPDATE',
|
||||
'CONTACTS_SET',
|
||||
'CONTACTS_UPSERT',
|
||||
'CONTACTS_UPDATE',
|
||||
@ -151,5 +152,8 @@ export class EventController {
|
||||
'TYPEBOT_CHANGE_STATUS',
|
||||
'REMOVE_INSTANCE',
|
||||
'LOGOUT_INSTANCE',
|
||||
'INSTANCE_CREATE',
|
||||
'INSTANCE_DELETE',
|
||||
'STATUS_INSTANCE',
|
||||
];
|
||||
}
|
||||
|
@ -26,6 +26,11 @@ export class EventDto {
|
||||
events?: string[];
|
||||
};
|
||||
|
||||
nats?: {
|
||||
enabled?: boolean;
|
||||
events?: string[];
|
||||
};
|
||||
|
||||
pusher?: {
|
||||
enabled?: boolean;
|
||||
appId?: string;
|
||||
@ -63,6 +68,11 @@ export function EventInstanceMixin<TBase extends Constructor>(Base: TBase) {
|
||||
events?: string[];
|
||||
};
|
||||
|
||||
nats?: {
|
||||
enabled?: boolean;
|
||||
events?: string[];
|
||||
};
|
||||
|
||||
pusher?: {
|
||||
enabled?: boolean;
|
||||
appId?: string;
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { NatsController } from '@api/integrations/event/nats/nats.controller';
|
||||
import { PusherController } from '@api/integrations/event/pusher/pusher.controller';
|
||||
import { RabbitmqController } from '@api/integrations/event/rabbitmq/rabbitmq.controller';
|
||||
import { SqsController } from '@api/integrations/event/sqs/sqs.controller';
|
||||
@ -13,6 +14,7 @@ export class EventManager {
|
||||
private websocketController: WebsocketController;
|
||||
private webhookController: WebhookController;
|
||||
private rabbitmqController: RabbitmqController;
|
||||
private natsController: NatsController;
|
||||
private sqsController: SqsController;
|
||||
private pusherController: PusherController;
|
||||
|
||||
@ -23,6 +25,7 @@ export class EventManager {
|
||||
this.websocket = new WebsocketController(prismaRepository, waMonitor);
|
||||
this.webhook = new WebhookController(prismaRepository, waMonitor);
|
||||
this.rabbitmq = new RabbitmqController(prismaRepository, waMonitor);
|
||||
this.nats = new NatsController(prismaRepository, waMonitor);
|
||||
this.sqs = new SqsController(prismaRepository, waMonitor);
|
||||
this.pusher = new PusherController(prismaRepository, waMonitor);
|
||||
}
|
||||
@ -67,6 +70,14 @@ export class EventManager {
|
||||
return this.rabbitmqController;
|
||||
}
|
||||
|
||||
public set nats(nats: NatsController) {
|
||||
this.natsController = nats;
|
||||
}
|
||||
|
||||
public get nats() {
|
||||
return this.natsController;
|
||||
}
|
||||
|
||||
public set sqs(sqs: SqsController) {
|
||||
this.sqsController = sqs;
|
||||
}
|
||||
@ -85,6 +96,7 @@ export class EventManager {
|
||||
public init(httpServer: Server): void {
|
||||
this.websocket.init(httpServer);
|
||||
this.rabbitmq.init();
|
||||
this.nats.init();
|
||||
this.sqs.init();
|
||||
this.pusher.init();
|
||||
}
|
||||
@ -103,6 +115,7 @@ export class EventManager {
|
||||
}): Promise<void> {
|
||||
await this.websocket.emit(eventData);
|
||||
await this.rabbitmq.emit(eventData);
|
||||
await this.nats.emit(eventData);
|
||||
await this.sqs.emit(eventData);
|
||||
await this.webhook.emit(eventData);
|
||||
await this.pusher.emit(eventData);
|
||||
@ -125,6 +138,14 @@ export class EventManager {
|
||||
},
|
||||
});
|
||||
|
||||
if (data.nats)
|
||||
await this.nats.set(instanceName, {
|
||||
nats: {
|
||||
enabled: true,
|
||||
events: data.nats?.events,
|
||||
},
|
||||
});
|
||||
|
||||
if (data.sqs)
|
||||
await this.sqs.set(instanceName, {
|
||||
sqs: {
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { NatsRouter } from '@api/integrations/event/nats/nats.router';
|
||||
import { PusherRouter } from '@api/integrations/event/pusher/pusher.router';
|
||||
import { RabbitmqRouter } from '@api/integrations/event/rabbitmq/rabbitmq.router';
|
||||
import { SqsRouter } from '@api/integrations/event/sqs/sqs.router';
|
||||
@ -14,6 +15,7 @@ export class EventRouter {
|
||||
this.router.use('/webhook', new WebhookRouter(configService, ...guards).router);
|
||||
this.router.use('/websocket', new WebsocketRouter(...guards).router);
|
||||
this.router.use('/rabbitmq', new RabbitmqRouter(...guards).router);
|
||||
this.router.use('/nats', new NatsRouter(...guards).router);
|
||||
this.router.use('/pusher', new PusherRouter(...guards).router);
|
||||
this.router.use('/sqs', new SqsRouter(...guards).router);
|
||||
}
|
||||
|
@ -16,6 +16,9 @@ export const eventSchema: JSONSchema7 = {
|
||||
rabbitmq: {
|
||||
$ref: '#/$defs/event',
|
||||
},
|
||||
nats: {
|
||||
$ref: '#/$defs/event',
|
||||
},
|
||||
sqs: {
|
||||
$ref: '#/$defs/event',
|
||||
},
|
||||
|
161
src/api/integrations/event/nats/nats.controller.ts
Normal file
161
src/api/integrations/event/nats/nats.controller.ts
Normal file
@ -0,0 +1,161 @@
|
||||
import { PrismaRepository } from '@api/repository/repository.service';
|
||||
import { WAMonitoringService } from '@api/services/monitor.service';
|
||||
import { configService, Log, Nats } from '@config/env.config';
|
||||
import { Logger } from '@config/logger.config';
|
||||
import { connect, NatsConnection, StringCodec } from 'nats';
|
||||
|
||||
import { EmitData, EventController, EventControllerInterface } from '../event.controller';
|
||||
|
||||
export class NatsController extends EventController implements EventControllerInterface {
|
||||
public natsClient: NatsConnection | null = null;
|
||||
private readonly logger = new Logger('NatsController');
|
||||
private readonly sc = StringCodec();
|
||||
|
||||
constructor(prismaRepository: PrismaRepository, waMonitor: WAMonitoringService) {
|
||||
super(prismaRepository, waMonitor, configService.get<Nats>('NATS')?.ENABLED, 'nats');
|
||||
}
|
||||
|
||||
public async init(): Promise<void> {
|
||||
if (!this.status) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const uri = configService.get<Nats>('NATS').URI;
|
||||
|
||||
this.natsClient = await connect({ servers: uri });
|
||||
|
||||
this.logger.info('NATS initialized');
|
||||
|
||||
if (configService.get<Nats>('NATS')?.GLOBAL_ENABLED) {
|
||||
await this.initGlobalSubscriptions();
|
||||
}
|
||||
} catch (error) {
|
||||
this.logger.error('Failed to connect to NATS:');
|
||||
this.logger.error(error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
public async emit({
|
||||
instanceName,
|
||||
origin,
|
||||
event,
|
||||
data,
|
||||
serverUrl,
|
||||
dateTime,
|
||||
sender,
|
||||
apiKey,
|
||||
integration,
|
||||
}: EmitData): Promise<void> {
|
||||
if (integration && !integration.includes('nats')) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.status || !this.natsClient) {
|
||||
return;
|
||||
}
|
||||
|
||||
const instanceNats = await this.get(instanceName);
|
||||
const natsLocal = instanceNats?.events;
|
||||
const natsGlobal = configService.get<Nats>('NATS').GLOBAL_ENABLED;
|
||||
const natsEvents = configService.get<Nats>('NATS').EVENTS;
|
||||
const prefixKey = configService.get<Nats>('NATS').PREFIX_KEY;
|
||||
const we = event.replace(/[.-]/gm, '_').toUpperCase();
|
||||
const logEnabled = configService.get<Log>('LOG').LEVEL.includes('WEBHOOKS');
|
||||
|
||||
const message = {
|
||||
event,
|
||||
instance: instanceName,
|
||||
data,
|
||||
server_url: serverUrl,
|
||||
date_time: dateTime,
|
||||
sender,
|
||||
apikey: apiKey,
|
||||
};
|
||||
|
||||
// Instância específica
|
||||
if (instanceNats?.enabled) {
|
||||
if (Array.isArray(natsLocal) && natsLocal.includes(we)) {
|
||||
const subject = `${instanceName}.${event.toLowerCase()}`;
|
||||
|
||||
try {
|
||||
this.natsClient.publish(subject, this.sc.encode(JSON.stringify(message)));
|
||||
|
||||
if (logEnabled) {
|
||||
const logData = {
|
||||
local: `${origin}.sendData-NATS`,
|
||||
...message,
|
||||
};
|
||||
this.logger.log(logData);
|
||||
}
|
||||
} catch (error) {
|
||||
this.logger.error(`Failed to publish to NATS (instance): ${error}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Global
|
||||
if (natsGlobal && natsEvents[we]) {
|
||||
try {
|
||||
const subject = prefixKey ? `${prefixKey}.${event.toLowerCase()}` : event.toLowerCase();
|
||||
|
||||
this.natsClient.publish(subject, this.sc.encode(JSON.stringify(message)));
|
||||
|
||||
if (logEnabled) {
|
||||
const logData = {
|
||||
local: `${origin}.sendData-NATS-Global`,
|
||||
...message,
|
||||
};
|
||||
this.logger.log(logData);
|
||||
}
|
||||
} catch (error) {
|
||||
this.logger.error(`Failed to publish to NATS (global): ${error}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async initGlobalSubscriptions(): Promise<void> {
|
||||
this.logger.info('Initializing global subscriptions');
|
||||
|
||||
const events = configService.get<Nats>('NATS').EVENTS;
|
||||
const prefixKey = configService.get<Nats>('NATS').PREFIX_KEY;
|
||||
|
||||
if (!events) {
|
||||
this.logger.warn('No events to initialize on NATS');
|
||||
return;
|
||||
}
|
||||
|
||||
const eventKeys = Object.keys(events);
|
||||
|
||||
for (const event of eventKeys) {
|
||||
if (events[event] === false) continue;
|
||||
|
||||
const subject = prefixKey ? `${prefixKey}.${event.toLowerCase()}` : event.toLowerCase();
|
||||
|
||||
// Criar uma subscription para cada evento
|
||||
try {
|
||||
const subscription = this.natsClient.subscribe(subject);
|
||||
this.logger.info(`Subscribed to: ${subject}`);
|
||||
|
||||
// Processar mensagens (exemplo básico)
|
||||
(async () => {
|
||||
for await (const msg of subscription) {
|
||||
try {
|
||||
const data = JSON.parse(this.sc.decode(msg.data));
|
||||
// Aqui você pode adicionar a lógica de processamento
|
||||
this.logger.debug(`Received message on ${subject}:`);
|
||||
this.logger.debug(data);
|
||||
} catch (error) {
|
||||
this.logger.error(`Error processing message on ${subject}:`);
|
||||
this.logger.error(error);
|
||||
}
|
||||
}
|
||||
})();
|
||||
} catch (error) {
|
||||
this.logger.error(`Failed to subscribe to ${subject}:`);
|
||||
this.logger.error(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
36
src/api/integrations/event/nats/nats.router.ts
Normal file
36
src/api/integrations/event/nats/nats.router.ts
Normal file
@ -0,0 +1,36 @@
|
||||
import { RouterBroker } from '@api/abstract/abstract.router';
|
||||
import { InstanceDto } from '@api/dto/instance.dto';
|
||||
import { EventDto } from '@api/integrations/event/event.dto';
|
||||
import { HttpStatus } from '@api/routes/index.router';
|
||||
import { eventManager } from '@api/server.module';
|
||||
import { eventSchema, instanceSchema } from '@validate/validate.schema';
|
||||
import { RequestHandler, Router } from 'express';
|
||||
|
||||
export class NatsRouter extends RouterBroker {
|
||||
constructor(...guards: RequestHandler[]) {
|
||||
super();
|
||||
this.router
|
||||
.post(this.routerPath('set'), ...guards, async (req, res) => {
|
||||
const response = await this.dataValidate<EventDto>({
|
||||
request: req,
|
||||
schema: eventSchema,
|
||||
ClassRef: EventDto,
|
||||
execute: (instance, data) => eventManager.nats.set(instance.instanceName, data),
|
||||
});
|
||||
|
||||
res.status(HttpStatus.CREATED).json(response);
|
||||
})
|
||||
.get(this.routerPath('find'), ...guards, async (req, res) => {
|
||||
const response = await this.dataValidate<InstanceDto>({
|
||||
request: req,
|
||||
schema: instanceSchema,
|
||||
ClassRef: InstanceDto,
|
||||
execute: (instance) => eventManager.nats.get(instance.instanceName),
|
||||
});
|
||||
|
||||
res.status(HttpStatus.OK).json(response);
|
||||
});
|
||||
}
|
||||
|
||||
public readonly router: Router = Router();
|
||||
}
|
@ -1,10 +1,11 @@
|
||||
import { PrismaRepository } from '@api/repository/repository.service';
|
||||
import { WAMonitoringService } from '@api/services/monitor.service';
|
||||
import { SQS } from '@aws-sdk/client-sqs';
|
||||
import { CreateQueueCommand, DeleteQueueCommand, ListQueuesCommand, SQS } from '@aws-sdk/client-sqs';
|
||||
import { configService, Log, Sqs } from '@config/env.config';
|
||||
import { Logger } from '@config/logger.config';
|
||||
|
||||
import { EmitData, EventController, EventControllerInterface } from '../event.controller';
|
||||
import { EventDto } from '../event.dto';
|
||||
|
||||
export class SqsController extends EventController implements EventControllerInterface {
|
||||
private sqs: SQS;
|
||||
@ -45,6 +46,39 @@ export class SqsController extends EventController implements EventControllerInt
|
||||
return this.sqs;
|
||||
}
|
||||
|
||||
override async set(instanceName: string, data: EventDto): Promise<any> {
|
||||
if (!this.status) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!data[this.name]?.enabled) {
|
||||
data[this.name].events = [];
|
||||
} else {
|
||||
if (0 === data[this.name].events.length) {
|
||||
data[this.name].events = EventController.events;
|
||||
}
|
||||
}
|
||||
|
||||
await this.saveQueues(instanceName, data[this.name].events, data[this.name]?.enabled);
|
||||
|
||||
const payload: any = {
|
||||
where: {
|
||||
instanceId: this.monitor.waInstances[instanceName].instanceId,
|
||||
},
|
||||
update: {
|
||||
enabled: data[this.name]?.enabled,
|
||||
events: data[this.name].events,
|
||||
},
|
||||
create: {
|
||||
enabled: data[this.name]?.enabled,
|
||||
events: data[this.name].events,
|
||||
instanceId: this.monitor.waInstances[instanceName].instanceId,
|
||||
},
|
||||
};
|
||||
console.log('*** payload: ', payload);
|
||||
return this.prisma[this.name].upsert(payload);
|
||||
}
|
||||
|
||||
public async emit({
|
||||
instanceName,
|
||||
origin,
|
||||
@ -121,70 +155,92 @@ export class SqsController extends EventController implements EventControllerInt
|
||||
}
|
||||
}
|
||||
|
||||
public async initQueues(instanceName: string, events: string[]) {
|
||||
if (!events || !events.length) return;
|
||||
private async saveQueues(instanceName: string, events: string[], enable: boolean) {
|
||||
if (enable) {
|
||||
const eventsFinded = await this.listQueuesByInstance(instanceName);
|
||||
console.log('eventsFinded', eventsFinded);
|
||||
|
||||
const queues = events.map((event) => {
|
||||
return `${event.replace(/_/g, '_').toLowerCase()}`;
|
||||
});
|
||||
for (const event of events) {
|
||||
const normalizedEvent = event.toLowerCase();
|
||||
|
||||
queues.forEach((event) => {
|
||||
const queueName = `${instanceName}_${event}.fifo`;
|
||||
if (eventsFinded.includes(normalizedEvent)) {
|
||||
this.logger.info(`A queue para o evento "${normalizedEvent}" já existe. Ignorando criação.`);
|
||||
continue;
|
||||
}
|
||||
|
||||
this.sqs.createQueue(
|
||||
{
|
||||
QueueName: queueName,
|
||||
Attributes: {
|
||||
FifoQueue: 'true',
|
||||
},
|
||||
},
|
||||
(err, data) => {
|
||||
if (err) {
|
||||
this.logger.error(`Error creating queue ${queueName}: ${err.message}`);
|
||||
} else {
|
||||
this.logger.info(`Queue ${queueName} created: ${data.QueueUrl}`);
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
const queueName = `${instanceName}_${normalizedEvent}.fifo`;
|
||||
|
||||
try {
|
||||
const createCommand = new CreateQueueCommand({
|
||||
QueueName: queueName,
|
||||
Attributes: {
|
||||
FifoQueue: 'true',
|
||||
},
|
||||
});
|
||||
const data = await this.sqs.send(createCommand);
|
||||
this.logger.info(`Queue ${queueName} criada: ${data.QueueUrl}`);
|
||||
} catch (err: any) {
|
||||
this.logger.error(`Erro ao criar queue ${queueName}: ${err.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async removeQueues(instanceName: string, events: any) {
|
||||
const eventsArray = Array.isArray(events) ? events.map((event) => String(event)) : [];
|
||||
if (!events || !eventsArray.length) return;
|
||||
private async listQueuesByInstance(instanceName: string) {
|
||||
let existingQueues: string[] = [];
|
||||
try {
|
||||
const listCommand = new ListQueuesCommand({
|
||||
QueueNamePrefix: `${instanceName}_`,
|
||||
});
|
||||
const listData = await this.sqs.send(listCommand);
|
||||
if (listData.QueueUrls && listData.QueueUrls.length > 0) {
|
||||
// Extrai o nome da fila a partir da URL
|
||||
existingQueues = listData.QueueUrls.map((queueUrl) => {
|
||||
const parts = queueUrl.split('/');
|
||||
return parts[parts.length - 1];
|
||||
});
|
||||
}
|
||||
} catch (error: any) {
|
||||
this.logger.error(`Erro ao listar filas para a instância ${instanceName}: ${error.message}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const queues = eventsArray.map((event) => {
|
||||
return `${event.replace(/_/g, '_').toLowerCase()}`;
|
||||
});
|
||||
// Mapeia os eventos já existentes nas filas: remove o prefixo e o sufixo ".fifo"
|
||||
return existingQueues
|
||||
.map((queueName) => {
|
||||
// Espera-se que o nome seja `${instanceName}_${event}.fifo`
|
||||
if (queueName.startsWith(`${instanceName}_`) && queueName.endsWith('.fifo')) {
|
||||
return queueName.substring(instanceName.length + 1, queueName.length - 5).toLowerCase();
|
||||
}
|
||||
return '';
|
||||
})
|
||||
.filter((event) => event !== '');
|
||||
}
|
||||
|
||||
queues.forEach((event) => {
|
||||
const queueName = `${instanceName}_${event}.fifo`;
|
||||
// Para uma futura feature de exclusão forçada das queues
|
||||
private async removeQueuesByInstance(instanceName: string) {
|
||||
try {
|
||||
const listCommand = new ListQueuesCommand({
|
||||
QueueNamePrefix: `${instanceName}_`,
|
||||
});
|
||||
const listData = await this.sqs.send(listCommand);
|
||||
|
||||
this.sqs.getQueueUrl(
|
||||
{
|
||||
QueueName: queueName,
|
||||
},
|
||||
(err, data) => {
|
||||
if (err) {
|
||||
this.logger.error(`Error getting queue URL for ${queueName}: ${err.message}`);
|
||||
} else {
|
||||
const queueUrl = data.QueueUrl;
|
||||
if (!listData.QueueUrls || listData.QueueUrls.length === 0) {
|
||||
this.logger.info(`No queues found for instance ${instanceName}`);
|
||||
return;
|
||||
}
|
||||
|
||||
this.sqs.deleteQueue(
|
||||
{
|
||||
QueueUrl: queueUrl,
|
||||
},
|
||||
(deleteErr) => {
|
||||
if (deleteErr) {
|
||||
this.logger.error(`Error deleting queue ${queueName}: ${deleteErr.message}`);
|
||||
} else {
|
||||
this.logger.info(`Queue ${queueName} deleted`);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
for (const queueUrl of listData.QueueUrls) {
|
||||
try {
|
||||
const deleteCommand = new DeleteQueueCommand({ QueueUrl: queueUrl });
|
||||
await this.sqs.send(deleteCommand);
|
||||
this.logger.info(`Queue ${queueUrl} deleted`);
|
||||
} catch (err: any) {
|
||||
this.logger.error(`Error deleting queue ${queueUrl}: ${err.message}`);
|
||||
}
|
||||
}
|
||||
} catch (err: any) {
|
||||
this.logger.error(`Error listing queues for instance ${instanceName}: ${err.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -19,7 +19,7 @@ export class WebhookController extends EventController implements EventControlle
|
||||
}
|
||||
|
||||
override async set(instanceName: string, data: EventDto): Promise<wa.LocalWebHook> {
|
||||
if (!isURL(data.webhook.url, { require_tld: false })) {
|
||||
if (!/^(https?:\/\/)/.test(data.webhook.url)) {
|
||||
throw new BadRequestException('Invalid "url" property');
|
||||
}
|
||||
|
||||
@ -88,6 +88,7 @@ export class WebhookController extends EventController implements EventControlle
|
||||
const we = event.replace(/[.-]/gm, '_').toUpperCase();
|
||||
const transformedWe = we.replace(/_/gm, '-').toLowerCase();
|
||||
const enabledLog = configService.get<Log>('LOG').LEVEL.includes('WEBHOOKS');
|
||||
const regex = /^(https?:\/\/)/;
|
||||
|
||||
const webhookData = {
|
||||
event,
|
||||
@ -121,7 +122,7 @@ export class WebhookController extends EventController implements EventControlle
|
||||
}
|
||||
|
||||
try {
|
||||
if (instance?.enabled && isURL(instance.url, { require_tld: false })) {
|
||||
if (instance?.enabled && regex.test(instance.url)) {
|
||||
const httpService = axios.create({
|
||||
baseURL,
|
||||
headers: webhookHeaders as Record<string, string> | undefined,
|
||||
@ -165,7 +166,7 @@ export class WebhookController extends EventController implements EventControlle
|
||||
}
|
||||
|
||||
try {
|
||||
if (isURL(globalURL)) {
|
||||
if (regex.test(globalURL)) {
|
||||
const httpService = axios.create({ baseURL: globalURL });
|
||||
|
||||
await this.retryWebhookRequest(
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { PrismaRepository } from '@api/repository/repository.service';
|
||||
import { WAMonitoringService } from '@api/services/monitor.service';
|
||||
import { configService, Cors, Log, Websocket } from '@config/env.config';
|
||||
import { Auth, configService, Cors, Log, Websocket } from '@config/env.config';
|
||||
import { Logger } from '@config/logger.config';
|
||||
import { Server } from 'http';
|
||||
import { Server as SocketIO } from 'socket.io';
|
||||
@ -24,8 +24,40 @@ export class WebsocketController extends EventController implements EventControl
|
||||
}
|
||||
|
||||
this.socket = new SocketIO(httpServer, {
|
||||
cors: {
|
||||
origin: this.cors,
|
||||
cors: { origin: this.cors },
|
||||
allowRequest: async (req, callback) => {
|
||||
try {
|
||||
const url = new URL(req.url || '', 'http://localhost');
|
||||
const params = new URLSearchParams(url.search);
|
||||
|
||||
// Permite conexões internas do Socket.IO (EIO=4 é o Engine.IO v4)
|
||||
if (params.has('EIO')) {
|
||||
return callback(null, true);
|
||||
}
|
||||
|
||||
const apiKey = params.get('apikey') || (req.headers.apikey as string);
|
||||
|
||||
if (!apiKey) {
|
||||
this.logger.error('Connection rejected: apiKey not provided');
|
||||
return callback('apiKey is required', false);
|
||||
}
|
||||
|
||||
const instance = await this.prismaRepository.instance.findFirst({ where: { token: apiKey } });
|
||||
|
||||
if (!instance) {
|
||||
const globalToken = configService.get<Auth>('AUTHENTICATION').API_KEY.KEY;
|
||||
if (apiKey !== globalToken) {
|
||||
this.logger.error('Connection rejected: invalid global token');
|
||||
return callback('Invalid global token', false);
|
||||
}
|
||||
}
|
||||
|
||||
callback(null, true);
|
||||
} catch (error) {
|
||||
this.logger.error('Authentication error:');
|
||||
this.logger.error(error);
|
||||
callback('Authentication error', false);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@ -101,10 +133,7 @@ export class WebsocketController extends EventController implements EventControl
|
||||
this.socket.emit(event, message);
|
||||
|
||||
if (logEnabled) {
|
||||
this.logger.log({
|
||||
local: `${origin}.sendData-WebsocketGlobal`,
|
||||
...message,
|
||||
});
|
||||
this.logger.log({ local: `${origin}.sendData-WebsocketGlobal`, ...message });
|
||||
}
|
||||
}
|
||||
|
||||
@ -119,10 +148,7 @@ export class WebsocketController extends EventController implements EventControl
|
||||
this.socket.of(`/${instanceName}`).emit(event, message);
|
||||
|
||||
if (logEnabled) {
|
||||
this.logger.log({
|
||||
local: `${origin}.sendData-Websocket`,
|
||||
...message,
|
||||
});
|
||||
this.logger.log({ local: `${origin}.sendData-Websocket`, ...message });
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
|
@ -63,9 +63,9 @@ const createBucket = async () => {
|
||||
if (!exists) {
|
||||
await minioClient.makeBucket(bucketName);
|
||||
}
|
||||
|
||||
await setBucketPolicy();
|
||||
|
||||
if (!BUCKET.SKIP_POLICY) {
|
||||
await setBucketPolicy();
|
||||
}
|
||||
logger.info(`S3 Bucket ${bucketName} - ON`);
|
||||
return true;
|
||||
} catch (error) {
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { Auth, ConfigService, ProviderSession } from '@config/env.config';
|
||||
import { Logger } from '@config/logger.config';
|
||||
import axios from 'axios';
|
||||
import { execSync } from 'child_process';
|
||||
import { execFileSync } from 'child_process';
|
||||
|
||||
type ResponseSuccess = { status: number; data?: any };
|
||||
type ResponseProvider = Promise<[ResponseSuccess?, Error?]>;
|
||||
@ -36,7 +36,7 @@ export class ProviderFiles {
|
||||
} catch (error) {
|
||||
this.logger.error(['Failed to connect to the file server', error?.message, error?.stack]);
|
||||
const pid = process.pid;
|
||||
execSync(`kill -9 ${pid}`);
|
||||
execFileSync('kill', ['-9', `${pid}`]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
37
src/api/routes/business.router.ts
Normal file
37
src/api/routes/business.router.ts
Normal file
@ -0,0 +1,37 @@
|
||||
import { RouterBroker } from '@api/abstract/abstract.router';
|
||||
import { NumberDto } from '@api/dto/chat.dto';
|
||||
import { businessController } from '@api/server.module';
|
||||
import { catalogSchema, collectionsSchema } from '@validate/validate.schema';
|
||||
import { RequestHandler, Router } from 'express';
|
||||
|
||||
import { HttpStatus } from './index.router';
|
||||
|
||||
export class BusinessRouter extends RouterBroker {
|
||||
constructor(...guards: RequestHandler[]) {
|
||||
super();
|
||||
this.router
|
||||
.post(this.routerPath('getCatalog'), ...guards, async (req, res) => {
|
||||
const response = await this.dataValidate<NumberDto>({
|
||||
request: req,
|
||||
schema: catalogSchema,
|
||||
ClassRef: NumberDto,
|
||||
execute: (instance, data) => businessController.fetchCatalog(instance, data),
|
||||
});
|
||||
|
||||
return res.status(HttpStatus.OK).json(response);
|
||||
})
|
||||
|
||||
.post(this.routerPath('getCollections'), ...guards, async (req, res) => {
|
||||
const response = await this.dataValidate<NumberDto>({
|
||||
request: req,
|
||||
schema: collectionsSchema,
|
||||
ClassRef: NumberDto,
|
||||
execute: (instance, data) => businessController.fetchCollections(instance, data),
|
||||
});
|
||||
|
||||
return res.status(HttpStatus.OK).json(response);
|
||||
});
|
||||
}
|
||||
|
||||
public readonly router: Router = Router();
|
||||
}
|
@ -207,7 +207,6 @@ export class ChatRouter extends RouterBroker {
|
||||
|
||||
return res.status(HttpStatus.OK).json(response);
|
||||
})
|
||||
|
||||
.post(this.routerPath('updateProfileName'), ...guards, async (req, res) => {
|
||||
const response = await this.dataValidate<ProfileNameDto>({
|
||||
request: req,
|
||||
|
@ -11,6 +11,7 @@ import fs from 'fs';
|
||||
import mimeTypes from 'mime-types';
|
||||
import path from 'path';
|
||||
|
||||
import { BusinessRouter } from './business.router';
|
||||
import { CallRouter } from './call.router';
|
||||
import { ChatRouter } from './chat.router';
|
||||
import { GroupRouter } from './group.router';
|
||||
@ -82,6 +83,7 @@ router
|
||||
.use('/message', new MessageRouter(...guards).router)
|
||||
.use('/call', new CallRouter(...guards).router)
|
||||
.use('/chat', new ChatRouter(...guards).router)
|
||||
.use('/business', new BusinessRouter(...guards).router)
|
||||
.use('/group', new GroupRouter(...guards).router)
|
||||
.use('/template', new TemplateRouter(configService, ...guards).router)
|
||||
.use('/settings', new SettingsRouter(...guards).router)
|
||||
|
@ -15,7 +15,6 @@ export class InstanceRouter extends RouterBroker {
|
||||
super();
|
||||
this.router
|
||||
.post('/create', ...guards, async (req, res) => {
|
||||
console.log('create instance', req.body);
|
||||
const response = await this.dataValidate<InstanceDto>({
|
||||
request: req,
|
||||
schema: instanceSchema,
|
||||
|
@ -3,6 +3,7 @@ import { Chatwoot, configService, ProviderSession } from '@config/env.config';
|
||||
import { eventEmitter } from '@config/event.config';
|
||||
import { Logger } from '@config/logger.config';
|
||||
|
||||
import { BusinessController } from './controllers/business.controller';
|
||||
import { CallController } from './controllers/call.controller';
|
||||
import { ChatController } from './controllers/chat.controller';
|
||||
import { GroupController } from './controllers/group.controller';
|
||||
@ -98,6 +99,7 @@ export const instanceController = new InstanceController(
|
||||
export const sendMessageController = new SendMessageController(waMonitor);
|
||||
export const callController = new CallController(waMonitor);
|
||||
export const chatController = new ChatController(waMonitor);
|
||||
export const businessController = new BusinessController(waMonitor);
|
||||
export const groupController = new GroupController(waMonitor);
|
||||
export const labelController = new LabelController(waMonitor);
|
||||
|
||||
|
@ -503,8 +503,29 @@ export class ChannelStartupService {
|
||||
where['remoteJid'] = remoteJid;
|
||||
}
|
||||
|
||||
return await this.prismaRepository.contact.findMany({
|
||||
const contactFindManyArgs: Prisma.ContactFindManyArgs = {
|
||||
where,
|
||||
};
|
||||
|
||||
if (query.offset) contactFindManyArgs.take = query.offset;
|
||||
if (query.page) {
|
||||
const validPage = Math.max(query.page as number, 1);
|
||||
contactFindManyArgs.skip = query.offset * (validPage - 1);
|
||||
}
|
||||
|
||||
const contacts = await this.prismaRepository.contact.findMany(contactFindManyArgs);
|
||||
|
||||
return contacts.map((contact) => {
|
||||
const remoteJid = contact.remoteJid;
|
||||
const isGroup = remoteJid.endsWith('@g.us');
|
||||
const isSaved = !!contact.pushName || !!contact.profilePicUrl;
|
||||
const type = isGroup ? 'group' : isSaved ? 'contact' : 'group_member';
|
||||
return {
|
||||
...contact,
|
||||
isGroup,
|
||||
isSaved,
|
||||
type,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
@ -685,88 +706,93 @@ export class ChannelStartupService {
|
||||
const timestampFilter =
|
||||
query?.where?.messageTimestamp?.gte && query?.where?.messageTimestamp?.lte
|
||||
? Prisma.sql`
|
||||
AND "Message"."messageTimestamp" >= ${Math.floor(new Date(query.where.messageTimestamp.gte).getTime() / 1000)}
|
||||
AND "Message"."messageTimestamp" <= ${Math.floor(new Date(query.where.messageTimestamp.lte).getTime() / 1000)}`
|
||||
AND "Message"."messageTimestamp" >= ${Math.floor(new Date(query.where.messageTimestamp.gte).getTime() / 1000)}
|
||||
AND "Message"."messageTimestamp" <= ${Math.floor(new Date(query.where.messageTimestamp.lte).getTime() / 1000)}`
|
||||
: Prisma.sql``;
|
||||
|
||||
const limit = query?.take ? Prisma.sql`LIMIT ${query.take}` : Prisma.sql``;
|
||||
const offset = query?.skip ? Prisma.sql`OFFSET ${query.skip}` : Prisma.sql``;
|
||||
|
||||
const results = await this.prismaRepository.$queryRaw`
|
||||
WITH rankedMessages AS (
|
||||
SELECT DISTINCT ON ("Contact"."remoteJid")
|
||||
"Contact"."id",
|
||||
"Contact"."remoteJid",
|
||||
"Contact"."pushName",
|
||||
"Contact"."profilePicUrl",
|
||||
COALESCE(
|
||||
to_timestamp("Message"."messageTimestamp"::double precision),
|
||||
"Contact"."updatedAt"
|
||||
) as "updatedAt",
|
||||
"Chat"."createdAt" as "windowStart",
|
||||
"Chat"."createdAt" + INTERVAL '24 hours' as "windowExpires",
|
||||
CASE
|
||||
WHEN "Chat"."createdAt" + INTERVAL '24 hours' > NOW() THEN true
|
||||
ELSE false
|
||||
END as "windowActive",
|
||||
"Message"."id" AS lastMessageId,
|
||||
"Message"."key" AS lastMessage_key,
|
||||
"Message"."pushName" AS lastMessagePushName,
|
||||
"Message"."participant" AS lastMessageParticipant,
|
||||
"Message"."messageType" AS lastMessageMessageType,
|
||||
"Message"."message" AS lastMessageMessage,
|
||||
"Message"."contextInfo" AS lastMessageContextInfo,
|
||||
"Message"."source" AS lastMessageSource,
|
||||
"Message"."messageTimestamp" AS lastMessageMessageTimestamp,
|
||||
"Message"."instanceId" AS lastMessageInstanceId,
|
||||
"Message"."sessionId" AS lastMessageSessionId,
|
||||
"Message"."status" AS lastMessageStatus
|
||||
FROM "Contact"
|
||||
INNER JOIN "Message" ON "Message"."key"->>'remoteJid' = "Contact"."remoteJid"
|
||||
LEFT JOIN "Chat" ON "Chat"."remoteJid" = "Contact"."remoteJid"
|
||||
AND "Chat"."instanceId" = "Contact"."instanceId"
|
||||
WHERE
|
||||
"Contact"."instanceId" = ${this.instanceId}
|
||||
AND "Message"."instanceId" = ${this.instanceId}
|
||||
${remoteJid ? Prisma.sql`AND "Contact"."remoteJid" = ${remoteJid}` : Prisma.sql``}
|
||||
${timestampFilter}
|
||||
ORDER BY
|
||||
"Contact"."remoteJid",
|
||||
"Message"."messageTimestamp" DESC
|
||||
)
|
||||
SELECT * FROM rankedMessages
|
||||
ORDER BY "updatedAt" DESC NULLS LAST;
|
||||
WITH rankedMessages AS (
|
||||
SELECT DISTINCT ON ("Message"."key"->>'remoteJid')
|
||||
"Contact"."id" as "contactId",
|
||||
"Message"."key"->>'remoteJid' as "remoteJid",
|
||||
COALESCE("Contact"."pushName", "Message"."pushName") as "pushName",
|
||||
"Contact"."profilePicUrl",
|
||||
COALESCE(
|
||||
to_timestamp("Message"."messageTimestamp"::double precision),
|
||||
"Contact"."updatedAt"
|
||||
) as "updatedAt",
|
||||
"Chat"."createdAt" as "windowStart",
|
||||
"Chat"."createdAt" + INTERVAL '24 hours' as "windowExpires",
|
||||
CASE WHEN "Chat"."createdAt" + INTERVAL '24 hours' > NOW() THEN true ELSE false END as "windowActive",
|
||||
"Message"."id" AS lastMessageId,
|
||||
"Message"."key" AS lastMessage_key,
|
||||
"Message"."pushName" AS lastMessagePushName,
|
||||
"Message"."participant" AS lastMessageParticipant,
|
||||
"Message"."messageType" AS lastMessageMessageType,
|
||||
"Message"."message" AS lastMessageMessage,
|
||||
"Message"."contextInfo" AS lastMessageContextInfo,
|
||||
"Message"."source" AS lastMessageSource,
|
||||
"Message"."messageTimestamp" AS lastMessageMessageTimestamp,
|
||||
"Message"."instanceId" AS lastMessageInstanceId,
|
||||
"Message"."sessionId" AS lastMessageSessionId,
|
||||
"Message"."status" AS lastMessageStatus
|
||||
FROM "Message"
|
||||
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"
|
||||
WHERE "Message"."instanceId" = ${this.instanceId}
|
||||
${remoteJid ? Prisma.sql`AND "Message"."key"->>'remoteJid' = ${remoteJid}` : Prisma.sql``}
|
||||
${timestampFilter}
|
||||
ORDER BY "Message"."key"->>'remoteJid', "Message"."messageTimestamp" DESC
|
||||
)
|
||||
SELECT * FROM rankedMessages
|
||||
ORDER BY "updatedAt" DESC NULLS LAST
|
||||
${limit}
|
||||
${offset};
|
||||
`;
|
||||
|
||||
if (results && isArray(results) && results.length > 0) {
|
||||
const mappedResults = results.map((contact) => {
|
||||
const lastMessage = contact.lastMessageId
|
||||
const mappedResults = results.map((item) => {
|
||||
const lastMessage = item.lastMessageId
|
||||
? {
|
||||
id: contact.lastMessageId,
|
||||
key: contact.lastMessageKey,
|
||||
pushName: contact.lastMessagePushName,
|
||||
participant: contact.lastMessageParticipant,
|
||||
messageType: contact.lastMessageMessageType,
|
||||
message: contact.lastMessageMessage,
|
||||
contextInfo: contact.lastMessageContextInfo,
|
||||
source: contact.lastMessageSource,
|
||||
messageTimestamp: contact.lastMessageMessageTimestamp,
|
||||
instanceId: contact.lastMessageInstanceId,
|
||||
sessionId: contact.lastMessageSessionId,
|
||||
status: contact.lastMessageStatus,
|
||||
id: item.lastMessageId,
|
||||
key: item.lastMessage_key,
|
||||
pushName: item.lastMessagePushName,
|
||||
participant: item.lastMessageParticipant,
|
||||
messageType: item.lastMessageMessageType,
|
||||
message: item.lastMessageMessage,
|
||||
contextInfo: item.lastMessageContextInfo,
|
||||
source: item.lastMessageSource,
|
||||
messageTimestamp: item.lastMessageMessageTimestamp,
|
||||
instanceId: item.lastMessageInstanceId,
|
||||
sessionId: item.lastMessageSessionId,
|
||||
status: item.lastMessageStatus,
|
||||
}
|
||||
: undefined;
|
||||
|
||||
return {
|
||||
id: contact.id,
|
||||
remoteJid: contact.remoteJid,
|
||||
pushName: contact.pushName,
|
||||
profilePicUrl: contact.profilePicUrl,
|
||||
updatedAt: contact.updatedAt,
|
||||
windowStart: contact.windowStart,
|
||||
windowExpires: contact.windowExpires,
|
||||
windowActive: contact.windowActive,
|
||||
id: item.contactId || null,
|
||||
remoteJid: item.remoteJid,
|
||||
pushName: item.pushName,
|
||||
profilePicUrl: item.profilePicUrl,
|
||||
updatedAt: item.updatedAt,
|
||||
windowStart: item.windowStart,
|
||||
windowExpires: item.windowExpires,
|
||||
windowActive: item.windowActive,
|
||||
lastMessage: lastMessage ? this.cleanMessageData(lastMessage) : undefined,
|
||||
unreadCount: 0,
|
||||
isSaved: !!item.contactId,
|
||||
};
|
||||
});
|
||||
|
||||
if (query?.take && query?.skip) {
|
||||
const skip = query.skip || 0;
|
||||
const take = query.take || 20;
|
||||
return mappedResults.slice(skip, skip + take);
|
||||
}
|
||||
|
||||
return mappedResults;
|
||||
}
|
||||
|
||||
|
@ -7,7 +7,7 @@ import { CacheConf, Chatwoot, ConfigService, Database, DelInstance, ProviderSess
|
||||
import { Logger } from '@config/logger.config';
|
||||
import { INSTANCE_DIR, STORE_DIR } from '@config/path.config';
|
||||
import { NotFoundException } from '@exceptions';
|
||||
import { execSync } from 'child_process';
|
||||
import { execFileSync } from 'child_process';
|
||||
import EventEmitter2 from 'eventemitter2';
|
||||
import { rmSync } from 'fs';
|
||||
import { join } from 'path';
|
||||
@ -91,6 +91,7 @@ export class WAMonitoringService {
|
||||
Chatwoot: true,
|
||||
Proxy: true,
|
||||
Rabbitmq: true,
|
||||
Nats: true,
|
||||
Sqs: true,
|
||||
Websocket: true,
|
||||
Setting: true,
|
||||
@ -168,7 +169,8 @@ export class WAMonitoringService {
|
||||
|
||||
public async cleaningStoreData(instanceName: string) {
|
||||
if (this.configService.get<Chatwoot>('CHATWOOT').ENABLED) {
|
||||
execSync(`rm -rf ${join(STORE_DIR, 'chatwoot', instanceName + '*')}`);
|
||||
const instancePath = join(STORE_DIR, 'chatwoot', instanceName);
|
||||
execFileSync('rm', ['-rf', instancePath]);
|
||||
}
|
||||
|
||||
const instance = await this.prismaRepository.instance.findFirst({
|
||||
@ -190,6 +192,7 @@ export class WAMonitoringService {
|
||||
await this.prismaRepository.chatwoot.deleteMany({ where: { instanceId: instance.id } });
|
||||
await this.prismaRepository.proxy.deleteMany({ where: { instanceId: instance.id } });
|
||||
await this.prismaRepository.rabbitmq.deleteMany({ where: { instanceId: instance.id } });
|
||||
await this.prismaRepository.nats.deleteMany({ where: { instanceId: instance.id } });
|
||||
await this.prismaRepository.sqs.deleteMany({ where: { instanceId: instance.id } });
|
||||
await this.prismaRepository.integrationSession.deleteMany({ where: { instanceId: instance.id } });
|
||||
await this.prismaRepository.typebot.deleteMany({ where: { instanceId: instance.id } });
|
||||
|
@ -15,6 +15,7 @@ export enum Events {
|
||||
MESSAGES_UPDATE = 'messages.update',
|
||||
MESSAGES_DELETE = 'messages.delete',
|
||||
SEND_MESSAGE = 'send.message',
|
||||
SEND_MESSAGE_UPDATE = 'send.message.update',
|
||||
CONTACTS_SET = 'contacts.set',
|
||||
CONTACTS_UPSERT = 'contacts.upsert',
|
||||
CONTACTS_UPDATE = 'contacts.update',
|
||||
|
@ -72,6 +72,7 @@ export type EventsRabbitmq = {
|
||||
MESSAGES_UPDATE: boolean;
|
||||
MESSAGES_DELETE: boolean;
|
||||
SEND_MESSAGE: boolean;
|
||||
SEND_MESSAGE_UPDATE: boolean;
|
||||
CONTACTS_SET: boolean;
|
||||
CONTACTS_UPDATE: boolean;
|
||||
CONTACTS_UPSERT: boolean;
|
||||
@ -97,7 +98,16 @@ export type Rabbitmq = {
|
||||
EXCHANGE_NAME: string;
|
||||
GLOBAL_ENABLED: boolean;
|
||||
EVENTS: EventsRabbitmq;
|
||||
PREFIX_KEY: string;
|
||||
PREFIX_KEY?: string;
|
||||
};
|
||||
|
||||
export type Nats = {
|
||||
ENABLED: boolean;
|
||||
URI: string;
|
||||
EXCHANGE_NAME: string;
|
||||
GLOBAL_ENABLED: boolean;
|
||||
EVENTS: EventsRabbitmq;
|
||||
PREFIX_KEY?: string;
|
||||
};
|
||||
|
||||
export type Sqs = {
|
||||
@ -131,6 +141,7 @@ export type EventsWebhook = {
|
||||
MESSAGES_UPDATE: boolean;
|
||||
MESSAGES_DELETE: boolean;
|
||||
SEND_MESSAGE: boolean;
|
||||
SEND_MESSAGE_UPDATE: boolean;
|
||||
CONTACTS_SET: boolean;
|
||||
CONTACTS_UPDATE: boolean;
|
||||
CONTACTS_UPSERT: boolean;
|
||||
@ -163,6 +174,7 @@ export type EventsPusher = {
|
||||
MESSAGES_UPDATE: boolean;
|
||||
MESSAGES_DELETE: boolean;
|
||||
SEND_MESSAGE: boolean;
|
||||
SEND_MESSAGE_UPDATE: boolean;
|
||||
CONTACTS_SET: boolean;
|
||||
CONTACTS_UPDATE: boolean;
|
||||
CONTACTS_UPSERT: boolean;
|
||||
@ -251,6 +263,7 @@ export type S3 = {
|
||||
PORT?: number;
|
||||
USE_SSL?: boolean;
|
||||
REGION?: string;
|
||||
SKIP_POLICY?: boolean;
|
||||
};
|
||||
|
||||
export type CacheConf = { REDIS: CacheConfRedis; LOCAL: CacheConfLocal };
|
||||
@ -263,6 +276,7 @@ export interface Env {
|
||||
PROVIDER: ProviderSession;
|
||||
DATABASE: Database;
|
||||
RABBITMQ: Rabbitmq;
|
||||
NATS: Nats;
|
||||
SQS: Sqs;
|
||||
WEBSOCKET: Websocket;
|
||||
WA_BUSINESS: WaBusiness;
|
||||
@ -356,7 +370,7 @@ export class ConfigService {
|
||||
RABBITMQ: {
|
||||
ENABLED: process.env?.RABBITMQ_ENABLED === 'true',
|
||||
GLOBAL_ENABLED: process.env?.RABBITMQ_GLOBAL_ENABLED === 'true',
|
||||
PREFIX_KEY: process.env?.RABBITMQ_PREFIX_KEY || 'evolution',
|
||||
PREFIX_KEY: process.env?.RABBITMQ_PREFIX_KEY,
|
||||
EXCHANGE_NAME: process.env?.RABBITMQ_EXCHANGE_NAME || 'evolution_exchange',
|
||||
URI: process.env.RABBITMQ_URI || '',
|
||||
EVENTS: {
|
||||
@ -370,6 +384,7 @@ export class ConfigService {
|
||||
MESSAGES_UPDATE: process.env?.RABBITMQ_EVENTS_MESSAGES_UPDATE === 'true',
|
||||
MESSAGES_DELETE: process.env?.RABBITMQ_EVENTS_MESSAGES_DELETE === 'true',
|
||||
SEND_MESSAGE: process.env?.RABBITMQ_EVENTS_SEND_MESSAGE === 'true',
|
||||
SEND_MESSAGE_UPDATE: process.env?.RABBITMQ_EVENTS_SEND_MESSAGE_UPDATE === 'true',
|
||||
CONTACTS_SET: process.env?.RABBITMQ_EVENTS_CONTACTS_SET === 'true',
|
||||
CONTACTS_UPDATE: process.env?.RABBITMQ_EVENTS_CONTACTS_UPDATE === 'true',
|
||||
CONTACTS_UPSERT: process.env?.RABBITMQ_EVENTS_CONTACTS_UPSERT === 'true',
|
||||
@ -389,6 +404,43 @@ export class ConfigService {
|
||||
TYPEBOT_CHANGE_STATUS: process.env?.RABBITMQ_EVENTS_TYPEBOT_CHANGE_STATUS === 'true',
|
||||
},
|
||||
},
|
||||
NATS: {
|
||||
ENABLED: process.env?.NATS_ENABLED === 'true',
|
||||
GLOBAL_ENABLED: process.env?.NATS_GLOBAL_ENABLED === 'true',
|
||||
PREFIX_KEY: process.env?.NATS_PREFIX_KEY,
|
||||
EXCHANGE_NAME: process.env?.NATS_EXCHANGE_NAME || 'evolution_exchange',
|
||||
URI: process.env.NATS_URI || '',
|
||||
EVENTS: {
|
||||
APPLICATION_STARTUP: process.env?.NATS_EVENTS_APPLICATION_STARTUP === 'true',
|
||||
INSTANCE_CREATE: process.env?.NATS_EVENTS_INSTANCE_CREATE === 'true',
|
||||
INSTANCE_DELETE: process.env?.NATS_EVENTS_INSTANCE_DELETE === 'true',
|
||||
QRCODE_UPDATED: process.env?.NATS_EVENTS_QRCODE_UPDATED === 'true',
|
||||
MESSAGES_SET: process.env?.NATS_EVENTS_MESSAGES_SET === 'true',
|
||||
MESSAGES_UPSERT: process.env?.NATS_EVENTS_MESSAGES_UPSERT === 'true',
|
||||
MESSAGES_EDITED: process.env?.NATS_EVENTS_MESSAGES_EDITED === 'true',
|
||||
MESSAGES_UPDATE: process.env?.NATS_EVENTS_MESSAGES_UPDATE === 'true',
|
||||
MESSAGES_DELETE: process.env?.NATS_EVENTS_MESSAGES_DELETE === 'true',
|
||||
SEND_MESSAGE: process.env?.NATS_EVENTS_SEND_MESSAGE === 'true',
|
||||
SEND_MESSAGE_UPDATE: process.env?.NATS_EVENTS_SEND_MESSAGE_UPDATE === 'true',
|
||||
CONTACTS_SET: process.env?.NATS_EVENTS_CONTACTS_SET === 'true',
|
||||
CONTACTS_UPDATE: process.env?.NATS_EVENTS_CONTACTS_UPDATE === 'true',
|
||||
CONTACTS_UPSERT: process.env?.NATS_EVENTS_CONTACTS_UPSERT === 'true',
|
||||
PRESENCE_UPDATE: process.env?.NATS_EVENTS_PRESENCE_UPDATE === 'true',
|
||||
CHATS_SET: process.env?.NATS_EVENTS_CHATS_SET === 'true',
|
||||
CHATS_UPDATE: process.env?.NATS_EVENTS_CHATS_UPDATE === 'true',
|
||||
CHATS_UPSERT: process.env?.NATS_EVENTS_CHATS_UPSERT === 'true',
|
||||
CHATS_DELETE: process.env?.NATS_EVENTS_CHATS_DELETE === 'true',
|
||||
CONNECTION_UPDATE: process.env?.NATS_EVENTS_CONNECTION_UPDATE === 'true',
|
||||
LABELS_EDIT: process.env?.NATS_EVENTS_LABELS_EDIT === 'true',
|
||||
LABELS_ASSOCIATION: process.env?.NATS_EVENTS_LABELS_ASSOCIATION === 'true',
|
||||
GROUPS_UPSERT: process.env?.NATS_EVENTS_GROUPS_UPSERT === 'true',
|
||||
GROUP_UPDATE: process.env?.NATS_EVENTS_GROUPS_UPDATE === 'true',
|
||||
GROUP_PARTICIPANTS_UPDATE: process.env?.NATS_EVENTS_GROUP_PARTICIPANTS_UPDATE === 'true',
|
||||
CALL: process.env?.NATS_EVENTS_CALL === 'true',
|
||||
TYPEBOT_START: process.env?.NATS_EVENTS_TYPEBOT_START === 'true',
|
||||
TYPEBOT_CHANGE_STATUS: process.env?.NATS_EVENTS_TYPEBOT_CHANGE_STATUS === 'true',
|
||||
},
|
||||
},
|
||||
SQS: {
|
||||
ENABLED: process.env?.SQS_ENABLED === 'true',
|
||||
ACCESS_KEY_ID: process.env.SQS_ACCESS_KEY_ID || '',
|
||||
@ -421,6 +473,7 @@ export class ConfigService {
|
||||
MESSAGES_UPDATE: process.env?.PUSHER_EVENTS_MESSAGES_UPDATE === 'true',
|
||||
MESSAGES_DELETE: process.env?.PUSHER_EVENTS_MESSAGES_DELETE === 'true',
|
||||
SEND_MESSAGE: process.env?.PUSHER_EVENTS_SEND_MESSAGE === 'true',
|
||||
SEND_MESSAGE_UPDATE: process.env?.PUSHER_EVENTS_SEND_MESSAGE_UPDATE === 'true',
|
||||
CONTACTS_SET: process.env?.PUSHER_EVENTS_CONTACTS_SET === 'true',
|
||||
CONTACTS_UPDATE: process.env?.PUSHER_EVENTS_CONTACTS_UPDATE === 'true',
|
||||
CONTACTS_UPSERT: process.env?.PUSHER_EVENTS_CONTACTS_UPSERT === 'true',
|
||||
@ -477,6 +530,7 @@ export class ConfigService {
|
||||
MESSAGES_UPDATE: process.env?.WEBHOOK_EVENTS_MESSAGES_UPDATE === 'true',
|
||||
MESSAGES_DELETE: process.env?.WEBHOOK_EVENTS_MESSAGES_DELETE === 'true',
|
||||
SEND_MESSAGE: process.env?.WEBHOOK_EVENTS_SEND_MESSAGE === 'true',
|
||||
SEND_MESSAGE_UPDATE: process.env?.WEBHOOK_EVENTS_SEND_MESSAGE_UPDATE === 'true',
|
||||
CONTACTS_SET: process.env?.WEBHOOK_EVENTS_CONTACTS_SET === 'true',
|
||||
CONTACTS_UPDATE: process.env?.WEBHOOK_EVENTS_CONTACTS_UPDATE === 'true',
|
||||
CONTACTS_UPSERT: process.env?.WEBHOOK_EVENTS_CONTACTS_UPSERT === 'true',
|
||||
@ -555,6 +609,7 @@ export class ConfigService {
|
||||
PORT: Number.parseInt(process.env?.S3_PORT || '9000'),
|
||||
USE_SSL: process.env?.S3_USE_SSL === 'true',
|
||||
REGION: process.env?.S3_REGION,
|
||||
SKIP_POLICY: process.env?.S3_SKIP_POLICY === 'true',
|
||||
},
|
||||
AUTHENTICATION: {
|
||||
API_KEY: {
|
||||
|
@ -3,19 +3,19 @@ import { configService, S3 } from '@config/env.config';
|
||||
const getTypeMessage = (msg: any) => {
|
||||
let mediaId: string;
|
||||
|
||||
if (configService.get<S3>('S3').ENABLE) mediaId = msg.message.mediaUrl;
|
||||
else mediaId = msg.key.id;
|
||||
if (configService.get<S3>('S3').ENABLE) mediaId = msg.message?.mediaUrl;
|
||||
else mediaId = msg.key?.id;
|
||||
|
||||
const types = {
|
||||
conversation: msg?.message?.conversation,
|
||||
extendedTextMessage: msg?.message?.extendedTextMessage?.text,
|
||||
contactMessage: msg?.message?.contactMessage?.displayName,
|
||||
locationMessage: msg?.message?.locationMessage?.degreesLatitude,
|
||||
locationMessage: msg?.message?.locationMessage?.degreesLatitude.toString(),
|
||||
viewOnceMessageV2:
|
||||
msg?.message?.viewOnceMessageV2?.message?.imageMessage?.url ||
|
||||
msg?.message?.viewOnceMessageV2?.message?.videoMessage?.url ||
|
||||
msg?.message?.viewOnceMessageV2?.message?.audioMessage?.url,
|
||||
listResponseMessage: msg?.message?.listResponseMessage?.title,
|
||||
listResponseMessage: msg?.message?.listResponseMessage?.title || msg?.listResponseMessage?.title,
|
||||
responseRowId: msg?.message?.listResponseMessage?.singleSelectReply?.selectedRowId,
|
||||
templateButtonReplyMessage:
|
||||
msg?.message?.templateButtonReplyMessage?.selectedId || msg?.message?.buttonsResponseMessage?.selectedButtonId,
|
||||
|
17
src/validate/business.schema.ts
Normal file
17
src/validate/business.schema.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import { JSONSchema7 } from 'json-schema';
|
||||
|
||||
export const catalogSchema: JSONSchema7 = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
number: { type: 'string' },
|
||||
limit: { type: 'number' },
|
||||
},
|
||||
};
|
||||
|
||||
export const collectionsSchema: JSONSchema7 = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
number: { type: 'string' },
|
||||
limit: { type: 'number' },
|
||||
},
|
||||
};
|
@ -68,6 +68,7 @@ export const instanceSchema: JSONSchema7 = {
|
||||
'MESSAGES_UPDATE',
|
||||
'MESSAGES_DELETE',
|
||||
'SEND_MESSAGE',
|
||||
'SEND_MESSAGE_UPDATE',
|
||||
'CONTACTS_SET',
|
||||
'CONTACTS_UPSERT',
|
||||
'CONTACTS_UPDATE',
|
||||
@ -104,6 +105,44 @@ export const instanceSchema: JSONSchema7 = {
|
||||
'MESSAGES_UPDATE',
|
||||
'MESSAGES_DELETE',
|
||||
'SEND_MESSAGE',
|
||||
'SEND_MESSAGE_UPDATE',
|
||||
'CONTACTS_SET',
|
||||
'CONTACTS_UPSERT',
|
||||
'CONTACTS_UPDATE',
|
||||
'PRESENCE_UPDATE',
|
||||
'CHATS_SET',
|
||||
'CHATS_UPSERT',
|
||||
'CHATS_UPDATE',
|
||||
'CHATS_DELETE',
|
||||
'GROUPS_UPSERT',
|
||||
'GROUP_UPDATE',
|
||||
'GROUP_PARTICIPANTS_UPDATE',
|
||||
'CONNECTION_UPDATE',
|
||||
'LABELS_EDIT',
|
||||
'LABELS_ASSOCIATION',
|
||||
'CALL',
|
||||
'TYPEBOT_START',
|
||||
'TYPEBOT_CHANGE_STATUS',
|
||||
],
|
||||
},
|
||||
},
|
||||
// NATS
|
||||
natsEnabled: { type: 'boolean' },
|
||||
natsEvents: {
|
||||
type: 'array',
|
||||
minItems: 0,
|
||||
items: {
|
||||
type: 'string',
|
||||
enum: [
|
||||
'APPLICATION_STARTUP',
|
||||
'QRCODE_UPDATED',
|
||||
'MESSAGES_SET',
|
||||
'MESSAGES_UPSERT',
|
||||
'MESSAGES_EDITED',
|
||||
'MESSAGES_UPDATE',
|
||||
'MESSAGES_DELETE',
|
||||
'SEND_MESSAGE',
|
||||
'SEND_MESSAGE_UPDATE',
|
||||
'CONTACTS_SET',
|
||||
'CONTACTS_UPSERT',
|
||||
'CONTACTS_UPDATE',
|
||||
@ -140,6 +179,7 @@ export const instanceSchema: JSONSchema7 = {
|
||||
'MESSAGES_UPDATE',
|
||||
'MESSAGES_DELETE',
|
||||
'SEND_MESSAGE',
|
||||
'SEND_MESSAGE_UPDATE',
|
||||
'CONTACTS_SET',
|
||||
'CONTACTS_UPSERT',
|
||||
'CONTACTS_UPDATE',
|
||||
|
@ -1,4 +1,5 @@
|
||||
// Integrations Schema
|
||||
export * from './business.schema';
|
||||
export * from './chat.schema';
|
||||
export * from './group.schema';
|
||||
export * from './instance.schema';
|
||||
|
Loading…
Reference in New Issue
Block a user