Compare commits

...

44 Commits
1.1.0 ... 1.1.3

Author SHA1 Message Date
Davidson Gomes
97abb256d5 Merge branch 'release/1.1.3' 2023-07-06 11:44:47 -03:00
Davidson Gomes
a342911dae changelog to release 1.1.3 2023-07-06 11:44:20 -03:00
Davidson Gomes
1f43563295 file postman removed 2023-07-06 11:38:18 -03:00
Davidson Gomes
f23ebf1e99 feat: Added verbose logs 2023-07-06 11:37:30 -03:00
Davidson Gomes
d66b751c2e doc: adjusts in readme 2023-07-06 09:34:48 -03:00
Davidson Gomes
6a7c76a9ba added url in vcard 2023-07-05 21:20:13 -03:00
Davidson Gomes
2338787dbb added url in vcard 2023-07-05 21:20:04 -03:00
Davidson Gomes
a216c9cc37 change changelog file 2023-07-05 21:15:01 -03:00
Davidson Gomes
b7da8d2193 added email in vcard 2023-07-05 21:14:44 -03:00
Davidson Gomes
41f191902b added organization name in vcard 2023-07-05 20:56:42 -03:00
Davidson Gomes
37397c7a69 change changelog file 2023-07-05 20:16:39 -03:00
Davidson Gomes
153695288e change changelog file 2023-07-05 20:06:45 -03:00
Davidson Gomes
a5e29758a4 added organization name in vcard 2023-07-05 20:05:24 -03:00
Davidson Gomes
964427e533 change changelog file 2023-07-05 19:17:06 -03:00
Davidson Gomes
0a925df2a9 adjust in store files 2023-07-05 19:16:47 -03:00
Davidson Gomes
db95de6731 log verbose in file redis 2023-07-05 15:58:46 -03:00
Davidson Gomes
bc2191eae6 log verbose in file redis 2023-07-05 15:49:46 -03:00
Davidson Gomes
9fed844e59 log verbose in file redis 2023-07-05 15:48:02 -03:00
Davidson Gomes
a2c125ee90 log verbose in file redis 2023-07-05 15:45:41 -03:00
Davidson Gomes
a2779612be log verbose in file redis 2023-07-05 12:54:19 -03:00
Davidson Gomes
f51c3b6519 feat: Added audio to mp4 converter in optionally get Base64 From MediaMessage 2023-07-04 17:16:31 -03:00
Davidson Gomes
870968a7c5 fix: Fixed problem when disconnecting the instance it removes the instance 2023-07-04 14:27:38 -03:00
Davidson Gomes
c4f39ab85c fix: Improved how Redis works for instances 2023-07-04 12:38:29 -03:00
Davidson Gomes
8cb431ad40 Fixed cash when sending stickers via url 2023-07-02 21:31:10 -03:00
Davidson Gomes
77f98c2438 Added destructor in redis connection 2023-07-02 20:17:13 -03:00
Davidson Gomes
d7d0e5ec82 Correction in decryption of poll votes 2023-07-02 19:58:05 -03:00
Davidson Gomes
2519efb3ea Added timestamp internally in urls to avoid caching 2023-07-01 10:17:29 -03:00
Davidson Gomes
b1c527cb71 Merge tag '1.1.2' into develop
* Fixed baileys version in package.json
* Fixed problem that did not validate if the token passed in create instance already existed
* Fixed problem that does not delete instance files in server mode
2023-06-28 13:45:14 -03:00
Davidson Gomes
b87f687e55 Merge branch 'release/1.1.2' 2023-06-28 13:45:04 -03:00
Davidson Gomes
a62d27ffc2 fix: fix problems in create instance and delete instance files 2023-06-28 13:44:58 -03:00
Davidson Gomes
a9a1c6c49b Merge tag '1.1.2' into develop
* Fixed problem that did not validate if the token passed in create instance already existed
* Fixed problem that does not delete instance files in server mode
2023-06-28 13:44:07 -03:00
Davidson Gomes
4989d6ddfa Merge branch 'release/1.1.2' 2023-06-28 13:43:48 -03:00
Davidson Gomes
fd01b9de36 fix: fix problems in create instance and delete instance files 2023-06-28 13:43:36 -03:00
Davidson Gomes
1017010e15 fix: fix problems in create instance and delete instance files 2023-06-28 13:42:35 -03:00
Davidson Gomes
e0bd06489f Merge tag '1.1.1' into develop
* Added group invitation sending
* Added webhook configuration per event in the individual instance registration
2023-06-28 10:29:48 -03:00
Davidson Gomes
4a6301c2af Merge branch 'release/1.1.1' 2023-06-28 10:29:37 -03:00
Davidson Gomes
bca830dc54 feat: Added group invitation sending and Added webhook configuration per event in the individual instance registration 2023-06-28 10:29:24 -03:00
Davidson Gomes
77bde5325e Merge tag '1.1.0-2' into develop
Adjusts reame.md
2023-06-23 08:39:41 -03:00
Davidson Gomes
bdca506dd4 Merge branch 'release/1.1.0-2' 2023-06-23 08:39:28 -03:00
Davidson Gomes
34faaa28ca Adjusts readme.md 2023-06-23 08:39:12 -03:00
Davidson Gomes
a08d433d0a Merge tag '1.1.0-1' into develop
v
2023-06-21 16:53:16 -03:00
Davidson Gomes
6d08162012 Merge branch 'release/1.1.0-1' 2023-06-21 16:53:13 -03:00
Davidson Gomes
2481650028 Remove recording of old messages on sync 2023-06-21 16:52:50 -03:00
Davidson Gomes
5e551fee41 Merge tag '1.1.0' into develop
* Improved fetch instances endpoint, now it also fetch other instances even if they are not connected
* Added conversion of audios for sending recorded audio, now it is possible to send mp3 audios and not just ogg
* Route to fetch all groups that the connection is part of
* Route to fetch all privacy settings
* Route to update the privacy settings
* Route to update group subject
* Route to update group description
* Route to accept invite code
* Added configuration of events by webhook of instances
* Now the api key can be exposed in fetch instances if the EXPOSE_IN_FETCH_INSTANCES variable is set to true
* Added option to generate qrcode as soon as the instance is created
* The created instance token can now also be optionally defined manually in the creation endpoint
* Route to send Sticker

* Adjust dockerfile variables
* tweaks in docker-compose to pass variables
* Adjust the route getProfileBusiness to fetchProfileBusiness
* fix error after logout and try to get status or to connect again
* fix sending narrated audio on whatsapp android and ios
* fixed the problem of not disabling the global webhook by the variable
* Adjustment in the recording of temporary files and periodic cleaning
* Fix for container mode also work only with files
2023-06-21 11:18:43 -03:00
53 changed files with 1081 additions and 2641 deletions

4
.gitignore vendored
View File

