mirror of
https://github.com/EvolutionAPI/evolution-api.git
synced 2025-12-19 03:42:23 -06:00
Compare commits
146 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8fe75cd210 | ||
|
|
303effebbc | ||
|
|
f32a34190d | ||
|
|
8588ef1d8a | ||
|
|
51ec4821f3 | ||
|
|
957033a7bb | ||
|
|
62c74deac3 | ||
|
|
29fd448998 | ||
|
|
e55cb08a6a | ||
|
|
047359e8dc | ||
|
|
eb814e181a | ||
|
|
857031ff5a | ||
|
|
d5eeb68714 | ||
|
|
966b287026 | ||
|
|
a348729109 | ||
|
|
1f29b7733e | ||
|
|
e26ae30f6f | ||
|
|
547943a05c | ||
|
|
46aa229531 | ||
|
|
28bd796289 | ||
|
|
6a3f82ed7e | ||
|
|
523f3301c0 | ||
|
|
f085343a99 | ||
|
|
f76a924700 | ||
|
|
f8e3b76a4a | ||
|
|
b8f1e8a7ef | ||
|
|
d007fc49d8 | ||
|
|
f6d8ebd8d3 | ||
|
|
6ff9c4578a | ||
|
|
33acfe1464 | ||
|
|
f5eeb16bb1 | ||
|
|
c35c5faaa4 | ||
|
|
8425ebc13f | ||
|
|
6fc37a4298 | ||
|
|
99f3e77c12 | ||
|
|
a9c087c45f | ||
|
|
3f4333087f | ||
|
|
e1ac29683d | ||
|
|
5c74cbfe19 | ||
|
|
3fdb3fa673 | ||
|
|
ba584974cb | ||
|
|
bddd6408ac | ||
|
|
413ad66a07 | ||
|
|
0ef5d884cc | ||
|
|
f6b6d23e93 | ||
|
|
50be69f3d3 | ||
|
|
5a75e4d5e6 | ||
|
|
ec463df9d6 | ||
|
|
b648334323 | ||
|
|
916972aeb1 | ||
|
|
3893e01af9 | ||
|
|
7835f32c8b | ||
|
|
37302244ee | ||
|
|
f1be7ddb83 | ||
|
|
7447a65a83 | ||
|
|
129009d602 | ||
|
|
c656bd6f4b | ||
|
|
36528d7484 | ||
|
|
f9c85b6a67 | ||
|
|
4dfe6bdbe8 | ||
|
|
ee343f2fa1 | ||
|
|
1a1f5f85b2 | ||
|
|
bd64b0c884 | ||
|
|
e1c8928ed9 | ||
|
|
1f98940445 | ||
|
|
707aa22a6d | ||
|
|
32da15fa8a | ||
|
|
97cd6e289a | ||
|
|
def6576fb9 | ||
|
|
196c10b253 | ||
|
|
cfdca38d59 | ||
|
|
ccd90a69ee | ||
|
|
c16f962d2b | ||
|
|
8fccf69ceb | ||
|
|
adc8833670 | ||
|
|
ecbf90ded8 | ||
|
|
c5d2d7782a | ||
|
|
72dae22ef4 | ||
|
|
9a9cd41009 | ||
|
|
0c4c8162c2 | ||
|
|
d47cc5d5f4 | ||
|
|
cb942e512d | ||
|
|
bd0a479645 | ||
|
|
cd0da914f4 | ||
|
|
7a7e72897a | ||
|
|
c1542054c9 | ||
|
|
250e67e7ae | ||
|
|
cdbe839b35 | ||
|
|
240a77dcce | ||
|
|
1dc5bb8bd1 | ||
|
|
23534da27d | ||
|
|
8652d4031c | ||
|
|
384e311c7a | ||
|
|
2791f88b4c | ||
|
|
402d5af19a | ||
|
|
b554d8c19c | ||
|
|
7d9dd64303 | ||
|
|
41bea8931f | ||
|
|
f83d8de476 | ||
|
|
af2a652098 | ||
|
|
a3c911263d | ||
|
|
836c677837 | ||
|
|
7e4dbfdd7e | ||
|
|
6eda556242 | ||
|
|
3ea454c7ed | ||
|
|
9123d7014d | ||
|
|
3ea3abe81a | ||
|
|
7289c3d7f9 | ||
|
|
d00e1df29c | ||
|
|
16a18c4f22 | ||
|
|
30b156d92f | ||
|
|
931f9d33e4 | ||
|
|
e61fe4d092 | ||
|
|
5bc33ac654 | ||
|
|
878ba5a869 | ||
|
|
92632b2b96 | ||
|
|
d803a9e298 | ||
|
|
b9f67533dd | ||
|
|
7ade78bedf | ||
|
|
b4eca48f4d | ||
|
|
2cf8e317fa | ||
|
|
821a422ab5 | ||
|
|
91c7b4f2cd | ||
|
|
adb43ec5b3 | ||
|
|
c9721d7bc9 | ||
|
|
bbc2b8a396 | ||
|
|
54cfa67d52 | ||
|
|
9b72b3e332 | ||
|
|
c03919be2d | ||
|
|
706cc6f49c | ||
|
|
10c7e81e02 | ||
|
|
03637b2d4d | ||
|
|
b7218a05be | ||
|
|
a2cd57d9c6 | ||
|
|
001849eeaa | ||
|
|
bf09a70096 | ||
|
|
29e429a02e | ||
|
|
907a0ee135 | ||
|
|
22a03f77ab | ||
|
|
9761b10bf6 | ||
|
|
6e401eecde | ||
|
|
f32e259d2f | ||
|
|
83ed0e6454 | ||
|
|
e26b440a66 | ||
|
|
d6194316e1 | ||
|
|
04a6f7c954 |
51
CHANGELOG.md
51
CHANGELOG.md
@@ -1,3 +1,54 @@
|
||||
# 1.5.4 (2023-10-09 20:43)
|
||||
|
||||
### Fixed
|
||||
|
||||
* Baileys logger typing issue resolved
|
||||
* Solved problem with duplicate messages in chatwoot
|
||||
|
||||
# 1.5.3 (2023-10-06 18:55)
|
||||
|
||||
### Feature
|
||||
|
||||
* Swagger documentation
|
||||
* Added base 64 sending option via webhook
|
||||
|
||||
### Fixed
|
||||
|
||||
* Remove rabbitmq queues when delete instances
|
||||
* Improvement in restart instance to completely redo the connection
|
||||
* Update node version: v20
|
||||
* Correction of messages sent by the api and typebot not appearing in chatwoot
|
||||
* Adjustment to start typebot, added startSession parameter
|
||||
* Chatwoot now receives messages sent via api and typebot
|
||||
* Fixed problem with starting with an input in typebot
|
||||
* Added check to ensure variables are not empty before executing foreach in start typebot
|
||||
|
||||
# 1.5.2 (2023-09-28 17:56)
|
||||
|
||||
### Fixed
|
||||
|
||||
* Fix chatwootSchema in chatwoot model to store reopen_conversation and conversation_pending options
|
||||
* Problem resolved when sending files from minio to typebot
|
||||
* Improvement in the "startTypebot" method to create persistent session when triggered
|
||||
* New manager for Evo 1.5.2 - Set Typebot update
|
||||
* Resolved problems when reading/querying instances
|
||||
|
||||
# 1.5.1 (2023-09-17 13:50)
|
||||
|
||||
### Feature
|
||||
|
||||
* Added listening_from_me option in Set Typebot
|
||||
* Added variables options in Start Typebot
|
||||
* Added webhooks for typebot events
|
||||
* Added ChamaAI integration
|
||||
* Added webhook to send errors
|
||||
* Added support for messaging with ads on chatwoot
|
||||
|
||||
### Fixed
|
||||
|
||||
* Fix looping connection messages in chatwoot
|
||||
* Improved performance of fetch instances
|
||||
|
||||
# 1.5.0 (2023-08-18 12:47)
|
||||
|
||||
### Feature
|
||||
|
||||
@@ -81,11 +81,19 @@ WEBHOOK_EVENTS_CONNECTION_UPDATE=true
|
||||
WEBHOOK_EVENTS_CALL=true
|
||||
# This event fires every time a new token is requested via the refresh route
|
||||
WEBHOOK_EVENTS_NEW_JWT_TOKEN=false
|
||||
# This events is used with Typebot
|
||||
WEBHOOK_EVENTS_TYPEBOT_START=false
|
||||
WEBHOOK_EVENTS_TYPEBOT_CHANGE_STATUS=false
|
||||
# This event is used with Chama AI
|
||||
WEBHOOK_EVENTS_CHAMA_AI_ACTION=false
|
||||
# This event is used to send errors
|
||||
WEBHOOK_EVENTS_ERRORS=false
|
||||
WEBHOOK_EVENTS_ERRORS_WEBHOOK=
|
||||
|
||||
# Name that will be displayed on smartphone connection
|
||||
CONFIG_SESSION_PHONE_CLIENT=EvolutionAPI
|
||||
# Browser Name = chrome | firefox | edge | opera | safari
|
||||
CONFIG_SESSION_PHONE_NAME=chrome
|
||||
# Browser Name = Chrome | Firefox | Edge | Opera | Safari
|
||||
CONFIG_SESSION_PHONE_NAME=Chrome
|
||||
|
||||
# Set qrcode display limit
|
||||
QRCODE_LIMIT=30
|
||||
|
||||
22
Docker/docker-compose.yaml
Normal file
22
Docker/docker-compose.yaml
Normal file
@@ -0,0 +1,22 @@
|
||||
version: '3.3'
|
||||
|
||||
services:
|
||||
|
||||
api:
|
||||
container_name: evolution_api
|
||||
image: davidsongomes/evolution-api
|
||||
restart: always
|
||||
ports:
|
||||
- 8080:8080
|
||||
volumes:
|
||||
- evolution_instances:/evolution/instances
|
||||
- evolution_store:/evolution/store
|
||||
env_file:
|
||||
- .env
|
||||
command: ['node', './dist/src/main.js']
|
||||
expose:
|
||||
- 8080
|
||||
|
||||
volumes:
|
||||
evolution_instances:
|
||||
evolution_store:
|
||||
109
Docker/evolution-api-all-services/.env
Normal file
109
Docker/evolution-api-all-services/.env
Normal file
@@ -0,0 +1,109 @@
|
||||
# Server URL - Set your application url
|
||||
SERVER_URL='http://localhost:8080'
|
||||
|
||||
# Cors - * for all or set separate by commas - ex.: 'yourdomain1.com, yourdomain2.com'
|
||||
CORS_ORIGIN='*'
|
||||
CORS_METHODS='POST,GET,PUT,DELETE'
|
||||
CORS_CREDENTIALS=true
|
||||
|
||||
# Determine the logs to be displayed
|
||||
LOG_LEVEL='ERROR,WARN,DEBUG,INFO,LOG,VERBOSE,DARK,WEBHOOKS'
|
||||
LOG_COLOR=true
|
||||
# Log Baileys - "fatal" | "error" | "warn" | "info" | "debug" | "trace"
|
||||
LOG_BAILEYS=error
|
||||
|
||||
# Determine how long the instance should be deleted from memory in case of no connection.
|
||||
# Default time: 5 minutes
|
||||
# If you don't even want an expiration, enter the value false
|
||||
DEL_INSTANCE=false
|
||||
|
||||
# Temporary data storage
|
||||
STORE_MESSAGES=true
|
||||
STORE_MESSAGE_UP=true
|
||||
STORE_CONTACTS=true
|
||||
STORE_CHATS=true
|
||||
|
||||
# Set Store Interval in Seconds (7200 = 2h)
|
||||
CLEAN_STORE_CLEANING_INTERVAL=7200
|
||||
CLEAN_STORE_MESSAGES=true
|
||||
CLEAN_STORE_MESSAGE_UP=true
|
||||
CLEAN_STORE_CONTACTS=true
|
||||
CLEAN_STORE_CHATS=true
|
||||
|
||||
# Permanent data storage
|
||||
DATABASE_ENABLED=true
|
||||
DATABASE_CONNECTION_URI=mongodb://root:root@mongodb:27017/?authSource=admin&readPreference=primary&ssl=false&directConnection=true
|
||||
DATABASE_CONNECTION_DB_PREFIX_NAME=evolution
|
||||
|
||||
# Choose the data you want to save in the application's database or store
|
||||
DATABASE_SAVE_DATA_INSTANCE=false
|
||||
DATABASE_SAVE_DATA_NEW_MESSAGE=false
|
||||
DATABASE_SAVE_MESSAGE_UPDATE=false
|
||||
DATABASE_SAVE_DATA_CONTACTS=false
|
||||
DATABASE_SAVE_DATA_CHATS=false
|
||||
|
||||
REDIS_ENABLED=true
|
||||
REDIS_URI=redis://redis:6379
|
||||
REDIS_PREFIX_KEY=evolution
|
||||
|
||||
# Global Webhook Settings
|
||||
# Each instance's Webhook URL and events will be requested at the time it is created
|
||||
## Define a global webhook that will listen for enabled events from all instances
|
||||
WEBHOOK_GLOBAL_URL=''
|
||||
WEBHOOK_GLOBAL_ENABLED=false
|
||||
# With this option activated, you work with a url per webhook event, respecting the global url and the name of each event
|
||||
WEBHOOK_GLOBAL_WEBHOOK_BY_EVENTS=false
|
||||
## Set the events you want to hear
|
||||
WEBHOOK_EVENTS_APPLICATION_STARTUP=false
|
||||
WEBHOOK_EVENTS_QRCODE_UPDATED=true
|
||||
WEBHOOK_EVENTS_MESSAGES_SET=true
|
||||
WEBHOOK_EVENTS_MESSAGES_UPSERT=true
|
||||
WEBHOOK_EVENTS_MESSAGES_UPDATE=true
|
||||
WEBHOOK_EVENTS_MESSAGES_DELETE=true
|
||||
WEBHOOK_EVENTS_SEND_MESSAGE=true
|
||||
WEBHOOK_EVENTS_CONTACTS_SET=true
|
||||
WEBHOOK_EVENTS_CONTACTS_UPSERT=true
|
||||
WEBHOOK_EVENTS_CONTACTS_UPDATE=true
|
||||
WEBHOOK_EVENTS_PRESENCE_UPDATE=true
|
||||
WEBHOOK_EVENTS_CHATS_SET=true
|
||||
WEBHOOK_EVENTS_CHATS_UPSERT=true
|
||||
WEBHOOK_EVENTS_CHATS_UPDATE=true
|
||||
WEBHOOK_EVENTS_CHATS_DELETE=true
|
||||
WEBHOOK_EVENTS_GROUPS_UPSERT=true
|
||||
WEBHOOK_EVENTS_GROUPS_UPDATE=true
|
||||
WEBHOOK_EVENTS_GROUP_PARTICIPANTS_UPDATE=true
|
||||
WEBHOOK_EVENTS_CONNECTION_UPDATE=true
|
||||
# This event fires every time a new token is requested via the refresh route
|
||||
WEBHOOK_EVENTS_NEW_JWT_TOKEN=false
|
||||
|
||||
# Name that will be displayed on smartphone connection
|
||||
CONFIG_SESSION_PHONE_CLIENT='Evolution API'
|
||||
# Browser Name = chrome | firefox | edge | opera | safari
|
||||
CONFIG_SESSION_PHONE_NAME=chrome
|
||||
|
||||
# Set qrcode display limit
|
||||
QRCODE_LIMIT=30
|
||||
|
||||
# Defines an authentication type for the api
|
||||
# We recommend using the apikey because it will allow you to use a custom token,
|
||||
# if you use jwt, a random token will be generated and may be expired and you will have to generate a new token
|
||||
# jwt or 'apikey'
|
||||
AUTHENTICATION_TYPE='apikey'
|
||||
## Define a global apikey to access all instances.
|
||||
### OBS: This key must be inserted in the request header to create an instance.
|
||||
AUTHENTICATION_API_KEY='B6D711FCDE4D4FD5936544120E713976'
|
||||
AUTHENTICATION_EXPOSE_IN_FETCH_INSTANCES=true
|
||||
## Set the secret key to encrypt and decrypt your token and its expiration time
|
||||
# seconds - 3600s ===1h | zero (0) - never expires
|
||||
AUTHENTICATION_JWT_EXPIRIN_IN=0
|
||||
AUTHENTICATION_JWT_SECRET='L0YWtjb2w554WFqPG'
|
||||
# Set the instance name and webhook url to create an instance in init the application
|
||||
# With this option activated, you work with a url per webhook event, respecting the local url and the name of each event
|
||||
# container or server
|
||||
AUTHENTICATION_INSTANCE_MODE=server
|
||||
# if you are using container mode, set the container name and the webhook url to default instance
|
||||
AUTHENTICATION_INSTANCE_NAME=evolution
|
||||
AUTHENTICATION_INSTANCE_WEBHOOK_URL=''
|
||||
AUTHENTICATION_INSTANCE_CHATWOOT_ACCOUNT_ID=1
|
||||
AUTHENTICATION_INSTANCE_CHATWOOT_TOKEN=123456
|
||||
AUTHENTICATION_INSTANCE_CHATWOOT_URL=''
|
||||
91
Docker/evolution-api-all-services/docker-compose.yaml
Normal file
91
Docker/evolution-api-all-services/docker-compose.yaml
Normal file
@@ -0,0 +1,91 @@
|
||||
version: '3.3'
|
||||
|
||||
services:
|
||||
|
||||
mongodb:
|
||||
container_name: mongodb
|
||||
image: mongo
|
||||
restart: on-failure
|
||||
ports:
|
||||
- 27017:27017
|
||||
environment:
|
||||
- MONGO_INITDB_ROOT_USERNAME=root
|
||||
- MONGO_INITDB_ROOT_PASSWORD=root
|
||||
- PUID=1000
|
||||
- PGID=1000
|
||||
volumes:
|
||||
- evolution_mongodb_data:/data/db
|
||||
- evolution_mongodb_configdb:/data/configdb
|
||||
expose:
|
||||
- 27017
|
||||
|
||||
mongo-express:
|
||||
container_name: mongodb-express
|
||||
image: mongo-express
|
||||
restart: on-failure
|
||||
ports:
|
||||
- 8081:8081
|
||||
depends_on:
|
||||
- mongodb
|
||||
environment:
|
||||
ME_CONFIG_BASICAUTH_USERNAME: root
|
||||
ME_CONFIG_BASICAUTH_PASSWORD: root
|
||||
ME_CONFIG_MONGODB_SERVER: mongodb
|
||||
ME_CONFIG_MONGODB_ADMINUSERNAME: root
|
||||
ME_CONFIG_MONGODB_ADMINPASSWORD: root
|
||||
links:
|
||||
- mongodb
|
||||
|
||||
redis:
|
||||
container_name: redis
|
||||
image: redis:latest
|
||||
restart: on-failure
|
||||
ports:
|
||||
- 6379:6379
|
||||
command: >
|
||||
redis-server
|
||||
--port 6379
|
||||
--appendonly yes
|
||||
volumes:
|
||||
- evolution_redis:/data
|
||||
|
||||
rebrow:
|
||||
container_name: rebrow
|
||||
image: marian/rebrow
|
||||
restart: on-failure
|
||||
depends_on:
|
||||
- redis
|
||||
ports:
|
||||
- 5001:5001
|
||||
links:
|
||||
- redis
|
||||
|
||||
api:
|
||||
container_name: evolution_api
|
||||
image: davidsongomes/evolution-api
|
||||
restart: always
|
||||
depends_on:
|
||||
- mongodb
|
||||
- redis
|
||||
ports:
|
||||
- 8080:8080
|
||||
volumes:
|
||||
- evolution_instances:/evolution/instances
|
||||
- evolution_store:/evolution/store
|
||||
env_file:
|
||||
- .env
|
||||
command: ['node', './dist/src/main.js']
|
||||
expose:
|
||||
- 8080
|
||||
|
||||
volumes:
|
||||
evolution_mongodb_data:
|
||||
evolution_mongodb_configdb:
|
||||
evolution_redis:
|
||||
evolution_instances:
|
||||
evolution_store:
|
||||
|
||||
networks:
|
||||
evolution-net:
|
||||
external: true
|
||||
|
||||
@@ -35,7 +35,8 @@ volumes:
|
||||
evolution_mongodb_data:
|
||||
evolution_mongodb_configdb:
|
||||
|
||||
|
||||
networks:
|
||||
evolution-net:
|
||||
external: true
|
||||
|
||||
name: evolution-net
|
||||
driver: bridge
|
||||
|
||||
@@ -5,17 +5,17 @@ services:
|
||||
image: redis:latest
|
||||
container_name: redis
|
||||
command: >
|
||||
redis-server
|
||||
--port 6379
|
||||
--appendonly yes
|
||||
redis-server --port 6379 --appendonly yes
|
||||
volumes:
|
||||
- evolution_redis:/data
|
||||
ports:
|
||||
- 6379:6379
|
||||
|
||||
|
||||
volumes:
|
||||
evolution_redis:
|
||||
|
||||
|
||||
networks:
|
||||
evolution-net:
|
||||
external: true
|
||||
name: evolution-net
|
||||
driver: bridge
|
||||
|
||||
14
Dockerfile
14
Dockerfile
@@ -1,6 +1,6 @@
|
||||
FROM node:16.18-alpine
|
||||
FROM node:20.7.0-alpine
|
||||
|
||||
LABEL version="1.5.0" description="Api to control whatsapp features through http requests."
|
||||
LABEL version="1.5.4" description="Api to control whatsapp features through http requests."
|
||||
LABEL maintainer="Davidson Gomes" git="https://github.com/DavidsonGomes"
|
||||
LABEL contact="contato@agenciadgcode.com"
|
||||
|
||||
@@ -84,8 +84,16 @@ ENV WEBHOOK_EVENTS_CALL=true
|
||||
|
||||
ENV WEBHOOK_EVENTS_NEW_JWT_TOKEN=false
|
||||
|
||||
ENV WEBHOOK_EVENTS_TYPEBOT_START=false
|
||||
ENV WEBHOOK_EVENTS_TYPEBOT_CHANGE_STATUS=false
|
||||
|
||||
ENV WEBHOOK_EVENTS_CHAMA_AI_ACTION=false
|
||||
|
||||
ENV WEBHOOK_EVENTS_ERRORS=false
|
||||
ENV WEBHOOK_EVENTS_ERRORS_WEBHOOK=
|
||||
|
||||
ENV CONFIG_SESSION_PHONE_CLIENT=EvolutionAPI
|
||||
ENV CONFIG_SESSION_PHONE_NAME=chrome
|
||||
ENV CONFIG_SESSION_PHONE_NAME=Chrome
|
||||
|
||||
ENV QRCODE_LIMIT=30
|
||||
ENV QRCODE_COLOR=#198754
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -4,6 +4,7 @@ services:
|
||||
api:
|
||||
container_name: evolution_api
|
||||
image: evolution/api:local
|
||||
build: .
|
||||
restart: always
|
||||
ports:
|
||||
- 8080:8080
|
||||
@@ -24,5 +25,5 @@ volumes:
|
||||
|
||||
networks:
|
||||
evolution-net:
|
||||
external: true
|
||||
|
||||
name: evolution-net
|
||||
driver: bridge
|
||||
|
||||
@@ -4,6 +4,7 @@ services:
|
||||
api:
|
||||
container_name: evolution_api
|
||||
image: evolution/api:local
|
||||
build: .
|
||||
restart: always
|
||||
ports:
|
||||
- 8080:8080
|
||||
@@ -75,5 +76,5 @@ volumes:
|
||||
|
||||
networks:
|
||||
evolution-net:
|
||||
external: true
|
||||
|
||||
name: evolution-net
|
||||
driver: bridge
|
||||
|
||||
@@ -24,5 +24,5 @@ volumes:
|
||||
|
||||
networks:
|
||||
evolution-net:
|
||||
external: true
|
||||
|
||||
name: evolution-net
|
||||
driver: bridge
|
||||
|
||||
13
docker.sh
13
docker.sh
@@ -1,13 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
NET='evolution-net'
|
||||
IMAGE='evolution/api:local'
|
||||
|
||||
if !(docker network ls | grep ${NET} > /dev/null)
|
||||
then
|
||||
docker network create -d bridge ${NET}
|
||||
fi
|
||||
|
||||
docker build -t ${IMAGE} .
|
||||
|
||||
docker compose up -d
|
||||
13
package.json
13
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "evolution-api",
|
||||
"version": "1.5.0",
|
||||
"version": "1.5.4",
|
||||
"description": "Rest api for communication with WhatsApp",
|
||||
"main": "./dist/src/main.js",
|
||||
"scripts": {
|
||||
@@ -43,10 +43,10 @@
|
||||
"dependencies": {
|
||||
"@adiwajshing/keyed-db": "^0.2.4",
|
||||
"@ffmpeg-installer/ffmpeg": "^1.1.0",
|
||||
"@figuro/chatwoot-sdk": "^1.1.14",
|
||||
"@figuro/chatwoot-sdk": "^1.1.16",
|
||||
"@hapi/boom": "^10.0.1",
|
||||
"@sentry/node": "^7.59.2",
|
||||
"@whiskeysockets/baileys": "github:EvolutionAPI/Baileys",
|
||||
"@whiskeysockets/baileys": "^6.5.0",
|
||||
"amqplib": "^0.10.3",
|
||||
"axios": "^1.3.5",
|
||||
"class-validator": "^0.13.2",
|
||||
@@ -69,6 +69,7 @@
|
||||
"mongoose": "^6.10.5",
|
||||
"node-cache": "^5.1.2",
|
||||
"node-mime-types": "^1.1.0",
|
||||
"node-windows": "^1.0.0-beta.8",
|
||||
"pino": "^8.11.0",
|
||||
"proxy-agent": "^6.3.0",
|
||||
"qrcode": "^1.5.1",
|
||||
@@ -76,7 +77,10 @@
|
||||
"redis": "^4.6.5",
|
||||
"sharp": "^0.30.7",
|
||||
"socket.io": "^4.7.1",
|
||||
"uuid": "^9.0.0"
|
||||
"socks-proxy-agent": "^8.0.1",
|
||||
"swagger-ui-express": "^5.0.0",
|
||||
"uuid": "^9.0.0",
|
||||
"yamljs": "^0.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/compression": "^1.7.2",
|
||||
@@ -86,6 +90,7 @@
|
||||
"@types/jsonwebtoken": "^8.5.9",
|
||||
"@types/mime-types": "^2.1.1",
|
||||
"@types/node": "^18.15.11",
|
||||
"@types/node-windows": "^0.1.2",
|
||||
"@types/qrcode": "^1.5.0",
|
||||
"@types/qrcode-terminal": "^0.12.0",
|
||||
"@types/uuid": "^8.3.4",
|
||||
|
||||
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
@@ -70,6 +70,10 @@ export type Websocket = {
|
||||
ENABLED: boolean;
|
||||
};
|
||||
|
||||
export type Chatwoot = {
|
||||
USE_REPLY_ID: boolean;
|
||||
};
|
||||
|
||||
export type EventsWebhook = {
|
||||
APPLICATION_STARTUP: boolean;
|
||||
QRCODE_UPDATED: boolean;
|
||||
@@ -92,6 +96,11 @@ export type EventsWebhook = {
|
||||
GROUP_PARTICIPANTS_UPDATE: boolean;
|
||||
CALL: boolean;
|
||||
NEW_JWT_TOKEN: boolean;
|
||||
TYPEBOT_START: boolean;
|
||||
TYPEBOT_CHANGE_STATUS: boolean;
|
||||
CHAMA_AI_ACTION: boolean;
|
||||
ERRORS: boolean;
|
||||
ERRORS_WEBHOOK: string;
|
||||
};
|
||||
|
||||
export type ApiKey = { KEY: string };
|
||||
@@ -134,6 +143,7 @@ export interface Env {
|
||||
QRCODE: QrCode;
|
||||
AUTHENTICATION: Auth;
|
||||
PRODUCTION?: Production;
|
||||
CHATWOOT?: Chatwoot;
|
||||
}
|
||||
|
||||
export type Key = keyof Env;
|
||||
@@ -166,17 +176,17 @@ export class ConfigService {
|
||||
return {
|
||||
SERVER: {
|
||||
TYPE: process.env.SERVER_TYPE as 'http' | 'https',
|
||||
PORT: Number.parseInt(process.env.SERVER_PORT),
|
||||
PORT: Number.parseInt(process.env.SERVER_PORT) || 8080,
|
||||
URL: process.env.SERVER_URL,
|
||||
},
|
||||
CORS: {
|
||||
ORIGIN: process.env.CORS_ORIGIN.split(','),
|
||||
METHODS: process.env.CORS_METHODS.split(',') as HttpMethods[],
|
||||
ORIGIN: process.env.CORS_ORIGIN.split(',') || ['*'],
|
||||
METHODS: (process.env.CORS_METHODS.split(',') as HttpMethods[]) || ['POST', 'GET', 'PUT', 'DELETE'],
|
||||
CREDENTIALS: process.env?.CORS_CREDENTIALS === 'true',
|
||||
},
|
||||
SSL_CONF: {
|
||||
PRIVKEY: process.env?.SSL_CONF_PRIVKEY,
|
||||
FULLCHAIN: process.env?.SSL_CONF_FULLCHAIN,
|
||||
PRIVKEY: process.env?.SSL_CONF_PRIVKEY || '',
|
||||
FULLCHAIN: process.env?.SSL_CONF_FULLCHAIN || '',
|
||||
},
|
||||
STORE: {
|
||||
MESSAGES: process.env?.STORE_MESSAGES === 'true',
|
||||
@@ -195,8 +205,8 @@ export class ConfigService {
|
||||
},
|
||||
DATABASE: {
|
||||
CONNECTION: {
|
||||
URI: process.env.DATABASE_CONNECTION_URI,
|
||||
DB_PREFIX_NAME: process.env.DATABASE_CONNECTION_DB_PREFIX_NAME,
|
||||
URI: process.env.DATABASE_CONNECTION_URI || '',
|
||||
DB_PREFIX_NAME: process.env.DATABASE_CONNECTION_DB_PREFIX_NAME || 'evolution',
|
||||
},
|
||||
ENABLED: process.env?.DATABASE_ENABLED === 'true',
|
||||
SAVE_DATA: {
|
||||
@@ -209,18 +219,27 @@ export class ConfigService {
|
||||
},
|
||||
REDIS: {
|
||||
ENABLED: process.env?.REDIS_ENABLED === 'true',
|
||||
URI: process.env.REDIS_URI,
|
||||
PREFIX_KEY: process.env.REDIS_PREFIX_KEY,
|
||||
URI: process.env.REDIS_URI || '',
|
||||
PREFIX_KEY: process.env.REDIS_PREFIX_KEY || 'evolution',
|
||||
},
|
||||
RABBITMQ: {
|
||||
ENABLED: process.env?.RABBITMQ_ENABLED === 'true',
|
||||
URI: process.env.RABBITMQ_URI,
|
||||
URI: process.env.RABBITMQ_URI || '',
|
||||
},
|
||||
WEBSOCKET: {
|
||||
ENABLED: process.env?.WEBSOCKET_ENABLED === 'true',
|
||||
},
|
||||
LOG: {
|
||||
LEVEL: process.env?.LOG_LEVEL.split(',') as LogLevel[],
|
||||
LEVEL: (process.env?.LOG_LEVEL.split(',') as LogLevel[]) || [
|
||||
'ERROR',
|
||||
'WARN',
|
||||
'DEBUG',
|
||||
'INFO',
|
||||
'LOG',
|
||||
'VERBOSE',
|
||||
'DARK',
|
||||
'WEBHOOKS',
|
||||
],
|
||||
COLOR: process.env?.LOG_COLOR === 'true',
|
||||
BAILEYS: (process.env?.LOG_BAILEYS as LogBaileys) || 'error',
|
||||
},
|
||||
@@ -229,7 +248,7 @@ export class ConfigService {
|
||||
: Number.parseInt(process.env.DEL_INSTANCE) || false,
|
||||
WEBHOOK: {
|
||||
GLOBAL: {
|
||||
URL: process.env?.WEBHOOK_GLOBAL_URL,
|
||||
URL: process.env?.WEBHOOK_GLOBAL_URL || '',
|
||||
ENABLED: process.env?.WEBHOOK_GLOBAL_ENABLED === 'true',
|
||||
WEBHOOK_BY_EVENTS: process.env?.WEBHOOK_GLOBAL_WEBHOOK_BY_EVENTS === 'true',
|
||||
},
|
||||
@@ -255,29 +274,37 @@ export class ConfigService {
|
||||
GROUP_PARTICIPANTS_UPDATE: process.env?.WEBHOOK_EVENTS_GROUP_PARTICIPANTS_UPDATE === 'true',
|
||||
CALL: process.env?.WEBHOOK_EVENTS_CALL === 'true',
|
||||
NEW_JWT_TOKEN: process.env?.WEBHOOK_EVENTS_NEW_JWT_TOKEN === 'true',
|
||||
TYPEBOT_START: process.env?.WEBHOOK_EVENTS_TYPEBOT_START === 'true',
|
||||
TYPEBOT_CHANGE_STATUS: process.env?.WEBHOOK_EVENTS_TYPEBOT_CHANGE_STATUS === 'true',
|
||||
CHAMA_AI_ACTION: process.env?.WEBHOOK_EVENTS_CHAMA_AI_ACTION === 'true',
|
||||
ERRORS: process.env?.WEBHOOK_EVENTS_ERRORS === 'true',
|
||||
ERRORS_WEBHOOK: process.env?.WEBHOOK_EVENTS_ERRORS_WEBHOOK || '',
|
||||
},
|
||||
},
|
||||
CONFIG_SESSION_PHONE: {
|
||||
CLIENT: process.env?.CONFIG_SESSION_PHONE_CLIENT || 'Evolution API',
|
||||
NAME: process.env?.CONFIG_SESSION_PHONE_NAME || 'chrome',
|
||||
NAME: process.env?.CONFIG_SESSION_PHONE_NAME || 'Chrome',
|
||||
},
|
||||
QRCODE: {
|
||||
LIMIT: Number.parseInt(process.env.QRCODE_LIMIT) || 30,
|
||||
COLOR: process.env.QRCODE_COLOR || '#198754',
|
||||
},
|
||||
AUTHENTICATION: {
|
||||
TYPE: process.env.AUTHENTICATION_TYPE as 'jwt',
|
||||
TYPE: process.env.AUTHENTICATION_TYPE as 'apikey',
|
||||
API_KEY: {
|
||||
KEY: process.env.AUTHENTICATION_API_KEY,
|
||||
KEY: process.env.AUTHENTICATION_API_KEY || 'BQYHJGJHJ',
|
||||
},
|
||||
EXPOSE_IN_FETCH_INSTANCES: process.env?.AUTHENTICATION_EXPOSE_IN_FETCH_INSTANCES === 'true',
|
||||
JWT: {
|
||||
EXPIRIN_IN: Number.isInteger(process.env?.AUTHENTICATION_JWT_EXPIRIN_IN)
|
||||
? Number.parseInt(process.env.AUTHENTICATION_JWT_EXPIRIN_IN)
|
||||
: 3600,
|
||||
SECRET: process.env.AUTHENTICATION_JWT_SECRET,
|
||||
SECRET: process.env.AUTHENTICATION_JWT_SECRET || 'L=0YWt]b2w[WF>#>:&E`',
|
||||
},
|
||||
},
|
||||
CHATWOOT: {
|
||||
USE_REPLY_ID: process.env?.USE_REPLY_ID === 'true',
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ export function onUnexpectedError() {
|
||||
stderr: process.stderr.fd,
|
||||
error,
|
||||
});
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
process.on('unhandledRejection', (error, origin) => {
|
||||
@@ -17,5 +18,6 @@ export function onUnexpectedError() {
|
||||
stderr: process.stderr.fd,
|
||||
error,
|
||||
});
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -83,7 +83,7 @@ RABBITMQ:
|
||||
ENABLED: false
|
||||
URI: "amqp://guest:guest@localhost:5672"
|
||||
|
||||
WEBSOCKET:
|
||||
WEBSOCKET:
|
||||
ENABLED: false
|
||||
|
||||
# Global Webhook Settings
|
||||
@@ -120,16 +120,24 @@ WEBHOOK:
|
||||
CALL: true
|
||||
# This event fires every time a new token is requested via the refresh route
|
||||
NEW_JWT_TOKEN: false
|
||||
# This events is used with Typebot
|
||||
TYPEBOT_START: false
|
||||
TYPEBOT_CHANGE_STATUS: false
|
||||
# This event is used with Chama AI
|
||||
CHAMA_AI_ACTION: false
|
||||
# This event is used to send errors to the webhook
|
||||
ERRORS: false
|
||||
ERRORS_WEBHOOK: <url>
|
||||
|
||||
CONFIG_SESSION_PHONE:
|
||||
# Name that will be displayed on smartphone connection
|
||||
CLIENT: "Evolution API"
|
||||
NAME: chrome # chrome | firefox | edge | opera | safari
|
||||
NAME: Chrome # Chrome | Firefox | Edge | Opera | Safari
|
||||
|
||||
# Set qrcode display limit
|
||||
QRCODE:
|
||||
LIMIT: 30
|
||||
COLOR: '#198754'
|
||||
COLOR: "#198754"
|
||||
|
||||
# Defines an authentication type for the api
|
||||
# We recommend using the apikey because it will allow you to use a custom token,
|
||||
@@ -145,4 +153,8 @@ AUTHENTICATION:
|
||||
# Set the secret key to encrypt and decrypt your token and its expiration time.
|
||||
JWT:
|
||||
EXPIRIN_IN: 0 # seconds - 3600s === 1h | zero (0) - never expires
|
||||
SECRET: L=0YWt]b2w[WF>#>:&E`
|
||||
SECRET: L=0YWt]b2w[WF>#>:&E`
|
||||
|
||||
# Configure to chatwoot
|
||||
CHATWOOT:
|
||||
USE_REPLY_ID: false
|
||||
|
||||
17
src/docs/swagger.conf.ts
Normal file
17
src/docs/swagger.conf.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { Router } from 'express';
|
||||
import { join } from 'path';
|
||||
import swaggerUi from 'swagger-ui-express';
|
||||
import YAML from 'yamljs';
|
||||
|
||||
const document = YAML.load(join(process.cwd(), 'src', 'docs', 'swagger.yaml'));
|
||||
|
||||
const router = Router();
|
||||
|
||||
export const swaggerRouter = router.use('/docs', swaggerUi.serve).get(
|
||||
'/docs',
|
||||
swaggerUi.setup(document, {
|
||||
customCssUrl: '/css/dark-theme-swagger.css',
|
||||
customSiteTitle: 'Evolution API',
|
||||
customfavIcon: '/images/logo.svg',
|
||||
}),
|
||||
);
|
||||
2597
src/docs/swagger.yaml
Normal file
2597
src/docs/swagger.yaml
Normal file
File diff suppressed because it is too large
Load Diff
@@ -41,3 +41,60 @@ export const initAMQP = () => {
|
||||
export const getAMQP = (): amqp.Channel | null => {
|
||||
return amqpChannel;
|
||||
};
|
||||
|
||||
export const initQueues = (instanceName: string, events: string[]) => {
|
||||
if (!events || !events.length) return;
|
||||
|
||||
const queues = events.map((event) => {
|
||||
return `${event.replace(/_/g, '.').toLowerCase()}`;
|
||||
});
|
||||
|
||||
queues.forEach((event) => {
|
||||
const amqp = getAMQP();
|
||||
const exchangeName = instanceName ?? 'evolution_exchange';
|
||||
|
||||
amqp.assertExchange(exchangeName, 'topic', {
|
||||
durable: true,
|
||||
autoDelete: false,
|
||||
});
|
||||
|
||||
const queueName = `${instanceName}.${event}`;
|
||||
|
||||
amqp.assertQueue(queueName, {
|
||||
durable: true,
|
||||
autoDelete: false,
|
||||
arguments: {
|
||||
'x-queue-type': 'quorum',
|
||||
},
|
||||
});
|
||||
|
||||
amqp.bindQueue(queueName, exchangeName, event);
|
||||
});
|
||||
};
|
||||
|
||||
export const removeQueues = (instanceName: string, events: string[]) => {
|
||||
if (!events || !events.length) return;
|
||||
|
||||
const channel = getAMQP();
|
||||
|
||||
const queues = events.map((event) => {
|
||||
return `${event.replace(/_/g, '.').toLowerCase()}`;
|
||||
});
|
||||
|
||||
const exchangeName = instanceName ?? 'evolution_exchange';
|
||||
|
||||
queues.forEach((event) => {
|
||||
const amqp = getAMQP();
|
||||
|
||||
amqp.assertExchange(exchangeName, 'topic', {
|
||||
durable: true,
|
||||
autoDelete: false,
|
||||
});
|
||||
|
||||
const queueName = `${instanceName}.${event}`;
|
||||
|
||||
amqp.deleteQueue(queueName);
|
||||
});
|
||||
|
||||
channel.deleteExchange(exchangeName);
|
||||
};
|
||||
|
||||
@@ -5,6 +5,10 @@ import { Redis } from '../config/env.config';
|
||||
import { Logger } from '../config/logger.config';
|
||||
|
||||
export class RedisCache {
|
||||
async disconnect() {
|
||||
await this.client.disconnect();
|
||||
this.statusConnection = false;
|
||||
}
|
||||
constructor() {
|
||||
this.logger.verbose('instance created');
|
||||
process.on('beforeExit', async () => {
|
||||
|
||||
@@ -11,7 +11,7 @@ let io: SocketIO;
|
||||
const cors = configService.get<Cors>('CORS').ORIGIN;
|
||||
|
||||
export const initIO = (httpServer: Server) => {
|
||||
if (configService.get<Websocket>('WEBSOCKET').ENABLED) {
|
||||
if (configService.get<Websocket>('WEBSOCKET')?.ENABLED) {
|
||||
io = new SocketIO(httpServer, {
|
||||
cors: {
|
||||
origin: cors,
|
||||
|
||||
41
src/main.ts
41
src/main.ts
@@ -1,14 +1,16 @@
|
||||
import 'express-async-errors';
|
||||
|
||||
import axios from 'axios';
|
||||
import compression from 'compression';
|
||||
import cors from 'cors';
|
||||
import express, { json, NextFunction, Request, Response, urlencoded } from 'express';
|
||||
import { join } from 'path';
|
||||
|
||||
import { configService, Cors, HttpServer, Rabbitmq } from './config/env.config';
|
||||
import { Auth, configService, Cors, HttpServer, Rabbitmq, Webhook } from './config/env.config';
|
||||
import { onUnexpectedError } from './config/error.config';
|
||||
import { Logger } from './config/logger.config';
|
||||
import { ROOT_DIR } from './config/path.config';
|
||||
import { swaggerRouter } from './docs/swagger.conf';
|
||||
import { initAMQP } from './libs/amqp.server';
|
||||
import { initIO } from './libs/socket.server';
|
||||
import { ServerUP } from './utils/server-up';
|
||||
@@ -47,11 +49,46 @@ function bootstrap() {
|
||||
app.set('views', join(ROOT_DIR, 'views'));
|
||||
app.use(express.static(join(ROOT_DIR, 'public')));
|
||||
|
||||
app.use('/store', express.static(join(ROOT_DIR, 'store')));
|
||||
|
||||
app.use('/', router);
|
||||
app.use(swaggerRouter);
|
||||
|
||||
app.use(
|
||||
(err: Error, req: Request, res: Response, next: NextFunction) => {
|
||||
if (err) {
|
||||
const webhook = configService.get<Webhook>('WEBHOOK');
|
||||
|
||||
if (webhook.EVENTS.ERRORS_WEBHOOK && webhook.EVENTS.ERRORS_WEBHOOK != '' && webhook.EVENTS.ERRORS) {
|
||||
const tzoffset = new Date().getTimezoneOffset() * 60000; //offset in milliseconds
|
||||
const localISOTime = new Date(Date.now() - tzoffset).toISOString();
|
||||
const now = localISOTime;
|
||||
const globalApiKey = configService.get<Auth>('AUTHENTICATION').API_KEY.KEY;
|
||||
const serverUrl = configService.get<HttpServer>('SERVER').URL;
|
||||
|
||||
const errorData = {
|
||||
event: 'error',
|
||||
data: {
|
||||
error: err['error'] || 'Internal Server Error',
|
||||
message: err['message'] || 'Internal Server Error',
|
||||
status: err['status'] || 500,
|
||||
response: {
|
||||
message: err['message'] || 'Internal Server Error',
|
||||
},
|
||||
},
|
||||
date_time: now,
|
||||
api_key: globalApiKey,
|
||||
server_url: serverUrl,
|
||||
};
|
||||
|
||||
logger.error(errorData);
|
||||
|
||||
const baseURL = webhook.EVENTS.ERRORS_WEBHOOK;
|
||||
const httpService = axios.create({ baseURL });
|
||||
|
||||
httpService.post('', errorData);
|
||||
}
|
||||
|
||||
return res.status(err['status'] || 500).json({
|
||||
status: err['status'] || 500,
|
||||
error: err['error'] || 'Internal Server Error',
|
||||
@@ -89,7 +126,7 @@ function bootstrap() {
|
||||
|
||||
initIO(server);
|
||||
|
||||
if (configService.get<Rabbitmq>('RABBITMQ').ENABLED) initAMQP();
|
||||
if (configService.get<Rabbitmq>('RABBITMQ')?.ENABLED) initAMQP();
|
||||
|
||||
onUnexpectedError();
|
||||
}
|
||||
|
||||
@@ -55,6 +55,9 @@ export const instanceNameSchema: JSONSchema7 = {
|
||||
'CONNECTION_UPDATE',
|
||||
'CALL',
|
||||
'NEW_JWT_TOKEN',
|
||||
'TYPEBOT_START',
|
||||
'TYPEBOT_CHANGE_STATUS',
|
||||
'CHAMA_AI_ACTION',
|
||||
],
|
||||
},
|
||||
},
|
||||
@@ -856,6 +859,9 @@ export const webhookSchema: JSONSchema7 = {
|
||||
'CONNECTION_UPDATE',
|
||||
'CALL',
|
||||
'NEW_JWT_TOKEN',
|
||||
'TYPEBOT_START',
|
||||
'TYPEBOT_CHANGE_STATUS',
|
||||
'CHAMA_AI_ACTION',
|
||||
],
|
||||
},
|
||||
},
|
||||
@@ -927,6 +933,9 @@ export const websocketSchema: JSONSchema7 = {
|
||||
'CONNECTION_UPDATE',
|
||||
'CALL',
|
||||
'NEW_JWT_TOKEN',
|
||||
'TYPEBOT_START',
|
||||
'TYPEBOT_CHANGE_STATUS',
|
||||
'CHAMA_AI_ACTION',
|
||||
],
|
||||
},
|
||||
},
|
||||
@@ -967,6 +976,9 @@ export const rabbitmqSchema: JSONSchema7 = {
|
||||
'CONNECTION_UPDATE',
|
||||
'CALL',
|
||||
'NEW_JWT_TOKEN',
|
||||
'TYPEBOT_START',
|
||||
'TYPEBOT_CHANGE_STATUS',
|
||||
'CHAMA_AI_ACTION',
|
||||
],
|
||||
},
|
||||
},
|
||||
@@ -985,9 +997,10 @@ export const typebotSchema: JSONSchema7 = {
|
||||
expire: { type: 'integer' },
|
||||
delay_message: { type: 'integer' },
|
||||
unknown_message: { type: 'string' },
|
||||
listening_from_me: { type: 'boolean', enum: [true, false] },
|
||||
},
|
||||
required: ['enabled', 'url', 'typebot', 'expire'],
|
||||
...isNotEmpty('enabled', 'url', 'typebot', 'expire'),
|
||||
required: ['enabled', 'url', 'typebot', 'expire', 'delay_message', 'unknown_message', 'listening_from_me'],
|
||||
...isNotEmpty('enabled', 'url', 'typebot', 'expire', 'delay_message', 'unknown_message', 'listening_from_me'),
|
||||
};
|
||||
|
||||
export const typebotStatusSchema: JSONSchema7 = {
|
||||
@@ -1023,3 +1036,17 @@ export const proxySchema: JSONSchema7 = {
|
||||
required: ['enabled', 'proxy'],
|
||||
...isNotEmpty('enabled', 'proxy'),
|
||||
};
|
||||
|
||||
export const chamaaiSchema: JSONSchema7 = {
|
||||
$id: v4(),
|
||||
type: 'object',
|
||||
properties: {
|
||||
enabled: { type: 'boolean', enum: [true, false] },
|
||||
url: { type: 'string' },
|
||||
token: { type: 'string' },
|
||||
waNumber: { type: 'string' },
|
||||
answerByAudio: { type: 'boolean', enum: [true, false] },
|
||||
},
|
||||
required: ['enabled', 'url', 'token', 'waNumber', 'answerByAudio'],
|
||||
...isNotEmpty('enabled', 'url', 'token', 'waNumber', 'answerByAudio'),
|
||||
};
|
||||
|
||||
29
src/whatsapp/controllers/chamaai.controller.ts
Normal file
29
src/whatsapp/controllers/chamaai.controller.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { Logger } from '../../config/logger.config';
|
||||
import { ChamaaiDto } from '../dto/chamaai.dto';
|
||||
import { InstanceDto } from '../dto/instance.dto';
|
||||
import { ChamaaiService } from '../services/chamaai.service';
|
||||
|
||||
const logger = new Logger('ChamaaiController');
|
||||
|
||||
export class ChamaaiController {
|
||||
constructor(private readonly chamaaiService: ChamaaiService) {}
|
||||
|
||||
public async createChamaai(instance: InstanceDto, data: ChamaaiDto) {
|
||||
logger.verbose('requested createChamaai from ' + instance.instanceName + ' instance');
|
||||
|
||||
if (!data.enabled) {
|
||||
logger.verbose('chamaai disabled');
|
||||
data.url = '';
|
||||
data.token = '';
|
||||
data.waNumber = '';
|
||||
data.answerByAudio = false;
|
||||
}
|
||||
|
||||
return this.chamaaiService.create(instance, data);
|
||||
}
|
||||
|
||||
public async findChamaai(instance: InstanceDto) {
|
||||
logger.verbose('requested findChamaai from ' + instance.instanceName + ' instance');
|
||||
return this.chamaaiService.find(instance);
|
||||
}
|
||||
}
|
||||
@@ -41,6 +41,7 @@ export class InstanceController {
|
||||
instanceName,
|
||||
webhook,
|
||||
webhook_by_events,
|
||||
webhook_base64,
|
||||
events,
|
||||
qrcode,
|
||||
number,
|
||||
@@ -67,6 +68,7 @@ export class InstanceController {
|
||||
typebot_keyword_finish,
|
||||
typebot_delay_message,
|
||||
typebot_unknown_message,
|
||||
typebot_listening_from_me,
|
||||
}: InstanceDto) {
|
||||
try {
|
||||
this.logger.verbose('requested createInstance from ' + instanceName + ' instance');
|
||||
@@ -126,6 +128,9 @@ export class InstanceController {
|
||||
'CONNECTION_UPDATE',
|
||||
'CALL',
|
||||
'NEW_JWT_TOKEN',
|
||||
'TYPEBOT_START',
|
||||
'TYPEBOT_CHANGE_STATUS',
|
||||
'CHAMA_AI_ACTION',
|
||||
];
|
||||
} else {
|
||||
newEvents = events;
|
||||
@@ -135,6 +140,7 @@ export class InstanceController {
|
||||
url: webhook,
|
||||
events: newEvents,
|
||||
webhook_by_events,
|
||||
webhook_base64,
|
||||
});
|
||||
|
||||
webhookEvents = (await this.webhookService.find(instance)).events;
|
||||
@@ -172,9 +178,12 @@ export class InstanceController {
|
||||
'CONNECTION_UPDATE',
|
||||
'CALL',
|
||||
'NEW_JWT_TOKEN',
|
||||
'TYPEBOT_START',
|
||||
'TYPEBOT_CHANGE_STATUS',
|
||||
'CHAMA_AI_ACTION',
|
||||
];
|
||||
} else {
|
||||
newEvents = events;
|
||||
newEvents = websocket_events;
|
||||
}
|
||||
this.websocketService.create(instance, {
|
||||
enabled: true,
|
||||
@@ -216,9 +225,12 @@ export class InstanceController {
|
||||
'CONNECTION_UPDATE',
|
||||
'CALL',
|
||||
'NEW_JWT_TOKEN',
|
||||
'TYPEBOT_START',
|
||||
'TYPEBOT_CHANGE_STATUS',
|
||||
'CHAMA_AI_ACTION',
|
||||
];
|
||||
} else {
|
||||
newEvents = events;
|
||||
newEvents = rabbitmq_events;
|
||||
}
|
||||
this.rabbitmqService.create(instance, {
|
||||
enabled: true,
|
||||
@@ -247,6 +259,7 @@ export class InstanceController {
|
||||
keyword_finish: typebot_keyword_finish,
|
||||
delay_message: typebot_delay_message,
|
||||
unknown_message: typebot_unknown_message,
|
||||
listening_from_me: typebot_listening_from_me,
|
||||
});
|
||||
} catch (error) {
|
||||
this.logger.log(error);
|
||||
@@ -286,6 +299,7 @@ export class InstanceController {
|
||||
webhook: {
|
||||
webhook,
|
||||
webhook_by_events,
|
||||
webhook_base64,
|
||||
events: webhookEvents,
|
||||
},
|
||||
websocket: {
|
||||
@@ -304,6 +318,7 @@ export class InstanceController {
|
||||
keyword_finish: typebot_keyword_finish,
|
||||
delay_message: typebot_delay_message,
|
||||
unknown_message: typebot_unknown_message,
|
||||
listening_from_me: typebot_listening_from_me,
|
||||
},
|
||||
settings,
|
||||
qrcode: getQrcode,
|
||||
@@ -378,6 +393,7 @@ export class InstanceController {
|
||||
webhook: {
|
||||
webhook,
|
||||
webhook_by_events,
|
||||
webhook_base64,
|
||||
events: webhookEvents,
|
||||
},
|
||||
websocket: {
|
||||
@@ -396,6 +412,7 @@ export class InstanceController {
|
||||
keyword_finish: typebot_keyword_finish,
|
||||
delay_message: typebot_delay_message,
|
||||
unknown_message: typebot_unknown_message,
|
||||
listening_from_me: typebot_listening_from_me,
|
||||
},
|
||||
settings,
|
||||
chatwoot: {
|
||||
@@ -462,10 +479,19 @@ export class InstanceController {
|
||||
try {
|
||||
this.logger.verbose('requested restartInstance from ' + instanceName + ' instance');
|
||||
|
||||
this.logger.verbose('logging out instance: ' + instanceName);
|
||||
this.waMonitor.waInstances[instanceName]?.client?.ws?.close();
|
||||
const instance = this.waMonitor.waInstances[instanceName];
|
||||
const state = instance?.connectionStatus?.state;
|
||||
|
||||
return { status: 'SUCCESS', error: false, response: { message: 'Instance restarted' } };
|
||||
switch (state) {
|
||||
case 'open':
|
||||
this.logger.verbose('logging out instance: ' + instanceName);
|
||||
await instance.reloadConnection();
|
||||
await delay(2000);
|
||||
|
||||
return await this.connectionState({ instanceName });
|
||||
default:
|
||||
return await this.connectionState({ instanceName });
|
||||
}
|
||||
} catch (error) {
|
||||
this.logger.error(error);
|
||||
}
|
||||
@@ -482,12 +508,13 @@ export class InstanceController {
|
||||
}
|
||||
|
||||
public async fetchInstances({ instanceName }: InstanceDto) {
|
||||
this.logger.verbose('requested fetchInstances from ' + instanceName + ' instance');
|
||||
if (instanceName) {
|
||||
this.logger.verbose('requested fetchInstances from ' + instanceName + ' instance');
|
||||
this.logger.verbose('instanceName: ' + instanceName);
|
||||
return this.waMonitor.instanceInfo(instanceName);
|
||||
}
|
||||
|
||||
this.logger.verbose('requested fetchInstances (all instances)');
|
||||
return this.waMonitor.instanceInfo();
|
||||
}
|
||||
|
||||
@@ -520,6 +547,8 @@ export class InstanceController {
|
||||
throw new BadRequestException('The "' + instanceName + '" instance needs to be disconnected');
|
||||
}
|
||||
try {
|
||||
this.waMonitor.waInstances[instanceName]?.removeRabbitmqQueues();
|
||||
|
||||
if (instance.state === 'connecting') {
|
||||
this.logger.verbose('logging out instance: ' + instanceName);
|
||||
|
||||
|
||||
@@ -40,6 +40,9 @@ export class RabbitmqController {
|
||||
'CONNECTION_UPDATE',
|
||||
'CALL',
|
||||
'NEW_JWT_TOKEN',
|
||||
'TYPEBOT_START',
|
||||
'TYPEBOT_CHANGE_STATUS',
|
||||
'CHAMA_AI_ACTION',
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -1,15 +1,21 @@
|
||||
import { Request, Response } from 'express';
|
||||
|
||||
import { ConfigService } from '../../config/env.config';
|
||||
import { Auth, ConfigService, HttpServer } from '../../config/env.config';
|
||||
import { HttpStatus } from '../routers/index.router';
|
||||
import { WAMonitoringService } from '../services/monitor.service';
|
||||
|
||||
export class ViewsController {
|
||||
constructor(private readonly waMonit: WAMonitoringService, private readonly configService: ConfigService) {}
|
||||
constructor(private readonly waMonitor: WAMonitoringService, private readonly configService: ConfigService) {}
|
||||
|
||||
public async manager(request: Request, response: Response) {
|
||||
try {
|
||||
return response.status(HttpStatus.OK).render('manager');
|
||||
const token = this.configService.get<Auth>('AUTHENTICATION').API_KEY.KEY;
|
||||
const port = this.configService.get<HttpServer>('SERVER').PORT;
|
||||
|
||||
const instances = await this.waMonitor.instanceInfo();
|
||||
|
||||
console.log('INSTANCES: ', instances);
|
||||
return response.status(HttpStatus.OK).render('manager', { token, port, instances });
|
||||
} catch (error) {
|
||||
console.log('ERROR: ', error);
|
||||
}
|
||||
|
||||
@@ -48,6 +48,9 @@ export class WebhookController {
|
||||
'CONNECTION_UPDATE',
|
||||
'CALL',
|
||||
'NEW_JWT_TOKEN',
|
||||
'TYPEBOT_START',
|
||||
'TYPEBOT_CHANGE_STATUS',
|
||||
'CHAMA_AI_ACTION',
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -40,6 +40,9 @@ export class WebsocketController {
|
||||
'CONNECTION_UPDATE',
|
||||
'CALL',
|
||||
'NEW_JWT_TOKEN',
|
||||
'TYPEBOT_START',
|
||||
'TYPEBOT_CHANGE_STATUS',
|
||||
'CHAMA_AI_ACTION',
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
7
src/whatsapp/dto/chamaai.dto.ts
Normal file
7
src/whatsapp/dto/chamaai.dto.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
export class ChamaaiDto {
|
||||
enabled: boolean;
|
||||
url: string;
|
||||
token: string;
|
||||
waNumber: string;
|
||||
answerByAudio: boolean;
|
||||
}
|
||||
@@ -5,6 +5,7 @@ export class InstanceDto {
|
||||
token?: string;
|
||||
webhook?: string;
|
||||
webhook_by_events?: boolean;
|
||||
webhook_base64?: boolean;
|
||||
events?: string[];
|
||||
reject_call?: boolean;
|
||||
msg_call?: string;
|
||||
@@ -28,6 +29,7 @@ export class InstanceDto {
|
||||
typebot_keyword_finish?: string;
|
||||
typebot_delay_message?: number;
|
||||
typebot_unknown_message?: string;
|
||||
typebot_listening_from_me?: boolean;
|
||||
proxy_enabled?: boolean;
|
||||
proxy_proxy?: string;
|
||||
}
|
||||
|
||||
@@ -4,6 +4,13 @@ export class Session {
|
||||
status?: string;
|
||||
createdAt?: number;
|
||||
updateAt?: number;
|
||||
prefilledVariables?: PrefilledVariables;
|
||||
}
|
||||
|
||||
export class PrefilledVariables {
|
||||
remoteJid?: string;
|
||||
pushName?: string;
|
||||
additionalData?: { [key: string]: any };
|
||||
}
|
||||
|
||||
export class TypebotDto {
|
||||
@@ -14,5 +21,6 @@ export class TypebotDto {
|
||||
keyword_finish?: string;
|
||||
delay_message?: number;
|
||||
unknown_message?: string;
|
||||
listening_from_me?: boolean;
|
||||
sessions?: Session[];
|
||||
}
|
||||
|
||||
@@ -3,4 +3,5 @@ export class WebhookDto {
|
||||
url?: string;
|
||||
events?: string[];
|
||||
webhook_by_events?: boolean;
|
||||
webhook_base64?: boolean;
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@ async function jwtGuard(req: Request, res: Response, next: NextFunction) {
|
||||
}
|
||||
}
|
||||
|
||||
async function apikey(req: Request, res: Response, next: NextFunction) {
|
||||
async function apikey(req: Request, _: Response, next: NextFunction) {
|
||||
const env = configService.get<Auth>('AUTHENTICATION').API_KEY;
|
||||
const key = req.get('apikey');
|
||||
|
||||
|
||||
@@ -4,31 +4,40 @@ import { join } from 'path';
|
||||
|
||||
import { configService, Database, Redis } from '../../config/env.config';
|
||||
import { INSTANCE_DIR } from '../../config/path.config';
|
||||
import { BadRequestException, ForbiddenException, NotFoundException } from '../../exceptions';
|
||||
import {
|
||||
BadRequestException,
|
||||
ForbiddenException,
|
||||
InternalServerErrorException,
|
||||
NotFoundException,
|
||||
} from '../../exceptions';
|
||||
import { dbserver } from '../../libs/db.connect';
|
||||
import { InstanceDto } from '../dto/instance.dto';
|
||||
import { cache, waMonitor } from '../whatsapp.module';
|
||||
|
||||
async function getInstance(instanceName: string) {
|
||||
const db = configService.get<Database>('DATABASE');
|
||||
const redisConf = configService.get<Redis>('REDIS');
|
||||
try {
|
||||
const db = configService.get<Database>('DATABASE');
|
||||
const redisConf = configService.get<Redis>('REDIS');
|
||||
|
||||
const exists = !!waMonitor.waInstances[instanceName];
|
||||
const exists = !!waMonitor.waInstances[instanceName];
|
||||
|
||||
if (redisConf.ENABLED) {
|
||||
const keyExists = await cache.keyExists();
|
||||
return exists || keyExists;
|
||||
if (redisConf.ENABLED) {
|
||||
const keyExists = await cache.keyExists();
|
||||
return exists || keyExists;
|
||||
}
|
||||
|
||||
if (db.ENABLED) {
|
||||
const collection = dbserver
|
||||
.getClient()
|
||||
.db(db.CONNECTION.DB_PREFIX_NAME + '-instances')
|
||||
.collection(instanceName);
|
||||
return exists || (await collection.find({}).toArray()).length > 0;
|
||||
}
|
||||
|
||||
return exists || existsSync(join(INSTANCE_DIR, instanceName));
|
||||
} catch (error) {
|
||||
throw new InternalServerErrorException(error?.toString());
|
||||
}
|
||||
|
||||
if (db.ENABLED) {
|
||||
const collection = dbserver
|
||||
.getClient()
|
||||
.db(db.CONNECTION.DB_PREFIX_NAME + '-instances')
|
||||
.collection(instanceName);
|
||||
return exists || (await collection.find({}).toArray()).length > 0;
|
||||
}
|
||||
|
||||
return exists || existsSync(join(INSTANCE_DIR, instanceName));
|
||||
}
|
||||
|
||||
export async function instanceExistsGuard(req: Request, _: Response, next: NextFunction) {
|
||||
@@ -56,6 +65,7 @@ export async function instanceLoggedGuard(req: Request, _: Response, next: NextF
|
||||
}
|
||||
|
||||
if (waMonitor.waInstances[instance.instanceName]) {
|
||||
waMonitor.waInstances[instance.instanceName]?.removeRabbitmqQueues();
|
||||
delete waMonitor.waInstances[instance.instanceName];
|
||||
}
|
||||
}
|
||||
|
||||
24
src/whatsapp/models/chamaai.model.ts
Normal file
24
src/whatsapp/models/chamaai.model.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { Schema } from 'mongoose';
|
||||
|
||||
import { dbserver } from '../../libs/db.connect';
|
||||
|
||||
export class ChamaaiRaw {
|
||||
_id?: string;
|
||||
enabled?: boolean;
|
||||
url?: string;
|
||||
token?: string;
|
||||
waNumber?: string;
|
||||
answerByAudio?: boolean;
|
||||
}
|
||||
|
||||
const chamaaiSchema = new Schema<ChamaaiRaw>({
|
||||
_id: { type: String, _id: true },
|
||||
enabled: { type: Boolean, required: true },
|
||||
url: { type: String, required: true },
|
||||
token: { type: String, required: true },
|
||||
waNumber: { type: String, required: true },
|
||||
answerByAudio: { type: Boolean, required: true },
|
||||
});
|
||||
|
||||
export const ChamaaiModel = dbserver?.model(ChamaaiRaw.name, chamaaiSchema, 'chamaai');
|
||||
export type IChamaaiModel = typeof ChamaaiModel;
|
||||
@@ -24,6 +24,8 @@ const chatwootSchema = new Schema<ChatwootRaw>({
|
||||
name_inbox: { type: String, required: true },
|
||||
sign_msg: { type: Boolean, required: true },
|
||||
number: { type: String, required: true },
|
||||
reopen_conversation: { type: Boolean, required: true },
|
||||
conversation_pending: { type: Boolean, required: true },
|
||||
});
|
||||
|
||||
export const ChatwootModel = dbserver?.model(ChatwootRaw.name, chatwootSchema, 'chatwoot');
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
export * from './auth.model';
|
||||
export * from './chamaai.model';
|
||||
export * from './chat.model';
|
||||
export * from './chatwoot.model';
|
||||
export * from './contact.model';
|
||||
|
||||
@@ -20,6 +20,8 @@ export class MessageRaw {
|
||||
messageTimestamp?: number | Long.Long;
|
||||
owner: string;
|
||||
source?: 'android' | 'web' | 'ios';
|
||||
source_id?: string;
|
||||
source_reply_id?: string;
|
||||
}
|
||||
|
||||
const messageSchema = new Schema<MessageRaw>({
|
||||
|
||||
@@ -8,6 +8,11 @@ class Session {
|
||||
status?: string;
|
||||
createdAt?: number;
|
||||
updateAt?: number;
|
||||
prefilledVariables?: {
|
||||
remoteJid?: string;
|
||||
pushName?: string;
|
||||
additionalData?: { [key: string]: any };
|
||||
};
|
||||
}
|
||||
|
||||
export class TypebotRaw {
|
||||
@@ -19,6 +24,7 @@ export class TypebotRaw {
|
||||
keyword_finish?: string;
|
||||
delay_message?: number;
|
||||
unknown_message?: string;
|
||||
listening_from_me?: boolean;
|
||||
sessions?: Session[];
|
||||
}
|
||||
|
||||
@@ -31,6 +37,7 @@ const typebotSchema = new Schema<TypebotRaw>({
|
||||
keyword_finish: { type: String, required: true },
|
||||
delay_message: { type: Number, required: true },
|
||||
unknown_message: { type: String, required: true },
|
||||
listening_from_me: { type: Boolean, required: true },
|
||||
sessions: [
|
||||
{
|
||||
remoteJid: { type: String, required: true },
|
||||
@@ -38,6 +45,11 @@ const typebotSchema = new Schema<TypebotRaw>({
|
||||
status: { type: String, required: true },
|
||||
createdAt: { type: Number, required: true },
|
||||
updateAt: { type: Number, required: true },
|
||||
prefilledVariables: {
|
||||
remoteJid: { type: String, required: false },
|
||||
pushName: { type: String, required: false },
|
||||
additionalData: { type: Schema.Types.Mixed, required: false },
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
@@ -8,6 +8,7 @@ export class WebhookRaw {
|
||||
enabled?: boolean;
|
||||
events?: string[];
|
||||
webhook_by_events?: boolean;
|
||||
webhook_base64?: boolean;
|
||||
}
|
||||
|
||||
const webhookSchema = new Schema<WebhookRaw>({
|
||||
@@ -16,6 +17,7 @@ const webhookSchema = new Schema<WebhookRaw>({
|
||||
enabled: { type: Boolean, required: true },
|
||||
events: { type: [String], required: true },
|
||||
webhook_by_events: { type: Boolean, required: true },
|
||||
webhook_base64: { type: Boolean, required: true },
|
||||
});
|
||||
|
||||
export const WebhookModel = dbserver?.model(WebhookRaw.name, webhookSchema, 'webhook');
|
||||
|
||||
62
src/whatsapp/repository/chamaai.repository.ts
Normal file
62
src/whatsapp/repository/chamaai.repository.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
import { readFileSync } from 'fs';
|
||||
import { join } from 'path';
|
||||
|
||||
import { ConfigService } from '../../config/env.config';
|
||||
import { Logger } from '../../config/logger.config';
|
||||
import { IInsert, Repository } from '../abstract/abstract.repository';
|
||||
import { ChamaaiRaw, IChamaaiModel } from '../models';
|
||||
|
||||
export class ChamaaiRepository extends Repository {
|
||||
constructor(private readonly chamaaiModel: IChamaaiModel, private readonly configService: ConfigService) {
|
||||
super(configService);
|
||||
}
|
||||
|
||||
private readonly logger = new Logger('ChamaaiRepository');
|
||||
|
||||
public async create(data: ChamaaiRaw, instance: string): Promise<IInsert> {
|
||||
try {
|
||||
this.logger.verbose('creating chamaai');
|
||||
if (this.dbSettings.ENABLED) {
|
||||
this.logger.verbose('saving chamaai to db');
|
||||
const insert = await this.chamaaiModel.replaceOne({ _id: instance }, { ...data }, { upsert: true });
|
||||
|
||||
this.logger.verbose('chamaai saved to db: ' + insert.modifiedCount + ' chamaai');
|
||||
return { insertCount: insert.modifiedCount };
|
||||
}
|
||||
|
||||
this.logger.verbose('saving chamaai to store');
|
||||
|
||||
this.writeStore<ChamaaiRaw>({
|
||||
path: join(this.storePath, 'chamaai'),
|
||||
fileName: instance,
|
||||
data,
|
||||
});
|
||||
|
||||
this.logger.verbose('chamaai saved to store in path: ' + join(this.storePath, 'chamaai') + '/' + instance);
|
||||
|
||||
this.logger.verbose('chamaai created');
|
||||
return { insertCount: 1 };
|
||||
} catch (error) {
|
||||
return error;
|
||||
}
|
||||
}
|
||||
|
||||
public async find(instance: string): Promise<ChamaaiRaw> {
|
||||
try {
|
||||
this.logger.verbose('finding chamaai');
|
||||
if (this.dbSettings.ENABLED) {
|
||||
this.logger.verbose('finding chamaai in db');
|
||||
return await this.chamaaiModel.findOne({ _id: instance });
|
||||
}
|
||||
|
||||
this.logger.verbose('finding chamaai in store');
|
||||
return JSON.parse(
|
||||
readFileSync(join(this.storePath, 'chamaai', instance + '.json'), {
|
||||
encoding: 'utf-8',
|
||||
}),
|
||||
) as ChamaaiRaw;
|
||||
} catch (error) {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import { join } from 'path';
|
||||
import { Auth, ConfigService, Database } from '../../config/env.config';
|
||||
import { Logger } from '../../config/logger.config';
|
||||
import { AuthRepository } from './auth.repository';
|
||||
import { ChamaaiRepository } from './chamaai.repository';
|
||||
import { ChatRepository } from './chat.repository';
|
||||
import { ChatwootRepository } from './chatwoot.repository';
|
||||
import { ContactRepository } from './contact.repository';
|
||||
@@ -29,6 +30,7 @@ export class RepositoryBroker {
|
||||
public readonly rabbitmq: RabbitmqRepository,
|
||||
public readonly typebot: TypebotRepository,
|
||||
public readonly proxy: ProxyRepository,
|
||||
public readonly chamaai: ChamaaiRepository,
|
||||
public readonly auth: AuthRepository,
|
||||
private configService: ConfigService,
|
||||
dbServer?: MongoClient,
|
||||
@@ -63,6 +65,7 @@ export class RepositoryBroker {
|
||||
const rabbitmqDir = join(storePath, 'rabbitmq');
|
||||
const typebotDir = join(storePath, 'typebot');
|
||||
const proxyDir = join(storePath, 'proxy');
|
||||
const chamaaiDir = join(storePath, 'chamaai');
|
||||
const tempDir = join(storePath, 'temp');
|
||||
|
||||
if (!fs.existsSync(authDir)) {
|
||||
@@ -113,6 +116,10 @@ export class RepositoryBroker {
|
||||
this.logger.verbose('creating proxy dir: ' + proxyDir);
|
||||
fs.mkdirSync(proxyDir, { recursive: true });
|
||||
}
|
||||
if (!fs.existsSync(chamaaiDir)) {
|
||||
this.logger.verbose('creating chamaai dir: ' + chamaaiDir);
|
||||
fs.mkdirSync(chamaaiDir, { recursive: true });
|
||||
}
|
||||
if (!fs.existsSync(tempDir)) {
|
||||
this.logger.verbose('creating temp dir: ' + tempDir);
|
||||
fs.mkdirSync(tempDir, { recursive: true });
|
||||
|
||||
52
src/whatsapp/routers/chamaai.router.ts
Normal file
52
src/whatsapp/routers/chamaai.router.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import { RequestHandler, Router } from 'express';
|
||||
|
||||
import { Logger } from '../../config/logger.config';
|
||||
import { chamaaiSchema, instanceNameSchema } from '../../validate/validate.schema';
|
||||
import { RouterBroker } from '../abstract/abstract.router';
|
||||
import { ChamaaiDto } from '../dto/chamaai.dto';
|
||||
import { InstanceDto } from '../dto/instance.dto';
|
||||
import { chamaaiController } from '../whatsapp.module';
|
||||
import { HttpStatus } from './index.router';
|
||||
|
||||
const logger = new Logger('ChamaaiRouter');
|
||||
|
||||
export class ChamaaiRouter extends RouterBroker {
|
||||
constructor(...guards: RequestHandler[]) {
|
||||
super();
|
||||
this.router
|
||||
.post(this.routerPath('set'), ...guards, async (req, res) => {
|
||||
logger.verbose('request received in setChamaai');
|
||||
logger.verbose('request body: ');
|
||||
logger.verbose(req.body);
|
||||
|
||||
logger.verbose('request query: ');
|
||||
logger.verbose(req.query);
|
||||
const response = await this.dataValidate<ChamaaiDto>({
|
||||
request: req,
|
||||
schema: chamaaiSchema,
|
||||
ClassRef: ChamaaiDto,
|
||||
execute: (instance, data) => chamaaiController.createChamaai(instance, data),
|
||||
});
|
||||
|
||||
res.status(HttpStatus.CREATED).json(response);
|
||||
})
|
||||
.get(this.routerPath('find'), ...guards, async (req, res) => {
|
||||
logger.verbose('request received in findChamaai');
|
||||
logger.verbose('request body: ');
|
||||
logger.verbose(req.body);
|
||||
|
||||
logger.verbose('request query: ');
|
||||
logger.verbose(req.query);
|
||||
const response = await this.dataValidate<InstanceDto>({
|
||||
request: req,
|
||||
schema: instanceNameSchema,
|
||||
ClassRef: InstanceDto,
|
||||
execute: (instance) => chamaaiController.findChamaai(instance),
|
||||
});
|
||||
|
||||
res.status(HttpStatus.OK).json(response);
|
||||
});
|
||||
}
|
||||
|
||||
public readonly router = Router();
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import fs from 'fs';
|
||||
import { Auth, configService } from '../../config/env.config';
|
||||
import { authGuard } from '../guards/auth.guard';
|
||||
import { instanceExistsGuard, instanceLoggedGuard } from '../guards/instance.guard';
|
||||
import { ChamaaiRouter } from './chamaai.router';
|
||||
import { ChatRouter } from './chat.router';
|
||||
import { ChatwootRouter } from './chatwoot.router';
|
||||
import { GroupRouter } from './group.router';
|
||||
@@ -39,6 +40,7 @@ router
|
||||
status: HttpStatus.OK,
|
||||
message: 'Welcome to the Evolution API, it is working!',
|
||||
version: packageJson.version,
|
||||
documentation: `${req.protocol}://${req.get('host')}/docs`,
|
||||
});
|
||||
})
|
||||
.use('/instance', new InstanceRouter(configService, ...guards).router)
|
||||
@@ -52,6 +54,7 @@ router
|
||||
.use('/websocket', new WebsocketRouter(...guards).router)
|
||||
.use('/rabbitmq', new RabbitmqRouter(...guards).router)
|
||||
.use('/typebot', new TypebotRouter(...guards).router)
|
||||
.use('/proxy', new ProxyRouter(...guards).router);
|
||||
.use('/proxy', new ProxyRouter(...guards).router)
|
||||
.use('/chamaai', new ChamaaiRouter(...guards).router);
|
||||
|
||||
export { HttpStatus, router };
|
||||
|
||||
230
src/whatsapp/services/chamaai.service.ts
Normal file
230
src/whatsapp/services/chamaai.service.ts
Normal file
@@ -0,0 +1,230 @@
|
||||
import axios from 'axios';
|
||||
import { writeFileSync } from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
import { ConfigService, HttpServer } from '../../config/env.config';
|
||||
import { Logger } from '../../config/logger.config';
|
||||
import { ChamaaiDto } from '../dto/chamaai.dto';
|
||||
import { InstanceDto } from '../dto/instance.dto';
|
||||
import { ChamaaiRaw } from '../models';
|
||||
import { Events } from '../types/wa.types';
|
||||
import { WAMonitoringService } from './monitor.service';
|
||||
|
||||
export class ChamaaiService {
|
||||
constructor(private readonly waMonitor: WAMonitoringService, private readonly configService: ConfigService) {}
|
||||
|
||||
private readonly logger = new Logger(ChamaaiService.name);
|
||||
|
||||
public create(instance: InstanceDto, data: ChamaaiDto) {
|
||||
this.logger.verbose('create chamaai: ' + instance.instanceName);
|
||||
this.waMonitor.waInstances[instance.instanceName].setChamaai(data);
|
||||
|
||||
return { chamaai: { ...instance, chamaai: data } };
|
||||
}
|
||||
|
||||
public async find(instance: InstanceDto): Promise<ChamaaiRaw> {
|
||||
try {
|
||||
this.logger.verbose('find chamaai: ' + instance.instanceName);
|
||||
const result = await this.waMonitor.waInstances[instance.instanceName].findChamaai();
|
||||
|
||||
if (Object.keys(result).length === 0) {
|
||||
throw new Error('Chamaai not found');
|
||||
}
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
return { enabled: false, url: '', token: '', waNumber: '', answerByAudio: false };
|
||||
}
|
||||
}
|
||||
|
||||
private getTypeMessage(msg: any) {
|
||||
this.logger.verbose('get type message');
|
||||
|
||||
const types = {
|
||||
conversation: msg.conversation,
|
||||
extendedTextMessage: msg.extendedTextMessage?.text,
|
||||
};
|
||||
|
||||
this.logger.verbose('type message: ' + types);
|
||||
|
||||
return types;
|
||||
}
|
||||
|
||||
private getMessageContent(types: any) {
|
||||
this.logger.verbose('get message content');
|
||||
const typeKey = Object.keys(types).find((key) => types[key] !== undefined);
|
||||
|
||||
const result = typeKey ? types[typeKey] : undefined;
|
||||
|
||||
this.logger.verbose('message content: ' + result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private getConversationMessage(msg: any) {
|
||||
this.logger.verbose('get conversation message');
|
||||
|
||||
const types = this.getTypeMessage(msg);
|
||||
|
||||
const messageContent = this.getMessageContent(types);
|
||||
|
||||
this.logger.verbose('conversation message: ' + messageContent);
|
||||
|
||||
return messageContent;
|
||||
}
|
||||
|
||||
private calculateTypingTime(text: string) {
|
||||
const wordsPerMinute = 100;
|
||||
|
||||
const wordCount = text.split(' ').length;
|
||||
const typingTimeInMinutes = wordCount / wordsPerMinute;
|
||||
const typingTimeInMilliseconds = typingTimeInMinutes * 60;
|
||||
return typingTimeInMilliseconds;
|
||||
}
|
||||
|
||||
private convertToMilliseconds(count: number) {
|
||||
const averageCharactersPerSecond = 15;
|
||||
const characterCount = count;
|
||||
const speakingTimeInSeconds = characterCount / averageCharactersPerSecond;
|
||||
return speakingTimeInSeconds;
|
||||
}
|
||||
|
||||
private getRegexPatterns() {
|
||||
const patternsToCheck = [
|
||||
'.*atend.*humano.*',
|
||||
'.*falar.*com.*um.*humano.*',
|
||||
'.*fala.*humano.*',
|
||||
'.*atend.*humano.*',
|
||||
'.*fala.*atend.*',
|
||||
'.*preciso.*ajuda.*',
|
||||
'.*quero.*suporte.*',
|
||||
'.*preciso.*assiste.*',
|
||||
'.*ajuda.*atend.*',
|
||||
'.*chama.*atendente.*',
|
||||
'.*suporte.*urgente.*',
|
||||
'.*atend.*por.*favor.*',
|
||||
'.*quero.*falar.*com.*alguém.*',
|
||||
'.*falar.*com.*um.*humano.*',
|
||||
'.*transfer.*humano.*',
|
||||
'.*transfer.*atend.*',
|
||||
'.*equipe.*humano.*',
|
||||
'.*suporte.*humano.*',
|
||||
];
|
||||
|
||||
const regexPatterns = patternsToCheck.map((pattern) => new RegExp(pattern, 'iu'));
|
||||
return regexPatterns;
|
||||
}
|
||||
|
||||
public async sendChamaai(instance: InstanceDto, remoteJid: string, msg: any) {
|
||||
const content = this.getConversationMessage(msg.message);
|
||||
const msgType = msg.messageType;
|
||||
const find = await this.find(instance);
|
||||
const url = find.url;
|
||||
const token = find.token;
|
||||
const waNumber = find.waNumber;
|
||||
const answerByAudio = find.answerByAudio;
|
||||
|
||||
if (!content && msgType !== 'audioMessage') {
|
||||
return;
|
||||
}
|
||||
|
||||
let data;
|
||||
let endpoint;
|
||||
|
||||
if (msgType === 'audioMessage') {
|
||||
const downloadBase64 = await this.waMonitor.waInstances[instance.instanceName].getBase64FromMediaMessage({
|
||||
message: {
|
||||
...msg,
|
||||
},
|
||||
});
|
||||
|
||||
const random = Math.random().toString(36).substring(7);
|
||||
const nameFile = `${random}.ogg`;
|
||||
|
||||
const fileData = Buffer.from(downloadBase64.base64, 'base64');
|
||||
|
||||
const fileName = `${path.join(
|
||||
this.waMonitor.waInstances[instance.instanceName].storePath,
|
||||
'temp',
|
||||
`${nameFile}`,
|
||||
)}`;
|
||||
|
||||
writeFileSync(fileName, fileData, 'utf8');
|
||||
|
||||
const urlServer = this.configService.get<HttpServer>('SERVER').URL;
|
||||
|
||||
const url = `${urlServer}/store/temp/${nameFile}`;
|
||||
|
||||
data = {
|
||||
waNumber: waNumber,
|
||||
audioUrl: url,
|
||||
queryNumber: remoteJid.split('@')[0],
|
||||
answerByAudio: answerByAudio,
|
||||
};
|
||||
endpoint = 'processMessageAudio';
|
||||
} else {
|
||||
data = {
|
||||
waNumber: waNumber,
|
||||
question: content,
|
||||
queryNumber: remoteJid.split('@')[0],
|
||||
answerByAudio: answerByAudio,
|
||||
};
|
||||
endpoint = 'processMessageText';
|
||||
}
|
||||
|
||||
const request = await axios.post(`${url}/${endpoint}`, data, {
|
||||
headers: {
|
||||
Authorization: `${token}`,
|
||||
},
|
||||
});
|
||||
|
||||
const answer = request.data?.answer;
|
||||
|
||||
const type = request.data?.type;
|
||||
|
||||
const characterCount = request.data?.characterCount;
|
||||
|
||||
if (answer) {
|
||||
if (type === 'text') {
|
||||
this.waMonitor.waInstances[instance.instanceName].textMessage({
|
||||
number: remoteJid.split('@')[0],
|
||||
options: {
|
||||
delay: this.calculateTypingTime(answer) * 1000 || 1000,
|
||||
presence: 'composing',
|
||||
linkPreview: false,
|
||||
quoted: {
|
||||
key: msg.key,
|
||||
message: msg.message,
|
||||
},
|
||||
},
|
||||
textMessage: {
|
||||
text: answer,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (type === 'audio') {
|
||||
this.waMonitor.waInstances[instance.instanceName].audioWhatsapp({
|
||||
number: remoteJid.split('@')[0],
|
||||
options: {
|
||||
delay: characterCount ? this.convertToMilliseconds(characterCount) * 1000 || 1000 : 1000,
|
||||
presence: 'recording',
|
||||
encoding: true,
|
||||
},
|
||||
audioMessage: {
|
||||
audio: answer,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (this.getRegexPatterns().some((pattern) => pattern.test(answer))) {
|
||||
this.waMonitor.waInstances[instance.instanceName].sendDataWebhook(Events.CHAMA_AI_ACTION, {
|
||||
remoteJid: remoteJid,
|
||||
message: msg,
|
||||
answer: answer,
|
||||
action: 'transfer',
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import ChatwootClient from '@figuro/chatwoot-sdk';
|
||||
import axios from 'axios';
|
||||
import FormData from 'form-data';
|
||||
import { createReadStream, readFileSync, unlinkSync, writeFileSync } from 'fs';
|
||||
import Jimp from 'jimp';
|
||||
import mimeTypes from 'mime-types';
|
||||
import path from 'path';
|
||||
|
||||
@@ -460,7 +461,9 @@ export class ChatwootService {
|
||||
let contact: any;
|
||||
if (body.key.fromMe) {
|
||||
if (findContact) {
|
||||
contact = findContact;
|
||||
contact = await this.updateContact(instance, findContact.id, {
|
||||
avatar_url: picture_url.profilePictureUrl || null,
|
||||
});
|
||||
} else {
|
||||
const jid = isGroup ? null : body.key.remoteJid;
|
||||
contact = await this.createContact(
|
||||
@@ -481,7 +484,9 @@ export class ChatwootService {
|
||||
avatar_url: picture_url.profilePictureUrl || null,
|
||||
});
|
||||
} else {
|
||||
contact = findContact;
|
||||
contact = await this.updateContact(instance, findContact.id, {
|
||||
avatar_url: picture_url.profilePictureUrl || null,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
const jid = isGroup ? null : body.key.remoteJid;
|
||||
@@ -909,7 +914,7 @@ export class ChatwootService {
|
||||
},
|
||||
};
|
||||
|
||||
await waInstance?.audioWhatsapp(data);
|
||||
await waInstance?.audioWhatsapp(data, true);
|
||||
|
||||
this.logger.verbose('audio sent');
|
||||
return;
|
||||
@@ -934,7 +939,7 @@ export class ChatwootService {
|
||||
data.mediaMessage.caption = caption;
|
||||
}
|
||||
|
||||
await waInstance?.mediaMessage(data);
|
||||
await waInstance?.mediaMessage(data, true);
|
||||
|
||||
this.logger.verbose('media sent');
|
||||
return;
|
||||
@@ -945,6 +950,8 @@ export class ChatwootService {
|
||||
|
||||
public async receiveWebhook(instance: InstanceDto, body: any) {
|
||||
try {
|
||||
await new Promise((resolve) => setTimeout(resolve, 500));
|
||||
|
||||
this.logger.verbose('receive webhook to chatwoot instance: ' + instance.instanceName);
|
||||
const client = await this.clientCw(instance);
|
||||
|
||||
@@ -1067,7 +1074,7 @@ export class ChatwootService {
|
||||
},
|
||||
};
|
||||
|
||||
await waInstance?.textMessage(data);
|
||||
await waInstance?.textMessage(data, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1118,6 +1125,20 @@ export class ChatwootService {
|
||||
return result;
|
||||
}
|
||||
|
||||
private getAdsMessage(msg: any) {
|
||||
interface AdsMessage {
|
||||
title: string;
|
||||
body: string;
|
||||
thumbnailUrl: string;
|
||||
sourceUrl: string;
|
||||
}
|
||||
const adsMessage: AdsMessage | undefined = msg.extendedTextMessage?.contextInfo?.externalAdReply;
|
||||
|
||||
this.logger.verbose('Get ads message if it exist');
|
||||
adsMessage && this.logger.verbose('Ads message: ' + adsMessage);
|
||||
return adsMessage;
|
||||
}
|
||||
|
||||
private getTypeMessage(msg: any) {
|
||||
this.logger.verbose('get type message');
|
||||
|
||||
@@ -1258,7 +1279,7 @@ export class ChatwootService {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (event === 'messages.upsert') {
|
||||
if (event === 'messages.upsert' || event === 'send.message') {
|
||||
this.logger.verbose('event messages.upsert');
|
||||
|
||||
if (body.key.remoteJid === 'status@broadcast') {
|
||||
@@ -1271,15 +1292,17 @@ export class ChatwootService {
|
||||
|
||||
const isMedia = this.isMediaMessage(body.message);
|
||||
|
||||
const adsMessage = this.getAdsMessage(body.message);
|
||||
|
||||
if (!bodyMessage && !isMedia) {
|
||||
this.logger.warn('no body message found');
|
||||
return;
|
||||
}
|
||||
|
||||
this.logger.verbose('get conversation in chatwoot');
|
||||
const getConversion = await this.createConversation(instance, body);
|
||||
const getConversation = await this.createConversation(instance, body);
|
||||
|
||||
if (!getConversion) {
|
||||
if (!getConversation) {
|
||||
this.logger.warn('conversation not found');
|
||||
return;
|
||||
}
|
||||
@@ -1330,7 +1353,7 @@ export class ChatwootService {
|
||||
}
|
||||
|
||||
this.logger.verbose('send data to chatwoot');
|
||||
const send = await this.sendData(getConversion, fileName, messageType, content);
|
||||
const send = await this.sendData(getConversation, fileName, messageType, content);
|
||||
|
||||
if (!send) {
|
||||
this.logger.warn('message not sent');
|
||||
@@ -1351,7 +1374,7 @@ export class ChatwootService {
|
||||
this.logger.verbose('message is not group');
|
||||
|
||||
this.logger.verbose('send data to chatwoot');
|
||||
const send = await this.sendData(getConversion, fileName, messageType, bodyMessage);
|
||||
const send = await this.sendData(getConversation, fileName, messageType, bodyMessage);
|
||||
|
||||
if (!send) {
|
||||
this.logger.warn('message not sent');
|
||||
@@ -1371,6 +1394,67 @@ export class ChatwootService {
|
||||
}
|
||||
}
|
||||
|
||||
this.logger.verbose('check if has Ads Message');
|
||||
if (adsMessage) {
|
||||
this.logger.verbose('message is from Ads');
|
||||
|
||||
this.logger.verbose('get base64 from media ads message');
|
||||
const imgBuffer = await axios.get(adsMessage.thumbnailUrl, { responseType: 'arraybuffer' });
|
||||
|
||||
const extension = mimeTypes.extension(imgBuffer.headers['content-type']);
|
||||
const mimeType = extension && mimeTypes.lookup(extension);
|
||||
|
||||
if (!mimeType) {
|
||||
this.logger.warn('mimetype of Ads message not found');
|
||||
return;
|
||||
}
|
||||
|
||||
const random = Math.random().toString(36).substring(7);
|
||||
const nameFile = `${random}.${mimeTypes.extension(mimeType)}`;
|
||||
const fileData = Buffer.from(imgBuffer.data, 'binary');
|
||||
const fileName = `${path.join(waInstance?.storePath, 'temp', `${nameFile}`)}`;
|
||||
|
||||
this.logger.verbose('temp file name: ' + nameFile);
|
||||
this.logger.verbose('create temp file');
|
||||
await Jimp.read(fileData)
|
||||
.then(async (img) => {
|
||||
await img.cover(320, 180).writeAsync(fileName);
|
||||
})
|
||||
.catch((err) => {
|
||||
this.logger.error(`image is not write: ${err}`);
|
||||
});
|
||||
const truncStr = (str: string, len: number) => {
|
||||
return str.length > len ? str.substring(0, len) + '...' : str;
|
||||
};
|
||||
|
||||
const title = truncStr(adsMessage.title, 40);
|
||||
const description = truncStr(adsMessage.body, 75);
|
||||
|
||||
this.logger.verbose('send data to chatwoot');
|
||||
const send = await this.sendData(
|
||||
getConversation,
|
||||
fileName,
|
||||
messageType,
|
||||
`${bodyMessage}\n\n\n**${title}**\n${description}\n${adsMessage.sourceUrl}`,
|
||||
);
|
||||
|
||||
if (!send) {
|
||||
this.logger.warn('message not sent');
|
||||
return;
|
||||
}
|
||||
|
||||
this.messageCacheFile = path.join(ROOT_DIR, 'store', 'chatwoot', `${instance.instanceName}_cache.txt`);
|
||||
|
||||
this.messageCache = this.loadMessageCache();
|
||||
|
||||
this.messageCache.add(send.id.toString());
|
||||
|
||||
this.logger.verbose('save message cache');
|
||||
this.saveMessageCache();
|
||||
|
||||
return send;
|
||||
}
|
||||
|
||||
this.logger.verbose('check if is group');
|
||||
if (body.key.remoteJid.includes('@g.us')) {
|
||||
this.logger.verbose('message is group');
|
||||
@@ -1387,7 +1471,7 @@ export class ChatwootService {
|
||||
}
|
||||
|
||||
this.logger.verbose('send data to chatwoot');
|
||||
const send = await this.createMessage(instance, getConversion, content, messageType);
|
||||
const send = await this.createMessage(instance, getConversation, content, messageType);
|
||||
|
||||
if (!send) {
|
||||
this.logger.warn('message not sent');
|
||||
@@ -1408,7 +1492,7 @@ export class ChatwootService {
|
||||
this.logger.verbose('message is not group');
|
||||
|
||||
this.logger.verbose('send data to chatwoot');
|
||||
const send = await this.createMessage(instance, getConversion, bodyMessage, messageType);
|
||||
const send = await this.createMessage(instance, getConversation, bodyMessage, messageType);
|
||||
|
||||
if (!send) {
|
||||
this.logger.warn('message not sent');
|
||||
@@ -1444,16 +1528,16 @@ export class ChatwootService {
|
||||
await this.createBotMessage(instance, msgStatus, 'incoming');
|
||||
}
|
||||
|
||||
if (event === 'connection.update') {
|
||||
this.logger.verbose('event connection.update');
|
||||
// if (event === 'connection.update') {
|
||||
// this.logger.verbose('event connection.update');
|
||||
|
||||
if (body.status === 'open') {
|
||||
const msgConnection = `🚀 Connection successfully established!`;
|
||||
// if (body.status === 'open') {
|
||||
// const msgConnection = `🚀 Connection successfully established!`;
|
||||
|
||||
this.logger.verbose('send message to chatwoot');
|
||||
await this.createBotMessage(instance, msgConnection, 'incoming');
|
||||
}
|
||||
}
|
||||
// this.logger.verbose('send message to chatwoot');
|
||||
// await this.createBotMessage(instance, msgConnection, 'incoming');
|
||||
// }
|
||||
// }
|
||||
|
||||
if (event === 'qrcode.updated') {
|
||||
this.logger.verbose('event qrcode.updated');
|
||||
|
||||
@@ -64,8 +64,10 @@ export class WAMonitoringService {
|
||||
await this.waInstances[instance]?.client?.logout('Log out instance: ' + instance);
|
||||
this.waInstances[instance]?.client?.ws?.close();
|
||||
this.waInstances[instance]?.client?.end(undefined);
|
||||
this.waInstances[instance]?.removeRabbitmqQueues();
|
||||
delete this.waInstances[instance];
|
||||
} else {
|
||||
this.waInstances[instance]?.removeRabbitmqQueues();
|
||||
delete this.waInstances[instance];
|
||||
this.eventEmitter.emit('remove.instance', instance, 'inner');
|
||||
}
|
||||
@@ -161,8 +163,7 @@ export class WAMonitoringService {
|
||||
});
|
||||
this.logger.verbose('instance files deleted: ' + name);
|
||||
});
|
||||
// } else if (this.redis.ENABLED) {
|
||||
} else {
|
||||
} else if (!this.redis.ENABLED) {
|
||||
const dir = opendirSync(INSTANCE_DIR, { encoding: 'utf-8' });
|
||||
for await (const dirent of dir) {
|
||||
if (dirent.isDirectory()) {
|
||||
@@ -220,6 +221,11 @@ export class WAMonitoringService {
|
||||
execSync(`rm -rf ${join(STORE_DIR, 'auth', 'apikey', instanceName + '.json')}`);
|
||||
execSync(`rm -rf ${join(STORE_DIR, 'webhook', instanceName + '.json')}`);
|
||||
execSync(`rm -rf ${join(STORE_DIR, 'chatwoot', instanceName + '*')}`);
|
||||
execSync(`rm -rf ${join(STORE_DIR, 'chamaai', instanceName + '*')}`);
|
||||
execSync(`rm -rf ${join(STORE_DIR, 'proxy', instanceName + '*')}`);
|
||||
execSync(`rm -rf ${join(STORE_DIR, 'rabbitmq', instanceName + '*')}`);
|
||||
execSync(`rm -rf ${join(STORE_DIR, 'typebot', instanceName + '*')}`);
|
||||
execSync(`rm -rf ${join(STORE_DIR, 'websocket', instanceName + '*')}`);
|
||||
execSync(`rm -rf ${join(STORE_DIR, 'settings', instanceName + '*')}`);
|
||||
|
||||
return;
|
||||
@@ -240,68 +246,85 @@ export class WAMonitoringService {
|
||||
}
|
||||
|
||||
public async loadInstance() {
|
||||
this.logger.verbose('load instances');
|
||||
const set = async (name: string) => {
|
||||
const instance = new WAStartupService(this.configService, this.eventEmitter, this.repository, this.cache);
|
||||
instance.instanceName = name;
|
||||
this.logger.verbose('instance loaded: ' + name);
|
||||
|
||||
await instance.connectToWhatsapp();
|
||||
this.logger.verbose('connectToWhatsapp: ' + name);
|
||||
|
||||
this.waInstances[name] = instance;
|
||||
};
|
||||
this.logger.verbose('Loading instances');
|
||||
|
||||
try {
|
||||
if (this.redis.ENABLED) {
|
||||
this.logger.verbose('redis enabled');
|
||||
await this.cache.connect(this.redis as Redis);
|
||||
const keys = await this.cache.instanceKeys();
|
||||
if (keys?.length > 0) {
|
||||
this.logger.verbose('reading instance keys and setting instances');
|
||||
keys.forEach(async (k) => await set(k.split(':')[1]));
|
||||
} else {
|
||||
this.logger.verbose('no instance keys found');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.db.ENABLED && this.db.SAVE_DATA.INSTANCE) {
|
||||
this.logger.verbose('database enabled');
|
||||
await this.repository.dbServer.connect();
|
||||
const collections: any[] = await this.dbInstance.collections();
|
||||
if (collections.length > 0) {
|
||||
this.logger.verbose('reading collections and setting instances');
|
||||
collections.forEach(async (coll) => await set(coll.namespace.replace(/^[\w-]+\./, '')));
|
||||
} else {
|
||||
this.logger.verbose('no collections found');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
this.logger.verbose('store in files enabled');
|
||||
const dir = opendirSync(INSTANCE_DIR, { encoding: 'utf-8' });
|
||||
for await (const dirent of dir) {
|
||||
if (dirent.isDirectory()) {
|
||||
this.logger.verbose('reading instance files and setting instances');
|
||||
const files = readdirSync(join(INSTANCE_DIR, dirent.name), {
|
||||
encoding: 'utf-8',
|
||||
});
|
||||
if (files.length === 0) {
|
||||
rmSync(join(INSTANCE_DIR, dirent.name), { recursive: true, force: true });
|
||||
break;
|
||||
}
|
||||
|
||||
await set(dirent.name);
|
||||
} else {
|
||||
this.logger.verbose('no instance files found');
|
||||
}
|
||||
await this.loadInstancesFromRedis();
|
||||
} else if (this.db.ENABLED && this.db.SAVE_DATA.INSTANCE) {
|
||||
await this.loadInstancesFromDatabase();
|
||||
} else {
|
||||
await this.loadInstancesFromFiles();
|
||||
}
|
||||
} catch (error) {
|
||||
this.logger.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
private async setInstance(name: string) {
|
||||
const instance = new WAStartupService(this.configService, this.eventEmitter, this.repository, this.cache);
|
||||
instance.instanceName = name;
|
||||
this.logger.verbose('Instance loaded: ' + name);
|
||||
|
||||
await instance.connectToWhatsapp();
|
||||
this.logger.verbose('connectToWhatsapp: ' + name);
|
||||
|
||||
this.waInstances[name] = instance;
|
||||
}
|
||||
|
||||
private async loadInstancesFromRedis() {
|
||||
this.logger.verbose('Redis enabled');
|
||||
await this.cache.connect(this.redis as Redis);
|
||||
const keys = await this.cache.instanceKeys();
|
||||
|
||||
if (keys?.length > 0) {
|
||||
this.logger.verbose('Reading instance keys and setting instances');
|
||||
await Promise.all(keys.map((k) => this.setInstance(k.split(':')[1])));
|
||||
} else {
|
||||
this.logger.verbose('No instance keys found');
|
||||
}
|
||||
}
|
||||
|
||||
private async loadInstancesFromDatabase() {
|
||||
this.logger.verbose('Database enabled');
|
||||
await this.repository.dbServer.connect();
|
||||
const collections: any[] = await this.dbInstance.collections();
|
||||
|
||||
if (collections.length > 0) {
|
||||
this.logger.verbose('Reading collections and setting instances');
|
||||
await Promise.all(collections.map((coll) => this.setInstance(coll.namespace.replace(/^[\w-]+\./, ''))));
|
||||
} else {
|
||||
this.logger.verbose('No collections found');
|
||||
}
|
||||
}
|
||||
|
||||
private async loadInstancesFromFiles() {
|
||||
this.logger.verbose('Store in files enabled');
|
||||
const dir = opendirSync(INSTANCE_DIR, { encoding: 'utf-8' });
|
||||
const instanceDirs = [];
|
||||
|
||||
for await (const dirent of dir) {
|
||||
if (dirent.isDirectory()) {
|
||||
instanceDirs.push(dirent.name);
|
||||
} else {
|
||||
this.logger.verbose('No instance files found');
|
||||
}
|
||||
}
|
||||
|
||||
await Promise.all(
|
||||
instanceDirs.map(async (instanceName) => {
|
||||
this.logger.verbose('Reading instance files and setting instances: ' + instanceName);
|
||||
const files = readdirSync(join(INSTANCE_DIR, instanceName), { encoding: 'utf-8' });
|
||||
|
||||
if (files.length === 0) {
|
||||
rmSync(join(INSTANCE_DIR, instanceName), { recursive: true, force: true });
|
||||
} else {
|
||||
await this.setInstance(instanceName);
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
private removeInstance() {
|
||||
this.eventEmitter.on('remove.instance', async (instanceName: string) => {
|
||||
this.logger.verbose('remove instance: ' + instanceName);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Logger } from '../../config/logger.config';
|
||||
import { initQueues } from '../../libs/amqp.server';
|
||||
import { InstanceDto } from '../dto/instance.dto';
|
||||
import { RabbitmqDto } from '../dto/rabbitmq.dto';
|
||||
import { RabbitmqRaw } from '../models';
|
||||
@@ -13,6 +14,7 @@ export class RabbitmqService {
|
||||
this.logger.verbose('create rabbitmq: ' + instance.instanceName);
|
||||
this.waMonitor.waInstances[instance.instanceName].setRabbitmq(data);
|
||||
|
||||
initQueues(instance.instanceName, data.events);
|
||||
return { rabbitmq: { ...instance, rabbitmq: data } };
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import { Logger } from '../../config/logger.config';
|
||||
import { InstanceDto } from '../dto/instance.dto';
|
||||
import { Session, TypebotDto } from '../dto/typebot.dto';
|
||||
import { MessageRaw } from '../models';
|
||||
import { Events } from '../types/wa.types';
|
||||
import { WAMonitoringService } from './monitor.service';
|
||||
|
||||
export class TypebotService {
|
||||
@@ -53,6 +54,7 @@ export class TypebotService {
|
||||
keyword_finish: findData.keyword_finish,
|
||||
delay_message: findData.delay_message,
|
||||
unknown_message: findData.unknown_message,
|
||||
listening_from_me: findData.listening_from_me,
|
||||
sessions: findData.sessions,
|
||||
};
|
||||
|
||||
@@ -76,11 +78,20 @@ export class TypebotService {
|
||||
keyword_finish: findData.keyword_finish,
|
||||
delay_message: findData.delay_message,
|
||||
unknown_message: findData.unknown_message,
|
||||
listening_from_me: findData.listening_from_me,
|
||||
sessions: findData.sessions,
|
||||
};
|
||||
|
||||
this.create(instance, typebotData);
|
||||
|
||||
this.waMonitor.waInstances[instance.instanceName].sendDataWebhook(Events.TYPEBOT_CHANGE_STATUS, {
|
||||
remoteJid: remoteJid,
|
||||
status: status,
|
||||
url: findData.url,
|
||||
typebot: findData.typebot,
|
||||
session,
|
||||
});
|
||||
|
||||
return { typebot: { ...instance, typebot: typebotData } };
|
||||
}
|
||||
|
||||
@@ -88,31 +99,82 @@ export class TypebotService {
|
||||
const remoteJid = data.remoteJid;
|
||||
const url = data.url;
|
||||
const typebot = data.typebot;
|
||||
const startSession = data.startSession;
|
||||
const variables = data.variables;
|
||||
const findTypebot = await this.find(instance);
|
||||
const sessions = (findTypebot.sessions as Session[]) ?? [];
|
||||
const expire = findTypebot.expire;
|
||||
const keyword_finish = findTypebot.keyword_finish;
|
||||
const delay_message = findTypebot.delay_message;
|
||||
const unknown_message = findTypebot.unknown_message;
|
||||
const listening_from_me = findTypebot.listening_from_me;
|
||||
|
||||
const id = Math.floor(Math.random() * 10000000000).toString();
|
||||
|
||||
const reqData = {
|
||||
sessionId: id,
|
||||
startParams: {
|
||||
typebot: data.typebot,
|
||||
prefilledVariables: {
|
||||
remoteJid: data.remoteJid,
|
||||
pushName: data.pushName,
|
||||
instanceName: instance.instanceName,
|
||||
},
|
||||
},
|
||||
const prefilledVariables = {
|
||||
remoteJid: remoteJid,
|
||||
instanceName: instance.instanceName,
|
||||
};
|
||||
console.log(reqData);
|
||||
|
||||
const request = await axios.post(data.url + '/api/v1/sendMessage', reqData);
|
||||
if (variables?.length) {
|
||||
variables.forEach((variable: { name: string | number; value: string }) => {
|
||||
prefilledVariables[variable.name] = variable.value;
|
||||
});
|
||||
}
|
||||
|
||||
await this.sendWAMessage(
|
||||
instance,
|
||||
remoteJid,
|
||||
request.data.messages,
|
||||
request.data.input,
|
||||
request.data.clientSideActions,
|
||||
);
|
||||
if (startSession) {
|
||||
const response = await this.createNewSession(instance, {
|
||||
url: url,
|
||||
typebot: typebot,
|
||||
remoteJid: remoteJid,
|
||||
expire: expire,
|
||||
keyword_finish: keyword_finish,
|
||||
delay_message: delay_message,
|
||||
unknown_message: unknown_message,
|
||||
listening_from_me: listening_from_me,
|
||||
sessions: sessions,
|
||||
prefilledVariables: prefilledVariables,
|
||||
});
|
||||
|
||||
if (response.sessionId) {
|
||||
await this.sendWAMessage(instance, remoteJid, response.messages, response.input, response.clientSideActions);
|
||||
|
||||
this.waMonitor.waInstances[instance.instanceName].sendDataWebhook(Events.TYPEBOT_START, {
|
||||
remoteJid: remoteJid,
|
||||
url: url,
|
||||
typebot: typebot,
|
||||
prefilledVariables: prefilledVariables,
|
||||
sessionId: `${response.sessionId}`,
|
||||
});
|
||||
} else {
|
||||
throw new Error('Session ID not found in response');
|
||||
}
|
||||
} else {
|
||||
const id = Math.floor(Math.random() * 10000000000).toString();
|
||||
|
||||
const reqData = {
|
||||
startParams: {
|
||||
typebot: data.typebot,
|
||||
prefilledVariables: prefilledVariables,
|
||||
},
|
||||
};
|
||||
|
||||
const request = await axios.post(data.url + '/api/v1/sendMessage', reqData);
|
||||
|
||||
await this.sendWAMessage(
|
||||
instance,
|
||||
remoteJid,
|
||||
request.data.messages,
|
||||
request.data.input,
|
||||
request.data.clientSideActions,
|
||||
);
|
||||
|
||||
this.waMonitor.waInstances[instance.instanceName].sendDataWebhook(Events.TYPEBOT_START, {
|
||||
remoteJid: remoteJid,
|
||||
url: url,
|
||||
typebot: typebot,
|
||||
variables: variables,
|
||||
sessionId: id,
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
typebot: {
|
||||
@@ -121,6 +183,7 @@ export class TypebotService {
|
||||
url: url,
|
||||
remoteJid: remoteJid,
|
||||
typebot: typebot,
|
||||
prefilledVariables: prefilledVariables,
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -165,12 +228,12 @@ export class TypebotService {
|
||||
public async createNewSession(instance: InstanceDto, data: any) {
|
||||
const id = Math.floor(Math.random() * 10000000000).toString();
|
||||
const reqData = {
|
||||
sessionId: id,
|
||||
startParams: {
|
||||
typebot: data.typebot,
|
||||
prefilledVariables: {
|
||||
...data.prefilledVariables,
|
||||
remoteJid: data.remoteJid,
|
||||
pushName: data.pushName,
|
||||
pushName: data.pushName || '',
|
||||
instanceName: instance.instanceName,
|
||||
},
|
||||
},
|
||||
@@ -185,6 +248,12 @@ export class TypebotService {
|
||||
status: 'opened',
|
||||
createdAt: Date.now(),
|
||||
updateAt: Date.now(),
|
||||
prefilledVariables: {
|
||||
...data.prefilledVariables,
|
||||
remoteJid: data.remoteJid,
|
||||
pushName: data.pushName || '',
|
||||
instanceName: instance.instanceName,
|
||||
},
|
||||
});
|
||||
|
||||
const typebotData = {
|
||||
@@ -195,6 +264,7 @@ export class TypebotService {
|
||||
keyword_finish: data.keyword_finish,
|
||||
delay_message: data.delay_message,
|
||||
unknown_message: data.unknown_message,
|
||||
listening_from_me: data.listening_from_me,
|
||||
sessions: data.sessions,
|
||||
};
|
||||
|
||||
@@ -354,13 +424,15 @@ export class TypebotService {
|
||||
}
|
||||
|
||||
public async sendTypebot(instance: InstanceDto, remoteJid: string, msg: MessageRaw) {
|
||||
const url = (await this.find(instance)).url;
|
||||
const typebot = (await this.find(instance)).typebot;
|
||||
const sessions = ((await this.find(instance)).sessions as Session[]) ?? [];
|
||||
const expire = (await this.find(instance)).expire;
|
||||
const keyword_finish = (await this.find(instance)).keyword_finish;
|
||||
const delay_message = (await this.find(instance)).delay_message;
|
||||
const unknown_message = (await this.find(instance)).unknown_message;
|
||||
const findTypebot = await this.find(instance);
|
||||
const url = findTypebot.url;
|
||||
const typebot = findTypebot.typebot;
|
||||
const sessions = (findTypebot.sessions as Session[]) ?? [];
|
||||
const expire = findTypebot.expire;
|
||||
const keyword_finish = findTypebot.keyword_finish;
|
||||
const delay_message = findTypebot.delay_message;
|
||||
const unknown_message = findTypebot.unknown_message;
|
||||
const listening_from_me = findTypebot.listening_from_me;
|
||||
|
||||
const session = sessions.find((session) => session.remoteJid === remoteJid);
|
||||
|
||||
@@ -381,6 +453,7 @@ export class TypebotService {
|
||||
keyword_finish: keyword_finish,
|
||||
delay_message: delay_message,
|
||||
unknown_message: unknown_message,
|
||||
listening_from_me: listening_from_me,
|
||||
sessions: sessions,
|
||||
remoteJid: remoteJid,
|
||||
pushName: msg.pushName,
|
||||
@@ -388,6 +461,62 @@ export class TypebotService {
|
||||
|
||||
await this.sendWAMessage(instance, remoteJid, data.messages, data.input, data.clientSideActions);
|
||||
|
||||
if (data.messages.length === 0) {
|
||||
const content = this.getConversationMessage(msg.message);
|
||||
|
||||
if (!content) {
|
||||
if (unknown_message) {
|
||||
this.waMonitor.waInstances[instance.instanceName].textMessage({
|
||||
number: remoteJid.split('@')[0],
|
||||
options: {
|
||||
delay: delay_message || 1000,
|
||||
presence: 'composing',
|
||||
},
|
||||
textMessage: {
|
||||
text: unknown_message,
|
||||
},
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (keyword_finish && content.toLowerCase() === keyword_finish.toLowerCase()) {
|
||||
sessions.splice(sessions.indexOf(session), 1);
|
||||
|
||||
const typebotData = {
|
||||
enabled: true,
|
||||
url: url,
|
||||
typebot: typebot,
|
||||
expire: expire,
|
||||
keyword_finish: keyword_finish,
|
||||
delay_message: delay_message,
|
||||
unknown_message: unknown_message,
|
||||
listening_from_me: listening_from_me,
|
||||
sessions,
|
||||
};
|
||||
|
||||
this.create(instance, typebotData);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const reqData = {
|
||||
message: content,
|
||||
sessionId: data.sessionId,
|
||||
};
|
||||
|
||||
const request = await axios.post(url + '/api/v1/sendMessage', reqData);
|
||||
|
||||
console.log('request', request);
|
||||
await this.sendWAMessage(
|
||||
instance,
|
||||
remoteJid,
|
||||
request.data.messages,
|
||||
request.data.input,
|
||||
request.data.clientSideActions,
|
||||
);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -404,6 +533,7 @@ export class TypebotService {
|
||||
keyword_finish: keyword_finish,
|
||||
delay_message: delay_message,
|
||||
unknown_message: unknown_message,
|
||||
listening_from_me: listening_from_me,
|
||||
sessions: sessions,
|
||||
remoteJid: remoteJid,
|
||||
pushName: msg.pushName,
|
||||
@@ -411,6 +541,61 @@ export class TypebotService {
|
||||
|
||||
await this.sendWAMessage(instance, remoteJid, data.messages, data.input, data.clientSideActions);
|
||||
|
||||
if (data.messages.length === 0) {
|
||||
const content = this.getConversationMessage(msg.message);
|
||||
|
||||
if (!content) {
|
||||
if (unknown_message) {
|
||||
this.waMonitor.waInstances[instance.instanceName].textMessage({
|
||||
number: remoteJid.split('@')[0],
|
||||
options: {
|
||||
delay: delay_message || 1000,
|
||||
presence: 'composing',
|
||||
},
|
||||
textMessage: {
|
||||
text: unknown_message,
|
||||
},
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (keyword_finish && content.toLowerCase() === keyword_finish.toLowerCase()) {
|
||||
sessions.splice(sessions.indexOf(session), 1);
|
||||
|
||||
const typebotData = {
|
||||
enabled: true,
|
||||
url: url,
|
||||
typebot: typebot,
|
||||
expire: expire,
|
||||
keyword_finish: keyword_finish,
|
||||
delay_message: delay_message,
|
||||
unknown_message: unknown_message,
|
||||
listening_from_me: listening_from_me,
|
||||
sessions,
|
||||
};
|
||||
|
||||
this.create(instance, typebotData);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const reqData = {
|
||||
message: content,
|
||||
sessionId: data.sessionId,
|
||||
};
|
||||
|
||||
const request = await axios.post(url + '/api/v1/sendMessage', reqData);
|
||||
|
||||
console.log('request', request);
|
||||
await this.sendWAMessage(
|
||||
instance,
|
||||
remoteJid,
|
||||
request.data.messages,
|
||||
request.data.input,
|
||||
request.data.clientSideActions,
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -428,6 +613,7 @@ export class TypebotService {
|
||||
keyword_finish: keyword_finish,
|
||||
delay_message: delay_message,
|
||||
unknown_message: unknown_message,
|
||||
listening_from_me: listening_from_me,
|
||||
sessions,
|
||||
};
|
||||
|
||||
@@ -451,7 +637,7 @@ export class TypebotService {
|
||||
return;
|
||||
}
|
||||
|
||||
if (content.toLowerCase() === keyword_finish.toLowerCase()) {
|
||||
if (keyword_finish && content.toLowerCase() === keyword_finish.toLowerCase()) {
|
||||
sessions.splice(sessions.indexOf(session), 1);
|
||||
|
||||
const typebotData = {
|
||||
@@ -462,6 +648,7 @@ export class TypebotService {
|
||||
keyword_finish: keyword_finish,
|
||||
delay_message: delay_message,
|
||||
unknown_message: unknown_message,
|
||||
listening_from_me: listening_from_me,
|
||||
sessions,
|
||||
};
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ export class WebhookService {
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
return { enabled: false, url: '', events: [], webhook_by_events: false };
|
||||
return { enabled: false, url: '', events: [], webhook_by_events: false, webhook_base64: false };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,7 +66,7 @@ import {
|
||||
import { Logger } from '../../config/logger.config';
|
||||
import { INSTANCE_DIR, ROOT_DIR } from '../../config/path.config';
|
||||
import { BadRequestException, InternalServerErrorException, NotFoundException } from '../../exceptions';
|
||||
import { getAMQP } from '../../libs/amqp.server';
|
||||
import { getAMQP, removeQueues } from '../../libs/amqp.server';
|
||||
import { dbserver } from '../../libs/db.connect';
|
||||
import { RedisCache } from '../../libs/redis.client';
|
||||
import { getIO } from '../../libs/socket.server';
|
||||
@@ -113,7 +113,7 @@ import {
|
||||
SendTextDto,
|
||||
StatusMessage,
|
||||
} from '../dto/sendMessage.dto';
|
||||
import { ProxyRaw, RabbitmqRaw, SettingsRaw, TypebotRaw } from '../models';
|
||||
import { ChamaaiRaw, ProxyRaw, RabbitmqRaw, SettingsRaw, TypebotRaw } from '../models';
|
||||
import { ChatRaw } from '../models/chat.model';
|
||||
import { ChatwootRaw } from '../models/chatwoot.model';
|
||||
import { ContactRaw } from '../models/contact.model';
|
||||
@@ -126,7 +126,9 @@ import { MessageUpQuery } from '../repository/messageUp.repository';
|
||||
import { RepositoryBroker } from '../repository/repository.manager';
|
||||
import { Events, MessageSubtype, TypeMediaMessage, wa } from '../types/wa.types';
|
||||
import { waMonitor } from '../whatsapp.module';
|
||||
import { ChamaaiService } from './chamaai.service';
|
||||
import { ChatwootService } from './chatwoot.service';
|
||||
//import { SocksProxyAgent } from './socks-proxy-agent';
|
||||
import { TypebotService } from './typebot.service';
|
||||
|
||||
export class WAStartupService {
|
||||
@@ -151,6 +153,7 @@ export class WAStartupService {
|
||||
private readonly localRabbitmq: wa.LocalRabbitmq = {};
|
||||
public readonly localTypebot: wa.LocalTypebot = {};
|
||||
private readonly localProxy: wa.LocalProxy = {};
|
||||
private readonly localChamaai: wa.LocalChamaai = {};
|
||||
public stateConnection: wa.StateConnection = { state: 'close' };
|
||||
public readonly storePath = join(ROOT_DIR, 'store');
|
||||
private readonly msgRetryCounterCache: CacheStore = new NodeCache();
|
||||
@@ -164,6 +167,8 @@ export class WAStartupService {
|
||||
|
||||
private typebotService = new TypebotService(waMonitor);
|
||||
|
||||
private chamaaiService = new ChamaaiService(waMonitor, this.configService);
|
||||
|
||||
public set instanceName(name: string) {
|
||||
this.logger.verbose(`Initializing instance '${name}'`);
|
||||
if (!name) {
|
||||
@@ -271,6 +276,9 @@ export class WAStartupService {
|
||||
this.localWebhook.webhook_by_events = data?.webhook_by_events;
|
||||
this.logger.verbose(`Webhook by events: ${this.localWebhook.webhook_by_events}`);
|
||||
|
||||
this.localWebhook.webhook_base64 = data?.webhook_base64;
|
||||
this.logger.verbose(`Webhook by webhook_base64: ${this.localWebhook.webhook_base64}`);
|
||||
|
||||
this.logger.verbose('Webhook loaded');
|
||||
}
|
||||
|
||||
@@ -490,6 +498,14 @@ export class WAStartupService {
|
||||
return data;
|
||||
}
|
||||
|
||||
public async removeRabbitmqQueues() {
|
||||
this.logger.verbose('Removing rabbitmq');
|
||||
|
||||
if (this.localRabbitmq.enabled) {
|
||||
removeQueues(this.instanceName, this.localRabbitmq.events);
|
||||
}
|
||||
}
|
||||
|
||||
private async loadTypebot() {
|
||||
this.logger.verbose('Loading typebot');
|
||||
const data = await this.repository.typebot.find(this.instanceName);
|
||||
@@ -515,6 +531,9 @@ export class WAStartupService {
|
||||
this.localTypebot.unknown_message = data?.unknown_message;
|
||||
this.logger.verbose(`Typebot unknown_message: ${this.localTypebot.unknown_message}`);
|
||||
|
||||
this.localTypebot.listening_from_me = data?.listening_from_me;
|
||||
this.logger.verbose(`Typebot listening_from_me: ${this.localTypebot.listening_from_me}`);
|
||||
|
||||
this.localTypebot.sessions = data?.sessions;
|
||||
|
||||
this.logger.verbose('Typebot loaded');
|
||||
@@ -528,6 +547,7 @@ export class WAStartupService {
|
||||
this.logger.verbose(`Typebot keyword_finish: ${data.keyword_finish}`);
|
||||
this.logger.verbose(`Typebot delay_message: ${data.delay_message}`);
|
||||
this.logger.verbose(`Typebot unknown_message: ${data.unknown_message}`);
|
||||
this.logger.verbose(`Typebot listening_from_me: ${data.listening_from_me}`);
|
||||
Object.assign(this.localTypebot, data);
|
||||
this.logger.verbose('Typebot set');
|
||||
}
|
||||
@@ -579,6 +599,52 @@ export class WAStartupService {
|
||||
return data;
|
||||
}
|
||||
|
||||
private async loadChamaai() {
|
||||
this.logger.verbose('Loading chamaai');
|
||||
const data = await this.repository.chamaai.find(this.instanceName);
|
||||
|
||||
this.localChamaai.enabled = data?.enabled;
|
||||
this.logger.verbose(`Chamaai enabled: ${this.localChamaai.enabled}`);
|
||||
|
||||
this.localChamaai.url = data?.url;
|
||||
this.logger.verbose(`Chamaai url: ${this.localChamaai.url}`);
|
||||
|
||||
this.localChamaai.token = data?.token;
|
||||
this.logger.verbose(`Chamaai token: ${this.localChamaai.token}`);
|
||||
|
||||
this.localChamaai.waNumber = data?.waNumber;
|
||||
this.logger.verbose(`Chamaai waNumber: ${this.localChamaai.waNumber}`);
|
||||
|
||||
this.localChamaai.answerByAudio = data?.answerByAudio;
|
||||
this.logger.verbose(`Chamaai answerByAudio: ${this.localChamaai.answerByAudio}`);
|
||||
|
||||
this.logger.verbose('Chamaai loaded');
|
||||
}
|
||||
|
||||
public async setChamaai(data: ChamaaiRaw) {
|
||||
this.logger.verbose('Setting chamaai');
|
||||
await this.repository.chamaai.create(data, this.instanceName);
|
||||
this.logger.verbose(`Chamaai url: ${data.url}`);
|
||||
this.logger.verbose(`Chamaai token: ${data.token}`);
|
||||
this.logger.verbose(`Chamaai waNumber: ${data.waNumber}`);
|
||||
this.logger.verbose(`Chamaai answerByAudio: ${data.answerByAudio}`);
|
||||
|
||||
Object.assign(this.localChamaai, data);
|
||||
this.logger.verbose('Chamaai set');
|
||||
}
|
||||
|
||||
public async findChamaai() {
|
||||
this.logger.verbose('Finding chamaai');
|
||||
const data = await this.repository.chamaai.find(this.instanceName);
|
||||
|
||||
if (!data) {
|
||||
this.logger.verbose('Chamaai not found');
|
||||
throw new NotFoundException('Chamaai not found');
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
public async sendDataWebhook<T = any>(event: Events, data: T, local = true) {
|
||||
const webhookGlobal = this.configService.get<Webhook>('WEBHOOK');
|
||||
const webhookLocal = this.localWebhook.events;
|
||||
@@ -633,11 +699,30 @@ export class WAStartupService {
|
||||
}
|
||||
|
||||
amqp.publish(exchangeName, event, Buffer.from(JSON.stringify(message)));
|
||||
|
||||
if (this.configService.get<Log>('LOG').LEVEL.includes('WEBHOOKS')) {
|
||||
const logData = {
|
||||
local: WAStartupService.name + '.sendData-RabbitMQ',
|
||||
event,
|
||||
instance: this.instance.name,
|
||||
data,
|
||||
server_url: serverUrl,
|
||||
apikey: (expose && instanceApikey) || null,
|
||||
date_time: now,
|
||||
sender: this.wuid,
|
||||
};
|
||||
|
||||
if (expose && instanceApikey) {
|
||||
logData['apikey'] = instanceApikey;
|
||||
}
|
||||
|
||||
this.logger.log(logData);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (this.configService.get<Websocket>('WEBSOCKET').ENABLED && this.localWebsocket.enabled) {
|
||||
if (this.configService.get<Websocket>('WEBSOCKET')?.ENABLED && this.localWebsocket.enabled) {
|
||||
this.logger.verbose('Sending data to websocket on channel: ' + this.instance.name);
|
||||
if (Array.isArray(websocketLocal) && websocketLocal.includes(we)) {
|
||||
this.logger.verbose('Sending data to websocket on event: ' + event);
|
||||
@@ -658,6 +743,25 @@ export class WAStartupService {
|
||||
|
||||
this.logger.verbose('Sending data to socket.io in channel: ' + this.instance.name);
|
||||
io.of(`/${this.instance.name}`).emit(event, message);
|
||||
|
||||
if (this.configService.get<Log>('LOG').LEVEL.includes('WEBHOOKS')) {
|
||||
const logData = {
|
||||
local: WAStartupService.name + '.sendData-Websocket',
|
||||
event,
|
||||
instance: this.instance.name,
|
||||
data,
|
||||
server_url: serverUrl,
|
||||
apikey: (expose && instanceApikey) || null,
|
||||
date_time: now,
|
||||
sender: this.wuid,
|
||||
};
|
||||
|
||||
if (expose && instanceApikey) {
|
||||
logData['apikey'] = instanceApikey;
|
||||
}
|
||||
|
||||
this.logger.log(logData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -696,7 +800,7 @@ export class WAStartupService {
|
||||
}
|
||||
|
||||
try {
|
||||
if (this.localWebhook.enabled && isURL(this.localWebhook.url)) {
|
||||
if (this.localWebhook.enabled && isURL(this.localWebhook.url, { require_tld: false })) {
|
||||
const httpService = axios.create({ baseURL });
|
||||
const postData = {
|
||||
event,
|
||||
@@ -1065,6 +1169,7 @@ export class WAStartupService {
|
||||
this.loadRabbitmq();
|
||||
this.loadTypebot();
|
||||
this.loadProxy();
|
||||
this.loadChamaai();
|
||||
|
||||
this.instance.authState = await this.defineAuthState();
|
||||
|
||||
@@ -1088,7 +1193,7 @@ export class WAStartupService {
|
||||
...options,
|
||||
auth: {
|
||||
creds: this.instance.authState.state.creds,
|
||||
keys: makeCacheableSignalKeyStore(this.instance.authState.state.keys, P({ level: 'error' })),
|
||||
keys: makeCacheableSignalKeyStore(this.instance.authState.state.keys, P({ level: 'error' }) as any),
|
||||
},
|
||||
logger: P({ level: this.logBaileys }),
|
||||
printQRInTerminal: false,
|
||||
@@ -1146,6 +1251,74 @@ export class WAStartupService {
|
||||
}
|
||||
}
|
||||
|
||||
public async reloadConnection(): Promise<WASocket> {
|
||||
try {
|
||||
this.instance.authState = await this.defineAuthState();
|
||||
|
||||
const { version } = await fetchLatestBaileysVersion();
|
||||
const session = this.configService.get<ConfigSessionPhone>('CONFIG_SESSION_PHONE');
|
||||
const browser: WABrowserDescription = [session.CLIENT, session.NAME, release()];
|
||||
|
||||
let options;
|
||||
|
||||
if (this.localProxy.enabled) {
|
||||
this.logger.verbose('Proxy enabled');
|
||||
options = {
|
||||
agent: new ProxyAgent(this.localProxy.proxy as any),
|
||||
fetchAgent: new ProxyAgent(this.localProxy.proxy as any),
|
||||
};
|
||||
}
|
||||
|
||||
const socketConfig: UserFacingSocketConfig = {
|
||||
...options,
|
||||
auth: {
|
||||
creds: this.instance.authState.state.creds,
|
||||
keys: makeCacheableSignalKeyStore(this.instance.authState.state.keys, P({ level: 'error' }) as any),
|
||||
},
|
||||
logger: P({ level: this.logBaileys }),
|
||||
printQRInTerminal: false,
|
||||
browser,
|
||||
version,
|
||||
markOnlineOnConnect: this.localSettings.always_online,
|
||||
connectTimeoutMs: 60_000,
|
||||
qrTimeout: 40_000,
|
||||
defaultQueryTimeoutMs: undefined,
|
||||
emitOwnEvents: false,
|
||||
msgRetryCounterCache: this.msgRetryCounterCache,
|
||||
getMessage: async (key) => (await this.getMessage(key)) as Promise<proto.IMessage>,
|
||||
generateHighQualityLinkPreview: true,
|
||||
syncFullHistory: true,
|
||||
userDevicesCache: this.userDevicesCache,
|
||||
transactionOpts: { maxCommitRetries: 1, delayBetweenTriesMs: 10 },
|
||||
patchMessageBeforeSending: (message) => {
|
||||
const requiresPatch = !!(message.buttonsMessage || message.listMessage || message.templateMessage);
|
||||
if (requiresPatch) {
|
||||
message = {
|
||||
viewOnceMessageV2: {
|
||||
message: {
|
||||
messageContextInfo: {
|
||||
deviceListMetadataVersion: 2,
|
||||
deviceListMetadata: {},
|
||||
},
|
||||
...message,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return message;
|
||||
},
|
||||
};
|
||||
|
||||
this.client = makeWASocket(socketConfig);
|
||||
|
||||
return this.client;
|
||||
} catch (error) {
|
||||
this.logger.error(error);
|
||||
throw new InternalServerErrorException(error?.toString());
|
||||
}
|
||||
}
|
||||
|
||||
private readonly chatHandle = {
|
||||
'chats.upsert': async (chats: Chat[], database: Database) => {
|
||||
this.logger.verbose('Event received: chats.upsert');
|
||||
@@ -1338,7 +1511,13 @@ export class WAStartupService {
|
||||
this.logger.verbose('Event received: messages.upsert');
|
||||
const received = messages[0];
|
||||
|
||||
if (type !== 'notify' || received.message?.protocolMessage || received.message?.pollUpdateMessage) {
|
||||
if (
|
||||
type !== 'notify' ||
|
||||
!received?.message ||
|
||||
received.message?.protocolMessage ||
|
||||
received.message.senderKeyDistributionMessage ||
|
||||
received.message?.pollUpdateMessage
|
||||
) {
|
||||
this.logger.verbose('message rejected');
|
||||
return;
|
||||
}
|
||||
@@ -1352,15 +1531,45 @@ export class WAStartupService {
|
||||
return;
|
||||
}
|
||||
|
||||
const messageRaw: MessageRaw = {
|
||||
key: received.key,
|
||||
pushName: received.pushName,
|
||||
message: { ...received.message },
|
||||
messageType: getContentType(received.message),
|
||||
messageTimestamp: received.messageTimestamp as number,
|
||||
owner: this.instance.name,
|
||||
source: getDevice(received.key.id),
|
||||
};
|
||||
let messageRaw: MessageRaw;
|
||||
|
||||
if (
|
||||
(this.localWebhook.webhook_base64 === true && received?.message.documentMessage) ||
|
||||
received?.message.imageMessage
|
||||
) {
|
||||
const buffer = await downloadMediaMessage(
|
||||
{ key: received.key, message: received?.message },
|
||||
'buffer',
|
||||
{},
|
||||
{
|
||||
logger: P({ level: 'error' }) as any,
|
||||
reuploadRequest: this.client.updateMediaMessage,
|
||||
},
|
||||
);
|
||||
console.log(buffer);
|
||||
messageRaw = {
|
||||
key: received.key,
|
||||
pushName: received.pushName,
|
||||
message: {
|
||||
...received.message,
|
||||
base64: buffer ? buffer.toString('base64') : undefined,
|
||||
},
|
||||
messageType: getContentType(received.message),
|
||||
messageTimestamp: received.messageTimestamp as number,
|
||||
owner: this.instance.name,
|
||||
source: getDevice(received.key.id),
|
||||
};
|
||||
} else {
|
||||
messageRaw = {
|
||||
key: received.key,
|
||||
pushName: received.pushName,
|
||||
message: { ...received.message },
|
||||
messageType: getContentType(received.message),
|
||||
messageTimestamp: received.messageTimestamp as number,
|
||||
owner: this.instance.name,
|
||||
source: getDevice(received.key.id),
|
||||
};
|
||||
}
|
||||
|
||||
if (this.localSettings.read_messages && received.key.id !== 'status@broadcast') {
|
||||
await this.client.readMessages([received.key]);
|
||||
@@ -1383,8 +1592,18 @@ export class WAStartupService {
|
||||
);
|
||||
}
|
||||
|
||||
if (this.localTypebot.enabled && messageRaw.key.remoteJid.includes('@s.whatsapp.net')) {
|
||||
await this.typebotService.sendTypebot(
|
||||
if (this.localTypebot.enabled) {
|
||||
if (!(this.localTypebot.listening_from_me === false && messageRaw.key.fromMe === true)) {
|
||||
await this.typebotService.sendTypebot(
|
||||
{ instanceName: this.instance.name },
|
||||
messageRaw.key.remoteJid,
|
||||
messageRaw,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.localChamaai.enabled && messageRaw.key.fromMe === false) {
|
||||
await this.chamaaiService.sendChamaai(
|
||||
{ instanceName: this.instance.name },
|
||||
messageRaw.key.remoteJid,
|
||||
messageRaw,
|
||||
@@ -1795,15 +2014,15 @@ export class WAStartupService {
|
||||
this.logger.verbose('Getting profile with jid: ' + jid);
|
||||
try {
|
||||
this.logger.verbose('Getting profile info');
|
||||
const business = await this.fetchBusinessProfile(jid);
|
||||
|
||||
if (number) {
|
||||
const info = (await this.whatsappNumber({ numbers: [jid] }))?.shift();
|
||||
const picture = await this.profilePicture(jid);
|
||||
const status = await this.getStatus(jid);
|
||||
const picture = await this.profilePicture(info?.jid);
|
||||
const status = await this.getStatus(info?.jid);
|
||||
const business = await this.fetchBusinessProfile(info?.jid);
|
||||
|
||||
return {
|
||||
wuid: jid,
|
||||
wuid: info?.jid || jid,
|
||||
name: info?.name,
|
||||
numberExists: info?.exists,
|
||||
picture: picture?.profilePictureUrl,
|
||||
@@ -1815,6 +2034,7 @@ export class WAStartupService {
|
||||
};
|
||||
} else {
|
||||
const info = await waMonitor.instanceInfo(instanceName);
|
||||
const business = await this.fetchBusinessProfile(jid);
|
||||
|
||||
return {
|
||||
wuid: jid,
|
||||
@@ -1841,12 +2061,18 @@ export class WAStartupService {
|
||||
}
|
||||
}
|
||||
|
||||
private async sendMessageWithTyping<T = proto.IMessage>(number: string, message: T, options?: Options) {
|
||||
private async sendMessageWithTyping<T = proto.IMessage>(
|
||||
number: string,
|
||||
message: T,
|
||||
options?: Options,
|
||||
isChatwoot = false,
|
||||
) {
|
||||
this.logger.verbose('Sending message with typing');
|
||||
|
||||
const numberWA = await this.whatsappNumber({ numbers: [number] });
|
||||
const isWA = numberWA[0];
|
||||
this.logger.verbose(`Check if number "${number}" is WhatsApp`);
|
||||
const isWA = (await this.whatsappNumber({ numbers: [number] }))?.shift();
|
||||
|
||||
this.logger.verbose(`Exists: "${isWA.exists}" | jid: ${isWA.jid}`);
|
||||
if (!isWA.exists && !isJidGroup(isWA.jid) && !isWA.jid.includes('@broadcast')) {
|
||||
throw new BadRequestException(isWA);
|
||||
}
|
||||
@@ -1890,9 +2116,9 @@ export class WAStartupService {
|
||||
let mentions: string[];
|
||||
if (isJidGroup(sender)) {
|
||||
try {
|
||||
const groupMetadata = await this.client.groupMetadata(sender);
|
||||
const group = await this.findGroup({ groupJid: sender }, 'inner');
|
||||
|
||||
if (!groupMetadata) {
|
||||
if (!group) {
|
||||
throw new NotFoundException('Group not found');
|
||||
}
|
||||
|
||||
@@ -1903,7 +2129,7 @@ export class WAStartupService {
|
||||
this.logger.verbose('Mentions everyone');
|
||||
|
||||
this.logger.verbose('Getting group metadata');
|
||||
mentions = groupMetadata.participants.map((participant) => participant.id);
|
||||
mentions = group.participants.map((participant) => participant.id);
|
||||
this.logger.verbose('Getting group metadata for mentions');
|
||||
} else if (options.mentions?.mentioned?.length) {
|
||||
this.logger.verbose('Mentions manually defined');
|
||||
@@ -1911,7 +2137,6 @@ export class WAStartupService {
|
||||
const jid = this.createJid(mention);
|
||||
if (isJidGroup(jid)) {
|
||||
return null;
|
||||
// throw new BadRequestException('Mentions must be a number');
|
||||
}
|
||||
return jid;
|
||||
});
|
||||
@@ -1999,7 +2224,7 @@ export class WAStartupService {
|
||||
this.logger.verbose('Sending data to webhook in event SEND_MESSAGE');
|
||||
await this.sendDataWebhook(Events.SEND_MESSAGE, messageRaw);
|
||||
|
||||
if (this.localChatwoot.enabled) {
|
||||
if (this.localChatwoot.enabled && !isChatwoot) {
|
||||
this.chatwootService.eventWhatsapp(Events.SEND_MESSAGE, { instanceName: this.instance.name }, messageRaw);
|
||||
}
|
||||
|
||||
@@ -2024,7 +2249,7 @@ export class WAStartupService {
|
||||
}
|
||||
|
||||
// Send Message Controller
|
||||
public async textMessage(data: SendTextDto) {
|
||||
public async textMessage(data: SendTextDto, isChatwoot = false) {
|
||||
this.logger.verbose('Sending text message');
|
||||
return await this.sendMessageWithTyping(
|
||||
data.number,
|
||||
@@ -2032,6 +2257,7 @@ export class WAStartupService {
|
||||
conversation: data.textMessage.text,
|
||||
},
|
||||
data?.options,
|
||||
isChatwoot,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2308,14 +2534,14 @@ export class WAStartupService {
|
||||
return result;
|
||||
}
|
||||
|
||||
public async mediaMessage(data: SendMediaDto) {
|
||||
public async mediaMessage(data: SendMediaDto, isChatwoot = false) {
|
||||
this.logger.verbose('Sending media message');
|
||||
const generate = await this.prepareMediaMessage(data.mediaMessage);
|
||||
|
||||
return await this.sendMessageWithTyping(data.number, { ...generate.message }, data?.options);
|
||||
return await this.sendMessageWithTyping(data.number, { ...generate.message }, data?.options, isChatwoot);
|
||||
}
|
||||
|
||||
private async processAudio(audio: string, number: string) {
|
||||
public async processAudio(audio: string, number: string) {
|
||||
this.logger.verbose('Processing audio');
|
||||
let tempAudioPath: string;
|
||||
let outputAudio: string;
|
||||
@@ -2369,7 +2595,7 @@ export class WAStartupService {
|
||||
});
|
||||
}
|
||||
|
||||
public async audioWhatsapp(data: SendAudioDto) {
|
||||
public async audioWhatsapp(data: SendAudioDto, isChatwoot = false) {
|
||||
this.logger.verbose('Sending audio whatsapp');
|
||||
|
||||
if (!data.options?.encoding && data.options?.encoding !== false) {
|
||||
@@ -2388,6 +2614,7 @@ export class WAStartupService {
|
||||
mimetype: 'audio/mp4',
|
||||
},
|
||||
{ presence: 'recording', delay: data?.options?.delay },
|
||||
isChatwoot,
|
||||
);
|
||||
|
||||
fs.unlinkSync(convert);
|
||||
@@ -2409,6 +2636,7 @@ export class WAStartupService {
|
||||
mimetype: 'audio/ogg; codecs=opus',
|
||||
},
|
||||
{ presence: 'recording', delay: data?.options?.delay },
|
||||
isChatwoot,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2589,6 +2817,7 @@ export class WAStartupService {
|
||||
|
||||
public async markMessageAsRead(data: ReadMessageDto) {
|
||||
this.logger.verbose('Marking message as read');
|
||||
|
||||
try {
|
||||
const keys: proto.IMessageKey[] = [];
|
||||
data.read_messages.forEach((read) => {
|
||||
@@ -2718,7 +2947,7 @@ export class WAStartupService {
|
||||
'buffer',
|
||||
{},
|
||||
{
|
||||
logger: P({ level: 'error' }),
|
||||
logger: P({ level: 'error' }) as any,
|
||||
reuploadRequest: this.client.updateMediaMessage,
|
||||
},
|
||||
);
|
||||
@@ -2969,7 +3198,9 @@ export class WAStartupService {
|
||||
public async createGroup(create: CreateGroupDto) {
|
||||
this.logger.verbose('Creating group: ' + create.subject);
|
||||
try {
|
||||
const participants = create.participants.map((p) => this.createJid(p));
|
||||
const participants = (await this.whatsappNumber({ numbers: create.participants }))
|
||||
.filter((participant) => participant.exists)
|
||||
.map((participant) => participant.jid);
|
||||
const { id } = await this.client.groupCreate(create.subject, participants);
|
||||
this.logger.verbose('Group created: ' + id);
|
||||
|
||||
@@ -2979,7 +3210,7 @@ export class WAStartupService {
|
||||
}
|
||||
|
||||
if (create?.promoteParticipants) {
|
||||
this.logger.verbose('Prometing group participants: ' + create.description);
|
||||
this.logger.verbose('Prometing group participants: ' + participants);
|
||||
await this.updateGParticipant({
|
||||
groupJid: id,
|
||||
action: 'promote',
|
||||
@@ -2987,8 +3218,8 @@ export class WAStartupService {
|
||||
});
|
||||
}
|
||||
|
||||
const group = await this.client.groupMetadata(id);
|
||||
this.logger.verbose('Getting group metadata');
|
||||
const group = await this.client.groupMetadata(id);
|
||||
|
||||
return group;
|
||||
} catch (error) {
|
||||
|
||||
@@ -23,6 +23,9 @@ export enum Events {
|
||||
GROUPS_UPDATE = 'groups.update',
|
||||
GROUP_PARTICIPANTS_UPDATE = 'group-participants.update',
|
||||
CALL = 'call',
|
||||
TYPEBOT_START = 'typebot.start',
|
||||
TYPEBOT_CHANGE_STATUS = 'typebot.change-status',
|
||||
CHAMA_AI_ACTION = 'chama-ai.action',
|
||||
}
|
||||
|
||||
export declare namespace wa {
|
||||
@@ -47,6 +50,7 @@ export declare namespace wa {
|
||||
url?: string;
|
||||
events?: string[];
|
||||
webhook_by_events?: boolean;
|
||||
webhook_base64?: boolean;
|
||||
};
|
||||
|
||||
export type LocalChatwoot = {
|
||||
@@ -94,6 +98,7 @@ export declare namespace wa {
|
||||
keyword_finish?: string;
|
||||
delay_message?: number;
|
||||
unknown_message?: string;
|
||||
listening_from_me?: boolean;
|
||||
sessions?: Session[];
|
||||
};
|
||||
|
||||
@@ -102,6 +107,14 @@ export declare namespace wa {
|
||||
proxy?: string;
|
||||
};
|
||||
|
||||
export type LocalChamaai = {
|
||||
enabled?: boolean;
|
||||
url?: string;
|
||||
token?: string;
|
||||
waNumber?: string;
|
||||
answerByAudio?: boolean;
|
||||
};
|
||||
|
||||
export type StateConnection = {
|
||||
instance?: string;
|
||||
state?: WAConnectionState | 'refused';
|
||||
|
||||
@@ -3,6 +3,7 @@ import { eventEmitter } from '../config/event.config';
|
||||
import { Logger } from '../config/logger.config';
|
||||
import { dbserver } from '../libs/db.connect';
|
||||
import { RedisCache } from '../libs/redis.client';
|
||||
import { ChamaaiController } from './controllers/chamaai.controller';
|
||||
import { ChatController } from './controllers/chat.controller';
|
||||
import { ChatwootController } from './controllers/chatwoot.controller';
|
||||
import { GroupController } from './controllers/group.controller';
|
||||
@@ -17,6 +18,7 @@ import { WebhookController } from './controllers/webhook.controller';
|
||||
import { WebsocketController } from './controllers/websocket.controller';
|
||||
import {
|
||||
AuthModel,
|
||||
ChamaaiModel,
|
||||
ChatModel,
|
||||
ChatwootModel,
|
||||
ContactModel,
|
||||
@@ -30,6 +32,7 @@ import {
|
||||
WebsocketModel,
|
||||
} from './models';
|
||||
import { AuthRepository } from './repository/auth.repository';
|
||||
import { ChamaaiRepository } from './repository/chamaai.repository';
|
||||
import { ChatRepository } from './repository/chat.repository';
|
||||
import { ChatwootRepository } from './repository/chatwoot.repository';
|
||||
import { ContactRepository } from './repository/contact.repository';
|
||||
@@ -43,6 +46,7 @@ import { TypebotRepository } from './repository/typebot.repository';
|
||||
import { WebhookRepository } from './repository/webhook.repository';
|
||||
import { WebsocketRepository } from './repository/websocket.repository';
|
||||
import { AuthService } from './services/auth.service';
|
||||
import { ChamaaiService } from './services/chamaai.service';
|
||||
import { ChatwootService } from './services/chatwoot.service';
|
||||
import { WAMonitoringService } from './services/monitor.service';
|
||||
import { ProxyService } from './services/proxy.service';
|
||||
@@ -62,6 +66,7 @@ const typebotRepository = new TypebotRepository(TypebotModel, configService);
|
||||
const webhookRepository = new WebhookRepository(WebhookModel, configService);
|
||||
const websocketRepository = new WebsocketRepository(WebsocketModel, configService);
|
||||
const proxyRepository = new ProxyRepository(ProxyModel, configService);
|
||||
const chamaaiRepository = new ChamaaiRepository(ChamaaiModel, configService);
|
||||
const rabbitmqRepository = new RabbitmqRepository(RabbitmqModel, configService);
|
||||
const chatwootRepository = new ChatwootRepository(ChatwootModel, configService);
|
||||
const settingsRepository = new SettingsRepository(SettingsModel, configService);
|
||||
@@ -79,6 +84,7 @@ export const repository = new RepositoryBroker(
|
||||
rabbitmqRepository,
|
||||
typebotRepository,
|
||||
proxyRepository,
|
||||
chamaaiRepository,
|
||||
authRepository,
|
||||
configService,
|
||||
dbserver?.getClient(),
|
||||
@@ -106,6 +112,10 @@ const proxyService = new ProxyService(waMonitor);
|
||||
|
||||
export const proxyController = new ProxyController(proxyService);
|
||||
|
||||
const chamaaiService = new ChamaaiService(waMonitor, configService);
|
||||
|
||||
export const chamaaiController = new ChamaaiController(chamaaiService);
|
||||
|
||||
const rabbitmqService = new RabbitmqService(waMonitor);
|
||||
|
||||
export const rabbitmqController = new RabbitmqController(rabbitmqService);
|
||||
|
||||
110
views/manager-wip.hbs
Normal file
110
views/manager-wip.hbs
Normal file
@@ -0,0 +1,110 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="pt-br">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="shortcut icon" href="https://evolution-api.com/files/evolution-api-favicon.png" type="image/x-icon">
|
||||
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/css/bootstrap.min.css"
|
||||
integrity="sha384-xOolHFLEh07PJGoPkLv1IbcEPTNtaed2xpHsD9ESMhqIYd0nLMwNLD69Npy4HI+N" crossorigin="anonymous">
|
||||
<title>Instance Manager</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="container mt-4">
|
||||
<!-- Botão para abrir o modal de adicionar nova instância -->
|
||||
<button class="btn btn-primary mb-3" data-toggle="modal" data-target="#actionModal" data-action="add">Nova
|
||||
Instância</button>
|
||||
|
||||
<!-- Tabela de instâncias -->
|
||||
<table class="table table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Nome da Instância</th>
|
||||
<th>Status</th>
|
||||
<th>API Key</th>
|
||||
<th>Ações</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<!-- Iterando sobre as instâncias e preenchendo a tabela -->
|
||||
{{#each instances}}
|
||||
<tr>
|
||||
<td>{{this.instance.instanceName}}</td>
|
||||
<td>{{this.instance.status}}</td>
|
||||
<td>{{this.instance.apikey}}</td>
|
||||
<td>
|
||||
<!-- Dropdown de ações para cada instância -->
|
||||
<div class="dropdown">
|
||||
<button class="btn btn-secondary dropdown-toggle" type="button" id="actionDropdown"
|
||||
data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
Ações
|
||||
</button>
|
||||
<div class="dropdown-menu" aria-labelledby="actionDropdown">
|
||||
<a class="dropdown-item" href="#" data-toggle="modal" data-target="#actionModal"
|
||||
data-action="connect">Connect</a>
|
||||
<a class="dropdown-item" href="#" data-toggle="modal" data-target="#actionModal"
|
||||
data-action="restart">Restart</a>
|
||||
<!-- Adicione mais itens de ação aqui -->
|
||||
<!-- ... -->
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{{/each}}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Modal de ações -->
|
||||
<div class="modal fade" id="actionModal" tabindex="-1" role="dialog" aria-labelledby="actionModalLabel"
|
||||
aria-hidden="true">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="actionModalLabel">Ação</h5>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">Fechar</button>
|
||||
<button type="button" class="btn btn-primary">Salvar</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/jquery@3.5.1/dist/jquery.slim.min.js"
|
||||
integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj"
|
||||
crossorigin="anonymous"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js"
|
||||
integrity="sha384-9/reFTGAW83EW2RDu2S0VKaIzap3H66lZH81PoYlFhbGU+6BZp6G7niu735Sk7lN"
|
||||
crossorigin="anonymous"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/js/bootstrap.min.js"
|
||||
integrity="sha384-+sLIOodYLS7CIrQpBjl+C7nPvqq+FbNUBDunl/OZv93DB7Ln/533i8e/mZXLi/P+"
|
||||
crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
$(document).ready(function(){
|
||||
$('#actionModal').on('show.bs.modal', function(event) {
|
||||
var button = $(event.relatedTarget);
|
||||
var action = button.data('action');
|
||||
|
||||
console.log(action);
|
||||
|
||||
if (action === 'connect') {
|
||||
|
||||
} else if (action === 'restart') {
|
||||
|
||||
}
|
||||
});
|
||||
})
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
<body>
|
||||
|
||||
<iframe src="https://app.smith.dgcode.com.br/app/evolutionapi-public/home-64ca60783615e270291978b4?embed=true" frameborder="0" style="width: 100%; height: 100vh;"></iframe>
|
||||
<iframe src="https://manager.evolution-api.com" frameborder="0" style="width: 100%; height: 100vh;"></iframe>
|
||||
|
||||
</body>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user