Compare commits

..

62 Commits
1.0.8 ... 1.1.2

Author SHA1 Message Date
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
Davidson Gomes
e05690af4c Merge branch 'release/1.1.0' 2023-06-21 11:18:25 -03:00
Davidson Gomes
8e65dd0546 changelog release 1.1.0 2023-06-21 11:18:11 -03:00
Davidson Gomes
509442a2f3 changelog release 1.1.0 2023-06-21 11:17:40 -03:00
Davidson Gomes
a5102d1b94 new postman file 2023-06-21 11:16:44 -03:00
Davidson Gomes
19ee2b3f34 fix: fix for container mode also work only with files 2023-06-20 19:31:39 -03:00
Davidson Gomes
5d29c2d881 fix: fix for container mode also work only with files 2023-06-20 19:29:47 -03:00
Davidson Gomes
a08bbab9dc feat: route to send Sticker 2023-06-20 16:47:26 -03:00
Davidson Gomes
84f6394f1f fix: Adjustment in the recording of temporary files and periodic cleaning 2023-06-20 14:57:30 -03:00
Davidson Gomes
d359949310 fix: Adjustment in the recording of temporary files and periodic cleaning 2023-06-20 14:57:19 -03:00
Davidson Gomes
30cd8a03eb feat: the created instance token can now also be optionally defined manually in the creation endpoint 2023-06-20 09:07:26 -03:00
Davidson Gomes
1b7015c0df conversion of audios for sending recorded audio, now it is possible to send mp3 audios and not just ogg 2023-06-19 22:37:42 -03:00
Davidson Gomes
55b14641e0 conversion of audios for sending recorded audio, now it is possible to send mp3 audios and not just ogg 2023-06-19 22:30:59 -03:00
Davidson Gomes
4936d7fcc6 conversion of audios for sending recorded audio, now it is possible to send mp3 audios and not just ogg 2023-06-19 22:29:07 -03:00
Davidson Gomes
b8fa43296d conversion of audios for sending recorded audio, now it is possible to send mp3 audios and not just ogg 2023-06-19 22:28:39 -03:00
Davidson Gomes
631dd01c92 feat: added option to generate qrcode as soon as the instance is created 2023-06-13 17:42:30 -03:00
Davidson Gomes
fdc72bc84e feat: added option to generate qrcode as soon as the instance is created 2023-06-13 17:35:13 -03:00
Davidson Gomes
c63da9cd6e feat: added option to generate qrcode as soon as the instance is created 2023-06-13 17:23:52 -03:00
Davidson Gomes
849d570bcb fixed docker files and quoted message option 2023-06-13 14:08:26 -03:00
Davidson Gomes
85e6efb8b0 fixed docker files and quoted message option 2023-06-13 13:14:53 -03:00
Davidson Gomes
485c8c3113 fixed docker files and quoted message option 2023-06-13 13:11:24 -03:00
Davidson Gomes
f2d0a8eb8c fixed the problem of not disabling the global webhook by the variable and apikey only appears if it is enabled to be exposed 2023-06-13 11:52:30 -03:00
Davidson Gomes
9201bc1022 fix sending narrated audio on whatsapp android and ios 2023-06-13 11:41:17 -03:00
Davidson Gomes
2847a95c57 feat: expose api key in fetch instances 2023-06-12 16:07:38 -03:00
Davidson Gomes
fc30bb9852 feat: Added configuration of events by webhook of instances 2023-06-12 14:40:26 -03:00
Davidson Gomes
0f360d34e8 fix: readme 2023-06-12 13:45:51 -03:00
Davidson Gomes
ec435e086f fix: readme 2023-06-12 13:41:33 -03:00
Davidson Gomes
1efe7f7cde fix: readme 2023-06-12 13:35:47 -03:00
Davidson Gomes
5759341c52 feat: accept invite code 2023-06-12 12:08:53 -03:00
Davidson Gomes
ea9ba27f22 feat: Route to update group description 2023-06-12 12:05:52 -03:00
Davidson Gomes
a28bbce1f9 feat: Route to update group subject 2023-06-12 12:02:10 -03:00
Davidson Gomes
75b48aa8ac fix error after logout and try to get status or to connect again 2023-06-12 10:32:47 -03:00
Davidson Gomes
573e56cd8c feat: Route to update the privacy settings 2023-06-11 19:24:19 -03:00
Davidson Gomes
ab28b4c0c5 feat: Route to update the privacy settings 2023-06-11 11:38:26 -03:00
Davidson Gomes
55e36f24a5 feat: Route to fetch all privacy settings 2023-06-11 11:04:52 -03:00
Davidson Gomes
ea8d6bf6c8 feat: Route to fetch all groups that the connection is part of 2023-06-11 11:00:40 -03:00
Davidson Gomes
75b8bcb853 feat: Added conversion of audios for sending recorded audio 2023-06-11 10:36:59 -03:00
Davidson Gomes
2b2cc2effc feat: Added conversion of audios for sending recorded audio 2023-06-11 10:29:49 -03:00
Davidson Gomes
ae1c5908ae feat: Improved fetch instances endpoint and prepare changelog to version 1.1.0 in homolog 2023-06-10 18:56:11 -03:00
Davidson Gomes
f067cb99c8 fix: adjust in docker-compose file and added all variables in dockerfile 2023-06-10 18:11:28 -03:00
Davidson Gomes
7e7d3a1659 fix: adjust in docker-compose file and added all variables in dockerfile 2023-06-10 17:46:45 -03:00
Davidson Gomes
c5f364f3e4 fix: adjust in docker-compose file and added all variables in dockerfile 2023-06-10 15:48:28 -03:00
Davidson Gomes
2a6366ef85 Merge tag '1.0.9' into develop
* Adjust dockerfile variables
2023-06-10 12:06:13 -03:00
Davidson Gomes
66fa855485 Merge branch 'release/1.0.9' 2023-06-10 12:06:03 -03:00
Davidson Gomes
829b078b25 fix: adjust dockerfile variables 2023-06-10 12:05:54 -03:00
Davidson Gomes
5cf1eacd1f fix: adjust dockerfile variables 2023-06-10 12:04:33 -03:00
Davidson Gomes
7041cd7483 Merge tag '1.0.8' into develop
* Added Docker compose file
* Added ChangeLog file
2023-06-09 10:21:22 -03:00
54 changed files with 1976 additions and 1283 deletions

BIN
.DS_Store vendored Normal file

Binary file not shown.

3
.gitignore vendored
View File

