Merge branch 'release/1.1.0'
3
.gitignore
vendored
@ -11,6 +11,9 @@ yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
|
||||
/store/*
|
||||
/docker-compose-data
|
||||
|
||||
# Package
|
||||
/yarn.lock
|
||||
/package-lock.json
|
||||
|
29
CHANGELOG.md
@ -1,3 +1,32 @@
|
||||
# 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
|
||||
|
||||
# 1.0.9 (2023-06-10)
|
||||
|
||||
### Fixed
|
||||
|
11
Docker/.env
@ -12,11 +12,17 @@ LOG_COLOR=true
|
||||
DEL_INSTANCE=5
|
||||
|
||||
# Temporary data storage
|
||||
STORE_CLEANING_INTERVAL=7200 # seconds ===2h
|
||||
STORE_MESSAGE=true
|
||||
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>'
|
||||
@ -70,6 +76,7 @@ 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'
|
||||
|
34
Dockerfile
@ -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,12 +19,18 @@ 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
|
||||
@ -37,7 +46,7 @@ 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=$WEBHOOK_EVENTS_STATUS_INSTANCE
|
||||
@ -59,19 +68,20 @@ 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_CLIENT=$CONFIG_SESSION_PHONE_CLIENT
|
||||
ENV CONFIG_SESSION_PHONE_NAME="Chrome"
|
||||
|
||||
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
|
||||
|
72
README.md
@ -1,22 +1,19 @@
|
||||
<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">
|
||||
|
||||
<!-- [](#)
|
||||
[](#)
|
||||
<!-- [](#)-->
|
||||
<!-- [](#) -->
|
||||
[](./LICENSE)
|
||||
[](https://app.picpay.com/user/davidsongomes1998) -->
|
||||
[](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
|
||||
@ -44,7 +41,7 @@ sudo usermod -aG docker ${USER}
|
||||
### Nodejs installation
|
||||
|
||||
```sh
|
||||
nvm install 16.17.0
|
||||
nvm install 16.18.1
|
||||
```
|
||||
|
||||
### pm2 installation
|
||||
@ -72,7 +69,7 @@ Using the database is optional.
|
||||
|
||||
Cloning the Repository
|
||||
```
|
||||
git clone https://github.com/code-chat-br/whatsapp-api.git
|
||||
git clone https://github.com/EvolutionAPI/evolution-api.git
|
||||
```
|
||||
|
||||
Go to the project directory and install all dependencies.</br>
|
||||
@ -91,7 +88,7 @@ npm run start
|
||||
npm run start:prod
|
||||
|
||||
# pm2
|
||||
pm2 start 'npm run start:prod' --name ApiCodechat
|
||||
pm2 start 'npm run start:prod' --name ApiEvolution
|
||||
```
|
||||
## Authentication
|
||||
|
||||
@ -121,7 +118,7 @@ Content-Type: application/json
|
||||
apikey: t8OOEeISKzpmc3jjcMqBWYSaJH2PIxns
|
||||
|
||||
{
|
||||
"instanceName": "codechat"
|
||||
"instanceName": "evolution"
|
||||
}
|
||||
```
|
||||
##### cURL
|
||||
@ -131,7 +128,7 @@ curl --location --request POST 'http://localhost:8080/instance/create' \
|
||||
--header 'Content-Type: application/json' \
|
||||
--header 'apikey: t8OOEeISKzpmc3jjcMqBWYSaJH2PIxns' \
|
||||
--data-raw '{
|
||||
"instanceName": "codechat"
|
||||
"instanceName": "evolution"
|
||||
}'
|
||||
```
|
||||
### Response
|
||||
@ -139,7 +136,7 @@ curl --location --request POST 'http://localhost:8080/instance/create' \
|
||||
```ts
|
||||
{
|
||||
"instance": {
|
||||
"instanceName": "codechat",
|
||||
"instanceName": "evolution",
|
||||
"status": "created"
|
||||
},
|
||||
"hash": {
|
||||
@ -155,23 +152,23 @@ curl --location --request POST 'http://localhost:8080/instance/create' \
|
||||
##### HTTP
|
||||
|
||||
```http
|
||||
GET /instance/connect/codechat HTTP/1.1
|
||||
GET /instance/connect/evolution HTTP/1.1
|
||||
Host: localhost:8080
|
||||
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9. [...]
|
||||
```
|
||||
```http
|
||||
GET /instance/connect/codechat HTTP/1.1
|
||||
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/codechat' \
|
||||
curl --location --request GET 'http://localhost:8080/instance/connect/evolution' \
|
||||
--header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9. [...]'
|
||||
```
|
||||
```bash
|
||||
curl --location --request GET 'http://localhost:8080/instance/connect/codechat' \
|
||||
curl --location --request GET 'http://localhost:8080/instance/connect/evolution' \
|
||||
--header 'apikey: 88513847-1B0E-4188-8D76-4A2750C9B6C3'
|
||||
```
|
||||
|
||||
@ -188,7 +185,7 @@ curl --location --request GET 'http://localhost:8080/instance/connect/codechat'
|
||||
- [docker run](./docker.sh)
|
||||
- [docker-compose](./docker-compose.yml)
|
||||
- [env for docker](./Docker/.env)
|
||||
- [DockerHub-codechat/api](https://hub.docker.com/r/codechat/api)
|
||||
- [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
|
||||
@ -198,17 +195,17 @@ docker-compose up
|
||||
| | |
|
||||
|-----|---|
|
||||
| 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 | ✔ |
|
||||
| Send Buttons (Deprecated) | ❌ |
|
||||
| Send List (Deprecated) | ❌ |
|
||||
|
||||
## Postman collections
|
||||
- [Postman Json](./postman.json)
|
||||
@ -236,6 +233,33 @@ docker-compose up
|
||||
| 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)**.
|
||||
@ -254,8 +278,6 @@ This code was produced based on the baileys library and it is still under develo
|
||||
|
||||
# Donate to the project.
|
||||
|
||||
#### Pix: 2b526ada-4ef4-4db4-bbeb-f60da2421fce
|
||||
|
||||
#### PicPay
|
||||
|
||||
<div align="center">
|
||||
|
@ -5,14 +5,139 @@ 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
|
||||
depends_on:
|
||||
- mongodb
|
||||
- redis
|
||||
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=true
|
||||
- DATABASE_CONNECTION_URI=mongodb://root:root@mongodb:27017/?authSource=admin&readPreference=primary&ssl=false&directConnection=true
|
||||
- DATABASE_CONNECTION_DB_PREFIX_NAME=evolution
|
||||
# Choose the data you want to save in the application's database or store
|
||||
- DATABASE_SAVE_DATA_INSTANCE=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=true
|
||||
- REDIS_URI=redis://redis:6379
|
||||
- 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_STATUS_INSTANCE=true
|
||||
- WEBHOOK_EVENTS_APPLICATION_STARTUP=false
|
||||
- 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=true
|
||||
- WEBHOOK_EVENTS_GROUPS_UPDATE=true
|
||||
- WEBHOOK_EVENTS_GROUP_PARTICIPANTS_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"
|
||||
# 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_WEBHOOK_BY_EVENTS=false
|
||||
- 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
|
||||
|
||||
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:
|
||||
evolution_instances:
|
||||
evolution_store:
|
||||
evolution_mongodb_data:
|
||||
evolution_mongodb_configdb:
|
||||
evolution_redis:
|
18
docker.sh
Executable file
@ -0,0 +1,18 @@
|
||||
#!/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
|
||||
|
||||
docker build -t ${IMAGE} .
|
||||
|
||||
docker compose up -d
|
@ -42,6 +42,7 @@
|
||||
"dependencies": {
|
||||
"@adiwajshing/keyed-db": "^0.2.4",
|
||||
"@evolution/base": "github:WhiskeySockets/Baileys",
|
||||
"@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": {
|
||||
|
1431
postman.json
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 120 KiB After Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 108 KiB After Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 36 KiB |
@ -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;
|
||||
};
|
||||
@ -82,6 +91,7 @@ export type Instance = {
|
||||
};
|
||||
export type Auth = {
|
||||
API_KEY: ApiKey;
|
||||
EXPOSE_IN_FETCH_INSTANCES: boolean;
|
||||
JWT: Jwt;
|
||||
TYPE: 'jwt' | 'apikey';
|
||||
INSTANCE: Instance;
|
||||
@ -105,6 +115,7 @@ export interface Env {
|
||||
CORS: Cors;
|
||||
SSL_CONF: SslConf;
|
||||
STORE: StoreConf;
|
||||
CLEAN_STORE: CleanStoreConf;
|
||||
DATABASE: Database;
|
||||
REDIS: Redis;
|
||||
LOG: Log;
|
||||
@ -134,7 +145,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 +169,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 +207,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 +251,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)
|
||||
|
@ -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',
|
||||
|
@ -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);
|
||||
|
@ -7,7 +7,7 @@
|
||||
# Choose the server type for the application
|
||||
SERVER:
|
||||
TYPE: http # https
|
||||
PORT: 8083 # 443
|
||||
PORT: 8080 # 443
|
||||
|
||||
CORS:
|
||||
ORIGIN:
|
||||
@ -45,8 +45,15 @@ DEL_INSTANCE: false # 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
|
||||
|
||||
@ -118,6 +125,8 @@ AUTHENTICATION:
|
||||
API_KEY:
|
||||
# OBS: This key must be inserted in the request header to create an instance.
|
||||
KEY: B6D711FC-DE4D-4FD5-9365-44120E713976
|
||||
# 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
|
||||
|
@ -27,6 +27,36 @@ export const instanceNameSchema: JSONSchema7 = {
|
||||
properties: {
|
||||
instanceName: { type: 'string' },
|
||||
webhook: { type: 'string' },
|
||||
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 +210,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 +456,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',
|
||||
@ -650,7 +728,7 @@ export const toggleEphemeralSchema: JSONSchema7 = {
|
||||
...isNotEmpty('groupJid', 'expiration'),
|
||||
};
|
||||
|
||||
export const updateGroupPicture: JSONSchema7 = {
|
||||
export const updateGroupPictureSchema: JSONSchema7 = {
|
||||
$id: v4(),
|
||||
type: 'object',
|
||||
properties: {
|
||||
@ -661,6 +739,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 +768,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'),
|
||||
|
@ -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;
|
||||
|
||||
|
@ -3,6 +3,7 @@ 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) {
|
||||
|
@ -1,8 +1,10 @@
|
||||
import {
|
||||
CreateGroupDto,
|
||||
GroupDescriptionDto,
|
||||
GroupInvite,
|
||||
GroupJid,
|
||||
GroupPictureDto,
|
||||
GroupSubjectDto,
|
||||
GroupToggleEphemeralDto,
|
||||
GroupUpdateParticipantDto,
|
||||
GroupUpdateSettingDto,
|
||||
@ -23,10 +25,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 +56,12 @@ export class GroupController {
|
||||
return await this.waMonitor.waInstances[instance.instanceName].inviteInfo(inviteCode);
|
||||
}
|
||||
|
||||
public async acceptInvite(instance: InstanceDto, inviteCode: GroupInvite) {
|
||||
return await this.waMonitor.waInstances[instance.instanceName].acceptInvite(
|
||||
inviteCode,
|
||||
);
|
||||
}
|
||||
|
||||
public async revokeInviteCode(instance: InstanceDto, groupJid: GroupJid) {
|
||||
return await this.waMonitor.waInstances[instance.instanceName].revokeInviteCode(
|
||||
groupJid,
|
||||
|
@ -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,16 @@ 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,
|
||||
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',
|
||||
@ -44,13 +49,20 @@ 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 });
|
||||
|
||||
getEvents = (await this.webhookService.find(instance)).events;
|
||||
} catch (error) {
|
||||
this.logger.log(error);
|
||||
}
|
||||
@ -63,6 +75,7 @@ export class InstanceController {
|
||||
},
|
||||
hash,
|
||||
webhook,
|
||||
events: getEvents,
|
||||
};
|
||||
} else {
|
||||
const instance = new WAStartupService(
|
||||
@ -74,18 +87,33 @@ 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 });
|
||||
|
||||
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 +121,8 @@ export class InstanceController {
|
||||
},
|
||||
hash,
|
||||
webhook,
|
||||
events: getEvents,
|
||||
qrcode: getQrcode,
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -100,7 +130,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 +143,12 @@ export class InstanceController {
|
||||
return await this.connectionState({ instanceName });
|
||||
}
|
||||
} catch (error) {
|
||||
this.logger.log(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) {
|
||||
|
@ -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);
|
||||
|
@ -1,3 +1,9 @@
|
||||
import {
|
||||
WAPrivacyOnlineValue,
|
||||
WAPrivacyValue,
|
||||
WAReadReceiptsValue,
|
||||
} from '@evolution/base';
|
||||
|
||||
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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -1,4 +1,7 @@
|
||||
export class InstanceDto {
|
||||
instanceName: string;
|
||||
webhook?: string;
|
||||
events?: string[];
|
||||
qrcode?: boolean;
|
||||
token?: string;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -1,4 +1,5 @@
|
||||
export class WebhookDto {
|
||||
enabled?: boolean;
|
||||
url?: string;
|
||||
events?: string[];
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -5,12 +5,14 @@ export class WebhookRaw {
|
||||
_id?: string;
|
||||
url?: string;
|
||||
enabled?: boolean;
|
||||
events?: string[];
|
||||
}
|
||||
|
||||
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');
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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),
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ import {
|
||||
deleteMessageSchema,
|
||||
messageUpSchema,
|
||||
messageValidateSchema,
|
||||
privacySettingsSchema,
|
||||
profileNameSchema,
|
||||
profilePictureSchema,
|
||||
profileStatusSchema,
|
||||
@ -15,6 +16,7 @@ import {
|
||||
ArchiveChatDto,
|
||||
DeleteMessage,
|
||||
NumberDto,
|
||||
PrivacySettingDto,
|
||||
ProfileNameDto,
|
||||
ProfilePictureDto,
|
||||
ProfileStatusDto,
|
||||
@ -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);
|
||||
|
@ -5,7 +5,9 @@ import {
|
||||
updateParticipantsSchema,
|
||||
updateSettingsSchema,
|
||||
toggleEphemeralSchema,
|
||||
updateGroupPicture,
|
||||
updateGroupPictureSchema,
|
||||
updateGroupSubjectSchema,
|
||||
updateGroupDescriptionSchema,
|
||||
groupInviteSchema,
|
||||
} from '../../validate/validate.schema';
|
||||
import { RouterBroker } from '../abstract/abstract.router';
|
||||
@ -14,6 +16,8 @@ import {
|
||||
GroupInvite,
|
||||
GroupJid,
|
||||
GroupPictureDto,
|
||||
GroupSubjectDto,
|
||||
GroupDescriptionDto,
|
||||
GroupUpdateParticipantDto,
|
||||
GroupUpdateSettingDto,
|
||||
GroupToggleEphemeralDto,
|
||||
@ -35,16 +39,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 +80,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 +120,16 @@ export class GroupRouter extends RouterBroker {
|
||||
|
||||
res.status(HttpStatus.OK).json(response);
|
||||
})
|
||||
.get(this.routerPath('acceptInvite'), ...guards, async (req, res) => {
|
||||
const response = await this.inviteCodeValidate<GroupInvite>({
|
||||
request: req,
|
||||
schema: groupInviteSchema,
|
||||
ClassRef: GroupInvite,
|
||||
execute: (instance, data) => groupController.acceptInvite(instance, data),
|
||||
});
|
||||
|
||||
res.status(HttpStatus.OK).json(response);
|
||||
})
|
||||
.put(this.routerPath('revokeInviteCode'), ...guards, async (req, res) => {
|
||||
const response = await this.groupValidate<GroupJid>({
|
||||
request: req,
|
||||
|
@ -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[]) {
|
||||
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
@ -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,11 @@ export class AuthService {
|
||||
return { apikey };
|
||||
}
|
||||
|
||||
public async generateHash(instance: InstanceDto) {
|
||||
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) {
|
||||
|
@ -4,7 +4,13 @@ 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';
|
||||
@ -60,16 +66,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,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -176,6 +222,8 @@ export class WAMonitoringService {
|
||||
}
|
||||
|
||||
await set(dirent.name);
|
||||
} else {
|
||||
initInstance();
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
|
@ -29,17 +29,19 @@ import makeWASocket, {
|
||||
WAMessage,
|
||||
WAMessageUpdate,
|
||||
WASocket,
|
||||
getAggregateVotesInPollMessage,
|
||||
} from '@evolution/base';
|
||||
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,8 @@ import {
|
||||
GroupUpdateParticipantDto,
|
||||
GroupUpdateSettingDto,
|
||||
GroupToggleEphemeralDto,
|
||||
GroupSubjectDto,
|
||||
GroupDescriptionDto,
|
||||
} from '../dto/group.dto';
|
||||
import { MessageUpQuery } from '../repository/messageUp.repository';
|
||||
import { useMultiFileAuthStateDb } from '../../utils/use-multi-file-auth-state-db';
|
||||
@ -105,6 +112,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 +203,7 @@ 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;
|
||||
}
|
||||
|
||||
public async setWebhook(data: WebhookRaw) {
|
||||
@ -207,14 +216,16 @@ 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) {
|
||||
@ -222,6 +233,16 @@ export class WAStartupService {
|
||||
} 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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -455,7 +484,7 @@ export class WAStartupService {
|
||||
|
||||
const { version } = await fetchLatestBaileysVersion();
|
||||
const session = this.configService.get<ConfigSessionPhone>('CONFIG_SESSION_PHONE');
|
||||
const browser: WABrowserDescription = [session.CLIENT, session.NAME, release()];
|
||||
const browser: WABrowserDescription = [session.CLIENT, 'Chrome', release()];
|
||||
|
||||
const socketConfig: UserFacingSocketConfig = {
|
||||
auth: {
|
||||
@ -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,14 @@ export class WAStartupService {
|
||||
}
|
||||
}
|
||||
|
||||
public async acceptInvite(id: GroupInvite) {
|
||||
try {
|
||||
return await this.client.groupAcceptInvite(id.inviteCode);
|
||||
} catch (error) {
|
||||
throw new NotFoundException('No invite info', id.inviteCode);
|
||||
}
|
||||
}
|
||||
|
||||
public async revokeInviteCode(id: GroupJid) {
|
||||
try {
|
||||
const inviteCode = await this.client.groupRevokeInvite(id.groupJid);
|
||||
|
@ -34,7 +34,7 @@ export declare namespace wa {
|
||||
profilePictureUrl?: string;
|
||||
};
|
||||
|
||||
export type LocalWebHook = { enabled?: boolean; url?: string };
|
||||
export type LocalWebHook = { enabled?: boolean; url?: string; events?: string[] };
|
||||
|
||||
export type StateConnection = {
|
||||
instance?: string;
|
||||
|
24
start.sh
Executable 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
BIN
temp/sticker.webp
Normal file
After Width: | Height: | Size: 8.6 KiB |
BIN
temp/sticker.webp_original
Normal file
After Width: | Height: | Size: 8.4 KiB |