@@ -11,7 +11,6 @@ yarn-debug.log*
yarn-error.log* yarn-error.log*
lerna-debug.log* lerna-debug.log*
/store/*
/docker-compose-data /docker-compose-data
# Package # Package
@@ -33,3 +32,6 @@ lerna-debug.log*
!/instances/.gitkeep !/instances/.gitkeep
/test/ /test/
/src/env.yml /src/env.yml
/store
/temp/*

View File

@@ -1,3 +1,44 @@
# 1.1.3 (2023-07-06 11:43)
### Features
* Added configuration for Baileys log level in env
* Added audio to mp4 converter in optionally get Base64 From MediaMessage
* Added organization name in vcard
* Added email in vcard
* Added url in vcard
* Added verbose logs
### Fixed
* Added timestamp internally in urls to avoid caching
* Correction in decryption of poll votes
* Change in the way the api sent and saved the sent messages, now it goes in the messages.upsert event
* Fixed cash when sending stickers via url
* Improved how Redis works for instances
* Fixed problem when disconnecting the instance it removes the instance
* Fixed problem sending ack when preview is done by me
* Adjust in store files
# 1.1.2 (2023-06-28 13:43)
### Fixed
* Fixed baileys version in package.json
* Fixed problem that did not validate if the token passed in create instance already existed
* Fixed problem that does not delete instance files in server mode
# 1.1.1 (2023-06-28 10:27)
### Features
* Added group invitation sending
* Added webhook configuration per event in the individual instance registration
### Fixed
* Adjust dockerfile variables
# 1.1.0 (2023-06-21 11:17) # 1.1.0 (2023-06-21 11:17)
### Features ### Features
@@ -26,6 +67,7 @@
* fixed the problem of not disabling the global webhook by the variable * fixed the problem of not disabling the global webhook by the variable
* Adjustment in the recording of temporary files and periodic cleaning * Adjustment in the recording of temporary files and periodic cleaning
* Fix for container mode also work only with files * Fix for container mode also work only with files
* Remove recording of old messages on sync
# 1.0.9 (2023-06-10) # 1.0.9 (2023-06-10)

View File

@@ -1,87 +0,0 @@
CORS_ORIGIN='*' # Or separate by commas - ex.: 'yourdomain1.com, yourdomain2.com'
CORS_METHODS='POST,GET,PUT,DELETE'
CORS_CREDENTIALS=true
# Determine the logs to be displayed
LOG_LEVEL='ERROR,WARN,DEBUG,INFO,LOG,VERBOSE,DARK'
LOG_COLOR=true
# 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=5
# Temporary data storage
STORE_MESSAGES=true
STORE_MESSAGE_UP=true
STORE_CONTACTS=false
STORE_CHATS=false
CLEAN_STORE_CLEANING_INTERVAL=7200 # seconds ===2h
CLEAN_STORE_MESSAGES=true
CLEAN_STORE_MESSAGE_UP=true
CLEAN_STORE_CONTACTS=false
CLEAN_STORE_CHATS=false
# Permanent data storage
DATABASE_ENABLED=false
DATABASE_CONNECTION_URI='<uri>'
DATABASE_CONNECTION_DB_PREFIX_NAME='evolution'
DATABASE_SAVE_DATA_INSTANCE=false
DATABASE_SAVE_DATA_OLD_MESSAGE=false
DATABASE_SAVE_DATA_NEW_MESSAGE=true
DATABASE_SAVE_MESSAGE_UPDATE=true
DATABASE_SAVE_DATA_CONTACTS=true
DATABASE_SAVE_DATA_CHATS=true
REDIS_ENABLED=true
REDIS_URI='<uri>/1'
REDIS_PREFIX_KEY='evolution'
# Webhook Settings
## Define a global webhook that will listen for enabled events from all instances
WEBHOOK_GLOBAL_URL='<url>'
WEBHOOK_GLOBAL_ENABLED=false
WEBHOOK_GLOBAL_WEBHOOK_BY_EVENTS=false
## Set the events you want to hear
WEBHOOK_EVENTS_STATUS_INSTANCE=true
WEBHOOK_EVENTS_APPLICATION_STARTUP=true
WEBHOOK_EVENTS_QRCODE_UPDATED=true
WEBHOOK_EVENTS_MESSAGES_SET=true
WEBHOOK_EVENTS_MESSAGES_UPDATE=true
WEBHOOK_EVENTS_MESSAGES_UPSERT=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_CONNECTION_UPDATE=true
WEBHOOK_EVENTS_GROUPS_UPSERT=false
WEBHOOK_EVENTS_GROUPS_UPDATE=false
WEBHOOK_EVENTS_GROUP_PARTICIPANTS_UPDATE=false
## This event fires every time a new token is requested via the refresh route
WEBHOOK_EVENTS_NEW_JWT_TOKEN=true
CONFIG_SESSION_PHONE_CLIENT='Evolution API'
CONFIG_SESSION_PHONE_NAME='Chrome'
# Set qrcode display limit
QRCODE_LIMIT=6
# Defines an authentication type for the api
AUTHENTICATION_TYPE='jwt' # or '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='t8OOEeISKzpmc3jjcMqBWYSaJsafdefer'
AUTHENTICATION_EXPOSE_IN_FETCH_INSTANCES=true
## Set the secret key to encrypt and decrypt your token and its expiration time
AUTHENTICATION_JWT_EXPIRIN_IN=3600 # seconds - 3600s ===1h | zero (0) - never expires
AUTHENTICATION_JWT_SECRET='L0YWtjb2w554WFqPG'
AUTHENTICATION_INSTANCE_NAME='evolution'
AUTHENTICATION_INSTANCE_WEBHOOK_URL='<url>'
AUTHENTICATION_INSTANCE_MODE='container' # or 'server'
AUTHENTICATION_INSTANCE_WEBHOOK_BY_EVENTS=false

View File

@@ -0,0 +1,27 @@
version: '3.3'
networks:
evolution-net:
driver: bridge
services:
mongodb:
container_name: mongodb
image: mongo
restart: always
volumes:
- evolution_mongodb_data:/data/db
- evolution_mongodb_configdb:/data/configdb
ports:
- 27017:27017
environment:
MONGO_INITDB_ROOT_USERNAME: root
MONGO_INITDB_ROOT_PASSWORD: root
networks:
- evolution-net
expose:
- 27017
volumes:
evolution_mongodb_data:
evolution_mongodb_configdb:

View File

@@ -0,0 +1,27 @@
version: '3.3'
networks:
evolution-net:
driver: bridge
services:
redis:
image: redis:latest
command: >
redis-server
--port 6379
--appendonly yes
--save 900 1
--save 300 10
--save 60 10000
--appendfsync everysec
volumes:
- evolution_redis:/data
container_name: redis
ports:
- 6379:6379
networks:
- evolution-net
volumes:
evolution_redis:

View File

@@ -1,5 +1,9 @@
FROM node:16.18-alpine FROM node:16.18-alpine
LABEL version="1.1.3" description="Api to control whatsapp features through http requests."
LABEL maintainer="Davidson Gomes" git="https://github.com/DavidsonGomes"
LABEL contact="contato@agenciadgcode.com"
RUN apk update && apk upgrade && \ RUN apk update && apk upgrade && \
apk add --no-cache git apk add --no-cache git
@@ -16,7 +20,7 @@ ENV CORS_ORIGIN="*"
ENV CORS_METHODS="POST,GET,PUT,DELETE" ENV CORS_METHODS="POST,GET,PUT,DELETE"
ENV CORS_CREDENTIALS=true ENV CORS_CREDENTIALS=true
ENV LOG_LEVEL="ERROR,WARN,DEBUG,INFO,LOG,VERBOSE,DARK" ENV LOG_LEVEL=$LOG_LEVEL
ENV LOG_COLOR=true ENV LOG_COLOR=true
ENV DEL_INSTANCE=$DEL_INSTANCE ENV DEL_INSTANCE=$DEL_INSTANCE
@@ -49,13 +53,11 @@ ENV WEBHOOK_GLOBAL_URL=$WEBHOOK_GLOBAL_URL
ENV WEBHOOK_GLOBAL_ENABLED=$WEBHOOK_GLOBAL_ENABLED ENV WEBHOOK_GLOBAL_ENABLED=$WEBHOOK_GLOBAL_ENABLED
ENV WEBHOOK_GLOBAL_WEBHOOK_BY_EVENTS=$WEBHOOK_GLOBAL_WEBHOOK_BY_EVENTS ENV WEBHOOK_GLOBAL_WEBHOOK_BY_EVENTS=$WEBHOOK_GLOBAL_WEBHOOK_BY_EVENTS
ENV WEBHOOK_EVENTS_STATUS_INSTANCE=$WEBHOOK_EVENTS_STATUS_INSTANCE
ENV WEBHOOK_EVENTS_APPLICATION_STARTUP=$WEBHOOK_EVENTS_APPLICATION_STARTUP ENV WEBHOOK_EVENTS_APPLICATION_STARTUP=$WEBHOOK_EVENTS_APPLICATION_STARTUP
ENV WEBHOOK_EVENTS_QRCODE_UPDATED=$WEBHOOK_EVENTS_QRCODE_UPDATED ENV WEBHOOK_EVENTS_QRCODE_UPDATED=$WEBHOOK_EVENTS_QRCODE_UPDATED
ENV WEBHOOK_EVENTS_MESSAGES_SET=$WEBHOOK_EVENTS_MESSAGES_SET ENV WEBHOOK_EVENTS_MESSAGES_SET=$WEBHOOK_EVENTS_MESSAGES_SET
ENV WEBHOOK_EVENTS_MESSAGES_UPDATE=$WEBHOOK_EVENTS_MESSAGES_UPDATE ENV WEBHOOK_EVENTS_MESSAGES_UPDATE=$WEBHOOK_EVENTS_MESSAGES_UPDATE
ENV WEBHOOK_EVENTS_MESSAGES_UPSERT=$WEBHOOK_EVENTS_MESSAGES_UPSERT ENV WEBHOOK_EVENTS_MESSAGES_UPSERT=$WEBHOOK_EVENTS_MESSAGES_UPSERT
ENV WEBHOOK_EVENTS_SEND_MESSAGE=$WEBHOOK_EVENTS_SEND_MESSAGE
ENV WEBHOOK_EVENTS_CONTACTS_SET=$WEBHOOK_EVENTS_CONTACTS_SET ENV WEBHOOK_EVENTS_CONTACTS_SET=$WEBHOOK_EVENTS_CONTACTS_SET
ENV WEBHOOK_EVENTS_CONTACTS_UPSERT=$WEBHOOK_EVENTS_CONTACTS_UPSERT ENV WEBHOOK_EVENTS_CONTACTS_UPSERT=$WEBHOOK_EVENTS_CONTACTS_UPSERT
ENV WEBHOOK_EVENTS_CONTACTS_UPDATE=$WEBHOOK_EVENTS_CONTACTS_UPDATE ENV WEBHOOK_EVENTS_CONTACTS_UPDATE=$WEBHOOK_EVENTS_CONTACTS_UPDATE
@@ -71,7 +73,7 @@ ENV WEBHOOK_EVENTS_GROUP_PARTICIPANTS_UPDATE=$WEBHOOK_EVENTS_GROUP_PARTICIPANTS_
ENV WEBHOOK_EVENTS_NEW_JWT_TOKEN=$WEBHOOK_EVENTS_NEW_JWT_TOKEN ENV WEBHOOK_EVENTS_NEW_JWT_TOKEN=$WEBHOOK_EVENTS_NEW_JWT_TOKEN
ENV CONFIG_SESSION_PHONE_CLIENT=$CONFIG_SESSION_PHONE_CLIENT ENV CONFIG_SESSION_PHONE_CLIENT=$CONFIG_SESSION_PHONE_CLIENT
ENV CONFIG_SESSION_PHONE_NAME="Chrome" ENV CONFIG_SESSION_PHONE_NAME=$CONFIG_SESSION_PHONE_NAME
ENV QRCODE_LIMIT=$QRCODE_LIMIT ENV QRCODE_LIMIT=$QRCODE_LIMIT
@@ -86,7 +88,6 @@ ENV AUTHENTICATION_JWT_SECRET="L=0YWt]b2w[WF>#>:&E`"
ENV AUTHENTICATION_INSTANCE_NAME=$AUTHENTICATION_INSTANCE_NAME ENV AUTHENTICATION_INSTANCE_NAME=$AUTHENTICATION_INSTANCE_NAME
ENV AUTHENTICATION_INSTANCE_WEBHOOK_URL=$AUTHENTICATION_INSTANCE_WEBHOOK_URL ENV AUTHENTICATION_INSTANCE_WEBHOOK_URL=$AUTHENTICATION_INSTANCE_WEBHOOK_URL
ENV AUTHENTICATION_INSTANCE_MODE=$AUTHENTICATION_INSTANCE_MODE ENV AUTHENTICATION_INSTANCE_MODE=$AUTHENTICATION_INSTANCE_MODE
ENV AUTHENTICATION_INSTANCE_WEBHOOK_BY_EVENTS=$AUTHENTICATION_INSTANCE_WEBHOOK_BY_EVENTS
RUN npm install RUN npm install

255
README.md
View File

@@ -2,8 +2,9 @@
<div align="center"> <div align="center">
<!-- [![Telegram Group](https://img.shields.io/badge/Group-Telegram-%2333C1FF)](#)--> [![Whatsapp Group](https://img.shields.io/badge/Group-WhatsApp-%2322BC18)](https://evolution-api.com/whatsapp)
<!-- [![Whatsapp Group](https://img.shields.io/badge/Group-WhatsApp-%2322BC18)](#) --> [![Postman Collection](https://img.shields.io/badge/Postman-Collection-orange)](https://evolution-api.com/docs/evolution-documentation/getting-started/postman-collection/)
[![Documentation](https://img.shields.io/badge/Documentation-Official-green)](https://evolution-api.com)
[![License](https://img.shields.io/badge/license-GPL--3.0-orange)](./LICENSE) [![License](https://img.shields.io/badge/license-GPL--3.0-orange)](./LICENSE)
[![Support](https://img.shields.io/badge/Buy%20me-coffe-orange)](https://app.picpay.com/user/davidsongomes1998) [![Support](https://img.shields.io/badge/Buy%20me-coffe-orange)](https://app.picpay.com/user/davidsongomes1998)
@@ -16,256 +17,6 @@
This project is based on the [CodeChat](https://github.com/code-chat-br/whatsapp-api). The original project is an implementation of [Baileys](https://github.com/WhiskeySockets/Baileys), serving as a Restful API service that controls WhatsApp functions.</br> This project is based on the [CodeChat](https://github.com/code-chat-br/whatsapp-api). The original project is an implementation of [Baileys](https://github.com/WhiskeySockets/Baileys), serving as a Restful API service that controls WhatsApp functions.</br>
The code allows the creation of multiservice chats, service bots, or any other system that utilizes WhatsApp. The documentation provides instructions on how to set up and use the project, as well as additional information about its features and configuration options. The code allows the creation of multiservice chats, service bots, or any other system that utilizes WhatsApp. The documentation provides instructions on how to set up and use the project, as well as additional information about its features and configuration options.
## Infrastructure
### Nvm installation
```sh
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.3/install.sh | bash
# or
wget -qO- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.3/install.sh | bash
```
>
> After finishing, restart the terminal to load the new information.
>
### Docker installation \[optional\]
```sh
curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh
sudo usermod -aG docker ${USER}
```
### Nodejs installation
```sh
nvm install 16.18.1
```
### pm2 installation
```sh
npm i -g pm2
```
```sh
docker --version
node --version
```
## MongoDb [optional]
After installing docker and docker-compose, up the container.
- [compose from mongodb](./mongodb/docker-compose.yaml)
In the same directory where the file is located, run the following command:
```sh
bash docker.sh
```
Using the database is optional.
## Application startup
Cloning the Repository
```
git clone https://github.com/EvolutionAPI/evolution-api.git
```
Go to the project directory and install all dependencies.</br>
```sh
cd whatsapp-api
npm i
```
Finally, run the command below to start the application:
```sh
# Under development
npm run start
# In production
npm run start:prod
# pm2
pm2 start 'npm run start:prod' --name ApiEvolution
```
## Authentication
You can define two authentication **types** for the routes in the **[env file](./src/dev-env.yml)**.
Authentications must be inserted in the request header.
1. **apikey**
2. **jwt:** A JWT is a standard for authentication and information exchange defined with a signature.
> Authentications are generated at instance creation time.
**Note:** There is also the possibility to define a global api key, which can access and control all instances.
### Connection
#### Create an instance
##### HTTP
> *NOTE:* This key must be inserted in the request header to create an instance.
```http
POST /instance/create HTTP/1.1
Host: localhost:8080
Content-Type: application/json
apikey: t8OOEeISKzpmc3jjcMqBWYSaJH2PIxns
```
##### cURL
```bash
curl --location --request POST 'http://localhost:8080/instance/create' \
--header 'Content-Type: application/json' \
--header 'apikey: t8OOEeISKzpmc3jjcMqBWYSaJH2PIxns' \
--data-raw '{
"instanceName": "evolution"
}'
```
### Response
```ts
{
"instance": {
"instanceName": "evolution",
"status": "created"
},
"hash": {
"jwt": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9. [...]"
// or
// "apikey": "88513847-1B0E-4188-8D76-4A2750C9B6C3"
}
}
```
#### Connection with qrcode
##### HTTP
```http
GET /instance/connect/evolution HTTP/1.1
Host: localhost:8080
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9. [...]
```
```http
GET /instance/connect/evolution HTTP/1.1
Host: localhost:8080
apikey: 88513847-1B0E-4188-8D76-4A2750C9B6C3
```
##### cURL
```bash
curl --location --request GET 'http://localhost:8080/instance/connect/evolution' \
--header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9. [...]'
```
```bash
curl --location --request GET 'http://localhost:8080/instance/connect/evolution' \
--header 'apikey: 88513847-1B0E-4188-8D76-4A2750C9B6C3'
```
### Response
```ts
{
"code": "2@nXSUgRJSBY6T0XJmiFKZ0 [...] ,XsgJhJHYa+0MPpXANdPHHt6Ke/I7O2QyXT/Lsge0uSg=",
"base64": "data:image/png;base64,iVBORw0KGgoAAAANSUhE [...] LkMtqAAAAABJRU5ErkJggg=="
}
```
### App in Docker
- [docker run](./docker.sh)
- [docker-compose](./docker-compose.yml)
- [env for docker](./Docker/.env)
- [DockerHub Evolution API](https://hub.docker.com/repository/docker/davidsongomes/evolution-api)
After building the application, in the same directory as the files above, run the following command:
```sh
docker-compose up
```
## Send Messages
| | |
|-----|---|
| Send Text | ✔ |
| Send Template | ❌ |
| Send Media: audio - video - image - document - gif <br></br>base64: ```true``` | ✔ |
| Send Media File | ❌ |
| Send Audio type WhatsApp | ✔ |
| Send Location | ✔ |
| Send Link Preview | ✔ |
| Send Contact | ✔ |
| Send Reaction - emoji | ✔ |
| Send Poll Message | ✔ |
| Send Buttons (Deprecated) | ❌ |
| Send List (Deprecated) | ❌ |
## Postman collections
- [Postman Json](./postman.json)
## Webhook Events
| Name | Event | TypeData | Description |
|------|-------|-----------|------------|
| APPLICATION_STARTUP | application.startup | json | Notifies you when a application startup |
| QRCODE_UPDATED | qrcode.updated | json | Sends the base64 of the qrcode for reading |
| CONNECTION_UPDATE | connection.update | json | Informs the status of the connection with whatsapp |
| MESSAGES_SET | message.set | json | Sends a list of all your messages uploaded on whatsapp</br>This event occurs only once |
| MESSAGES_UPSERT | message.upsert | json | Notifies you when a message is received |
| MESSAGES_UPDATE | message.update | json | Tells you when a message is updated |
| SEND_MESSAGE | send.message | json | Notifies when a message is sent |
| CONTACTS_SET | contacts.set | json | Performs initial loading of all contacts</br>This event occurs only once |
| CONTACTS_UPSERT | contacts.upsert | json | Reloads all contacts with additional information</br>This event occurs only once |
| CONTACTS_UPDATE | contacts.update | json | Informs you when the chat is updated |
| PRESENCE_UPDATE | presence.update | json | Informs if the user is online, if he is performing some action like writing or recording and his last seen</br>'unavailable' | 'available' | 'composing' | 'recording' | 'paused' |
| CHATS_SET | chats.set | json | Send a list of all loaded chats |
| CHATS_UPDATE | chats.update | json | Informs you when the chat is updated |
| CHATS_UPSERT | chats.upsert | json | Sends any new chat information |
| GROUPS_UPSERT | groups.upsert | JSON | Notifies when a group is created |
| GROUPS_UPDATE | groups.update | JSON | Notifies when a group has its information updated |
| GROUP_PARTICIPANTS_UPDATE | group-participants.update | JSON | Notifies when an action occurs involving a participant</br>'add' | 'remove' | 'promote' | 'demote' |
| NEW_TOKEN | new.jwt | JSON | Notifies when the token (jwt) is updated
## Webhook Routes
When enabling the WEBHOOK_BY_EVENTS options in the global and local webhooks, the following paths will be added at the end of the webhook.
<br><br>
Example:
https://sub.domain.com/webhook-test/exclusive-webhook-code/qrcode-updated
| Name | Path |
|------|-------|
| APPLICATION_STARTUP | /application-startup |
| QRCODE_UPDATED | /qrcode-updated |
| CONNECTION_UPDATE | /connection-update |
| MESSAGES_SET | /messages-set |
| MESSAGES_UPSERT | /messages-upsert |
| MESSAGES_UPDATE | /messages-update |
| SEND_MESSAGE | /send-message |
| CONTACTS_SET | /contacts-set |
| CONTACTS_UPSERT | /contacts-upsert |
| CONTACTS_UPDATE | /contacts-update |
| PRESENCE_UPDATE | /presence-update |
| CHATS_SET | /chats-set |
| CHATS_UPDATE | /chats-update |
| CHATS_UPSERT | /chats-upsert |
| GROUPS_UPSERT | /groups-upsert |
| GROUPS_UPDATE | /groups-update |
| GROUP_PARTICIPANTS_UPDATE | /groups-participants-update |
| NEW_TOKEN | /new-token |
## Env File
See additional settings that can be applied through the **env** file by clicking **[here](./src/dev-env.yml)**.
> **⚠Attention⚠:** rename the **dev-env.yml** file to **env.yml**.
## SSL
To install the SSL certificate, follow the **[instructions](https://certbot.eff.org/instructions?ws=other&os=ubuntufocal)** below.
## SSL ## SSL
To install the SSL certificate, follow the **[instructions](https://certbot.eff.org/instructions?ws=other&os=ubuntufocal)** below. To install the SSL certificate, follow the **[instructions](https://certbot.eff.org/instructions?ws=other&os=ubuntufocal)** below.

View File

@@ -13,14 +13,13 @@ services:
volumes: volumes:
- evolution_instances:/evolution/instances - evolution_instances:/evolution/instances
- evolution_store:/evolution/store - evolution_store:/evolution/store
depends_on:
- mongodb
- redis
environment: environment:
- LOG_LEVEL=ERROR,WARN,DEBUG,INFO,LOG,VERBOSE,DARK,WEBHOOKS
- LOG_BAILEYS=error
# Determine how long the instance should be deleted from memory in case of no connection. # Determine how long the instance should be deleted from memory in case of no connection.
# Default time: 5 minutes # Default time: 5 minutes
# If you don't even want an expiration, enter the value false # If you don't even want an expiration, enter the value false
- DEL_INSTANCE=5 # or false - DEL_INSTANCE=false # 5 or false
# Temporary data storage # Temporary data storage
- STORE_MESSAGES=true - STORE_MESSAGES=true
- STORE_MESSAGE_UP=true - STORE_MESSAGE_UP=true
@@ -32,7 +31,7 @@ services:
- CLEAN_STORE_CONTACTS=true - CLEAN_STORE_CONTACTS=true
- CLEAN_STORE_CHATS=true - CLEAN_STORE_CHATS=true
# Permanent data storage # Permanent data storage
- DATABASE_ENABLED=true - DATABASE_ENABLED=false
- DATABASE_CONNECTION_URI=mongodb://root:root@mongodb:27017/?authSource=admin&readPreference=primary&ssl=false&directConnection=true - DATABASE_CONNECTION_URI=mongodb://root:root@mongodb:27017/?authSource=admin&readPreference=primary&ssl=false&directConnection=true
- DATABASE_CONNECTION_DB_PREFIX_NAME=evolution - DATABASE_CONNECTION_DB_PREFIX_NAME=evolution
# Choose the data you want to save in the application's database or store # Choose the data you want to save in the application's database or store
@@ -43,23 +42,21 @@ services:
- DATABASE_SAVE_DATA_CONTACTS=true - DATABASE_SAVE_DATA_CONTACTS=true
- DATABASE_SAVE_DATA_CHATS=true - DATABASE_SAVE_DATA_CHATS=true
- REDIS_ENABLED=true - REDIS_ENABLED=true
- REDIS_URI=redis://redis:6379 - REDIS_URI=redis://redis:6379/1
- REDIS_PREFIX_KEY=evolution - REDIS_PREFIX_KEY=evolution
# Webhook Settings # Webhook Settings
# Define a global webhook that will listen for enabled events from all instances # Define a global webhook that will listen for enabled events from all instances
- WEBHOOK_GLOBAL_URL=url - WEBHOOK_GLOBAL_URL=<url>
- WEBHOOK_GLOBAL_ENABLED=false - 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 # 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 - WEBHOOK_GLOBAL_WEBHOOK_BY_EVENTS=false
# Automatically maps webhook paths # Automatically maps webhook paths
# Set the events you want to hear # Set the events you want to hear
- WEBHOOK_EVENTS_STATUS_INSTANCE=true
- WEBHOOK_EVENTS_APPLICATION_STARTUP=false - WEBHOOK_EVENTS_APPLICATION_STARTUP=false
- WEBHOOK_EVENTS_QRCODE_UPDATED=true - WEBHOOK_EVENTS_QRCODE_UPDATED=true
- WEBHOOK_EVENTS_MESSAGES_SET=true - WEBHOOK_EVENTS_MESSAGES_SET=true
- WEBHOOK_EVENTS_MESSAGES_UPDATE=true
- WEBHOOK_EVENTS_MESSAGES_UPSERT=true - WEBHOOK_EVENTS_MESSAGES_UPSERT=true
- WEBHOOK_EVENTS_SEND_MESSAGE=true - WEBHOOK_EVENTS_MESSAGES_UPDATE=true
- WEBHOOK_EVENTS_CONTACTS_SET=true - WEBHOOK_EVENTS_CONTACTS_SET=true
- WEBHOOK_EVENTS_CONTACTS_UPSERT=true - WEBHOOK_EVENTS_CONTACTS_UPSERT=true
- WEBHOOK_EVENTS_CONTACTS_UPDATE=true - WEBHOOK_EVENTS_CONTACTS_UPDATE=true
@@ -67,14 +64,16 @@ services:
- WEBHOOK_EVENTS_CHATS_SET=true - WEBHOOK_EVENTS_CHATS_SET=true
- WEBHOOK_EVENTS_CHATS_UPSERT=true - WEBHOOK_EVENTS_CHATS_UPSERT=true
- WEBHOOK_EVENTS_CHATS_UPDATE=true - WEBHOOK_EVENTS_CHATS_UPDATE=true
- WEBHOOK_EVENTS_CONNECTION_UPDATE=true - WEBHOOK_EVENTS_CHATS_DELETE=true
- WEBHOOK_EVENTS_GROUPS_UPSERT=true - WEBHOOK_EVENTS_GROUPS_UPSERT=true
- WEBHOOK_EVENTS_GROUPS_UPDATE=true - WEBHOOK_EVENTS_GROUPS_UPDATE=true
- WEBHOOK_EVENTS_GROUP_PARTICIPANTS_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 # This event fires every time a new token is requested via the refresh route
- WEBHOOK_EVENTS_NEW_JWT_TOKEN=true - WEBHOOK_EVENTS_NEW_JWT_TOKEN=true
# Name that will be displayed on smartphone connection # Name that will be displayed on smartphone connection
- CONFIG_SESSION_PHONE_CLIENT="Evolution API" - CONFIG_SESSION_PHONE_CLIENT=Evolution API
- CONFIG_SESSION_PHONE_NAME=chrome # chrome | firefox | edge | opera | safari
# Set qrcode display limit # Set qrcode display limit
- QRCODE_LIMIT=30 - QRCODE_LIMIT=30
# Defines an authentication type for the api # Defines an authentication type for the api
@@ -88,56 +87,16 @@ services:
- AUTHENTICATION_JWT_EXPIRIN_IN=0 # seconds - 3600s === 1h | zero (0) - never expires - AUTHENTICATION_JWT_EXPIRIN_IN=0 # seconds - 3600s === 1h | zero (0) - never expires
# Set the instance name and webhook url to create an instance in init the application # 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 # With this option activated, you work with a url per webhook event, respecting the local url and the name of each event
- AUTHENTICATION_INSTANCE_WEBHOOK_BY_EVENTS=false
- AUTHENTICATION_INSTANCE_MODE=server # container or server - AUTHENTICATION_INSTANCE_MODE=server # container or server
# if you are using container mode, set the container name and the webhook url to default instance # if you are using container mode, set the container name and the webhook url to default instance
- AUTHENTICATION_INSTANCE_NAME=evolution - AUTHENTICATION_INSTANCE_NAME=evolution
- AUTHENTICATION_INSTANCE_WEBHOOK_URL=url - AUTHENTICATION_INSTANCE_WEBHOOK_URL=<url>
command: ['node', './dist/src/main.js'] command: ['node', './dist/src/main.js']
networks: networks:
- evolution-net - evolution-net
expose: expose:
- 8080 - 8080
mongodb:
container_name: mongodb
image: mongo
restart: always
volumes:
- evolution_mongodb_data:/data/db
- evolution_mongodb_configdb:/data/configdb
ports:
- 27017:27017
environment:
MONGO_INITDB_ROOT_USERNAME: root
MONGO_INITDB_ROOT_PASSWORD: root
networks:
- evolution-net
expose:
- 27017
redis:
image: redis:latest
command: >
redis-server
--port 6379
--appendonly yes
--save 900 1
--save 300 10
--save 60 10000
--appendfsync everysec
volumes:
- evolution_redis:/data
container_name: redis
ports:
- 6379:6379
networks:
- evolution-net
volumes: volumes:
evolution_instances: evolution_instances:
evolution_store: evolution_store:
evolution_mongodb_data:
evolution_mongodb_configdb:
evolution_redis:

View File

@@ -8,10 +8,12 @@ then
docker network create -d bridge ${NET} docker network create -d bridge ${NET}
fi fi
sudo mkdir -p ./docker-data/instances # sudo mkdir -p ./docker-data/instances
sudo mkdir -p ./docker-data/mongodb # sudo mkdir -p ./docker-data/mongodb
sudo mkdir -p ./docker-data/mongodb/data # sudo mkdir -p ./docker-data/mongodb/data
sudo mkdir -p ./docker-data/mongodb/configdb # sudo mkdir -p ./docker-data/mongodb/configdb
# sudo mkdir -p ./docker-data/redis
# sudo mkdir -p ./docker-data/redis/data
docker build -t ${IMAGE} . docker build -t ${IMAGE} .

View File

@@ -1,28 +0,0 @@
version: '3.8'
networks:
api-net:
driver: bridge
services:
mongodb:
container_name: mongodb
# This image already has a single replica set
image: mongo
restart: always
volumes:
# sudo mkdir -p /data/mongodb
- /data/mongodb:/data/db
ports:
- 26712:27017
environment:
MONGO_INITDB_ROOT_USERNAME: root
# Set a password to access the bank
MONGO_INITDB_ROOT_PASSWORD: <password>
networks:
- api-net
expose:
- 26712

View File

@@ -1,8 +1,8 @@
{ {
"name": "evolution-api", "name": "evolution-api",
"version": "1.2.0", "version": "1.1.3",
"description": "Rest api for communication with WhatsApp", "description": "Rest api for communication with WhatsApp",
"main": "index.js", "main": "./dist/src/main.js",
"scripts": { "scripts": {
"build": "tsc", "build": "tsc",
"start": "ts-node --files --transpile-only ./src/main.ts", "start": "ts-node --files --transpile-only ./src/main.ts",
@@ -41,9 +41,9 @@
"homepage": "https://github.com/DavidsonGomes/evolution-api#readme", "homepage": "https://github.com/DavidsonGomes/evolution-api#readme",
"dependencies": { "dependencies": {
"@adiwajshing/keyed-db": "^0.2.4", "@adiwajshing/keyed-db": "^0.2.4",
"@evolution/base": "github:WhiskeySockets/Baileys",
"@ffmpeg-installer/ffmpeg": "^1.1.0", "@ffmpeg-installer/ffmpeg": "^1.1.0",
"@hapi/boom": "^10.0.1", "@hapi/boom": "^10.0.1",
"@whiskeysockets/baileys": "^6.3.0",
"axios": "^1.3.5", "axios": "^1.3.5",
"class-validator": "^0.13.2", "class-validator": "^0.13.2",
"compression": "^1.7.4", "compression": "^1.7.4",

File diff suppressed because one or more lines are too long

View File

@@ -13,10 +13,22 @@ export type Cors = {
CREDENTIALS: boolean; CREDENTIALS: boolean;
}; };
export type LogLevel = 'ERROR' | 'WARN' | 'DEBUG' | 'INFO' | 'LOG' | 'VERBOSE' | 'DARK'; export type LogBaileys = 'fatal' | 'error' | 'warn' | 'info' | 'debug' | 'trace';
export type LogLevel =
| 'ERROR'
| 'WARN'
| 'DEBUG'
| 'INFO'
| 'LOG'
| 'VERBOSE'
| 'DARK'
| 'WEBHOOKS';
export type Log = { export type Log = {
LEVEL: LogLevel[]; LEVEL: LogLevel[];
COLOR: boolean; COLOR: boolean;
BAILEYS: LogBaileys;
}; };
export type SaveData = { export type SaveData = {
@@ -65,7 +77,6 @@ export type EventsWebhook = {
MESSAGES_SET: boolean; MESSAGES_SET: boolean;
MESSAGES_UPSERT: boolean; MESSAGES_UPSERT: boolean;
MESSAGES_UPDATE: boolean; MESSAGES_UPDATE: boolean;
SEND_MESSAGE: boolean;
CONTACTS_SET: boolean; CONTACTS_SET: boolean;
CONTACTS_UPDATE: boolean; CONTACTS_UPDATE: boolean;
CONTACTS_UPSERT: boolean; CONTACTS_UPSERT: boolean;
@@ -87,7 +98,6 @@ export type Instance = {
NAME: string; NAME: string;
WEBHOOK_URL: string; WEBHOOK_URL: string;
MODE: string; MODE: string;
WEBHOOK_BY_EVENTS: boolean;
}; };
export type Auth = { export type Auth = {
API_KEY: ApiKey; API_KEY: ApiKey;
@@ -150,7 +160,9 @@ export class ConfigService {
} }
private envYaml(): Env { private envYaml(): Env {
return load(readFileSync(join(SRC_DIR, 'env.yml'), { encoding: 'utf-8' })) as Env; return load(
readFileSync(join(process.cwd(), 'src', 'env.yml'), { encoding: 'utf-8' }),
) as Env;
} }
private envProcess(): Env { private envProcess(): Env {
@@ -206,6 +218,7 @@ export class ConfigService {
LOG: { LOG: {
LEVEL: process.env?.LOG_LEVEL.split(',') as LogLevel[], LEVEL: process.env?.LOG_LEVEL.split(',') as LogLevel[],
COLOR: process.env?.LOG_COLOR === 'true', COLOR: process.env?.LOG_COLOR === 'true',
BAILEYS: (process.env?.LOG_BAILEYS as LogBaileys) || 'error',
}, },
DEL_INSTANCE: isBooleanString(process.env?.DEL_INSTANCE) DEL_INSTANCE: isBooleanString(process.env?.DEL_INSTANCE)
? process.env.DEL_INSTANCE === 'true' ? process.env.DEL_INSTANCE === 'true'
@@ -222,7 +235,6 @@ export class ConfigService {
MESSAGES_SET: process.env?.WEBHOOK_EVENTS_MESSAGES_SET === 'true', MESSAGES_SET: process.env?.WEBHOOK_EVENTS_MESSAGES_SET === 'true',
MESSAGES_UPSERT: process.env?.WEBHOOK_EVENTS_MESSAGES_UPSERT === 'true', MESSAGES_UPSERT: process.env?.WEBHOOK_EVENTS_MESSAGES_UPSERT === 'true',
MESSAGES_UPDATE: process.env?.WEBHOOK_EVENTS_MESSAGES_UPDATE === 'true', MESSAGES_UPDATE: process.env?.WEBHOOK_EVENTS_MESSAGES_UPDATE === 'true',
SEND_MESSAGE: process.env?.WEBHOOK_EVENTS_SEND_MESSAGE === 'true',
CONTACTS_SET: process.env?.WEBHOOK_EVENTS_CONTACTS_SET === 'true', CONTACTS_SET: process.env?.WEBHOOK_EVENTS_CONTACTS_SET === 'true',
CONTACTS_UPDATE: process.env?.WEBHOOK_EVENTS_CONTACTS_UPDATE === 'true', CONTACTS_UPDATE: process.env?.WEBHOOK_EVENTS_CONTACTS_UPDATE === 'true',
CONTACTS_UPSERT: process.env?.WEBHOOK_EVENTS_CONTACTS_UPSERT === 'true', CONTACTS_UPSERT: process.env?.WEBHOOK_EVENTS_CONTACTS_UPSERT === 'true',
@@ -263,8 +275,6 @@ export class ConfigService {
NAME: process.env.AUTHENTICATION_INSTANCE_NAME, NAME: process.env.AUTHENTICATION_INSTANCE_NAME,
WEBHOOK_URL: process.env.AUTHENTICATION_INSTANCE_WEBHOOK_URL, WEBHOOK_URL: process.env.AUTHENTICATION_INSTANCE_WEBHOOK_URL,
MODE: process.env.AUTHENTICATION_INSTANCE_MODE, MODE: process.env.AUTHENTICATION_INSTANCE_MODE,
WEBHOOK_BY_EVENTS:
process.env.AUTHENTICATION_INSTANCE_WEBHOOK_BY_EVENTS === 'true',
}, },
}, },
}; };

View File

@@ -67,7 +67,6 @@ export class Logger {
this.configService.get<Log>('LOG').LEVEL.forEach((level) => types.push(Type[level])); this.configService.get<Log>('LOG').LEVEL.forEach((level) => types.push(Type[level]));
const typeValue = typeof value; const typeValue = typeof value;
if (types.includes(type)) { if (types.includes(type)) {
if (configService.get<Log>('LOG').COLOR) { if (configService.get<Log>('LOG').COLOR) {
console.log( console.log(

View File

@@ -2,13 +2,23 @@ import mongoose from 'mongoose';
import { configService, Database } from '../config/env.config'; import { configService, Database } from '../config/env.config';
import { Logger } from '../config/logger.config'; import { Logger } from '../config/logger.config';
const logger = new Logger('Db Connection'); const logger = new Logger('MongoDB');
const db = configService.get<Database>('DATABASE'); const db = configService.get<Database>('DATABASE');
export const dbserver = db.ENABLED export const dbserver = (() => {
? mongoose.createConnection(db.CONNECTION.URI, { if (db.ENABLED) {
logger.verbose('connecting');
const dbs = mongoose.createConnection(db.CONNECTION.URI, {
dbName: db.CONNECTION.DB_PREFIX_NAME + '-whatsapp-api', dbName: db.CONNECTION.DB_PREFIX_NAME + '-whatsapp-api',
}) });
: null; logger.verbose('connected in ' + db.CONNECTION.URI);
logger.info('ON - dbName: ' + dbs['$dbName']);
db.ENABLED ? logger.info('ON - dbName: ' + dbserver['$dbName']) : null; process.on('beforeExit', () => {
logger.verbose('instance destroyed');
dbserver.destroy(true, (error) => logger.error(error));
});
return dbs;
}
})();

View File

@@ -1,24 +1,44 @@
import { createClient, RedisClientType } from '@redis/client'; import { createClient, RedisClientType } from '@redis/client';
import { Logger } from '../config/logger.config'; import { Logger } from '../config/logger.config';
import { BufferJSON } from '@evolution/base'; import { BufferJSON } from '@whiskeysockets/baileys';
import { Redis } from '../config/env.config'; import { Redis } from '../config/env.config';
export class RedisCache { export class RedisCache {
constructor(private readonly redisEnv: Partial<Redis>, private instanceName?: string) { constructor() {
this.client = createClient({ url: this.redisEnv.URI }); this.logger.verbose('instance created');
process.on('beforeExit', async () => {
this.client.connect(); this.logger.verbose('instance destroyed');
if (this.statusConnection) {
this.logger.verbose('instance disconnect');
await this.client.disconnect();
}
});
} }
private statusConnection = false;
private instanceName: string;
private redisEnv: Redis;
public set reference(reference: string) { public set reference(reference: string) {
this.logger.verbose('set reference: ' + reference);
this.instanceName = reference; this.instanceName = reference;
} }
public async connect(redisEnv: Redis) {
this.logger.verbose('connecting');
this.client = createClient({ url: redisEnv.URI });
this.logger.verbose('connected in ' + redisEnv.URI);
await this.client.connect();
this.statusConnection = true;
this.redisEnv = redisEnv;
}
private readonly logger = new Logger(RedisCache.name); private readonly logger = new Logger(RedisCache.name);
private client: RedisClientType; private client: RedisClientType;
public async instanceKeys(): Promise<string[]> { public async instanceKeys(): Promise<string[]> {
try { try {
this.logger.verbose('instance keys: ' + this.redisEnv.PREFIX_KEY + ':*');
return await this.client.sendCommand(['keys', this.redisEnv.PREFIX_KEY + ':*']); return await this.client.sendCommand(['keys', this.redisEnv.PREFIX_KEY + ':*']);
} catch (error) { } catch (error) {
this.logger.error(error); this.logger.error(error);
@@ -27,14 +47,18 @@ export class RedisCache {
public async keyExists(key?: string) { public async keyExists(key?: string) {
if (key) { if (key) {
this.logger.verbose('keyExists: ' + key);
return !!(await this.instanceKeys()).find((i) => i === key); return !!(await this.instanceKeys()).find((i) => i === key);
} }
this.logger.verbose('keyExists: ' + this.instanceName);
return !!(await this.instanceKeys()).find((i) => i === this.instanceName); return !!(await this.instanceKeys()).find((i) => i === this.instanceName);
} }
public async writeData(field: string, data: any) { public async writeData(field: string, data: any) {
try { try {
this.logger.verbose('writeData: ' + field);
const json = JSON.stringify(data, BufferJSON.replacer); const json = JSON.stringify(data, BufferJSON.replacer);
return await this.client.hSet( return await this.client.hSet(
this.redisEnv.PREFIX_KEY + ':' + this.instanceName, this.redisEnv.PREFIX_KEY + ':' + this.instanceName,
field, field,
@@ -47,13 +71,19 @@ export class RedisCache {
public async readData(field: string) { public async readData(field: string) {
try { try {
this.logger.verbose('readData: ' + field);
const data = await this.client.hGet( const data = await this.client.hGet(
this.redisEnv.PREFIX_KEY + ':' + this.instanceName, this.redisEnv.PREFIX_KEY + ':' + this.instanceName,
field, field,
); );
if (data) { if (data) {
this.logger.verbose('readData: ' + field + ' success');
return JSON.parse(data, BufferJSON.reviver); return JSON.parse(data, BufferJSON.reviver);
} }
this.logger.verbose('readData: ' + field + ' not found');
return null;
} catch (error) { } catch (error) {
this.logger.error(error); this.logger.error(error);
} }
@@ -61,6 +91,7 @@ export class RedisCache {
public async removeData(field: string) { public async removeData(field: string) {
try { try {
this.logger.verbose('removeData: ' + field);
return await this.client.hDel( return await this.client.hDel(
this.redisEnv.PREFIX_KEY + ':' + this.instanceName, this.redisEnv.PREFIX_KEY + ':' + this.instanceName,
field, field,
@@ -72,6 +103,7 @@ export class RedisCache {
public async delAll(hash?: string) { public async delAll(hash?: string) {
try { try {
this.logger.verbose('instance delAll: ' + hash);
return await this.client.del( return await this.client.del(
hash || this.redisEnv.PREFIX_KEY + ':' + this.instanceName, hash || this.redisEnv.PREFIX_KEY + ':' + this.instanceName,
); );

View File

@@ -36,7 +36,9 @@ LOG:
- LOG - LOG
- VERBOSE - VERBOSE
- DARK - DARK
- WEBHOOKS
COLOR: true COLOR: true
BAILEYS: error # "fatal" | "error" | "warn" | "info" | "debug" | "trace"
# Determine how long the instance should be deleted from memory in case of no connection. # Determine how long the instance should be deleted from memory in case of no connection.
# Default time: 5 minutes # Default time: 5 minutes
@@ -93,7 +95,6 @@ WEBHOOK:
MESSAGES_SET: true MESSAGES_SET: true
MESSAGES_UPSERT: true MESSAGES_UPSERT: true
MESSAGES_UPDATE: true MESSAGES_UPDATE: true
SEND_MESSAGE: true
CONTACTS_SET: true CONTACTS_SET: true
CONTACTS_UPSERT: true CONTACTS_UPSERT: true
CONTACTS_UPDATE: true CONTACTS_UPDATE: true
@@ -112,7 +113,7 @@ WEBHOOK:
CONFIG_SESSION_PHONE: CONFIG_SESSION_PHONE:
# Name that will be displayed on smartphone connection # Name that will be displayed on smartphone connection
CLIENT: 'Evolution API' CLIENT: 'Evolution API'
NAME: Chrome # firefox | edge | opera | safari NAME: chrome # chrome | firefox | edge | opera | safari
# Set qrcode display limit # Set qrcode display limit
QRCODE: QRCODE:
@@ -120,11 +121,11 @@ QRCODE:
# Defines an authentication type for the api # Defines an authentication type for the api
AUTHENTICATION: AUTHENTICATION:
TYPE: apikey # or jwt apikey TYPE: apikey # jwt or apikey
# Define a global apikey to access all instances # Define a global apikey to access all instances
API_KEY: API_KEY:
# OBS: This key must be inserted in the request header to create an instance. # OBS: This key must be inserted in the request header to create an instance.
KEY: B6D711FC-DE4D-4FD5-9365-44120E713976 KEY: B6D711FCDE4D4FD5936544120E713976
# Expose the api key on return from fetch instances # Expose the api key on return from fetch instances
EXPOSE_IN_FETCH_INSTANCES: true EXPOSE_IN_FETCH_INSTANCES: true
# Set the secret key to encrypt and decrypt your token and its expiration time. # Set the secret key to encrypt and decrypt your token and its expiration time.
@@ -134,7 +135,6 @@ AUTHENTICATION:
# Set the instance name and webhook url to create an instance in init the application # Set the instance name and webhook url to create an instance in init the application
INSTANCE: INSTANCE:
# With this option activated, you work with a url per webhook event, respecting the local url and the name of each event # With this option activated, you work with a url per webhook event, respecting the local url and the name of each event
WEBHOOK_BY_EVENTS: false
MODE: server # container or server MODE: server # container or server
# if you are using container mode, set the container name and the webhook url to default instance # if you are using container mode, set the container name and the webhook url to default instance
NAME: evolution NAME: evolution

View File

@@ -16,8 +16,6 @@ function initWA() {
} }
function bootstrap() { function bootstrap() {
initWA();
const logger = new Logger('SERVER'); const logger = new Logger('SERVER');
const app = express(); const app = express();
@@ -34,8 +32,8 @@ function bootstrap() {
methods: [...configService.get<Cors>('CORS').METHODS], methods: [...configService.get<Cors>('CORS').METHODS],
credentials: configService.get<Cors>('CORS').CREDENTIALS, credentials: configService.get<Cors>('CORS').CREDENTIALS,
}), }),
urlencoded({ extended: true, limit: '50mb' }), urlencoded({ extended: true, limit: '136mb' }),
json({ limit: '50mb' }), json({ limit: '136mb' }),
compression(), compression(),
); );
@@ -73,6 +71,8 @@ function bootstrap() {
logger.log(httpServer.TYPE.toUpperCase() + ' - ON: ' + httpServer.PORT), logger.log(httpServer.TYPE.toUpperCase() + ' - ON: ' + httpServer.PORT),
); );
initWA();
onUnexpectedError(); onUnexpectedError();
} }

View File

@@ -5,7 +5,7 @@ import {
initAuthCreds, initAuthCreds,
proto, proto,
SignalDataTypeMap, SignalDataTypeMap,
} from '@evolution/base'; } from '@whiskeysockets/baileys';
import { configService, Database } from '../config/env.config'; import { configService, Database } from '../config/env.config';
import { Logger } from '../config/logger.config'; import { Logger } from '../config/logger.config';
import { dbserver } from '../db/db.connect'; import { dbserver } from '../db/db.connect';

View File

@@ -4,22 +4,17 @@ import {
initAuthCreds, initAuthCreds,
proto, proto,
SignalDataTypeMap, SignalDataTypeMap,
} from '@evolution/base'; } from '@whiskeysockets/baileys';
import { RedisCache } from '../db/redis.client'; import { RedisCache } from '../db/redis.client';
import { Logger } from '../config/logger.config'; import { Logger } from '../config/logger.config';
import { Redis } from '../config/env.config'; import { Redis } from '../config/env.config';
export async function useMultiFileAuthStateRedisDb( export async function useMultiFileAuthStateRedisDb(cache: RedisCache): Promise<{
redisEnv: Partial<Redis>,
instanceName: string,
): Promise<{
state: AuthenticationState; state: AuthenticationState;
saveCreds: () => Promise<void>; saveCreds: () => Promise<void>;
}> { }> {
const logger = new Logger(useMultiFileAuthStateRedisDb.name); const logger = new Logger(useMultiFileAuthStateRedisDb.name);
const cache = new RedisCache(redisEnv, instanceName);
const writeData = async (data: any, key: string): Promise<any> => { const writeData = async (data: any, key: string): Promise<any> => {
try { try {
return await cache.writeData(key, data); return await cache.writeData(key, data);

View File

@@ -27,6 +27,7 @@ export const instanceNameSchema: JSONSchema7 = {
properties: { properties: {
instanceName: { type: 'string' }, instanceName: { type: 'string' },
webhook: { type: 'string' }, webhook: { type: 'string' },
webhook_by_events: { type: 'boolean' },
events: { events: {
type: 'array', type: 'array',
minItems: 1, minItems: 1,
@@ -38,7 +39,6 @@ export const instanceNameSchema: JSONSchema7 = {
'MESSAGES_SET', 'MESSAGES_SET',
'MESSAGES_UPSERT', 'MESSAGES_UPSERT',
'MESSAGES_UPDATE', 'MESSAGES_UPDATE',
'SEND_MESSAGE',
'CONTACTS_SET', 'CONTACTS_SET',
'CONTACTS_UPSERT', 'CONTACTS_UPSERT',
'CONTACTS_UPDATE', 'CONTACTS_UPDATE',
@@ -379,6 +379,9 @@ export const contactMessageSchema: JSONSchema7 = {
description: '"wuid" must be a numeric string', description: '"wuid" must be a numeric string',
}, },
phoneNumber: { type: 'string', minLength: 10 }, phoneNumber: { type: 'string', minLength: 10 },
organization: { type: 'string' },
email: { type: 'string' },
url: { type: 'string' },
}, },
required: ['fullName', 'wuid', 'phoneNumber'], required: ['fullName', 'wuid', 'phoneNumber'],
...isNotEmpty('fullName'), ...isNotEmpty('fullName'),
@@ -665,6 +668,28 @@ export const groupJidSchema: JSONSchema7 = {
...isNotEmpty('groupJid'), ...isNotEmpty('groupJid'),
}; };
export const groupSendInviteSchema: JSONSchema7 = {
$id: v4(),
type: 'object',
properties: {
groupJid: { type: 'string' },
description: { type: 'string' },
numbers: {
type: 'array',
minItems: 1,
uniqueItems: true,
items: {
type: 'string',
minLength: 10,
pattern: '\\d+',
description: '"numbers" must be an array of numeric strings',
},
},
},
required: ['groupJid', 'description', 'numbers'],
...isNotEmpty('groupJid', 'description', 'numbers'),
};
export const groupInviteSchema: JSONSchema7 = { export const groupInviteSchema: JSONSchema7 = {
$id: v4(), $id: v4(),
type: 'object', type: 'object',
@@ -779,7 +804,6 @@ export const webhookSchema: JSONSchema7 = {
'MESSAGES_SET', 'MESSAGES_SET',
'MESSAGES_UPSERT', 'MESSAGES_UPSERT',
'MESSAGES_UPDATE', 'MESSAGES_UPDATE',
'SEND_MESSAGE',
'CONTACTS_SET', 'CONTACTS_SET',
'CONTACTS_UPSERT', 'CONTACTS_UPSERT',
'CONTACTS_UPDATE', 'CONTACTS_UPDATE',

View File

@@ -1,4 +1,4 @@
import { proto } from '@evolution/base'; import { proto } from '@whiskeysockets/baileys';
import { import {
ArchiveChatDto, ArchiveChatDto,
DeleteMessage, DeleteMessage,
@@ -9,6 +9,7 @@ import {
ProfileStatusDto, ProfileStatusDto,
ReadMessageDto, ReadMessageDto,
WhatsAppNumberDto, WhatsAppNumberDto,
getBase64FromMediaMessageDto,
} from '../dto/chat.dto'; } from '../dto/chat.dto';
import { InstanceDto } from '../dto/instance.dto'; import { InstanceDto } from '../dto/instance.dto';
import { ContactQuery } from '../repository/contact.repository'; import { ContactQuery } from '../repository/contact.repository';
@@ -45,11 +46,9 @@ export class ChatController {
public async getBase64FromMediaMessage( public async getBase64FromMediaMessage(
{ instanceName }: InstanceDto, { instanceName }: InstanceDto,
message: proto.IWebMessageInfo, data: getBase64FromMediaMessageDto,
) { ) {
return await this.waMonitor.waInstances[instanceName].getBase64FromMediaMessage( return await this.waMonitor.waInstances[instanceName].getBase64FromMediaMessage(data);
message,
);
} }
public async fetchMessages({ instanceName }: InstanceDto, query: MessageQuery) { public async fetchMessages({ instanceName }: InstanceDto, query: MessageQuery) {

View File

@@ -4,6 +4,7 @@ import {
GroupInvite, GroupInvite,
GroupJid, GroupJid,
GroupPictureDto, GroupPictureDto,
GroupSendInvite,
GroupSubjectDto, GroupSubjectDto,
GroupToggleEphemeralDto, GroupToggleEphemeralDto,
GroupUpdateParticipantDto, GroupUpdateParticipantDto,
@@ -56,10 +57,8 @@ export class GroupController {
return await this.waMonitor.waInstances[instance.instanceName].inviteInfo(inviteCode); return await this.waMonitor.waInstances[instance.instanceName].inviteInfo(inviteCode);
} }
public async acceptInvite(instance: InstanceDto, inviteCode: GroupInvite) { public async sendInvite(instance: InstanceDto, data: GroupSendInvite) {
return await this.waMonitor.waInstances[instance.instanceName].acceptInvite( return await this.waMonitor.waInstances[instance.instanceName].sendInvite(data);
inviteCode,
);
} }
public async revokeInviteCode(instance: InstanceDto, groupJid: GroupJid) { public async revokeInviteCode(instance: InstanceDto, groupJid: GroupJid) {

View File

@@ -1,4 +1,4 @@
import { delay } from '@evolution/base'; import { delay } from '@whiskeysockets/baileys';
import EventEmitter2 from 'eventemitter2'; import EventEmitter2 from 'eventemitter2';
import { Auth, ConfigService } from '../../config/env.config'; import { Auth, ConfigService } from '../../config/env.config';
import { BadRequestException, InternalServerErrorException } from '../../exceptions'; import { BadRequestException, InternalServerErrorException } from '../../exceptions';
@@ -10,6 +10,7 @@ import { WAStartupService } from '../services/whatsapp.service';
import { WebhookService } from '../services/webhook.service'; import { WebhookService } from '../services/webhook.service';
import { Logger } from '../../config/logger.config'; import { Logger } from '../../config/logger.config';
import { wa } from '../types/wa.types'; import { wa } from '../types/wa.types';
import { RedisCache } from '../../db/redis.client';
export class InstanceController { export class InstanceController {
constructor( constructor(
@@ -19,6 +20,7 @@ export class InstanceController {
private readonly eventEmitter: EventEmitter2, private readonly eventEmitter: EventEmitter2,
private readonly authService: AuthService, private readonly authService: AuthService,
private readonly webhookService: WebhookService, private readonly webhookService: WebhookService,
private readonly cache: RedisCache,
) {} ) {}
private readonly logger = new Logger(InstanceController.name); private readonly logger = new Logger(InstanceController.name);
@@ -26,6 +28,7 @@ export class InstanceController {
public async createInstance({ public async createInstance({
instanceName, instanceName,
webhook, webhook,
webhook_by_events,
events, events,
qrcode, qrcode,
token, token,
@@ -40,10 +43,13 @@ export class InstanceController {
]); ]);
} }
await this.authService.checkDuplicateToken(token);
const instance = new WAStartupService( const instance = new WAStartupService(
this.configService, this.configService,
this.eventEmitter, this.eventEmitter,
this.repository, this.repository,
this.cache,
); );
instance.instanceName = instanceName; instance.instanceName = instanceName;
this.waMonitor.waInstances[instance.instanceName] = instance; this.waMonitor.waInstances[instance.instanceName] = instance;
@@ -60,7 +66,12 @@ export class InstanceController {
if (webhook) { if (webhook) {
try { try {
this.webhookService.create(instance, { enabled: true, url: webhook, events }); this.webhookService.create(instance, {
enabled: true,
url: webhook,
events,
webhook_by_events,
});
getEvents = (await this.webhookService.find(instance)).events; getEvents = (await this.webhookService.find(instance)).events;
} catch (error) { } catch (error) {
@@ -78,10 +89,13 @@ export class InstanceController {
events: getEvents, events: getEvents,
}; };
} else { } else {
await this.authService.checkDuplicateToken(token);
const instance = new WAStartupService( const instance = new WAStartupService(
this.configService, this.configService,
this.eventEmitter, this.eventEmitter,
this.repository, this.repository,
this.cache,
); );
instance.instanceName = instanceName; instance.instanceName = instanceName;
this.waMonitor.waInstances[instance.instanceName] = instance; this.waMonitor.waInstances[instance.instanceName] = instance;
@@ -98,7 +112,12 @@ export class InstanceController {
if (webhook) { if (webhook) {
try { try {
this.webhookService.create(instance, { enabled: true, url: webhook, events }); this.webhookService.create(instance, {
enabled: true,
url: webhook,
events,
webhook_by_events,
});
getEvents = (await this.webhookService.find(instance)).events; getEvents = (await this.webhookService.find(instance)).events;
} catch (error) { } catch (error) {
@@ -121,6 +140,7 @@ export class InstanceController {
}, },
hash, hash,
webhook, webhook,
webhook_by_events,
events: getEvents, events: getEvents,
qrcode: getQrcode, qrcode: getQrcode,
}; };
@@ -147,6 +167,27 @@ export class InstanceController {
} }
} }
public async restartInstance({ instanceName }: InstanceDto) {
try {
delete this.waMonitor.waInstances[instanceName];
console.log(this.waMonitor.waInstances[instanceName]);
const instance = new WAStartupService(
this.configService,
this.eventEmitter,
this.repository,
this.cache,
);
instance.instanceName = instanceName;
await instance.connectToWhatsapp();
this.waMonitor.waInstances[instance.instanceName] = instance;
return { error: false, message: 'Instance restarted' };
} catch (error) {
this.logger.error(error);
}
}
public async connectionState({ instanceName }: InstanceDto) { public async connectionState({ instanceName }: InstanceDto) {
return this.waMonitor.waInstances[instanceName]?.connectionStatus; return this.waMonitor.waInstances[instanceName]?.connectionStatus;
} }
@@ -166,7 +207,6 @@ export class InstanceController {
); );
this.waMonitor.waInstances[instanceName]?.client?.ws?.close(); this.waMonitor.waInstances[instanceName]?.client?.ws?.close();
this.waMonitor.waInstances[instanceName]?.client?.end(undefined);
return { error: false, message: 'Instance logged out' }; return { error: false, message: 'Instance logged out' };
} catch (error) { } catch (error) {
@@ -183,8 +223,15 @@ export class InstanceController {
]); ]);
} }
try { try {
delete this.waMonitor.waInstances[instanceName]; if (stateConn.state === 'connecting') {
return { error: false, message: 'Instance deleted' }; await this.logout({ instanceName });
delete this.waMonitor.waInstances[instanceName];
return { error: false, message: 'Instance deleted' };
} else {
delete this.waMonitor.waInstances[instanceName];
this.eventEmitter.emit('remove.instance', instanceName, 'inner');
return { error: false, message: 'Instance deleted' };
}
} catch (error) { } catch (error) {
throw new BadRequestException(error.toString()); throw new BadRequestException(error.toString());
} }

View File

@@ -2,7 +2,8 @@ import {
WAPrivacyOnlineValue, WAPrivacyOnlineValue,
WAPrivacyValue, WAPrivacyValue,
WAReadReceiptsValue, WAReadReceiptsValue,
} from '@evolution/base'; proto,
} from '@whiskeysockets/baileys';
export class OnWhatsAppDto { export class OnWhatsAppDto {
constructor( constructor(
@@ -12,6 +13,11 @@ export class OnWhatsAppDto {
) {} ) {}
} }
export class getBase64FromMediaMessageDto {
message: proto.WebMessageInfo;
convertToMp4?: boolean;
}
export class WhatsAppNumberDto { export class WhatsAppNumberDto {
numbers: string[]; numbers: string[];
} }

View File

@@ -27,6 +27,12 @@ export class GroupInvite {
inviteCode: string; inviteCode: string;
} }
export class GroupSendInvite {
groupJid: string;
description: string;
numbers: string[];
}
export class GroupUpdateParticipantDto extends GroupJid { export class GroupUpdateParticipantDto extends GroupJid {
action: 'add' | 'remove' | 'promote' | 'demote'; action: 'add' | 'remove' | 'promote' | 'demote';
participants: string[]; participants: string[];

View File

@@ -1,6 +1,7 @@
export class InstanceDto { export class InstanceDto {
instanceName: string; instanceName: string;
webhook?: string; webhook?: string;
webhook_by_events?: boolean;
events?: string[]; events?: string[];
qrcode?: boolean; qrcode?: boolean;
token?: string; token?: string;

View File

@@ -1,4 +1,4 @@
import { proto, WAPresence } from '@evolution/base'; import { proto, WAPresence } from '@whiskeysockets/baileys';
export class Quoted { export class Quoted {
key: proto.IMessageKey; key: proto.IMessageKey;
@@ -125,6 +125,9 @@ export class ContactMessage {
fullName: string; fullName: string;
wuid: string; wuid: string;
phoneNumber: string; phoneNumber: string;
organization?: string;
email?: string;
url?: string;
} }
export class SendContactDto extends Metadata { export class SendContactDto extends Metadata {
contactMessage: ContactMessage[]; contactMessage: ContactMessage[];

View File

@@ -2,4 +2,5 @@ export class WebhookDto {
enabled?: boolean; enabled?: boolean;
url?: string; url?: string;
events?: string[]; events?: string[];
webhook_by_events?: boolean;
} }

View File

@@ -9,7 +9,7 @@ import {
NotFoundException, NotFoundException,
} from '../../exceptions'; } from '../../exceptions';
import { InstanceDto } from '../dto/instance.dto'; import { InstanceDto } from '../dto/instance.dto';
import { waMonitor } from '../whatsapp.module'; import { cache, waMonitor } from '../whatsapp.module';
import { Database, Redis, configService } from '../../config/env.config'; import { Database, Redis, configService } from '../../config/env.config';
import { RedisCache } from '../../db/redis.client'; import { RedisCache } from '../../db/redis.client';
@@ -20,7 +20,6 @@ async function getInstance(instanceName: string) {
const exists = !!waMonitor.waInstances[instanceName]; const exists = !!waMonitor.waInstances[instanceName];
if (redisConf.ENABLED) { if (redisConf.ENABLED) {
const cache = new RedisCache(redisConf, instanceName);
const keyExists = await cache.keyExists(); const keyExists = await cache.keyExists();
return exists || keyExists; return exists || keyExists;
} }

View File

@@ -50,6 +50,7 @@ export class MessageUpdateRaw {
datetime?: number; datetime?: number;
status?: wa.StatusMessage; status?: wa.StatusMessage;
owner: string; owner: string;
pollUpdates?: any;
} }
const messageUpdateSchema = new Schema<MessageUpdateRaw>({ const messageUpdateSchema = new Schema<MessageUpdateRaw>({

View File

@@ -6,6 +6,7 @@ export class WebhookRaw {
url?: string; url?: string;
enabled?: boolean; enabled?: boolean;
events?: string[]; events?: string[];
webhook_by_events?: boolean;
} }
const webhookSchema = new Schema<WebhookRaw>({ const webhookSchema = new Schema<WebhookRaw>({

View File

@@ -4,6 +4,7 @@ import { IInsert, Repository } from '../abstract/abstract.repository';
import { IAuthModel, AuthRaw } from '../models'; import { IAuthModel, AuthRaw } from '../models';
import { readFileSync } from 'fs'; import { readFileSync } from 'fs';
import { AUTH_DIR } from '../../config/path.config'; import { AUTH_DIR } from '../../config/path.config';
import { InstanceDto } from '../dto/instance.dto';
export class AuthRepository extends Repository { export class AuthRepository extends Repository {
constructor( constructor(

View File

@@ -5,6 +5,9 @@ import { MessageUpRepository } from './messageUp.repository';
import { MongoClient } from 'mongodb'; import { MongoClient } from 'mongodb';
import { WebhookRepository } from './webhook.repository'; import { WebhookRepository } from './webhook.repository';
import { AuthRepository } from './auth.repository'; import { AuthRepository } from './auth.repository';
import { Auth, ConfigService, Database } from '../../config/env.config';
import { execSync } from 'child_process';
import { join } from 'path';
export class RepositoryBroker { export class RepositoryBroker {
constructor( constructor(
@@ -14,9 +17,11 @@ export class RepositoryBroker {
public readonly messageUpdate: MessageUpRepository, public readonly messageUpdate: MessageUpRepository,
public readonly webhook: WebhookRepository, public readonly webhook: WebhookRepository,
public readonly auth: AuthRepository, public readonly auth: AuthRepository,
private configService: ConfigService,
dbServer?: MongoClient, dbServer?: MongoClient,
) { ) {
this.dbClient = dbServer; this.dbClient = dbServer;
this.__init_repo_without_db__();
} }
private dbClient?: MongoClient; private dbClient?: MongoClient;
@@ -24,4 +29,22 @@ export class RepositoryBroker {
public get dbServer() { public get dbServer() {
return this.dbClient; return this.dbClient;
} }
private __init_repo_without_db__() {
if (!this.configService.get<Database>('DATABASE').ENABLED) {
const storePath = join(process.cwd(), 'store');
execSync(
`mkdir -p ${join(
storePath,
'auth',
this.configService.get<Auth>('AUTHENTICATION').TYPE,
)}`,
);
execSync(`mkdir -p ${join(storePath, 'chats')}`);
execSync(`mkdir -p ${join(storePath, 'contacts')}`);
execSync(`mkdir -p ${join(storePath, 'messages')}`);
execSync(`mkdir -p ${join(storePath, 'message-up')}`);
execSync(`mkdir -p ${join(storePath, 'webhook')}`);
}
}
} }

View File

@@ -22,6 +22,7 @@ import {
ProfileStatusDto, ProfileStatusDto,
ReadMessageDto, ReadMessageDto,
WhatsAppNumberDto, WhatsAppNumberDto,
getBase64FromMediaMessageDto,
} from '../dto/chat.dto'; } from '../dto/chat.dto';
import { ContactQuery } from '../repository/contact.repository'; import { ContactQuery } from '../repository/contact.repository';
import { MessageQuery } from '../repository/message.repository'; import { MessageQuery } from '../repository/message.repository';
@@ -29,7 +30,7 @@ import { chatController } from '../whatsapp.module';
import { RouterBroker } from '../abstract/abstract.router'; import { RouterBroker } from '../abstract/abstract.router';
import { HttpStatus } from './index.router'; import { HttpStatus } from './index.router';
import { MessageUpQuery } from '../repository/messageUp.repository'; import { MessageUpQuery } from '../repository/messageUp.repository';
import { proto } from '@evolution/base'; import { proto } from '@whiskeysockets/baileys';
import { InstanceDto } from '../dto/instance.dto'; import { InstanceDto } from '../dto/instance.dto';
export class ChatRouter extends RouterBroker { export class ChatRouter extends RouterBroker {
@@ -101,10 +102,10 @@ export class ChatRouter extends RouterBroker {
return res.status(HttpStatus.OK).json(response); return res.status(HttpStatus.OK).json(response);
}) })
.post(this.routerPath('getBase64FromMediaMessage'), ...guards, async (req, res) => { .post(this.routerPath('getBase64FromMediaMessage'), ...guards, async (req, res) => {
const response = await this.dataValidate<proto.IWebMessageInfo>({ const response = await this.dataValidate<getBase64FromMediaMessageDto>({
request: req, request: req,
schema: null, schema: null,
ClassRef: Object, ClassRef: getBase64FromMediaMessageDto,
execute: (instance, data) => execute: (instance, data) =>
chatController.getBase64FromMediaMessage(instance, data), chatController.getBase64FromMediaMessage(instance, data),
}); });

View File

@@ -9,6 +9,7 @@ import {
updateGroupSubjectSchema, updateGroupSubjectSchema,
updateGroupDescriptionSchema, updateGroupDescriptionSchema,
groupInviteSchema, groupInviteSchema,
groupSendInviteSchema,
} from '../../validate/validate.schema'; } from '../../validate/validate.schema';
import { RouterBroker } from '../abstract/abstract.router'; import { RouterBroker } from '../abstract/abstract.router';
import { import {
@@ -21,6 +22,7 @@ import {
GroupUpdateParticipantDto, GroupUpdateParticipantDto,
GroupUpdateSettingDto, GroupUpdateSettingDto,
GroupToggleEphemeralDto, GroupToggleEphemeralDto,
GroupSendInvite,
} from '../dto/group.dto'; } from '../dto/group.dto';
import { groupController } from '../whatsapp.module'; import { groupController } from '../whatsapp.module';
import { HttpStatus } from './index.router'; import { HttpStatus } from './index.router';
@@ -120,12 +122,12 @@ export class GroupRouter extends RouterBroker {
res.status(HttpStatus.OK).json(response); res.status(HttpStatus.OK).json(response);
}) })
.get(this.routerPath('acceptInvite'), ...guards, async (req, res) => { .post(this.routerPath('sendInvite'), ...guards, async (req, res) => {
const response = await this.inviteCodeValidate<GroupInvite>({ const response = await this.groupNoValidate<GroupSendInvite>({
request: req, request: req,
schema: groupInviteSchema, schema: groupSendInviteSchema,
ClassRef: GroupInvite, ClassRef: GroupSendInvite,
execute: (instance, data) => groupController.acceptInvite(instance, data), execute: (instance, data) => groupController.sendInvite(instance, data),
}); });
res.status(HttpStatus.OK).json(response); res.status(HttpStatus.OK).json(response);

View File

@@ -23,6 +23,16 @@ export class InstanceRouter extends RouterBroker {
return res.status(HttpStatus.CREATED).json(response); return res.status(HttpStatus.CREATED).json(response);
}) })
.put(this.routerPath('restart'), ...guards, async (req, res) => {
const response = await this.dataValidate<InstanceDto>({
request: req,
schema: instanceNameSchema,
ClassRef: InstanceDto,
execute: (instance) => instanceController.restartInstance(instance),
});
return res.status(HttpStatus.OK).json(response);
})
.get(this.routerPath('connect'), ...guards, async (req, res) => { .get(this.routerPath('connect'), ...guards, async (req, res) => {
const response = await this.dataValidate<InstanceDto>({ const response = await this.dataValidate<InstanceDto>({
request: req, request: req,

View File

@@ -43,8 +43,12 @@ export class AuthService {
{ expiresIn: jwtOpts.EXPIRIN_IN, encoding: 'utf8', subject: 'g-t' }, { expiresIn: jwtOpts.EXPIRIN_IN, encoding: 'utf8', subject: 'g-t' },
); );
this.logger.verbose('JWT token created: ' + token);
const auth = await this.repository.auth.create({ jwt: token }, instance.instanceName); const auth = await this.repository.auth.create({ jwt: token }, instance.instanceName);
this.logger.verbose('JWT token saved in database');
if (auth['error']) { if (auth['error']) {
this.logger.error({ this.logger.error({
localError: AuthService.name + '.jwt', localError: AuthService.name + '.jwt',
@@ -59,8 +63,14 @@ export class AuthService {
private async apikey(instance: InstanceDto, token?: string) { private async apikey(instance: InstanceDto, token?: string) {
const apikey = token ? token : v4().toUpperCase(); const apikey = token ? token : v4().toUpperCase();
this.logger.verbose(
token ? 'APIKEY defined: ' + apikey : 'APIKEY created: ' + apikey,
);
const auth = await this.repository.auth.create({ apikey }, instance.instanceName); const auth = await this.repository.auth.create({ apikey }, instance.instanceName);
this.logger.verbose('APIKEY saved in database');
if (auth['error']) { if (auth['error']) {
this.logger.error({ this.logger.error({
localError: AuthService.name + '.apikey', localError: AuthService.name + '.apikey',
@@ -72,45 +82,80 @@ export class AuthService {
return { apikey }; return { apikey };
} }
public async checkDuplicateToken(token: string) {
const instances = await this.waMonitor.instanceInfo();
this.logger.verbose('checking duplicate token');
const instance = instances.find((instance) => instance.instance.apikey === token);
if (instance) {
throw new BadRequestException('Token already exists');
}
this.logger.verbose('available token');
return true;
}
public async generateHash(instance: InstanceDto, token?: string) { public async generateHash(instance: InstanceDto, token?: string) {
const options = this.configService.get<Auth>('AUTHENTICATION'); const options = this.configService.get<Auth>('AUTHENTICATION');
this.logger.verbose(
'generating hash ' + options.TYPE + ' to instance: ' + instance.instanceName,
);
return (await this[options.TYPE](instance, token)) as return (await this[options.TYPE](instance, token)) as
| { jwt: string } | { jwt: string }
| { apikey: string }; | { apikey: string };
} }
public async refreshToken({ oldToken }: OldToken) { public async refreshToken({ oldToken }: OldToken) {
this.logger.verbose('refreshing token');
if (!isJWT(oldToken)) { if (!isJWT(oldToken)) {
throw new BadRequestException('Invalid "oldToken"'); throw new BadRequestException('Invalid "oldToken"');
} }
try { try {
const jwtOpts = this.configService.get<Auth>('AUTHENTICATION').JWT; const jwtOpts = this.configService.get<Auth>('AUTHENTICATION').JWT;
this.logger.verbose('checking oldToken');
const decode = verify(oldToken, jwtOpts.SECRET, { const decode = verify(oldToken, jwtOpts.SECRET, {
ignoreExpiration: true, ignoreExpiration: true,
}) as Pick<JwtPayload, 'apiName' | 'instanceName' | 'tokenId'>; }) as Pick<JwtPayload, 'apiName' | 'instanceName' | 'tokenId'>;
this.logger.verbose('checking token in database');
const tokenStore = await this.repository.auth.find(decode.instanceName); const tokenStore = await this.repository.auth.find(decode.instanceName);
const decodeTokenStore = verify(tokenStore.jwt, jwtOpts.SECRET, { const decodeTokenStore = verify(tokenStore.jwt, jwtOpts.SECRET, {
ignoreExpiration: true, ignoreExpiration: true,
}) as Pick<JwtPayload, 'apiName' | 'instanceName' | 'tokenId'>; }) as Pick<JwtPayload, 'apiName' | 'instanceName' | 'tokenId'>;
this.logger.verbose('checking tokenId');
if (decode.tokenId !== decodeTokenStore.tokenId) { if (decode.tokenId !== decodeTokenStore.tokenId) {
throw new BadRequestException('Invalid "oldToken"'); throw new BadRequestException('Invalid "oldToken"');
} }
this.logger.verbose('generating new token');
const token = { const token = {
jwt: (await this.jwt({ instanceName: decode.instanceName })).jwt, jwt: (await this.jwt({ instanceName: decode.instanceName })).jwt,
instanceName: decode.instanceName, instanceName: decode.instanceName,
}; };
try { try {
this.logger.verbose('checking webhook');
const webhook = await this.repository.webhook.find(decode.instanceName); const webhook = await this.repository.webhook.find(decode.instanceName);
if ( if (
webhook?.enabled && webhook?.enabled &&
this.configService.get<Webhook>('WEBHOOK').EVENTS.NEW_JWT_TOKEN this.configService.get<Webhook>('WEBHOOK').EVENTS.NEW_JWT_TOKEN
) { ) {
this.logger.verbose('sending webhook');
const httpService = axios.create({ baseURL: webhook.url }); const httpService = axios.create({ baseURL: webhook.url });
await httpService.post( await httpService.post(
'', '',
@@ -126,6 +171,8 @@ export class AuthService {
this.logger.error(error); this.logger.error(error);
} }
this.logger.verbose('token refreshed');
return token; return token;
} catch (error) { } catch (error) {
this.logger.error({ this.logger.error({

View File

@@ -14,16 +14,18 @@ import {
import { RepositoryBroker } from '../repository/repository.manager'; import { RepositoryBroker } from '../repository/repository.manager';
import { NotFoundException } from '../../exceptions'; import { NotFoundException } from '../../exceptions';
import { Db } from 'mongodb'; import { Db } from 'mongodb';
import { RedisCache } from '../../db/redis.client';
import { initInstance } from '../whatsapp.module'; import { initInstance } from '../whatsapp.module';
import { ValidationError } from 'class-validator'; import { RedisCache } from '../../db/redis.client';
export class WAMonitoringService { export class WAMonitoringService {
constructor( constructor(
private readonly eventEmitter: EventEmitter2, private readonly eventEmitter: EventEmitter2,
private readonly configService: ConfigService, private readonly configService: ConfigService,
private readonly repository: RepositoryBroker, private readonly repository: RepositoryBroker,
private readonly cache: RedisCache,
) { ) {
this.logger.verbose('instance created');
this.removeInstance(); this.removeInstance();
this.noConnection(); this.noConnection();
this.delInstanceFiles(); this.delInstanceFiles();
@@ -34,15 +36,12 @@ export class WAMonitoringService {
this.dbInstance = this.db.ENABLED this.dbInstance = this.db.ENABLED
? this.repository.dbServer?.db(this.db.CONNECTION.DB_PREFIX_NAME + '-instances') ? this.repository.dbServer?.db(this.db.CONNECTION.DB_PREFIX_NAME + '-instances')
: undefined; : undefined;
this.redisCache = this.redis.ENABLED ? new RedisCache(this.redis) : undefined;
} }
private readonly db: Partial<Database> = {}; private readonly db: Partial<Database> = {};
private readonly redis: Partial<Redis> = {}; private readonly redis: Partial<Redis> = {};
private dbInstance: Db; private dbInstance: Db;
private redisCache: RedisCache;
private readonly logger = new Logger(WAMonitoringService.name); private readonly logger = new Logger(WAMonitoringService.name);
public readonly waInstances: Record<string, WAStartupService> = {}; public readonly waInstances: Record<string, WAStartupService> = {};
@@ -50,15 +49,30 @@ export class WAMonitoringService {
public delInstanceTime(instance: string) { public delInstanceTime(instance: string) {
const time = this.configService.get<DelInstance>('DEL_INSTANCE'); const time = this.configService.get<DelInstance>('DEL_INSTANCE');
if (typeof time === 'number' && time > 0) { if (typeof time === 'number' && time > 0) {
setTimeout(() => { this.logger.verbose(
`Instance "${instance}" don't have connection, will be removed in ${time} minutes`,
);
setTimeout(async () => {
if (this.waInstances[instance]?.connectionStatus?.state !== 'open') { if (this.waInstances[instance]?.connectionStatus?.state !== 'open') {
delete this.waInstances[instance]; if (this.waInstances[instance]?.connectionStatus?.state === 'connecting') {
await this.waInstances[instance]?.client?.logout(
'Log out instance: ' + instance,
);
this.waInstances[instance]?.client?.ws?.close();
this.waInstances[instance]?.client?.end(undefined);
delete this.waInstances[instance];
} else {
delete this.waInstances[instance];
this.eventEmitter.emit('remove.instance', instance, 'inner');
}
} }
}, 1000 * 60 * time); }, 1000 * 60 * time);
} }
} }
public async instanceInfo(instanceName?: string) { public async instanceInfo(instanceName?: string) {
this.logger.verbose('get instance info');
if (instanceName && !this.waInstances[instanceName]) { if (instanceName && !this.waInstances[instanceName]) {
throw new NotFoundException(`Instance "${instanceName}" not found`); throw new NotFoundException(`Instance "${instanceName}" not found`);
} }
@@ -67,9 +81,14 @@ export class WAMonitoringService {
for await (const [key, value] of Object.entries(this.waInstances)) { for await (const [key, value] of Object.entries(this.waInstances)) {
if (value) { if (value) {
this.logger.verbose('get instance info: ' + key);
if (value.connectionStatus.state === 'open') { if (value.connectionStatus.state === 'open') {
this.logger.verbose('instance: ' + key + ' - connectionStatus: open');
let apikey: string; let apikey: string;
if (this.configService.get<Auth>('AUTHENTICATION').EXPOSE_IN_FETCH_INSTANCES) { if (this.configService.get<Auth>('AUTHENTICATION').EXPOSE_IN_FETCH_INSTANCES) {
this.logger.verbose(
'instance: ' + key + ' - hash exposed in fetch instances',
);
const tokenStore = await this.repository.auth.find(key); const tokenStore = await this.repository.auth.find(key);
apikey = tokenStore.apikey || 'Apikey not found'; apikey = tokenStore.apikey || 'Apikey not found';
@@ -84,6 +103,9 @@ export class WAMonitoringService {
}, },
}); });
} else { } else {
this.logger.verbose(
'instance: ' + key + ' - hash not exposed in fetch instances',
);
instances.push({ instances.push({
instance: { instance: {
instanceName: key, instanceName: key,
@@ -95,8 +117,14 @@ export class WAMonitoringService {
}); });
} }
} else { } else {
this.logger.verbose(
'instance: ' + key + ' - connectionStatus: ' + value.connectionStatus.state,
);
let apikey: string; let apikey: string;
if (this.configService.get<Auth>('AUTHENTICATION').EXPOSE_IN_FETCH_INSTANCES) { if (this.configService.get<Auth>('AUTHENTICATION').EXPOSE_IN_FETCH_INSTANCES) {
this.logger.verbose(
'instance: ' + key + ' - hash exposed in fetch instances',
);
const tokenStore = await this.repository.auth.find(key); const tokenStore = await this.repository.auth.find(key);
apikey = tokenStore.apikey || 'Apikey not found'; apikey = tokenStore.apikey || 'Apikey not found';
@@ -108,6 +136,9 @@ export class WAMonitoringService {
}, },
}); });
} else { } else {
this.logger.verbose(
'instance: ' + key + ' - hash not exposed in fetch instances',
);
instances.push({ instances.push({
instance: { instance: {
instanceName: key, instanceName: key,
@@ -119,10 +150,13 @@ export class WAMonitoringService {
} }
} }
this.logger.verbose('return instance info: ' + instances.length);
return instances.find((i) => i.instance.instanceName === instanceName) ?? instances; return instances.find((i) => i.instance.instanceName === instanceName) ?? instances;
} }
private delInstanceFiles() { private delInstanceFiles() {
this.logger.verbose('cron to delete instance files started');
setInterval(async () => { setInterval(async () => {
if (this.db.ENABLED && this.db.SAVE_DATA.INSTANCE) { if (this.db.ENABLED && this.db.SAVE_DATA.INSTANCE) {
const collections = await this.dbInstance.collections(); const collections = await this.dbInstance.collections();
@@ -134,7 +168,9 @@ export class WAMonitoringService {
{ _id: { $regex: /^session-.*/ } }, { _id: { $regex: /^session-.*/ } },
], ],
}); });
this.logger.verbose('instance files deleted: ' + name);
}); });
} else if (this.redis.ENABLED) {
} else { } else {
const dir = opendirSync(INSTANCE_DIR, { encoding: 'utf-8' }); const dir = opendirSync(INSTANCE_DIR, { encoding: 'utf-8' });
for await (const dirent of dir) { for await (const dirent of dir) {
@@ -150,14 +186,17 @@ export class WAMonitoringService {
}); });
} }
}); });
this.logger.verbose('instance files deleted: ' + dirent.name);
} }
} }
} }
}, 3600 * 1000 * 2); }, 3600 * 1000 * 2);
} }
private async cleaningUp(instanceName: string) { public async cleaningUp(instanceName: string) {
this.logger.verbose('cleaning up instance: ' + instanceName);
if (this.db.ENABLED && this.db.SAVE_DATA.INSTANCE) { if (this.db.ENABLED && this.db.SAVE_DATA.INSTANCE) {
this.logger.verbose('cleaning up instance in database: ' + instanceName);
await this.repository.dbServer.connect(); await this.repository.dbServer.connect();
const collections: any[] = await this.dbInstance.collections(); const collections: any[] = await this.dbInstance.collections();
if (collections.length > 0) { if (collections.length > 0) {
@@ -167,52 +206,70 @@ export class WAMonitoringService {
} }
if (this.redis.ENABLED) { if (this.redis.ENABLED) {
this.redisCache.reference = instanceName; this.logger.verbose('cleaning up instance in redis: ' + instanceName);
await this.redisCache.delAll(); this.cache.reference = instanceName;
await this.cache.delAll();
return; return;
} }
this.logger.verbose('cleaning up instance in files: ' + instanceName);
rmSync(join(INSTANCE_DIR, instanceName), { recursive: true, force: true }); rmSync(join(INSTANCE_DIR, instanceName), { recursive: true, force: true });
} }
public async loadInstance() { public async loadInstance() {
this.logger.verbose('load instances');
const set = async (name: string) => { const set = async (name: string) => {
const instance = new WAStartupService( const instance = new WAStartupService(
this.configService, this.configService,
this.eventEmitter, this.eventEmitter,
this.repository, this.repository,
this.cache,
); );
instance.instanceName = name; instance.instanceName = name;
this.logger.verbose('instance loaded: ' + name);
await instance.connectToWhatsapp(); await instance.connectToWhatsapp();
this.logger.verbose('connectToWhatsapp: ' + name);
this.waInstances[name] = instance; this.waInstances[name] = instance;
}; };
try { try {
if (this.redis.ENABLED) { if (this.redis.ENABLED) {
const keys = await this.redisCache.instanceKeys(); this.logger.verbose('redis enabled');
await this.cache.connect(this.redis as Redis);
const keys = await this.cache.instanceKeys();
if (keys?.length > 0) { if (keys?.length > 0) {
this.logger.verbose('reading instance keys and setting instances');
keys.forEach(async (k) => await set(k.split(':')[1])); keys.forEach(async (k) => await set(k.split(':')[1]));
} else { } else {
this.logger.verbose('no instance keys found');
initInstance(); initInstance();
} }
return; return;
} }
if (this.db.ENABLED && this.db.SAVE_DATA.INSTANCE) { if (this.db.ENABLED && this.db.SAVE_DATA.INSTANCE) {
this.logger.verbose('database enabled');
await this.repository.dbServer.connect(); await this.repository.dbServer.connect();
const collections: any[] = await this.dbInstance.collections(); const collections: any[] = await this.dbInstance.collections();
if (collections.length > 0) { if (collections.length > 0) {
this.logger.verbose('reading collections and setting instances');
collections.forEach( collections.forEach(
async (coll) => await set(coll.namespace.replace(/^[\w-]+\./, '')), async (coll) => await set(coll.namespace.replace(/^[\w-]+\./, '')),
); );
} else { } else {
this.logger.verbose('no collections found');
initInstance(); initInstance();
} }
return; return;
} }
this.logger.verbose('store in files enabled');
const dir = opendirSync(INSTANCE_DIR, { encoding: 'utf-8' }); const dir = opendirSync(INSTANCE_DIR, { encoding: 'utf-8' });
for await (const dirent of dir) { for await (const dirent of dir) {
if (dirent.isDirectory()) { if (dirent.isDirectory()) {
this.logger.verbose('reading instance files and setting instances');
const files = readdirSync(join(INSTANCE_DIR, dirent.name), { const files = readdirSync(join(INSTANCE_DIR, dirent.name), {
encoding: 'utf-8', encoding: 'utf-8',
}); });
@@ -223,6 +280,7 @@ export class WAMonitoringService {
await set(dirent.name); await set(dirent.name);
} else { } else {
this.logger.verbose('no instance files found');
initInstance(); initInstance();
} }
} }
@@ -233,22 +291,38 @@ export class WAMonitoringService {
private removeInstance() { private removeInstance() {
this.eventEmitter.on('remove.instance', async (instanceName: string) => { this.eventEmitter.on('remove.instance', async (instanceName: string) => {
this.logger.verbose('remove instance: ' + instanceName);
try { try {
this.logger.verbose('instance: ' + instanceName + ' - removing from memory');
this.waInstances[instanceName] = undefined; this.waInstances[instanceName] = undefined;
} catch {} } catch {}
try { try {
this.logger.verbose('request cleaning up instance: ' + instanceName);
this.cleaningUp(instanceName); this.cleaningUp(instanceName);
} finally { } finally {
this.logger.warn(`Instance "${instanceName}" - REMOVED`); this.logger.warn(`Instance "${instanceName}" - REMOVED`);
} }
}); });
this.eventEmitter.on('logout.instance', async (instanceName: string) => {
this.logger.verbose('logout instance: ' + instanceName);
try {
this.logger.verbose('request cleaning up instance: ' + instanceName);
this.cleaningUp(instanceName);
} finally {
this.logger.warn(`Instance "${instanceName}" - LOGOUT`);
}
});
} }
private noConnection() { private noConnection() {
this.logger.verbose('checking instances without connection');
this.eventEmitter.on('no.connection', async (instanceName) => { this.eventEmitter.on('no.connection', async (instanceName) => {
try { try {
this.logger.verbose('instance: ' + instanceName + ' - removing from memory');
this.waInstances[instanceName] = undefined; this.waInstances[instanceName] = undefined;
this.logger.verbose('request cleaning up instance: ' + instanceName);
this.cleaningUp(instanceName); this.cleaningUp(instanceName);
} catch (error) { } catch (error) {
this.logger.error({ this.logger.error({

View File

@@ -1,11 +1,15 @@
import { InstanceDto } from '../dto/instance.dto'; import { InstanceDto } from '../dto/instance.dto';
import { WebhookDto } from '../dto/webhook.dto'; import { WebhookDto } from '../dto/webhook.dto';
import { WAMonitoringService } from './monitor.service'; import { WAMonitoringService } from './monitor.service';
import { Logger } from '../../config/logger.config';
export class WebhookService { export class WebhookService {
constructor(private readonly waMonitor: WAMonitoringService) {} constructor(private readonly waMonitor: WAMonitoringService) {}
private readonly logger = new Logger(WebhookService.name);
public create(instance: InstanceDto, data: WebhookDto) { public create(instance: InstanceDto, data: WebhookDto) {
this.logger.verbose('create webhook: ' + instance.instanceName);
this.waMonitor.waInstances[instance.instanceName].setWebhook(data); this.waMonitor.waInstances[instance.instanceName].setWebhook(data);
return { webhook: { ...instance, webhook: data } }; return { webhook: { ...instance, webhook: data } };
@@ -13,6 +17,7 @@ export class WebhookService {
public async find(instance: InstanceDto): Promise<WebhookDto> { public async find(instance: InstanceDto): Promise<WebhookDto> {
try { try {
this.logger.verbose('find webhook: ' + instance.instanceName);
return await this.waMonitor.waInstances[instance.instanceName].findWebhook(); return await this.waMonitor.waInstances[instance.instanceName].findWebhook();
} catch (error) { } catch (error) {
return { enabled: null, url: '' }; return { enabled: null, url: '' };

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,5 @@
/* eslint-disable @typescript-eslint/no-namespace */ /* eslint-disable @typescript-eslint/no-namespace */
import { AuthenticationState, WAConnectionState } from '@evolution/base'; import { AuthenticationState, WAConnectionState } from '@whiskeysockets/baileys';
export enum Events { export enum Events {
APPLICATION_STARTUP = 'application.startup', APPLICATION_STARTUP = 'application.startup',
@@ -9,7 +9,6 @@ export enum Events {
MESSAGES_SET = 'messages.set', MESSAGES_SET = 'messages.set',
MESSAGES_UPSERT = 'messages.upsert', MESSAGES_UPSERT = 'messages.upsert',
MESSAGES_UPDATE = 'messages.update', MESSAGES_UPDATE = 'messages.update',
SEND_MESSAGE = 'send.message',
CONTACTS_SET = 'contacts.set', CONTACTS_SET = 'contacts.set',
CONTACTS_UPSERT = 'contacts.upsert', CONTACTS_UPSERT = 'contacts.upsert',
CONTACTS_UPDATE = 'contacts.update', CONTACTS_UPDATE = 'contacts.update',
@@ -34,7 +33,12 @@ export declare namespace wa {
profilePictureUrl?: string; profilePictureUrl?: string;
}; };
export type LocalWebHook = { enabled?: boolean; url?: string; events?: string[] }; export type LocalWebHook = {
enabled?: boolean;
url?: string;
events?: string[];
webhook_by_events?: boolean;
};
export type StateConnection = { export type StateConnection = {
instance?: string; instance?: string;

View File

@@ -27,8 +27,9 @@ import { WebhookRepository } from './repository/webhook.repository';
import { WebhookModel } from './models/webhook.model'; import { WebhookModel } from './models/webhook.model';
import { AuthRepository } from './repository/auth.repository'; import { AuthRepository } from './repository/auth.repository';
import { WAStartupService } from './services/whatsapp.service'; import { WAStartupService } from './services/whatsapp.service';
import { delay } from '@evolution/base'; import { delay } from '@whiskeysockets/baileys';
import { Events } from './types/wa.types'; import { Events } from './types/wa.types';
import { RedisCache } from '../db/redis.client';
const logger = new Logger('WA MODULE'); const logger = new Logger('WA MODULE');
@@ -46,10 +47,18 @@ export const repository = new RepositoryBroker(
messageUpdateRepository, messageUpdateRepository,
webhookRepository, webhookRepository,
authRepository, authRepository,
configService,
dbserver?.getClient(), dbserver?.getClient(),
); );
export const waMonitor = new WAMonitoringService(eventEmitter, configService, repository); export const cache = new RedisCache();
export const waMonitor = new WAMonitoringService(
eventEmitter,
configService,
repository,
cache,
);
const authService = new AuthService(configService, waMonitor, repository); const authService = new AuthService(configService, waMonitor, repository);
@@ -64,6 +73,7 @@ export const instanceController = new InstanceController(
eventEmitter, eventEmitter,
authService, authService,
webhookService, webhookService,
cache,
); );
export const viewsController = new ViewsController(waMonitor, configService); export const viewsController = new ViewsController(waMonitor, configService);
export const sendMessageController = new SendMessageController(waMonitor); export const sendMessageController = new SendMessageController(waMonitor);
@@ -71,10 +81,11 @@ export const chatController = new ChatController(waMonitor);
export const groupController = new GroupController(waMonitor); export const groupController = new GroupController(waMonitor);
export async function initInstance() { export async function initInstance() {
const instance = new WAStartupService(configService, eventEmitter, repository); const instance = new WAStartupService(configService, eventEmitter, repository, cache);
const mode = configService.get<Auth>('AUTHENTICATION').INSTANCE.MODE; const mode = configService.get<Auth>('AUTHENTICATION').INSTANCE.MODE;
logger.verbose('Sending data webhook for event: ' + Events.APPLICATION_STARTUP);
instance.sendDataWebhook( instance.sendDataWebhook(
Events.APPLICATION_STARTUP, Events.APPLICATION_STARTUP,
{ {
@@ -85,9 +96,14 @@ export async function initInstance() {
); );
if (mode === 'container') { if (mode === 'container') {
logger.verbose('Application startup in container mode');
const instanceName = configService.get<Auth>('AUTHENTICATION').INSTANCE.NAME; const instanceName = configService.get<Auth>('AUTHENTICATION').INSTANCE.NAME;
logger.verbose('Instance name: ' + instanceName);
const instanceWebhook = const instanceWebhook =
configService.get<Auth>('AUTHENTICATION').INSTANCE.WEBHOOK_URL; configService.get<Auth>('AUTHENTICATION').INSTANCE.WEBHOOK_URL;
logger.verbose('Instance webhook: ' + instanceWebhook);
instance.instanceName = instanceName; instance.instanceName = instanceName;
@@ -96,13 +112,17 @@ export async function initInstance() {
const hash = await authService.generateHash({ const hash = await authService.generateHash({
instanceName: instance.instanceName, instanceName: instance.instanceName,
token: configService.get<Auth>('AUTHENTICATION').API_KEY.KEY,
}); });
logger.verbose('Hash generated: ' + hash);
if (instanceWebhook) { if (instanceWebhook) {
logger.verbose('Creating webhook for instance: ' + instanceName);
try { try {
webhookService.create(instance, { enabled: true, url: instanceWebhook }); webhookService.create(instance, { enabled: true, url: instanceWebhook });
logger.verbose('Webhook created');
} catch (error) { } catch (error) {
this.logger.log(error); logger.log(error);
} }
} }
@@ -120,7 +140,7 @@ export async function initInstance() {
return await this.connectionState({ instanceName }); return await this.connectionState({ instanceName });
} }
} catch (error) { } catch (error) {
this.logger.log(error); logger.log(error);
} }
const result = { const result = {

View File

View File

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.4 KiB