@@ -11,6 +11,9 @@ yarn-debug.log*
yarn-error.log*
lerna-debug.log*
/store/*
/docker-compose-data
# Package
/yarn.lock
/package-lock.json

View File

@@ -1,3 +1,58 @@
# 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)
### Features
* 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
### Fixed
* 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
* Remove recording of old messages on sync
# 1.0.9 (2023-06-10)
### Fixed
* Adjust dockerfile variables
# 1.0.8 (2023-06-09)
### Features

View File

@@ -1,80 +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_CLEANING_INTERVAL=7200 # seconds ===2h
STORE_MESSAGE=true
STORE_CONTACTS=false
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'
## 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

@@ -9,6 +9,9 @@ COPY ./package.json .
ENV DOCKER_ENV=true
ENV SERVER_TYPE="http"
ENV SERVER_PORT=8080
ENV CORS_ORIGIN="*"
ENV CORS_METHODS="POST,GET,PUT,DELETE"
ENV CORS_CREDENTIALS=true
@@ -16,67 +19,72 @@ ENV CORS_CREDENTIALS=true
ENV LOG_LEVEL="ERROR,WARN,DEBUG,INFO,LOG,VERBOSE,DARK"
ENV LOG_COLOR=true
ENV DEL_INSTANCE=false
ENV DEL_INSTANCE=$DEL_INSTANCE
ENV STORE_CLEANING_INTERVAL=7200
ENV STORE_MESSAGE=true
ENV STORE_CONTACTS=true
ENV STORE_CHATS=true
ENV STORE_MESSAGES=$STORE_MESSAGE
ENV STORE_MESSAGE_UP=$STORE_MESSAGE_UP
ENV STORE_CONTACTS=$STORE_CONTACTS
ENV STORE_CHATS=$STORE_CHATS
ENV CLEAN_STORE_CLEANING_INTERVAL=$CLEAN_STORE_CLEANING_INTERVAL
ENV CLEAN_STORE_MESSAGES=$CLEAN_STORE_MESSAGE
ENV CLEAN_STORE_MESSAGE_UP=$CLEAN_STORE_MESSAGE_UP
ENV CLEAN_STORE_CONTACTS=$CLEAN_STORE_CONTACTS
ENV CLEAN_STORE_CHATS=$CLEAN_STORE_CHATS
ENV DATABASE_ENABLED=$DATABASE_ENABLED
ENV DATABASE_CONNECTION_URI=$DATABASE_CONNECTION_URI
ENV DATABASE_CONNECTION_DB_PREFIX_NAME=$DATABASE_CONNECTION_DB_PREFIX_NAME
ENV DATABASE_SAVE_DATA_INSTANCE=false
ENV DATABASE_SAVE_DATA_OLD_MESSAGE=false
ENV DATABASE_SAVE_DATA_NEW_MESSAGE=true
ENV DATABASE_SAVE_MESSAGE_UPDATE=false
ENV DATABASE_SAVE_DATA_CONTACTS=true
ENV DATABASE_SAVE_DATA_CHATS=true
ENV DATABASE_SAVE_DATA_INSTANCE=$DATABASE_SAVE_DATA_INSTANCE
ENV DATABASE_SAVE_DATA_OLD_MESSAGE=$DATABASE_SAVE_DATA_OLD_MESSAGE
ENV DATABASE_SAVE_DATA_NEW_MESSAGE=$DATABASE_SAVE_DATA_NEW_MESSAGE
ENV DATABASE_SAVE_MESSAGE_UPDATE=$DATABASE_SAVE_MESSAGE_UPDATE
ENV DATABASE_SAVE_DATA_CONTACTS=$DATABASE_SAVE_DATA_CONTACTS
ENV DATABASE_SAVE_DATA_CHATS=$DATABASE_SAVE_DATA_CHATS
ENV REDIS_ENABLED=$REDIS_ENABLED
ENV REDIS_URI=$REDIS_URI
ENV WEBHOOK_GLOBAL_URL=$WEBHOOK_GLOBAL_URL
ENV WEBHOOK_GLOBAL_ENABLED=true
ENV WEBHOOK_GLOBAL_ENABLED=$WEBHOOK_GLOBAL_ENABLED
ENV WEBHOOK_GLOBAL_WEBHOOK_BY_EVENTS=$WEBHOOK_GLOBAL_WEBHOOK_BY_EVENTS
ENV WEBHOOK_EVENTS_STATUS_INSTANCE=true
ENV WEBHOOK_EVENTS_APPLICATION_STARTUP=true
ENV WEBHOOK_EVENTS_QRCODE_UPDATED=true
ENV WEBHOOK_EVENTS_MESSAGES_SET=true
ENV WEBHOOK_EVENTS_MESSAGES_UPDATE=true
ENV WEBHOOK_EVENTS_MESSAGES_UPSERT=true
ENV WEBHOOK_EVENTS_SEND_MESSAGE=true
ENV WEBHOOK_EVENTS_CONTACTS_SET=true
ENV WEBHOOK_EVENTS_CONTACTS_UPSERT=true
ENV WEBHOOK_EVENTS_CONTACTS_UPDATE=true
ENV WEBHOOK_EVENTS_PRESENCE_UPDATE=true
ENV WEBHOOK_EVENTS_CHATS_SET=true
ENV WEBHOOK_EVENTS_CHATS_UPSERT=true
ENV WEBHOOK_EVENTS_CHATS_UPDATE=true
ENV WEBHOOK_EVENTS_CONNECTION_UPDATE=true
ENV WEBHOOK_EVENTS_GROUPS_UPSERT=true
ENV WEBHOOK_EVENTS_GROUPS_UPDATE=true
ENV WEBHOOK_EVENTS_GROUP_PARTICIPANTS_UPDATE=true
ENV WEBHOOK_EVENTS_APPLICATION_STARTUP=$WEBHOOK_EVENTS_APPLICATION_STARTUP
ENV WEBHOOK_EVENTS_QRCODE_UPDATED=$WEBHOOK_EVENTS_QRCODE_UPDATED
ENV WEBHOOK_EVENTS_MESSAGES_SET=$WEBHOOK_EVENTS_MESSAGES_SET
ENV WEBHOOK_EVENTS_MESSAGES_UPDATE=$WEBHOOK_EVENTS_MESSAGES_UPDATE
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_UPSERT=$WEBHOOK_EVENTS_CONTACTS_UPSERT
ENV WEBHOOK_EVENTS_CONTACTS_UPDATE=$WEBHOOK_EVENTS_CONTACTS_UPDATE
ENV WEBHOOK_EVENTS_PRESENCE_UPDATE=$WEBHOOK_EVENTS_PRESENCE_UPDATE
ENV WEBHOOK_EVENTS_CHATS_SET=$WEBHOOK_EVENTS_CHATS_SET
ENV WEBHOOK_EVENTS_CHATS_UPSERT=$WEBHOOK_EVENTS_CHATS_UPSERT
ENV WEBHOOK_EVENTS_CHATS_UPDATE=$WEBHOOK_EVENTS_CHATS_UPDATE
ENV WEBHOOK_EVENTS_CONNECTION_UPDATE=$WEBHOOK_EVENTS_CONNECTION_UPDATE
ENV WEBHOOK_EVENTS_GROUPS_UPSERT=$WEBHOOK_EVENTS_GROUPS_UPSERT
ENV WEBHOOK_EVENTS_GROUPS_UPDATE=$WEBHOOK_EVENTS_GROUPS_UPDATE
ENV WEBHOOK_EVENTS_GROUP_PARTICIPANTS_UPDATE=$WEBHOOK_EVENTS_GROUP_PARTICIPANTS_UPDATE
ENV WEBHOOK_EVENTS_NEW_JWT_TOKEN=true
ENV WEBHOOK_EVENTS_NEW_JWT_TOKEN=$WEBHOOK_EVENTS_NEW_JWT_TOKEN
ENV CONFIG_SESSION_PHONE_CLIENT="Evolution API"
ENV CONFIG_SESSION_PHONE_NAME="Chrome"
ENV CONFIG_SESSION_PHONE_CLIENT=$CONFIG_SESSION_PHONE_CLIENT
ENV CONFIG_SESSION_PHONE_NAME=$CONFIG_SESSION_PHONE_NAME
ENV QRCODE_LIMIT=30
ENV QRCODE_LIMIT=$QRCODE_LIMIT
ENV AUTHENTICATION_TYPE="apikey"
ENV AUTHENTICATION_TYPE=$AUTHENTICATION_TYPE
ENV AUTHENTICATION_API_KEY=$AUTHENTICATION_API_KEY
ENV AUTHENTICATION_EXPOSE_IN_FETCH_INSTANCES=$AUTHENTICATION_EXPOSE_IN_FETCH_INSTANCES
ENV AUTHENTICATION_JWT_EXPIRIN_IN=0
ENV AUTHENTICATION_JWT_SECRET="L0YWtjb2w554WFqPG"
ENV AUTHENTICATION_JWT_EXPIRIN_IN=$AUTHENTICATION_JWT_EXPIRIN_IN
ENV AUTHENTICATION_JWT_SECRET="L=0YWt]b2w[WF>#>:&E`"
ENV AUTHENTICATION_INSTANCE_NAME=$AUTHENTICATION_INSTANCE_NAME
ENV AUTHENTICATION_INSTANCE_WEBHOOK_URL=$AUTHENTICATION_INSTANCE_WEBHOOK_URL
ENV AUTHENTICATION_INSTANCE_MODE=$AUTHENTICATION_INSTANCE_MODE
ENV AUTHENTICATION_INSTANCE_WEBHOOK_BY_EVENTS=$AUTHENTICATION_INSTANCE_WEBHOOK_BY_EVENTS
RUN npm install

242
README.md
View File

@@ -1,247 +1,21 @@
<h1 align="center">Evolution Api</h1>
<!--
</br>
<hr style="height: 5px;background: #007500;margin: 20px 0;box-shadow: 0px 3px 5px 0px rgb(204 204 204);">-->
<!-- <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)](#)
[![Whatsapp Group](https://img.shields.io/badge/Group-WhatsApp-%2322BC18)](https://doc.evolution-api.com)
[![Documentation](https://img.shields.io/badge/Documentation-Official-green)](https://doc.evolution-api.com)
[![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)
<!-- </div> -->
</div>
<!-- <div align="center"><img src="./public/images/cover.png"></div>-> -->
<div align="center"><img src="./public/images/cover.png"></div>
## WhatsApp-Api-NodeJs
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/adiwajshing/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.
## 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.17.0
```
### 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/code-chat-br/whatsapp-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 ApiCodechat
```
## 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": "codechat"
}'
```
### Response
```ts
{
"instance": {
"instanceName": "codechat",
"status": "created"
},
"hash": {
"jwt": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9. [...]"
// or
// "apikey": "88513847-1B0E-4188-8D76-4A2750C9B6C3"
}
}
```
#### Connection with qrcode
##### HTTP
```http
GET /instance/connect/codechat HTTP/1.1
Host: localhost:8080
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9. [...]
```
```http
GET /instance/connect/codechat HTTP/1.1
Host: localhost:8080
apikey: 88513847-1B0E-4188-8D76-4A2750C9B6C3
```
##### cURL
```bash
curl --location --request GET 'http://localhost:8080/instance/connect/codechat' \
--header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9. [...]'
```
```bash
curl --location --request GET 'http://localhost:8080/instance/connect/codechat' \
--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-codechat/api](https://hub.docker.com/r/codechat/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 Buttons | ✔ |
| Send Template | ❌ |
| Send Media: audio - video - image - document - gif <br></br>base64: ```true``` | ✔ |
| Send Media File | ❌ |
| Send Audio type WhatsApp | ✔ |
| Send Location | ✔ |
| Send List | ✔ |
| Send Link Preview | ✔ |
| Send Contact | ✔ |
| Send Reaction - emoji | ✔ |
| Send Poll Message | ✔ |
## 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
## 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
To install the SSL certificate, follow the **[instructions](https://certbot.eff.org/instructions?ws=other&os=ubuntufocal)** below.
@@ -254,8 +28,6 @@ This code was produced based on the baileys library and it is still under develo
# Donate to the project.
<div align="center">
#### PicPay
<div align="center">

View File

@@ -5,14 +5,97 @@ networks:
driver: bridge
services:
base:
api:
container_name: evolution_api
image: davidsongomes/evolution-api:latest
image: evolution/api:local
ports:
- 8080:8080
volumes:
- /data/instances:/evolution/instances
- evolution_instances:/evolution/instances
- evolution_store:/evolution/store
environment:
# 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 # or false
# Temporary data storage
- STORE_MESSAGES=true
- STORE_MESSAGE_UP=true
- STORE_CONTACTS=true
- STORE_CHATS=true
- CLEAN_STORE_CLEANING_INTERVAL=7200 # seconds === 2h
- CLEAN_STORE_MESSAGES=true
- CLEAN_STORE_MESSAGE_UP=true
- CLEAN_STORE_CONTACTS=true
- CLEAN_STORE_CHATS=true
# Permanent data storage
- DATABASE_ENABLED=false
- DATABASE_CONNECTION_URI=mongodb://root:root@mongodb:27017/?authSource=admin&readPreference=primary&ssl=false&directConnection=true
- DATABASE_CONNECTION_DB_PREFIX_NAME=evolution
# Choose the data you want to save in the application's database or store
- DATABASE_SAVE_DATA_INSTANCE=true
- 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=false
- REDIS_URI=redis://redis:6379/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
# 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
# Automatically maps webhook paths
# Set the events you want to hear
- WEBHOOK_EVENTS_APPLICATION_STARTUP=false
- WEBHOOK_EVENTS_QRCODE_UPDATED=true
- WEBHOOK_EVENTS_MESSAGES_SET=true
- WEBHOOK_EVENTS_MESSAGES_UPSERT=true
- WEBHOOK_EVENTS_MESSAGES_UPDATE=true
- WEBHOOK_EVENTS_SEND_MESSAGE=true
- WEBHOOK_EVENTS_CONTACTS_SET=true
- WEBHOOK_EVENTS_CONTACTS_UPSERT=true
- WEBHOOK_EVENTS_CONTACTS_UPDATE=true
- WEBHOOK_EVENTS_PRESENCE_UPDATE=true
- WEBHOOK_EVENTS_CHATS_SET=true
- WEBHOOK_EVENTS_CHATS_UPSERT=true
- WEBHOOK_EVENTS_CHATS_UPDATE=true
- WEBHOOK_EVENTS_CHATS_DELETE=true
- WEBHOOK_EVENTS_GROUPS_UPSERT=true
- WEBHOOK_EVENTS_GROUPS_UPDATE=true
- WEBHOOK_EVENTS_GROUP_PARTICIPANTS_UPDATE=true
- WEBHOOK_EVENTS_CONNECTION_UPDATE=true
# This event fires every time a new token is requested via the refresh route
- WEBHOOK_EVENTS_NEW_JWT_TOKEN=true
# Name that will be displayed on smartphone connection
- CONFIG_SESSION_PHONE_CLIENT=Evolution API
- CONFIG_SESSION_PHONE_NAME=chrome # chrome | firefox | edge | opera | safari
# Set qrcode display limit
- QRCODE_LIMIT=30
# Defines an authentication type for the api
- AUTHENTICATION_TYPE=apikey # 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=B6D711FCDE4D4FD5936544120E713976
# Expose the api key on return from fetch instances
- AUTHENTICATION_EXPOSE_IN_FETCH_INSTANCES=true
# Set the secret key to encrypt and decrypt your token and its expiration time.
- 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
# With this option activated, you work with a url per webhook event, respecting the local url and the name of each event
- AUTHENTICATION_INSTANCE_MODE=server # container or server
# if you are using container mode, set the container name and the webhook url to default instance
- AUTHENTICATION_INSTANCE_NAME=evolution
- AUTHENTICATION_INSTANCE_WEBHOOK_URL=<url>
command: ['node', './dist/src/main.js']
networks:
- evolution-net
- evolution-net
expose:
- 8080
volumes:
evolution_instances:
evolution_store:

20
docker.sh Executable file
View File

@@ -0,0 +1,20 @@
#!/bin/bash
NET='evolution-net'
IMAGE='evolution/api:local'
if !(docker network ls | grep ${NET} > /dev/null)
then
docker network create -d bridge ${NET}
fi
# sudo mkdir -p ./docker-data/instances
# sudo mkdir -p ./docker-data/mongodb
# sudo mkdir -p ./docker-data/mongodb/data
# 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 compose up -d

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

@@ -41,7 +41,8 @@
"homepage": "https://github.com/DavidsonGomes/evolution-api#readme",
"dependencies": {
"@adiwajshing/keyed-db": "^0.2.4",
"@evolution/base": "github:WhiskeySockets/Baileys",
"@whiskeysockets/baileys": "^6.3.0",
"@ffmpeg-installer/ffmpeg": "^1.1.0",
"@hapi/boom": "^10.0.1",
"axios": "^1.3.5",
"class-validator": "^0.13.2",
@@ -50,6 +51,7 @@
"cross-env": "^7.0.3",
"dayjs": "^1.11.7",
"eventemitter2": "^6.4.9",
"exiftool-vendored": "^22.0.0",
"express": "^4.18.2",
"express-async-errors": "^3.1.1",
"hbs": "^4.2.0",
@@ -66,6 +68,7 @@
"qrcode": "^1.5.1",
"qrcode-terminal": "^0.12.0",
"redis": "^4.6.5",
"sharp": "^0.30.7",
"uuid": "^9.0.0"
},
"devDependencies": {

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 120 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 108 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

View File

@@ -2,6 +2,7 @@ import { readFileSync } from 'fs';
import { load } from 'js-yaml';
import { join } from 'path';
import { SRC_DIR } from './path.config';
import { isBooleanString } from 'class-validator';
export type HttpServer = { TYPE: 'http' | 'https'; PORT: number };
@@ -28,8 +29,16 @@ export type SaveData = {
};
export type StoreConf = {
MESSAGES: boolean;
MESSAGE_UP: boolean;
CONTACTS: boolean;
CHATS: boolean;
};
export type CleanStoreConf = {
CLEANING_INTERVAL: number;
MESSAGES: boolean;
MESSAGE_UP: boolean;
CONTACTS: boolean;
CHATS: boolean;
};
@@ -78,10 +87,10 @@ export type Instance = {
NAME: string;
WEBHOOK_URL: string;
MODE: string;
WEBHOOK_BY_EVENTS: boolean;
};
export type Auth = {
API_KEY: ApiKey;
EXPOSE_IN_FETCH_INSTANCES: boolean;
JWT: Jwt;
TYPE: 'jwt' | 'apikey';
INSTANCE: Instance;
@@ -105,6 +114,7 @@ export interface Env {
CORS: Cors;
SSL_CONF: SslConf;
STORE: StoreConf;
CLEAN_STORE: CleanStoreConf;
DATABASE: Database;
REDIS: Redis;
LOG: Log;
@@ -134,7 +144,7 @@ export class ConfigService {
this.env.PRODUCTION = process.env?.NODE_ENV === 'PROD';
if (process.env?.DOCKER_ENV === 'true') {
this.env.SERVER.TYPE = 'http';
this.env.SERVER.PORT = Number.parseInt(process.env?.SERVER_PORT ?? '8080');
this.env.SERVER.PORT = 8080;
}
}
@@ -158,13 +168,20 @@ export class ConfigService {
FULLCHAIN: process.env?.SSL_CONF_FULLCHAIN,
},
STORE: {
CLEANING_INTERVAL: Number.isInteger(process.env?.STORE_CLEANING_TERMINAL)
? Number.parseInt(process.env.STORE_CLEANING_TERMINAL)
: undefined,
MESSAGES: process.env?.STORE_MESSAGE === 'true',
MESSAGES: process.env?.STORE_MESSAGES === 'true',
MESSAGE_UP: process.env?.STORE_MESSAGE_UP === 'true',
CONTACTS: process.env?.STORE_CONTACTS === 'true',
CHATS: process.env?.STORE_CHATS === 'true',
},
CLEAN_STORE: {
CLEANING_INTERVAL: Number.isInteger(process.env?.CLEAN_STORE_CLEANING_TERMINAL)
? Number.parseInt(process.env.CLEAN_STORE_CLEANING_TERMINAL)
: undefined,
MESSAGES: process.env?.CLEAN_STORE_MESSAGES === 'true',
MESSAGE_UP: process.env?.CLEAN_STORE_MESSAGE_UP === 'true',
CONTACTS: process.env?.CLEAN_STORE_CONTACTS === 'true',
CHATS: process.env?.CLEAN_STORE_CHATS === 'true',
},
DATABASE: {
CONNECTION: {
URI: process.env.DATABASE_CONNECTION_URI,
@@ -189,10 +206,9 @@ export class ConfigService {
LEVEL: process.env?.LOG_LEVEL.split(',') as LogLevel[],
COLOR: process.env?.LOG_COLOR === 'true',
},
DEL_INSTANCE:
typeof process.env?.DEL_INSTANCE === 'boolean'
? process.env.DEL_INSTANCE === 'true'
: Number.parseInt(process.env.DEL_INSTANCE),
DEL_INSTANCE: isBooleanString(process.env?.DEL_INSTANCE)
? process.env.DEL_INSTANCE === 'true'
: Number.parseInt(process.env.DEL_INSTANCE),
WEBHOOK: {
GLOBAL: {
URL: process.env?.WEBHOOK_GLOBAL_URL,
@@ -234,6 +250,8 @@ export class ConfigService {
API_KEY: {
KEY: process.env.AUTHENTICATION_API_KEY,
},
EXPOSE_IN_FETCH_INSTANCES:
process.env?.AUTHENTICATION_EXPOSE_IN_FETCH_INSTANCES === 'true',
JWT: {
EXPIRIN_IN: Number.isInteger(process.env?.AUTHENTICATION_JWT_EXPIRIN_IN)
? Number.parseInt(process.env.AUTHENTICATION_JWT_EXPIRIN_IN)
@@ -244,8 +262,6 @@ export class ConfigService {
NAME: process.env.AUTHENTICATION_INSTANCE_NAME,
WEBHOOK_URL: process.env.AUTHENTICATION_INSTANCE_WEBHOOK_URL,
MODE: process.env.AUTHENTICATION_INSTANCE_MODE,
WEBHOOK_BY_EVENTS:
process.env.AUTHENTICATION_INSTANCE_WEBHOOK_BY_EVENTS === 'true',
},
},
};

View File

@@ -4,7 +4,7 @@ import { Logger } from '../config/logger.config';
const logger = new Logger('Db Connection');
export const db = configService.get<Database>('DATABASE');
const db = configService.get<Database>('DATABASE');
export const dbserver = db.ENABLED
? mongoose.createConnection(db.CONNECTION.URI, {
dbName: db.CONNECTION.DB_PREFIX_NAME + '-whatsapp-api',

View File

@@ -1,6 +1,6 @@
import { createClient, RedisClientType } from '@redis/client';
import { Logger } from '../config/logger.config';
import { BufferJSON } from '@evolution/base';
import { BufferJSON } from '@whiskeysockets/baileys';
import { Redis } from '../config/env.config';
export class RedisCache {
@@ -25,6 +25,13 @@ export class RedisCache {
}
}
public async keyExists(key?: string) {
if (key) {
return !!(await this.instanceKeys()).find((i) => i === key);
}
return !!(await this.instanceKeys()).find((i) => i === this.instanceName);
}
public async writeData(field: string, data: any) {
try {
const json = JSON.stringify(data, BufferJSON.replacer);

View File

@@ -7,7 +7,7 @@
# Choose the server type for the application
SERVER:
TYPE: http # https
PORT: 8083 # 443
PORT: 8080 # 443
CORS:
ORIGIN:
@@ -41,12 +41,19 @@ LOG:
# Determine how long the instance should be deleted from memory in case of no connection.
# Default time: 5 minutes
# If you don't even want an expiration, enter the value false
DEL_INSTANCE: false # or false
DEL_INSTANCE: 5 # or false
# Temporary data storage
STORE:
CLEANING_INTERVAL: 7200 # seconds === 2h
MESSAGE: true
MESSAGES: true
MESSAGE_UP: true
CONTACTS: true
CHATS: true
CLEAN_STORE:
CLEANING_INTERVAL: 7200 # 7200 seconds === 2h
MESSAGES: true
MESSAGE_UP: true
CONTACTS: true
CHATS: true
@@ -105,7 +112,7 @@ WEBHOOK:
CONFIG_SESSION_PHONE:
# Name that will be displayed on smartphone connection
CLIENT: 'Evolution API'
NAME: Chrome # firefox | edge | opera | safari
NAME: chrome # chrome | firefox | edge | opera | safari
# Set qrcode display limit
QRCODE:
@@ -113,11 +120,13 @@ QRCODE:
# Defines an authentication type for the api
AUTHENTICATION:
TYPE: apikey # or jwt apikey
TYPE: apikey # jwt or apikey
# Define a global apikey to access all instances
API_KEY:
# 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_IN_FETCH_INSTANCES: true
# Set the secret key to encrypt and decrypt your token and its expiration time.
JWT:
EXPIRIN_IN: 0 # seconds - 3600s === 1h | zero (0) - never expires
@@ -125,7 +134,6 @@ AUTHENTICATION:
# Set the instance name and webhook url to create an instance in init the application
INSTANCE:
# 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
# if you are using container mode, set the container name and the webhook url to default instance
NAME: evolution

View File

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

View File

@@ -4,7 +4,7 @@ import {
initAuthCreds,
proto,
SignalDataTypeMap,
} from '@evolution/base';
} from '@whiskeysockets/baileys';
import { RedisCache } from '../db/redis.client';
import { Logger } from '../config/logger.config';
import { Redis } from '../config/env.config';

View File

@@ -27,6 +27,37 @@ export const instanceNameSchema: JSONSchema7 = {
properties: {
instanceName: { type: 'string' },
webhook: { type: 'string' },
webhook_by_events: { type: 'boolean' },
events: {
type: 'array',
minItems: 1,
items: {
type: 'string',
enum: [
'APPLICATION_STARTUP',
'QRCODE_UPDATED',
'MESSAGES_SET',
'MESSAGES_UPSERT',
'MESSAGES_UPDATE',
'SEND_MESSAGE',
'CONTACTS_SET',
'CONTACTS_UPSERT',
'CONTACTS_UPDATE',
'PRESENCE_UPDATE',
'CHATS_SET',
'CHATS_UPSERT',
'CHATS_UPDATE',
'CHATS_DELETE',
'GROUPS_UPSERT',
'GROUP_UPDATE',
'GROUP_PARTICIPANTS_UPDATE',
'CONNECTION_UPDATE',
'NEW_JWT_TOKEN',
],
},
},
qrcode: { type: 'boolean', enum: [true, false] },
token: { type: 'string' },
},
...isNotEmpty('instanceName'),
};
@@ -180,6 +211,24 @@ export const mediaMessageSchema: JSONSchema7 = {
required: ['mediaMessage', 'number'],
};
export const stickerMessageSchema: JSONSchema7 = {
$id: v4(),
type: 'object',
properties: {
number: { ...numberDefinition },
options: { ...optionsSchema },
stickerMessage: {
type: 'object',
properties: {
image: { type: 'string' },
},
required: ['image'],
...isNotEmpty('image'),
},
},
required: ['stickerMessage', 'number'],
};
export const audioMessageSchema: JSONSchema7 = {
$id: v4(),
type: 'object',
@@ -408,6 +457,36 @@ export const readMessageSchema: JSONSchema7 = {
required: ['readMessages'],
};
export const privacySettingsSchema: JSONSchema7 = {
$id: v4(),
type: 'object',
properties: {
privacySettings: {
type: 'object',
properties: {
readreceipts: { type: 'string', enum: ['all', 'none'] },
profile: {
type: 'string',
enum: ['all', 'contacts', 'contact_blacklist', 'none'],
},
status: {
type: 'string',
enum: ['all', 'contacts', 'contact_blacklist', 'none'],
},
online: { type: 'string', enum: ['all', 'match_last_seen'] },
last: { type: 'string', enum: ['all', 'contacts', 'contact_blacklist', 'none'] },
groupadd: {
type: 'string',
enum: ['all', 'contacts', 'contact_blacklist', 'none'],
},
},
required: ['readreceipts', 'profile', 'status', 'online', 'last', 'groupadd'],
...isNotEmpty('readreceipts', 'profile', 'status', 'online', 'last', 'groupadd'),
},
},
required: ['privacySettings'],
};
export const archiveChatSchema: JSONSchema7 = {
$id: v4(),
type: 'object',
@@ -587,6 +666,28 @@ export const groupJidSchema: JSONSchema7 = {
...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 = {
$id: v4(),
type: 'object',
@@ -650,7 +751,7 @@ export const toggleEphemeralSchema: JSONSchema7 = {
...isNotEmpty('groupJid', 'expiration'),
};
export const updateGroupPicture: JSONSchema7 = {
export const updateGroupPictureSchema: JSONSchema7 = {
$id: v4(),
type: 'object',
properties: {
@@ -661,6 +762,28 @@ export const updateGroupPicture: JSONSchema7 = {
...isNotEmpty('groupJid', 'image'),
};
export const updateGroupSubjectSchema: JSONSchema7 = {
$id: v4(),
type: 'object',
properties: {
groupJid: { type: 'string' },
subject: { type: 'string' },
},
required: ['groupJid', 'subject'],
...isNotEmpty('groupJid', 'subject'),
};
export const updateGroupDescriptionSchema: JSONSchema7 = {
$id: v4(),
type: 'object',
properties: {
groupJid: { type: 'string' },
description: { type: 'string' },
},
required: ['groupJid', 'description'],
...isNotEmpty('groupJid', 'description'),
};
// Webhook Schema
export const webhookSchema: JSONSchema7 = {
$id: v4(),
@@ -668,6 +791,34 @@ export const webhookSchema: JSONSchema7 = {
properties: {
url: { type: 'string' },
enabled: { type: 'boolean', enum: [true, false] },
events: {
type: 'array',
minItems: 1,
items: {
type: 'string',
enum: [
'APPLICATION_STARTUP',
'QRCODE_UPDATED',
'MESSAGES_SET',
'MESSAGES_UPSERT',
'MESSAGES_UPDATE',
'SEND_MESSAGE',
'CONTACTS_SET',
'CONTACTS_UPSERT',
'CONTACTS_UPDATE',
'PRESENCE_UPDATE',
'CHATS_SET',
'CHATS_UPSERT',
'CHATS_UPDATE',
'CHATS_DELETE',
'GROUPS_UPSERT',
'GROUP_UPDATE',
'GROUP_PARTICIPANTS_UPDATE',
'CONNECTION_UPDATE',
'NEW_JWT_TOKEN',
],
},
},
},
required: ['url', 'enabled'],
...isNotEmpty('url'),

View File

@@ -65,6 +65,37 @@ export abstract class RouterBroker {
return await execute(instance, ref);
}
public async groupNoValidate<T>(args: DataValidate<T>) {
const { request, ClassRef, schema, execute } = args;
const instance = request.params as unknown as InstanceDto;
const ref = new ClassRef();
Object.assign(ref, request.body);
const v = validate(ref, schema);
if (!v.valid) {
const message: any[] = v.errors.map(({ property, stack, schema }) => {
let message: string;
if (schema['description']) {
message = schema['description'];
} else {
message = stack.replace('instance.', '');
}
return {
property: property.replace('instance.', ''),
message,
};
});
logger.error([...message]);
throw new BadRequestException(...message);
}
return await execute(instance, ref);
}
public async groupValidate<T>(args: DataValidate<T>) {
const { request, ClassRef, schema, execute } = args;

View File

@@ -1,8 +1,9 @@
import { proto } from '@evolution/base';
import { proto } from '@whiskeysockets/baileys';
import {
ArchiveChatDto,
DeleteMessage,
NumberDto,
PrivacySettingDto,
ProfileNameDto,
ProfilePictureDto,
ProfileStatusDto,
@@ -63,11 +64,24 @@ export class ChatController {
return await this.waMonitor.waInstances[instanceName].fetchChats();
}
public async getBusinessProfile(
public async fetchPrivacySettings({ instanceName }: InstanceDto) {
return await this.waMonitor.waInstances[instanceName].fetchPrivacySettings();
}
public async updatePrivacySettings(
{ instanceName }: InstanceDto,
data: PrivacySettingDto,
) {
return await this.waMonitor.waInstances[instanceName].updatePrivacySettings(data);
}
public async fetchBusinessProfile(
{ instanceName }: InstanceDto,
data: ProfilePictureDto,
) {
return await this.waMonitor.waInstances[instanceName].getBusinessProfile(data.number);
return await this.waMonitor.waInstances[instanceName].fetchBusinessProfile(
data.number,
);
}
public async updateProfileName({ instanceName }: InstanceDto, data: ProfileNameDto) {

View File

@@ -1,8 +1,11 @@
import {
CreateGroupDto,
GroupDescriptionDto,
GroupInvite,
GroupJid,
GroupPictureDto,
GroupSendInvite,
GroupSubjectDto,
GroupToggleEphemeralDto,
GroupUpdateParticipantDto,
GroupUpdateSettingDto,
@@ -23,10 +26,29 @@ export class GroupController {
);
}
public async updateGroupSubject(instance: InstanceDto, update: GroupSubjectDto) {
return await this.waMonitor.waInstances[instance.instanceName].updateGroupSubject(
update,
);
}
public async updateGroupDescription(
instance: InstanceDto,
update: GroupDescriptionDto,
) {
return await this.waMonitor.waInstances[instance.instanceName].updateGroupDescription(
update,
);
}
public async findGroupInfo(instance: InstanceDto, groupJid: GroupJid) {
return await this.waMonitor.waInstances[instance.instanceName].findGroup(groupJid);
}
public async fetchAllGroups(instance: InstanceDto) {
return await this.waMonitor.waInstances[instance.instanceName].fetchAllGroups();
}
public async inviteCode(instance: InstanceDto, groupJid: GroupJid) {
return await this.waMonitor.waInstances[instance.instanceName].inviteCode(groupJid);
}
@@ -35,6 +57,10 @@ export class GroupController {
return await this.waMonitor.waInstances[instance.instanceName].inviteInfo(inviteCode);
}
public async sendInvite(instance: InstanceDto, data: GroupSendInvite) {
return await this.waMonitor.waInstances[instance.instanceName].sendInvite(data);
}
public async revokeInviteCode(instance: InstanceDto, groupJid: GroupJid) {
return await this.waMonitor.waInstances[instance.instanceName].revokeInviteCode(
groupJid,

View File

@@ -1,4 +1,4 @@
import { delay } from '@evolution/base';
import { delay } from '@whiskeysockets/baileys';
import EventEmitter2 from 'eventemitter2';
import { Auth, ConfigService } from '../../config/env.config';
import { BadRequestException, InternalServerErrorException } from '../../exceptions';
@@ -9,6 +9,7 @@ import { WAMonitoringService } from '../services/monitor.service';
import { WAStartupService } from '../services/whatsapp.service';
import { WebhookService } from '../services/webhook.service';
import { Logger } from '../../config/logger.config';
import { wa } from '../types/wa.types';
export class InstanceController {
constructor(
@@ -22,12 +23,17 @@ export class InstanceController {
private readonly logger = new Logger(InstanceController.name);
public async createInstance({ instanceName, webhook }: InstanceDto) {
//verifica se modo da instancia é container
public async createInstance({
instanceName,
webhook,
webhook_by_events,
events,
qrcode,
token,
}: InstanceDto) {
const mode = this.configService.get<Auth>('AUTHENTICATION').INSTANCE.MODE;
if (mode === 'container') {
//verifica se ja existe uma instancia criada com qualquer nome
if (Object.keys(this.waMonitor.waInstances).length > 0) {
throw new BadRequestException([
'Instance already created',
@@ -35,6 +41,8 @@ export class InstanceController {
]);
}
await this.authService.checkDuplicateToken(token);
const instance = new WAStartupService(
this.configService,
this.eventEmitter,
@@ -44,13 +52,25 @@ export class InstanceController {
this.waMonitor.waInstances[instance.instanceName] = instance;
this.waMonitor.delInstanceTime(instance.instanceName);
const hash = await this.authService.generateHash({
instanceName: instance.instanceName,
});
const hash = await this.authService.generateHash(
{
instanceName: instance.instanceName,
},
token,
);
let getEvents: string[];
if (webhook) {
try {
this.webhookService.create(instance, { enabled: true, url: webhook });
this.webhookService.create(instance, {
enabled: true,
url: webhook,
events,
webhook_by_events,
});
getEvents = (await this.webhookService.find(instance)).events;
} catch (error) {
this.logger.log(error);
}
@@ -63,8 +83,11 @@ export class InstanceController {
},
hash,
webhook,
events: getEvents,
};
} else {
await this.authService.checkDuplicateToken(token);
const instance = new WAStartupService(
this.configService,
this.eventEmitter,
@@ -74,18 +97,38 @@ export class InstanceController {
this.waMonitor.waInstances[instance.instanceName] = instance;
this.waMonitor.delInstanceTime(instance.instanceName);
const hash = await this.authService.generateHash({
instanceName: instance.instanceName,
});
const hash = await this.authService.generateHash(
{
instanceName: instance.instanceName,
},
token,
);
let getEvents: string[];
if (webhook) {
try {
this.webhookService.create(instance, { enabled: true, url: webhook });
this.webhookService.create(instance, {
enabled: true,
url: webhook,
events,
webhook_by_events,
});
getEvents = (await this.webhookService.find(instance)).events;
} catch (error) {
this.logger.log(error);
}
}
let getQrcode: wa.QrCode;
if (qrcode) {
await instance.connectToWhatsapp();
await delay(2000);
getQrcode = instance.qrCode;
}
return {
instance: {
instanceName: instance.instanceName,
@@ -93,6 +136,9 @@ export class InstanceController {
},
hash,
webhook,
webhook_by_events,
events: getEvents,
qrcode: getQrcode,
};
}
}
@@ -100,7 +146,7 @@ export class InstanceController {
public async connectToWhatsapp({ instanceName }: InstanceDto) {
try {
const instance = this.waMonitor.waInstances[instanceName];
const state = instance.connectionStatus?.state;
const state = instance?.connectionStatus?.state;
switch (state) {
case 'close':
@@ -113,12 +159,32 @@ export class InstanceController {
return await this.connectionState({ instanceName });
}
} catch (error) {
this.logger.log(error);
this.logger.error(error);
}
}
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,
);
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) {
return this.waMonitor.waInstances[instanceName].connectionStatus;
return this.waMonitor.waInstances[instanceName]?.connectionStatus;
}
public async fetchInstances({ instanceName }: InstanceDto) {
@@ -153,8 +219,15 @@ export class InstanceController {
]);
}
try {
delete this.waMonitor.waInstances[instanceName];
return { error: false, message: 'Instance deleted' };
if (stateConn.state === 'connecting') {
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) {
throw new BadRequestException(error.toString());
}

View File

@@ -11,6 +11,7 @@ import {
SendMediaDto,
SendPollDto,
SendReactionDto,
SendStickerDto,
SendTextDto,
} from '../dto/sendMessage.dto';
import { WAMonitoringService } from '../services/monitor.service';
@@ -32,6 +33,13 @@ export class SendMessageController {
throw new BadRequestException('Owned media must be a url or base64');
}
public async sendSticker({ instanceName }: InstanceDto, data: SendStickerDto) {
if (isURL(data.stickerMessage.image) || isBase64(data.stickerMessage.image)) {
return await this.waMonitor.waInstances[instanceName].mediaSticker(data);
}
throw new BadRequestException('Owned media must be a url or base64');
}
public async sendWhatsAppAudio({ instanceName }: InstanceDto, data: SendAudioDto) {
if (isURL(data.audioMessage.audio) || isBase64(data.audioMessage.audio)) {
return await this.waMonitor.waInstances[instanceName].audioWhatsapp(data);

View File

@@ -1,3 +1,9 @@
import {
WAPrivacyOnlineValue,
WAPrivacyValue,
WAReadReceiptsValue,
} from '@whiskeysockets/baileys';
export class OnWhatsAppDto {
constructor(
public readonly jid: string,
@@ -47,6 +53,19 @@ export class ArchiveChatDto {
archive: boolean;
}
class PrivacySetting {
readreceipts: WAReadReceiptsValue;
profile: WAPrivacyValue;
status: WAPrivacyValue;
online: WAPrivacyOnlineValue;
last: WAPrivacyValue;
groupadd: WAPrivacyValue;
}
export class PrivacySettingDto {
privacySettings: PrivacySetting;
}
export class DeleteMessage {
id: string;
fromMe: boolean;

View File

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

View File

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

View File

@@ -1,4 +1,4 @@
import { proto, WAPresence } from '@evolution/base';
import { proto, WAPresence } from '@whiskeysockets/baileys';
export class Quoted {
key: proto.IMessageKey;
@@ -62,6 +62,12 @@ export class MediaMessage {
export class SendMediaDto extends Metadata {
mediaMessage: MediaMessage;
}
class Sticker {
image: string;
}
export class SendStickerDto extends Metadata {
stickerMessage: Sticker;
}
class Audio {
audio: string;

View File

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

View File

@@ -2,7 +2,7 @@ import { NextFunction, Request, Response } from 'express';
import { existsSync } from 'fs';
import { join } from 'path';
import { INSTANCE_DIR } from '../../config/path.config';
import { db, dbserver } from '../../db/db.connect';
import { dbserver } from '../../db/db.connect';
import {
BadRequestException,
ForbiddenException,
@@ -10,9 +10,20 @@ import {
} from '../../exceptions';
import { InstanceDto } from '../dto/instance.dto';
import { waMonitor } from '../whatsapp.module';
import { Database, Redis, configService } from '../../config/env.config';
import { RedisCache } from '../../db/redis.client';
async function getInstance(instanceName: string) {
const exists = waMonitor.waInstances[instanceName];
const db = configService.get<Database>('DATABASE');
const redisConf = configService.get<Redis>('REDIS');
const exists = !!waMonitor.waInstances[instanceName];
if (redisConf.ENABLED) {
const cache = new RedisCache(redisConf, instanceName);
const keyExists = await cache.keyExists();
return exists || keyExists;
}
if (db.ENABLED) {
const collection = dbserver

View File

@@ -5,12 +5,15 @@ export class WebhookRaw {
_id?: string;
url?: string;
enabled?: boolean;
events?: string[];
webhook_by_events?: boolean;
}
const webhookSchema = new Schema<WebhookRaw>({
_id: { type: String, _id: true },
url: { type: String, required: true },
enabled: { type: Boolean, required: true },
events: { type: [String], required: true },
});
export const WebhookModel = dbserver?.model(WebhookRaw.name, webhookSchema, 'webhook');

View File

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

View File

@@ -1,5 +1,5 @@
import { join } from 'path';
import { ConfigService } from '../../config/env.config';
import { ConfigService, StoreConf } from '../../config/env.config';
import { IInsert, Repository } from '../abstract/abstract.repository';
import { opendirSync, readFileSync, rmSync } from 'fs';
import { ChatRaw, IChatModel } from '../models';
@@ -27,15 +27,21 @@ export class ChatRepository extends Repository {
return { insertCount: insert.length };
}
data.forEach((chat) => {
this.writeStore<ChatRaw>({
path: join(this.storePath, 'chats', chat.owner),
fileName: chat.id,
data: chat,
});
});
const store = this.configService.get<StoreConf>('STORE');
return { insertCount: data.length };
if (store.CHATS) {
data.forEach((chat) => {
this.writeStore<ChatRaw>({
path: join(this.storePath, 'chats', chat.owner),
fileName: chat.id,
data: chat,
});
});
return { insertCount: data.length };
}
return { insertCount: 0 };
} catch (error) {
return error;
} finally {

View File

@@ -1,6 +1,6 @@
import { opendirSync, readFileSync } from 'fs';
import { join } from 'path';
import { ConfigService } from '../../config/env.config';
import { ConfigService, StoreConf } from '../../config/env.config';
import { ContactRaw, IContactModel } from '../models';
import { IInsert, Repository } from '../abstract/abstract.repository';
@@ -27,15 +27,21 @@ export class ContactRepository extends Repository {
return { insertCount: insert.length };
}
data.forEach((contact) => {
this.writeStore({
path: join(this.storePath, 'contacts', contact.owner),
fileName: contact.id,
data: contact,
});
});
const store = this.configService.get<StoreConf>('STORE');
return { insertCount: data.length };
if (store.CONTACTS) {
data.forEach((contact) => {
this.writeStore({
path: join(this.storePath, 'contacts', contact.owner),
fileName: contact.id,
data: contact,
});
});
return { insertCount: data.length };
}
return { insertCount: 0 };
} catch (error) {
return error;
} finally {

View File

@@ -1,4 +1,4 @@
import { ConfigService } from '../../config/env.config';
import { ConfigService, StoreConf } from '../../config/env.config';
import { join } from 'path';
import { IMessageModel, MessageRaw } from '../models';
import { IInsert, Repository } from '../abstract/abstract.repository';
@@ -47,7 +47,9 @@ export class MessageRepository extends Repository {
return { insertCount: insert.length };
}
if (saveDb) {
const store = this.configService.get<StoreConf>('STORE');
if (store.MESSAGES) {
data.forEach((msg) =>
this.writeStore<MessageRaw>({
path: join(this.storePath, 'messages', msg.owner),

View File

@@ -1,4 +1,4 @@
import { ConfigService } from '../../config/env.config';
import { ConfigService, StoreConf } from '../../config/env.config';
import { IMessageUpModel, MessageUpdateRaw } from '../models';
import { IInsert, Repository } from '../abstract/abstract.repository';
import { join } from 'path';
@@ -28,13 +28,21 @@ export class MessageUpRepository extends Repository {
return { insertCount: insert.length };
}
data.forEach((update) => {
this.writeStore<MessageUpdateRaw>({
path: join(this.storePath, 'message-up', update.owner),
fileName: update.id,
data: update,
const store = this.configService.get<StoreConf>('STORE');
if (store.MESSAGE_UP) {
data.forEach((update) => {
this.writeStore<MessageUpdateRaw>({
path: join(this.storePath, 'message-up', update.owner),
fileName: update.id,
data: update,
});
});
});
return { insertCount: data.length };
}
return { insertCount: 0 };
} catch (error) {
return error;
}

View File

@@ -5,6 +5,7 @@ import {
deleteMessageSchema,
messageUpSchema,
messageValidateSchema,
privacySettingsSchema,
profileNameSchema,
profilePictureSchema,
profileStatusSchema,
@@ -15,6 +16,7 @@ import {
ArchiveChatDto,
DeleteMessage,
NumberDto,
PrivacySettingDto,
ProfileNameDto,
ProfilePictureDto,
ProfileStatusDto,
@@ -27,7 +29,7 @@ import { chatController } from '../whatsapp.module';
import { RouterBroker } from '../abstract/abstract.router';
import { HttpStatus } from './index.router';
import { MessageUpQuery } from '../repository/messageUp.repository';
import { proto } from '@evolution/base';
import { proto } from '@whiskeysockets/baileys';
import { InstanceDto } from '../dto/instance.dto';
export class ChatRouter extends RouterBroker {
@@ -140,12 +142,34 @@ export class ChatRouter extends RouterBroker {
return res.status(HttpStatus.OK).json(response);
})
// Profile routes
.post(this.routerPath('getBusinessProfile'), ...guards, async (req, res) => {
.get(this.routerPath('fetchPrivacySettings'), ...guards, async (req, res) => {
const response = await this.dataValidate<InstanceDto>({
request: req,
schema: null,
ClassRef: InstanceDto,
execute: (instance) => chatController.fetchPrivacySettings(instance),
});
return res.status(HttpStatus.OK).json(response);
})
.put(this.routerPath('updatePrivacySettings'), ...guards, async (req, res) => {
const response = await this.dataValidate<PrivacySettingDto>({
request: req,
schema: privacySettingsSchema,
ClassRef: PrivacySettingDto,
execute: (instance, data) =>
chatController.updatePrivacySettings(instance, data),
});
return res.status(HttpStatus.CREATED).json(response);
})
.post(this.routerPath('fetchBusinessProfile'), ...guards, async (req, res) => {
const response = await this.dataValidate<ProfilePictureDto>({
request: req,
schema: profilePictureSchema,
ClassRef: ProfilePictureDto,
execute: (instance, data) => chatController.getBusinessProfile(instance, data),
execute: (instance, data) =>
chatController.fetchBusinessProfile(instance, data),
});
return res.status(HttpStatus.OK).json(response);

View File

@@ -5,8 +5,11 @@ import {
updateParticipantsSchema,
updateSettingsSchema,
toggleEphemeralSchema,
updateGroupPicture,
updateGroupPictureSchema,
updateGroupSubjectSchema,
updateGroupDescriptionSchema,
groupInviteSchema,
groupSendInviteSchema,
} from '../../validate/validate.schema';
import { RouterBroker } from '../abstract/abstract.router';
import {
@@ -14,9 +17,12 @@ import {
GroupInvite,
GroupJid,
GroupPictureDto,
GroupSubjectDto,
GroupDescriptionDto,
GroupUpdateParticipantDto,
GroupUpdateSettingDto,
GroupToggleEphemeralDto,
GroupSendInvite,
} from '../dto/group.dto';
import { groupController } from '../whatsapp.module';
import { HttpStatus } from './index.router';
@@ -35,16 +41,37 @@ export class GroupRouter extends RouterBroker {
res.status(HttpStatus.CREATED).json(response);
})
.put(this.routerPath('updateGroupSubject'), ...guards, async (req, res) => {
const response = await this.groupValidate<GroupSubjectDto>({
request: req,
schema: updateGroupSubjectSchema,
ClassRef: GroupSubjectDto,
execute: (instance, data) => groupController.updateGroupSubject(instance, data),
});
res.status(HttpStatus.CREATED).json(response);
})
.put(this.routerPath('updateGroupPicture'), ...guards, async (req, res) => {
const response = await this.groupValidate<GroupPictureDto>({
request: req,
schema: updateGroupPicture,
schema: updateGroupPictureSchema,
ClassRef: GroupPictureDto,
execute: (instance, data) => groupController.updateGroupPicture(instance, data),
});
res.status(HttpStatus.CREATED).json(response);
})
.put(this.routerPath('updateGroupDescription'), ...guards, async (req, res) => {
const response = await this.groupValidate<GroupDescriptionDto>({
request: req,
schema: updateGroupDescriptionSchema,
ClassRef: GroupDescriptionDto,
execute: (instance, data) =>
groupController.updateGroupDescription(instance, data),
});
res.status(HttpStatus.CREATED).json(response);
})
.get(this.routerPath('findGroupInfos'), ...guards, async (req, res) => {
const response = await this.groupValidate<GroupJid>({
request: req,
@@ -55,6 +82,16 @@ export class GroupRouter extends RouterBroker {
res.status(HttpStatus.OK).json(response);
})
.get(this.routerPath('fetchAllGroups'), ...guards, async (req, res) => {
const response = await this.groupNoValidate<GroupJid>({
request: req,
schema: {},
ClassRef: GroupJid,
execute: (instance) => groupController.fetchAllGroups(instance),
});
res.status(HttpStatus.OK).json(response);
})
.get(this.routerPath('participants'), ...guards, async (req, res) => {
const response = await this.groupValidate<GroupJid>({
request: req,
@@ -85,6 +122,16 @@ export class GroupRouter extends RouterBroker {
res.status(HttpStatus.OK).json(response);
})
.post(this.routerPath('sendInvite'), ...guards, async (req, res) => {
const response = await this.groupNoValidate<GroupSendInvite>({
request: req,
schema: groupSendInviteSchema,
ClassRef: GroupSendInvite,
execute: (instance, data) => groupController.sendInvite(instance, data),
});
res.status(HttpStatus.OK).json(response);
})
.put(this.routerPath('revokeInviteCode'), ...guards, async (req, res) => {
const response = await this.groupValidate<GroupJid>({
request: req,

View File

@@ -7,7 +7,6 @@ import { HttpStatus } from './index.router';
import { OldToken } from '../services/auth.service';
import { Auth, ConfigService, Database } from '../../config/env.config';
import { dbserver } from '../../db/db.connect';
import { BadRequestException, InternalServerErrorException } from '../../exceptions';
export class InstanceRouter extends RouterBroker {
constructor(readonly configService: ConfigService, ...guards: RequestHandler[]) {
@@ -24,6 +23,16 @@ export class InstanceRouter extends RouterBroker {
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) => {
const response = await this.dataValidate<InstanceDto>({
request: req,

View File

@@ -9,6 +9,7 @@ import {
mediaMessageSchema,
pollMessageSchema,
reactionMessageSchema,
stickerMessageSchema,
textMessageSchema,
} from '../../validate/validate.schema';
import {
@@ -21,6 +22,7 @@ import {
SendMediaDto,
SendPollDto,
SendReactionDto,
SendStickerDto,
SendTextDto,
} from '../dto/sendMessage.dto';
import { sendMessageController } from '../whatsapp.module';
@@ -131,6 +133,16 @@ export class MessageRouter extends RouterBroker {
sendMessageController.sendLinkPreview(instance, data),
});
return res.status(HttpStatus.CREATED).json(response);
})
.post(this.routerPath('sendSticker'), ...guards, async (req, res) => {
const response = await this.dataValidate<SendStickerDto>({
request: req,
schema: stickerMessageSchema,
ClassRef: SendStickerDto,
execute: (instance, data) => sendMessageController.sendSticker(instance, data),
});
return res.status(HttpStatus.CREATED).json(response);
});
}

View File

@@ -56,14 +56,14 @@ export class AuthService {
return { jwt: token };
}
private async apikey(instance: InstanceDto) {
const apikey = v4().toUpperCase();
private async apikey(instance: InstanceDto, token?: string) {
const apikey = token ? token : v4().toUpperCase();
const auth = await this.repository.auth.create({ apikey }, instance.instanceName);
if (auth['error']) {
this.logger.error({
localError: AuthService.name + '.jwt',
localError: AuthService.name + '.apikey',
error: auth['error'],
});
throw new BadRequestException('Authentication error', auth['error']?.toString());
@@ -72,9 +72,23 @@ export class AuthService {
return { apikey };
}
public async generateHash(instance: InstanceDto) {
public async checkDuplicateToken(token: string) {
const instances = await this.waMonitor.instanceInfo();
const instance = instances.find((instance) => instance.instance.apikey === token);
if (instance) {
throw new BadRequestException('Token already exists');
}
return true;
}
public async generateHash(instance: InstanceDto, token?: string) {
const options = this.configService.get<Auth>('AUTHENTICATION');
return (await this[options.TYPE](instance)) as { jwt: string } | { apikey: string };
return (await this[options.TYPE](instance, token)) as
| { jwt: string }
| { apikey: string };
}
public async refreshToken({ oldToken }: OldToken) {

View File

@@ -4,13 +4,18 @@ import { INSTANCE_DIR } from '../../config/path.config';
import EventEmitter2 from 'eventemitter2';
import { join } from 'path';
import { Logger } from '../../config/logger.config';
import { ConfigService, Database, DelInstance, Redis } from '../../config/env.config';
import {
Auth,
ConfigService,
Database,
DelInstance,
Redis,
} from '../../config/env.config';
import { RepositoryBroker } from '../repository/repository.manager';
import { NotFoundException } from '../../exceptions';
import { Db } from 'mongodb';
import { RedisCache } from '../../db/redis.client';
import { initInstance } from '../whatsapp.module';
import { ValidationError } from 'class-validator';
export class WAMonitoringService {
constructor(
@@ -44,9 +49,19 @@ export class WAMonitoringService {
public delInstanceTime(instance: string) {
const time = this.configService.get<DelInstance>('DEL_INSTANCE');
if (typeof time === 'number' && time > 0) {
setTimeout(() => {
setTimeout(async () => {
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);
}
@@ -60,16 +75,56 @@ export class WAMonitoringService {
const instances: any[] = [];
for await (const [key, value] of Object.entries(this.waInstances)) {
if (value && value.connectionStatus.state === 'open') {
instances.push({
instance: {
instanceName: key,
owner: value.wuid,
profileName: (await value.getProfileName()) || 'not loaded',
profilePictureUrl: value.profilePictureUrl,
status: (await value.getProfileStatus()) || '',
},
});
if (value) {
if (value.connectionStatus.state === 'open') {
let apikey: string;
if (this.configService.get<Auth>('AUTHENTICATION').EXPOSE_IN_FETCH_INSTANCES) {
const tokenStore = await this.repository.auth.find(key);
apikey = tokenStore.apikey || 'Apikey not found';
instances.push({
instance: {
instanceName: key,
owner: value.wuid,
profileName: (await value.getProfileName()) || 'not loaded',
profilePictureUrl: value.profilePictureUrl,
status: (await value.getProfileStatus()) || '',
apikey,
},
});
} else {
instances.push({
instance: {
instanceName: key,
owner: value.wuid,
profileName: (await value.getProfileName()) || 'not loaded',
profilePictureUrl: value.profilePictureUrl,
status: (await value.getProfileStatus()) || '',
},
});
}
} else {
let apikey: string;
if (this.configService.get<Auth>('AUTHENTICATION').EXPOSE_IN_FETCH_INSTANCES) {
const tokenStore = await this.repository.auth.find(key);
apikey = tokenStore.apikey || 'Apikey not found';
instances.push({
instance: {
instanceName: key,
status: value.connectionStatus.state,
apikey,
},
});
} else {
instances.push({
instance: {
instanceName: key,
status: value.connectionStatus.state,
},
});
}
}
}
}
@@ -110,7 +165,7 @@ export class WAMonitoringService {
}, 3600 * 1000 * 2);
}
private async cleaningUp(instanceName: string) {
public async cleaningUp(instanceName: string) {
if (this.db.ENABLED && this.db.SAVE_DATA.INSTANCE) {
await this.repository.dbServer.connect();
const collections: any[] = await this.dbInstance.collections();
@@ -176,6 +231,8 @@ export class WAMonitoringService {
}
await set(dirent.name);
} else {
initInstance();
}
}
} catch (error) {

View File

@@ -29,17 +29,19 @@ import makeWASocket, {
WAMessage,
WAMessageUpdate,
WASocket,
} from '@evolution/base';
getAggregateVotesInPollMessage,
} from '@whiskeysockets/baileys';
import {
Auth,
CleanStoreConf,
ConfigService,
ConfigSessionPhone,
Database,
QrCode,
Redis,
StoreConf,
Webhook,
} from '../../config/env.config';
import fs from 'fs';
import { Logger } from '../../config/logger.config';
import { INSTANCE_DIR, ROOT_DIR } from '../../config/path.config';
import { existsSync, readFileSync } from 'fs';
@@ -53,7 +55,8 @@ import { Boom } from '@hapi/boom';
import EventEmitter2 from 'eventemitter2';
import { release } from 'os';
import P from 'pino';
import { execSync } from 'child_process';
import { execSync, exec } from 'child_process';
import ffmpegPath from '@ffmpeg-installer/ffmpeg';
import { RepositoryBroker } from '../repository/repository.manager';
import { MessageRaw, MessageUpdateRaw } from '../models/message.model';
import { ContactRaw } from '../models/contact.model';
@@ -73,12 +76,14 @@ import {
SendTextDto,
SendPollDto,
SendLinkPreviewDto,
SendStickerDto,
} from '../dto/sendMessage.dto';
import { arrayUnique, isBase64, isURL } from 'class-validator';
import {
ArchiveChatDto,
DeleteMessage,
OnWhatsAppDto,
PrivacySettingDto,
ReadMessageDto,
WhatsAppNumberDto,
} from '../dto/chat.dto';
@@ -97,6 +102,9 @@ import {
GroupUpdateParticipantDto,
GroupUpdateSettingDto,
GroupToggleEphemeralDto,
GroupSubjectDto,
GroupDescriptionDto,
GroupSendInvite,
} from '../dto/group.dto';
import { MessageUpQuery } from '../repository/messageUp.repository';
import { useMultiFileAuthStateDb } from '../../utils/use-multi-file-auth-state-db';
@@ -105,6 +113,7 @@ import { WebhookRaw } from '../models/webhook.model';
import { dbserver } from '../../db/db.connect';
import NodeCache from 'node-cache';
import { useMultiFileAuthStateRedisDb } from '../../utils/use-multi-file-auth-state-redis-db';
import sharp from 'sharp';
export class WAStartupService {
constructor(
@@ -195,6 +204,8 @@ export class WAStartupService {
const data = await this.repository.webhook.find(this.instanceName);
this.localWebhook.url = data?.url;
this.localWebhook.enabled = data?.enabled;
this.localWebhook.events = data?.events;
this.localWebhook.webhook_by_events = data?.webhook_by_events;
}
public async setWebhook(data: WebhookRaw) {
@@ -207,21 +218,31 @@ export class WAStartupService {
}
public async sendDataWebhook<T = any>(event: Events, data: T, local = true) {
const webhook = this.configService.get<Webhook>('WEBHOOK');
const webhookGlobal = this.configService.get<Webhook>('WEBHOOK');
const webhookLocal = this.localWebhook.events;
const we = event.replace(/[\.-]/gm, '_').toUpperCase();
const transformedWe = we.replace(/_/gm, '-').toLowerCase();
const instance = this.configService.get<Auth>('AUTHENTICATION').INSTANCE;
if (webhook.EVENTS[we]) {
if (Array.isArray(webhookLocal) && webhookLocal.includes(we)) {
if (local && instance.MODE !== 'container') {
const { WEBHOOK_BY_EVENTS } = instance;
let baseURL;
if (WEBHOOK_BY_EVENTS) {
if (this.localWebhook.webhook_by_events) {
baseURL = `${this.localWebhook.url}/${transformedWe}`;
} else {
baseURL = this.localWebhook.url;
}
this.logger.log({
local: WAStartupService.name + '.sendDataWebhook-local',
url: baseURL,
event,
instance: this.instance.name,
data,
destination: this.localWebhook.url,
});
try {
if (this.localWebhook.enabled && isURL(this.localWebhook.url)) {
const httpService = axios.create({ baseURL });
@@ -246,52 +267,60 @@ export class WAStartupService {
});
}
}
const globalWebhook = this.configService.get<Webhook>('WEBHOOK').GLOBAL;
let globalURL;
}
if (webhook.GLOBAL.WEBHOOK_BY_EVENTS) {
globalURL = `${globalWebhook.URL}/${transformedWe}`;
} else {
globalURL = globalWebhook.URL;
}
if (webhookGlobal.GLOBAL?.ENABLED) {
if (webhookGlobal.EVENTS[we]) {
const globalWebhook = this.configService.get<Webhook>('WEBHOOK').GLOBAL;
let localUrl;
let globalURL;
if (instance.MODE === 'container') {
localUrl = instance.WEBHOOK_URL;
} else {
localUrl = this.localWebhook.url;
}
if (webhookGlobal.GLOBAL.WEBHOOK_BY_EVENTS) {
globalURL = `${globalWebhook.URL}/${transformedWe}`;
} else {
globalURL = globalWebhook.URL;
}
this.logger.log({
url: globalURL,
event,
instance: this.instance.name,
data,
destination: localUrl,
});
try {
if (globalWebhook && globalWebhook?.ENABLED && isURL(globalURL)) {
const httpService = axios.create({ baseURL: globalURL });
await httpService.post('', {
event,
instance: this.instance.name,
data,
destination: localUrl,
let localUrl;
if (instance.MODE === 'container') {
localUrl = instance.WEBHOOK_URL;
} else {
localUrl = this.localWebhook.url;
}
this.logger.log({
local: WAStartupService.name + '.sendDataWebhook-global',
url: globalURL,
event,
instance: this.instance.name,
data,
destination: localUrl,
});
try {
if (globalWebhook && globalWebhook?.ENABLED && isURL(globalURL)) {
const httpService = axios.create({ baseURL: globalURL });
await httpService.post('', {
event,
instance: this.instance.name,
data,
destination: localUrl,
});
}
} catch (error) {
this.logger.error({
local: WAStartupService.name + '.sendDataWebhook-global',
message: error?.message,
hostName: error?.hostname,
syscall: error?.syscall,
code: error?.code,
error: error?.errno,
stack: error?.stack,
name: error?.name,
url: globalURL,
});
}
} catch (error) {
this.logger.error({
local: WAStartupService.name + '.sendDataWebhook-global',
message: error?.message,
hostName: error?.hostname,
syscall: error?.syscall,
code: error?.code,
error: error?.errno,
stack: error?.stack,
name: error?.name,
url: globalURL,
});
}
}
}
@@ -411,24 +440,24 @@ export class WAStartupService {
}
private cleanStore() {
const store = this.configService.get<StoreConf>('STORE');
const cleanStore = this.configService.get<CleanStoreConf>('CLEAN_STORE');
const database = this.configService.get<Database>('DATABASE');
if (store?.CLEANING_INTERVAL && !database.ENABLED) {
if (cleanStore?.CLEANING_INTERVAL && !database.ENABLED) {
setInterval(() => {
try {
for (const [key, value] of Object.entries(store)) {
for (const [key, value] of Object.entries(cleanStore)) {
if (value === true) {
execSync(
`rm -rf ${join(
this.storePath,
key.toLowerCase(),
key.toLowerCase().replace('_', '-'),
this.instance.wuid,
)}/*.json`,
);
}
}
} catch (error) {}
}, (store?.CLEANING_INTERVAL ?? 3600) * 1000);
}, (cleanStore?.CLEANING_INTERVAL ?? 3600) * 1000);
}
}
@@ -662,10 +691,10 @@ export class WAStartupService {
this.sendDataWebhook(Events.MESSAGES_SET, [...messagesRaw]);
await this.repository.message.insert(
[...messagesRaw],
database.SAVE_DATA.OLD_MESSAGE,
);
// await this.repository.message.insert(
// [...messagesRaw],
// database.SAVE_DATA.OLD_MESSAGE,
// );
messages = undefined;
},
@@ -716,6 +745,18 @@ export class WAStartupService {
};
for await (const { key, update } of args) {
if (key.remoteJid !== 'status@broadcast' && !key?.remoteJid?.match(/(:\d+)/)) {
if (update.pollUpdates) {
const pollCreation = await this.getMessage(key);
console.log('pollCreation: ', pollCreation);
if (pollCreation) {
const pollMessage = getAggregateVotesInPollMessage({
message: pollCreation as proto.IMessage,
pollUpdates: update.pollUpdates,
});
console.log('pollMessage: ', pollMessage);
}
}
const message: MessageUpdateRaw = {
...key,
status: status[update.status],
@@ -960,7 +1001,12 @@ export class WAStartupService {
quoted,
};
if (!message['audio'] && !message['poll'] && !message['linkPreview']) {
if (
!message['audio'] &&
!message['poll'] &&
!message['linkPreview'] &&
!message['sticker']
) {
if (!message['audio']) {
return await this.client.sendMessage(
sender,
@@ -1105,6 +1151,42 @@ export class WAStartupService {
}
}
private async convertToWebP(image: string) {
try {
let imagePath: string;
const outputPath = `${join(process.cwd(), 'temp', 'sticker.webp')}`;
if (isBase64(image)) {
const base64Data = image.replace(/^data:image\/(jpeg|png|gif);base64,/, '');
const imageBuffer = Buffer.from(base64Data, 'base64');
imagePath = `${join(process.cwd(), 'temp', 'temp-sticker.png')}`;
await sharp(imageBuffer).toFile(imagePath);
} else {
const response = await axios.get(image, { responseType: 'arraybuffer' });
const imageBuffer = Buffer.from(response.data, 'binary');
imagePath = `${join(process.cwd(), 'temp', 'temp-sticker.png')}`;
await sharp(imageBuffer).toFile(imagePath);
}
await sharp(imagePath).webp().toFile(outputPath);
return outputPath;
} catch (error) {
console.error('Erro ao converter a imagem para WebP:', error);
}
}
public async mediaSticker(data: SendStickerDto) {
const convert = await this.convertToWebP(data.stickerMessage.image);
return await this.sendMessageWithTyping(
data.number,
{
sticker: { url: convert },
},
data?.options,
);
}
public async mediaMessage(data: SendMediaDto) {
const generate = await this.prepareMediaMessage(data.mediaMessage);
@@ -1115,18 +1197,54 @@ export class WAStartupService {
);
}
private async processAudio(audio: string) {
let tempAudioPath: string;
let outputAudio: string;
if (isURL(audio)) {
outputAudio = `${join(process.cwd(), 'temp', 'audio.mp4')}`;
tempAudioPath = `${join(process.cwd(), 'temp', 'audioTemp.mp3')}`;
const response = await axios.get(audio, { responseType: 'arraybuffer' });
fs.writeFileSync(tempAudioPath, response.data);
} else {
outputAudio = `${join(process.cwd(), 'temp', 'audio.mp4')}`;
tempAudioPath = `${join(process.cwd(), 'temp', 'audioTemp.mp3')}`;
const audioBuffer = Buffer.from(audio, 'base64');
fs.writeFileSync(tempAudioPath, audioBuffer);
}
return new Promise((resolve, reject) => {
exec(
// `${ffmpegPath.path} -i ${tempAudioPath} -c:a libopus ${outputAudio} -y`,
`${ffmpegPath.path} -i ${tempAudioPath} -vn -ab 128k -ar 44100 -f ipod ${outputAudio} -y`,
(error, _stdout, _stderr) => {
fs.unlinkSync(tempAudioPath);
if (error) reject(error);
resolve(outputAudio);
},
);
});
}
public async audioWhatsapp(data: SendAudioDto) {
return this.sendMessageWithTyping<AnyMessageContent>(
data.number,
{
audio: isURL(data.audioMessage.audio)
? { url: data.audioMessage.audio }
: Buffer.from(data.audioMessage.audio, 'base64'),
ptt: true,
mimetype: 'audio/ogg; codecs=opus',
},
{ presence: 'recording', delay: data?.options?.delay },
);
const convert = await this.processAudio(data.audioMessage.audio);
if (typeof convert === 'string') {
const audio = fs.readFileSync(convert).toString('base64');
return this.sendMessageWithTyping<AnyMessageContent>(
data.number,
{
audio: Buffer.from(audio, 'base64'),
ptt: true,
// mimetype: 'audio/ogg; codecs=opus',
mimetype: 'audio/mp4',
},
{ presence: 'recording', delay: data?.options?.delay },
);
} else {
throw new InternalServerErrorException(convert);
}
}
public async buttonMessage(data: SendButtonDto) {
@@ -1442,7 +1560,31 @@ export class WAStartupService {
return await this.repository.chat.find({ where: { owner: this.instance.wuid } });
}
public async getBusinessProfile(number: string) {
public async fetchPrivacySettings() {
return await this.client.fetchPrivacySettings();
}
public async updatePrivacySettings(settings: PrivacySettingDto) {
try {
await this.client.updateReadReceiptsPrivacy(settings.privacySettings.readreceipts);
await this.client.updateProfilePicturePrivacy(settings.privacySettings.profile);
await this.client.updateStatusPrivacy(settings.privacySettings.status);
await this.client.updateOnlinePrivacy(settings.privacySettings.online);
await this.client.updateLastSeenPrivacy(settings.privacySettings.last);
await this.client.updateGroupsAddPrivacy(settings.privacySettings.groupadd);
// reinicia a instancia
return { update: 'success', data: await this.client.fetchPrivacySettings() };
} catch (error) {
throw new InternalServerErrorException(
'Error updating privacy settings',
error.toString(),
);
}
}
public async fetchBusinessProfile(number: string) {
try {
let jid;
@@ -1562,7 +1704,36 @@ export class WAStartupService {
return { update: 'success' };
} catch (error) {
throw new InternalServerErrorException('Error creating group', error.toString());
throw new InternalServerErrorException(
'Error update group picture',
error.toString(),
);
}
}
public async updateGroupSubject(data: GroupSubjectDto) {
try {
await this.client.groupUpdateSubject(data.groupJid, data.subject);
return { update: 'success' };
} catch (error) {
throw new InternalServerErrorException(
'Error updating group subject',
error.toString(),
);
}
}
public async updateGroupDescription(data: GroupDescriptionDto) {
try {
await this.client.groupUpdateDescription(data.groupJid, data.description);
return { update: 'success' };
} catch (error) {
throw new InternalServerErrorException(
'Error updating group description',
error.toString(),
);
}
}
@@ -1577,6 +1748,14 @@ export class WAStartupService {
}
}
public async fetchAllGroups() {
try {
return await this.client.groupFetchAllParticipating();
} catch (error) {
throw new NotFoundException('Error fetching group', error.toString());
}
}
public async inviteCode(id: GroupJid) {
try {
const code = await this.client.groupInviteCode(id.groupJid);
@@ -1594,6 +1773,31 @@ export class WAStartupService {
}
}
public async sendInvite(id: GroupSendInvite) {
try {
const inviteCode = await this.inviteCode({ groupJid: id.groupJid });
const inviteUrl = inviteCode.inviteUrl;
const numbers = id.numbers.map((number) => this.createJid(number));
const description = id.description ?? '';
const msg = `${description}\n${inviteUrl}`;
const message = {
linkPreview: {
text: msg,
},
};
for await (const number of numbers) {
await this.sendMessageWithTyping(number, message);
}
return { send: true, inviteUrl };
} catch (error) {
throw new NotFoundException('No send invite');
}
}
public async revokeInviteCode(id: GroupJid) {
try {
const inviteCode = await this.client.groupRevokeInvite(id.groupJid);
@@ -1644,7 +1848,7 @@ export class WAStartupService {
update.groupJid,
update.expiration,
);
return { toggleEphemeral: toggleEphemeral };
return { success: true };
} catch (error) {
throw new BadRequestException('Error updating setting', error.toString());
}

View File

@@ -1,5 +1,5 @@
/* eslint-disable @typescript-eslint/no-namespace */
import { AuthenticationState, WAConnectionState } from '@evolution/base';
import { AuthenticationState, WAConnectionState } from '@whiskeysockets/baileys';
export enum Events {
APPLICATION_STARTUP = 'application.startup',
@@ -34,7 +34,12 @@ export declare namespace wa {
profilePictureUrl?: string;
};
export type LocalWebHook = { enabled?: boolean; url?: string };
export type LocalWebHook = {
enabled?: boolean;
url?: string;
events?: string[];
webhook_by_events?: boolean;
};
export type StateConnection = {
instance?: string;

View File

@@ -27,7 +27,7 @@ import { WebhookRepository } from './repository/webhook.repository';
import { WebhookModel } from './models/webhook.model';
import { AuthRepository } from './repository/auth.repository';
import { WAStartupService } from './services/whatsapp.service';
import { delay } from '@evolution/base';
import { delay } from '@whiskeysockets/baileys';
import { Events } from './types/wa.types';
const logger = new Logger('WA MODULE');

24
start.sh Executable file
View File

@@ -0,0 +1,24 @@
#!/bin/sh
if [ "$DOCKER_ENV" = "true" ];
then
echo "Enabling environment variables for Docker"
echo "DOCKER_ENV=$DOCKER_ENV"
echo
else
cp ./src/env.yml ./dist/src
fi
echo "> removing dist"
rm -rf ./dist
echo
echo "> transpiling..."
npm run build
echo
echo "> Successfully build "
echo
echo "> Starting application..."
echo
node ./dist/src/main.js

BIN
temp/audio.mp4 Normal file

Binary file not shown.

BIN
temp/sticker.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

BIN
temp/sticker.webp_original Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB