Compare commits

..
27 Commits
Author SHA1 Message Date
allburov e60d972bff [core] Bump version to 2023.6.1
Release / deploy-docker (push) Waiting to run
2023-06-01 11:54:08 +07:00
allburov 3c4a139f52 [core] Update venom engine 2023-05-21 18:41:06 +07:00
allburov bf615efa96 [core] Apply pre-commit with prettier 2023-05-21 18:09:25 +07:00
allburov 2af3940be7 [core] Add prettier to pre-commit 2023-05-21 18:09:12 +07:00
allburov 64e145909e [core] Advanced sessions control 2023-05-21 18:01:04 +07:00
allburov e90f34cc96 [core] Pin markupsafe version
Release / deploy-docker (push) Waiting to run
Fix #97
2023-05-20 18:25:18 +07:00
allburov a23cf2dd26 [docs] Add port to receive message docker run command 2023-05-20 18:17:21 +07:00
allburov 7b623d0faa [core] Bump version 2023-05-20 18:11:36 +07:00
allburov 14788b1d53 [core] Update webjs engine 2023-05-20 18:11:33 +07:00
allburov e1bf8cf735 [core] Update swagger 2023-05-12 11:45:20 +07:00
allburov 2117731b46 [core] Add variables to Swagger 2023-05-12 11:44:29 +07:00
allburov 74f8fe1caf [core] bump version to 2023.5.11 2023-05-11 10:27:44 +07:00
allburov 9388a3b277 [core] Add caption to file 2023-05-11 10:27:41 +07:00
allburov 13e7a05e1b [core] Fix provide extended option dist/main.js:29:38 error 2023-05-07 10:31:23 +07:00
allburov 885fa25c0c [core] Add make build-plus command 2023-05-07 10:31:05 +07:00
allburov 966b35094f Bump version to 2023.5.3
Release / deploy-docker (push) Waiting to run
2023-05-03 09:17:44 +07:00
allburov 65607bf3b3 Handle extendedTextMessage
Fix #90
2023-05-03 09:17:24 +07:00
allburov 5fcba7a7db [core][docs] Remove the icon from headers 2023-04-27 18:37:05 +07:00
allburov f9ec3cb753 [core] Bump version 2023.4.27
Release / deploy-docker (push) Waiting to run
2023-04-27 09:13:03 +07:00
allburov bf6ffd65a9 [core] ignore all *sessions 2023-04-26 18:15:04 +07:00
allburov d680b6b43a [core] Add noweb engine 2023-04-26 18:14:38 +07:00
allburov 3ead3a0ba3 [core] Add GET /messages request
Fix #31
2023-04-26 18:14:08 +07:00
allburov 4c5c977310 [core][docs] No delay in bugfixes 2023-04-15 17:58:15 +07:00
allburov 89a9ce87da [core] Use the main branch in whatsapp webjs
Release / deploy-docker (push) Waiting to run
2023-04-14 10:43:02 +07:00
allburov 52e469f6ac [core] Bump version - 2023.4.14 2023-04-14 10:38:58 +07:00
allburov 4f275de8d4 [core] close session properly
Fix #83
2023-04-14 10:38:56 +07:00
allburov d9de41e350 [core] bump version - 2023.3.28 2023-04-14 10:38:54 +07:00
59 changed files with 4261 additions and 2066 deletions

No files matched your search

+1 -1
View File
@@ -1,5 +1,5 @@
Dockerfile
.sessions
.*sessions
files
venv
.wwebjs_auth
+1 -1
View File
@@ -1,4 +1,4 @@
.sessions
.*sessions
tokens
files
venv
+15 -6
View File
@@ -1,4 +1,13 @@
repos:
- repo: https://github.com/pre-commit/mirrors-prettier
rev: 'v2.7.1'
hooks:
- id: prettier
types_or: [javascript, ts]
files: |
(?x)^(
src/.*|
)$
- repo: https://github.com/Lucas-C/pre-commit-hooks-nodejs
rev: v1.1.2
hooks:
@@ -12,9 +21,9 @@ repos:
language: pygrep
entry: 'plus'
files: |
(?x)^(
src/api/.*|
src/core/.*|
src/structures/.*|
src/config.service.ts|
)$
(?x)^(
src/api/.*|
src/core/.*|
src/structures/.*|
src/config.service.ts|
)$
+9
View File
@@ -4,6 +4,15 @@
FROM node:18 as build
ENV PUPPETEER_SKIP_DOWNLOAD=True
# Install https://github.com/WhiskeySockets/Baileys
WORKDIR /
RUN git clone https://github.com/WhiskeySockets/Baileys WhiskeySockets-Baileys && \
cd WhiskeySockets-Baileys && \
git checkout e3ac5f596f16b706420ed016a349fa75bd3a157f && \
yarn install && \
yarn build:all
# npm packages
WORKDIR /src
COPY package.json .
+2 -20
View File
@@ -1,26 +1,8 @@
build:
docker build . -t devlikeapro/whatsapp-http-api
run-webjs:
docker run \
--rm -d \
-p 127.0.0.1:3000:3000/tcp \
-v `pwd`/.sessions:/app/.sessions \
-e WHATSAPP_HOOK_URL=https://httpbin.org/post \
-e WHATSAPP_HOOK_EVENTS=* \
-e WHATSAPP_API_KEY=321 \
--name whatsapp-http-api devlikeapro/whatsapp-http-api
run-venom:
docker run \
--rm -d \
-p 127.0.0.1:3000:3000/tcp \
-v `pwd`/.sessions:/app/.sessions \
-e WHATSAPP_HOOK_URL=https://httpbin.org/post \
-e WHATSAPP_HOOK_EVENTS=* \
-e WHATSAPP_DEFAULT_ENGINE=VENOM \
-e WHATSAPP_API_KEY=321 \
--name whatsapp-http-api devlikeapro/whatsapp-http-api
build-plus:
docker build . -t devlikeapro/whatsapp-http-api-plus
stop:
docker stop whatsapp-http-api
+1 -3
View File
@@ -61,8 +61,6 @@
## Updates
- Bug fixes and updates come as soon as they are implemented.
- One month delay in bug fixes and updates.
## Groups
- Create a group
@@ -123,7 +121,7 @@ Read more about how to get `PASSWORD` for [Plus Version →]({{< relref "plus-ve
Run WhatsApp HTTP API:
```bash
docker run -it --rm -p 127.0.0.1:3000:3000/tcp --name whatsapp-http-api devlikeapro/whatsapp-http-api
docker run -it --rm -p 3000:3000/tcp --name whatsapp-http-api devlikeapro/whatsapp-http-api
# It prints logs and the last line must be
# WhatsApp HTTP API is running on: http://[::1]:3000
+32
View File
@@ -0,0 +1,32 @@
version: '3'
services:
whatsapp-http-api:
image: devlikeapro/whatsapp-http-api
container_name: whatsapp-http-api
ports:
- "3000:3000/tcp"
environment:
# Environment variables from https://waha.devlike.pro/docs/how-to/config/
- WHATSAPP_HOOK_URL=https://httpbin.org/post
- WHATSAPP_HOOK_EVENTS=message
- WHATSAPP_DEFAULT_ENGINE=WEBJS
restart: always
whatsapp-http-api-plus:
image: devlikeapro/whatsapp-http-api-plus
container_name: whatsapp-http-api-plus
ports:
- "3000:3000/tcp"
volumes:
- "./.sessions:/app/.sessions"
environment:
# Environment variables from https://waha.devlike.pro/docs/how-to/config/
- WHATSAPP_RESTART_ALL_SESSIONS=True
- WHATSAPP_HOOK_URL=https://httpbin.org/post
- WHATSAPP_HOOK_EVENTS=message
- WHATSAPP_DEFAULT_ENGINE=WEBJS
- WHATSAPP_API_KEY=321
- WHATSAPP_SWAGGER_USERNAME=admin
- WHATSAPP_SWAGGER_PASSWORD=123
- WHATSAPP_API_HOSTNAME=localhost
restart: always
+1 -1
View File
@@ -74,7 +74,7 @@ copyRight = "Copyright (c) 2021-2023 Dev Like A Pro"
alert = true
alertDismissable = false
# alertText = "Introducing the Doks child theme, several DX + UX updates, and more! <a class=\"alert-link stretched-link\" href=\"https://getdoks.org/blog/doks-v0.2/\" target=\"_blank\" rel=\"noopener\">Check out Doks v0.2</a>"
alertText = "Enjoy Core version or want to get WAHA Plus verison? You can <a class=\"alert-link stretched-link\" href=\"https://waha.devlike.pro/docs/how-to/plus-version/\">support the project -></a>"
alertText = "Enjoy Core version or want to get WAHA Plus verison? <a class=\"alert-link stretched-link\" href=\"https://waha.devlike.pro/docs/how-to/plus-version/\">Support the project -></a>"
# Edit Page
# repoHost [Github | Gitea | GitLab | Bitbucket | BitbucketServer ] is used for building the edit link based on git hoster
+62 -7
View File
@@ -5,18 +5,64 @@ lead: "Changelog"
date: 2020-10-06T08:49:31+00:00
lastmod: 2020-10-06T08:49:31+00:00
draft: false
images: []
images: [ ]
menu:
docs:
parent: "help"
weight: 600
toc: true
---
## 2021.3
## 2021.6 - June 2023
Improvements on session management, restarting sessions and more:
- Added `WHATSAPP_RESTART_ALL_SESSIONS=True`: Set this variable to `True` to start all **STOPPED** sessions after
container restarts. By default, this variable is set to `False`.
- Please note that this will start all **STOPPED** sessions, not just the sessions that were working before the
restart. You can maintain the session list by
using `POST /api/session/stop` with the `logout: True` parameter or by calling `POST /api/session/logout` to remove
**STOPPED** sessions. You can see all sessions, including **STOPPED** sessions, in the `GET /api/sessions/all=True`
response.
- `WHATSAPP_START_SESSION` now support more than one session! Separate session names by command, and it'll start them
ALWAYS after container restart `WHATSAPP_START_SESSION=session1,session2`
- `WHATSAPP_SWAGGER_CONFIG_ADVANCED=true` enables advanced configuration options for Swagger documentation - you can customize host, port and base URL for the requests.
Disabled by default.
- Added `?all=true` parameter to `GET /api/session?all=True` endpoint - it'll show you ALL session, included **STOPPED
**, so you can know which one will be restarted if you set `WHATSAPP_RESTART_ALL_SESSIONS=True` environment variable.
- Added `POST /api/sessions/logout` that allow you to logout from session - remove saved credentials.
- Added `logout` boolean parameter to `POST /api/sessions/stop` request that allow you to stop the session AND logout at
the same time.
- Added [How to deploy page ->]({{< relref "/docs/how-to/deploy" >}}) with
[docker-compose.yaml](https://github.com/devlikeapro/whatsapp-http-api/blob/core/docker-compose.yaml) example
- Added `engine` field in webhook payload
```json
{
"event": "message",
"session": "default",
"engine": "WEBJS",
"payload": {}
}
```
## 2021.5 - May 2023
- Added new [NOWEB engine]({{< relref "/docs/how-to/engines" >}}). **NOWEB** engine does not require a browser to work
with
WhatsApp Web, it does so directly using a WebSocket.
- Less CPU and RAM usage!
- Send Locations API works!
- Send Link Preview API works!
- ⚠ Read the article before using it [How to avoid blocking ->]({{< relref "/docs/help/how-to-avoid-blocking" >}}).
## 2021.4 - March 2023
- Add [Groups API]({{< relref "/docs/how-to/groups" >}})
- Use Chromium by default instead of Chrome
## 2021.1
## 2021.1 - January 2023
- Added [Contacts API]({{< relref "/docs/how-to/contacts" >}})
- Get all contacts
- Get a contact
@@ -25,33 +71,42 @@ toc: true
- Check number exists (is registered in WhatsApp) - works even if the number is not in the contact list
- Block and unblock contact
## 2022.12
## 2022.12 - December 2023
- Added `GET /messages/` endpoint to get chat messages [#31](https://github.com/devlikeapro/whatsapp-http-api/issues/31)
### Security ![](/images/versions/plus.png)
- Added `WHATSAPP_SWAGGER_USERNAME` and `WHATSAPP_SWAGGER_PASSWORD` to hide and protect swagger panel.
## 2022.11
**Please test changes in test environment before update production!!**
### Engine ![](/images/versions/core.png)
1. WAHA has changed its underlying engine from Venom to Whatsapp Web.JS. It might change the response and webhook's payloads.
1. WAHA has changed its underlying engine from Venom to Whatsapp Web.JS. It might change the response and webhook's
payloads.
2. Optimize CPU and memory consumption.
### Requests ![](/images/versions/core.png)
- For all `/api/session/` requests use `name` field instead of `sessionName`.
- For all "chatting" requests use `session` field instead of `sessionName`.
### Sessions ![](/images/versions/plus.png)
Now you don't have to scan QR code each time you run WAHA, WAHA saves it for you! Available only in Plus version.
### Authentication ![](/images/versions/plus.png)
Now you can authenticate all requests for WAHA - use `WHATSAPP_API_KEY=secret` environment variable to set "secret key".
If `WHATSAPP_API_KEY` is set - requests must have `X-Api-Key` header with `secret` value, where `secret` - any random secret key.
If `WHATSAPP_API_KEY` is set - requests must have `X-Api-Key` header with `secret` value, where `secret` - any random
secret key.
### Webhooks ![](/images/versions/core.png)
#### Configuration
Instead of setting each webhook via environment variables - we use two environments variables:
+24 -2
View File
@@ -5,7 +5,7 @@ lead: "Answers to frequently asked questions."
date: 2020-10-06T08:49:31+00:00
lastmod: 2020-10-06T08:49:31+00:00
draft: false
images: []
images: [ ]
menu:
docs:
parent: "help"
@@ -13,7 +13,28 @@ weight: 630
toc: true
---
## How to update?
{{< alert icon="💡" text="Please test all new versions in the development environment before updating production!" />}}
![](/images/versions/core.png) For **Core** version the command is
```bash
docker pull devlikeapro/whatsapp-http-api
```
![](/images/versions/plus.png) For **Plus** version, we use login to get the image before:
```bash
docker login -u devlikeapro -p {PASSWORD}
docker pull devlikeapro/whatsapp-http-api-plus
docker logout
```
Read more about how to get `PASSWORD` for [Plus Version →]({{< relref "plus-version" >}})
## How much CPU and Memory does WhatsApp HTTP API consume?
The benchmark has been made on **Intel(R) Core(TM) i7-10510U CPU @ 1.80GHz**.
It may differ from case to case, it depends on usage pattern - how many messages you get, how many send, etc.
@@ -22,6 +43,7 @@ It may differ from case to case, it depends on usage pattern - how many messages
| 1 | 30% | 400MB |
| 10 | 270% | 1.5GB |
The nature of WhatsApp HTTP API and its underlying engines - is to run **real** WhatsApp Web version in Chrome and
communicate with it to prevent blocking from WhatsApp. It's the reason why it's so demanding on resources.
If you're looking for less resource demanded engine - [have a look at **NOWEB** engine ->]({{< relref "/docs/how-to/engines" >}})
@@ -0,0 +1,58 @@
---
title: "How to Avoid Blocking"
description: "How to Avoid Blocking From Whatsapp"
date: 2020-11-12T13:26:54+01:00
lastmod: 2020-11-12T13:26:54+01:00
draft: false
images: [ ]
menu:
docs:
parent: "help"
weight: 610
toc: true
---
How to Avoid Blocking in WhatsApp when developing bots.
It's important to keep in mind that WhatsApp has strict policies in place to prevent spamming and abuse of their
platform.
If you're developing a bot for WhatsApp, it's crucial to follow these guidelines to avoid getting blocked.
## Guidelines to Follow
### 1. Only Reply to Messages
When developing a bot for WhatsApp, you **should never initiate a conversation**.
Instead, your bot **should only reply** to messages that it receives.
This will prevent your bot from being flagged as spam by WhatsApp's users and algorithms.
### 2. Avoid Spamming and Sending Unnecessary Content
Sending too many messages or sending content that the user did not request can also lead to your bot being blocked. Make
sure to only send relevant and useful information to the user.
Additionally, do not send too many messages at once, as this can also trigger spam filters.
### 3. Other Considerations
There are other guidelines to follow when developing a bot for WhatsApp, such as avoiding the use of banned words and
not sharing sensitive or inappropriate content.
Make sure to read WhatsApp's policies thoroughly to ensure that your bot complies with all of their rules.
## How to Process Messages
When processing messages in your bot, it's important to follow certain steps to avoid being flagged as spam.
Here's a recommended process to follow:
1. **Send seen** before processing the message. This can be done by sending a `POST /api/sendSeen/` request to
the WAHA API.
2. **Start typing** before sending a message and wait for a random interval depending on the size of the message. This can
be done by sending a `POST /api/startTyping/` request.
3. **Stop typing** before sending the message. This can be done by sending a `POST /api/stopTyping/` request.
4. **Send the text message** using the `POST /api/sendText` request.
By following these steps, you can ensure that your bot processes messages in a way that's compliant with WhatsApp's
guidelines and reduces the risk of being blocked.
@@ -1,34 +0,0 @@
---
title: "How to Update"
description: "Regularly update the installed WAHA to keep your bot stable, usable, and secure."
lead: "Regularly update the installed WAHA to keep your bot stable, usable, and secure."
date: 2020-11-12T13:26:54+01:00
lastmod: 2020-11-12T13:26:54+01:00
draft: false
images: []
menu:
docs:
parent: "help"
weight: 610
toc: true
---
{{< alert icon="💡" text="Please test all new versions in the development environment before updating production!" />}}
## Core
![](/images/versions/core.png) For Core version the command is
```bash
docker pull devlikeapro/whatsapp-http-api
```
## Plus
![](/images/versions/plus.png) For Plus version, we use login to get the image before:
```bash
docker login -u devlikeapro -p {PASSWORD}
docker pull devlikeapro/whatsapp-http-api-plus
docker logout
```
Read more about how to get `PASSWORD` for [Plus Version →]({{< relref "plus-version" >}})
+104 -29
View File
@@ -1,13 +1,14 @@
---
title : "Configuration"
title: "Configuration"
description: "Configuration"
lead: ""
date: 2020-10-06T08:48:45+00:00
lastmod: 2020-10-06T08:48:45+00:00
draft: false
images: []
images: [ ]
weight: 800
---
You can configure WhatsApp HTTP API behaviour via environment variables, by adding `-e WHATSAPP_VARNAME=value` at the
begging of the command line or by using [other options](https://docs.docker.com/engine/reference/commandline/run/)
@@ -15,44 +16,118 @@ begging of the command line or by using [other options](https://docs.docker.com/
docker run -it -e "WHATSAPP_HOOK_EVENTS=*" -e WHATSAPP_HOOK_URL=https://httpbin.org/post devlikeapro/whatsapp-http-api
```
It's not necessary to always run such a long command - you can save all data in
[docker-compose.yaml](https://github.com/devlikeapro/whatsapp-http-api/blob/core/docker-compose.yaml)
file as described on [How to deploy page ->]({{< relref "/docs/how-to/deploy" >}}).
## Environment variables
- `DEBUG=1` - show debug and verbose logs, set in any value
- `WHATSAPP_API_PORT=3000` - listen port for HTTP server (default: `3000`)
- `WHATSAPP_API_HOSTNAME=localhost` - Hostname for HTTP server (default: `localhost`)
- `WHATSAPP_API_KEY=mysecret` - protect the api with a secret code. If you set it - add `X-Api-Key: mysecret` to all
requests.
- `WHATSAPP_SWAGGER_USERNAME=admin` + `WHATSAPP_SWAGGER_PASSWORD=admin` - protect the Swagger panel with `admin / admin`
credentials. It doesn't affect api access!
- `WHATSAPP_START_SESSION=default` - start session with the name right after launching the app
The following environment variables can be used to configure the WAHA:
## File storage ![](/images/versions/plus.png)
- `DEBUG=1`: Set this variable to any value to enable debug and verbose logs.
- `WHATSAPP_API_PORT=3000`: The port number that the HTTP server will listen on. The default value is `3000`.
- `WHATSAPP_API_HOSTNAME=localhost`: The hostname for the HTTP server. The default value is `localhost`.
- `WHATSAPP_API_KEY=mysecret`: If you set this variable, you must include the `X-Api-Key: mysecret` header in all
requests to the API. This will protect the API with a secret code.
- `WHATSAPP_SWAGGER_USERNAME=admin` and `WHATSAPP_SWAGGER_PASSWORD=admin`: These variables can be used to protect the
Swagger panel with `admin / admin` credentials. This does not affect API access.
- `WHATSAPP_SWAGGER_CONFIG_ADVANCED=true` - enables advanced configuration options for Swagger documentation - you can customize host, port and base URL for the requests.
Disabled by default.
- `WHATSAPP_RESTART_ALL_SESSIONS=True`: Set this variable to `True` to start all **STOPPED** sessions after container
restarts. By default, this variable is set to `False`.
- Please note that this will start all **STOPPED** sessions, not just the sessions that were working before the restart. You can maintain the session list by
using `POST /api/session/stop` with the `logout: True` parameter or by calling `POST /api/session/logout` to remove
**STOPPED** sessions. You can see all sessions, including **STOPPED** sessions, in the `GET /api/sessions/all=True`
response.
- `WHATSAPP_START_SESSION=session1,session2`: This variable can be used to start sessions with the specified names right
after launching the API. Separate session names with a comma.
- `WHATSAPP_FILES_MIMETYPES` - download only these mimetypes from messages (download all files be default). Mimetypes
must be separated by a comma, without spaces: `audio,image/png,image/gif`. In order to choose type use prefix (
like `audio,image`).
- `WHATSAPP_FILES_LIFETIME`- to keep free space files will be removed after this time (default: `180`, in seconds)
- `WHATSAPP_FILES_FOLDER` - folder where will be stored files from chats (images, voice messages) (
default: `/tmp/whatsapp-files`)
### Examples
## Engines
#### Debug Mode
Under the hood **WAHA** allows you to use two different engines. You can control what you want to run by settings
`WHATSAPP_DEFAULT_ENGINE` environment variables.
To enable debug mode, set the `DEBUG` environment variable to any value:
If you have any problems with one engine - try another!
```
DEBUG=1
```
{{< alert icon="👉" text="API responses and webhook payloads may differ significantly! We do our best to keep requests body stable between engines." />}}
#### Protecting the API with a Secret Code
### WhatsApp WebJS ![](/images/versions/core.png)
To protect the API with a secret code, set the `WHATSAPP_API_KEY` environment variable to your secret code:
**WAHA** uses **WhatsApp WebJS** engine by default
```
WHATSAPP_API_KEY=mysecret
```
- https://github.com/pedroslopez/whatsapp-web.js
- `WHATSAPP_DEFAULT_ENGINE=WEBJS`
You must include the `X-Api-Key: mysecret` header in all requests to the API.
### Venom ![](/images/versions/core.png)
#### Starting Sessions Automatically
- https://github.com/orkestral/venom
- `WHATSAPP_DEFAULT_ENGINE=VENOM`
To start sessions automatically when the API is launched, set the `WHATSAPP_START_SESSION` environment variable to a
comma-separated list of session names:
```
WHATSAPP_START_SESSION=session1,session2
```
#### Restarting All Sessions
To start all **STOPPED** sessions after container restarts, set the `WHATSAPP_RESTART_ALL_SESSIONS` environment variable
to `True`:
```
WHATSAPP_RESTART_ALL_SESSIONS=True
```
#### Protecting the Swagger Panel
To protect the Swagger panel with `admin / admin` credentials, set the `WHATSAPP_SWAGGER_USERNAME`
and `WHATSAPP_SWAGGER_PASSWORD` environment variables:
```
WHATSAPP_SWAGGER_USERNAME=admin
WHATSAPP_SWAGGER_PASSWORD=admin
```
## File storage variables ![](/images/versions/plus.png)
The following environment variables can be used to configure the file storage options for the WAHA:
- `WHATSAPP_FILES_MIMETYPES`: This variable can be used to download only specific mimetypes from messages. By default,
all files are downloaded. The mimetypes must be separated by a comma, without spaces. For
example: `audio,image/png,image/gif`. To choose a specific type, use a prefix (like `audio,image`).
- `WHATSAPP_FILES_LIFETIME`: This variable can be used to set the time (in seconds) after which files will be removed to
free up space. The default value is `180`.
- `WHATSAPP_FILES_FOLDER`: This variable can be used to set the folder where files from chats (images, voice messages)
will be stored. The default value is `/tmp/whatsapp-files`.
### Examples
#### Downloading Specific Mimetypes
To download only specific mimetypes from messages, set the `WHATSAPP_FILES_MIMETYPES` environment variable to a
comma-separated list of mimetypes:
```
WHATSAPP_FILES_MIMETYPES=audio,image/png,image/gif
```
#### Setting the File Lifetime
To set the time (in seconds) after which files will be removed to free up space, set the `WHATSAPP_FILES_LIFETIME`
environment variable:
```
WHATSAPP_FILES_LIFETIME=300
```
#### Setting the File Storage Folder
To set the folder where files from chats (images, voice messages) will be stored, set the `WHATSAPP_FILES_FOLDER`
environment variable:
```
WHATSAPP_FILES_FOLDER=/home/user/whatsapp-files
```
@@ -0,0 +1,90 @@
---
title: "Deploy"
description: "How to deploy WAHA"
lead: ""
date: 2020-10-06T08:48:45+00:00
lastmod: 2020-10-06T08:48:45+00:00
draft: false
images: [ ]
weight: 801
---
This page provides useful information to assist you in deploying the project.
It includes tips on how to avoid running long command lines, how to view logs, and other helpful advice.
## How to Use Docker Compose
Docker Compose is a tool that allows you to define and run multi-container Docker applications. It simplifies the
process of managing multiple containers and their dependencies. In this topic, we will walk you through how to use
Docker Compose with the WhatsApp HTTP API.
### Prerequisites
Before you start, you need to have Docker and Docker Compose installed on your machine. You can download Docker from the
official website [here](https://www.docker.com/products/docker-desktop). Docker Compose is included with Docker Desktop
for Windows and macOS, but for Linux, you need to install it separately. You can find the installation
instructions [here](https://docs.docker.com/compose/install/).
### Getting Started
To get started, you need to clone the docker-compose file to your local machine.
Open your terminal and run the following command:
```
wget https://raw.githubusercontent.com/devlikeapro/whatsapp-http-api/core/docker-compose.yaml
```
### Running the WhatsApp HTTP API Service
To start the `whatsapp-http-api` service, run the following command:
```
docker-compose up -d whatsapp-http-api
```
This will start the container in detached mode and map port 3000 to 3000 on the host machine.
To stop the service, run the command:
```
docker-compose down
```
This will stop and remove the container.
To restart the service, run the command:
```
docker-compose restart whtasapp-http-api
```
This will restart the container.
### Viewing the Logs
To view the logs for the `whatsapp-http-api` service, run the command:
```
docker-compose logs -f whatsapp-http-api
```
This will show the logs in real-time. To view the logs for the `whatsapp-http-api-plus` service,
replace `whatsapp-http-api` with `whatsapp-http-api-plus`.
To view the logs for a specific period, run the command:
```
docker-compose logs --since <time>
```
Replace `<time>` with the time period you want to view the logs for. For example, `docker-compose logs --since 1h` will
show the logs for the past hour.
### Using the WhatsApp HTTP API Services
There are two services provided in the `docker-compose.yml` file - `whatsapp-http-api` and `whatsapp-http-api-plus`.
It is important to note that these services must not be run at the same time.
By default, the `whatsapp-http-api` service will be used.
To use the `whatsapp-http-api-plus` service, you need to specify it in the command.
@@ -0,0 +1,52 @@
---
title: "Engines"
description: "Engines"
lead: ""
date: 2020-10-06T08:48:45+00:00
lastmod: 2020-10-06T08:48:45+00:00
draft: false
images: [ ]
weight: 800
---
Under the hood **WAHA** allows you to use two different engines. You can control what you want to run by settings
`WHATSAPP_DEFAULT_ENGINE` environment variables.
```bash
docker run -it -e "WHATSAPP_DEFAULT_ENGINE=WEBJS" devlikeapro/whatsapp-http-api
```
If you have any problems with one engine - try another!
{{< alert icon="👉" text="API responses and webhook payloads may differ significantly! We do our best to keep requests body stable between engines." />}}
All engines are available in Core version ![](/images/versions/core.png)
## WEBJS
A WhatsApp API client that connects through the WhatsApp Web browser app.
It uses Puppeteer to run a real instance of Whatsapp Web to avoid getting blocked.
**WAHA** uses **WhatsApp WebJS** engine by default.
- `WHATSAPP_DEFAULT_ENGINE=WEBJS`
- https://github.com/pedroslopez/whatsapp-web.js
## NOWEB
**NOWEB** engine does not require a browser to work with WhatsApp Web, it does so directly using a WebSocket.
Not running Chromium saves you CPU and Memory, so you can run more instances on a single server!
️Working with the engine requires more attention, because it's easy to be blocked with it!
⚠ Read the article before using **NOWEB** engine [How to avoid blocking ->]({{< relref "/docs/help/how-to-avoid-blocking" >}}).
- `WHATSAPP_DEFAULT_ENGINE=NOWEB`
- https://github.com/WhiskeySockets/Baileys
## VENOM
It's a high-performance system developed with JavaScript to create a bot for WhatsApp
- `WHATSAPP_DEFAULT_ENGINE=VENOM`
- https://github.com/orkestral/venom
@@ -32,8 +32,7 @@ The Plus additional features are:
4. [**Security** →]({{< relref "/docs/how-to/security" >}})
5. [**Saving sessions** →]({{< relref "/docs/how-to/sessions" >}})
6. [**Support multiple sessions** →]({{< relref "/docs/how-to/sessions" >}})
7. **Instant updates** - no one month delay in getting bugfixes or new features
8. **Priority in support** - on both bugs and features
7. **Priority in support** - on both bugs and features
## Differences
@@ -37,6 +37,7 @@ On the URL that you set via `WHATSAPP_HOOK_URL` you receive JSON-data with follo
{
"event": "message",
"session": "default",
"engine": "WEBJS",
"payload": {}
}
```
@@ -50,7 +51,7 @@ Below the list of all events that WhatsApp HTTP API sends to your.
Run the bellow command and see look at the logs - it prints body request for all events that happen in your WhatsApp!
```bash
docker run -it -e "WHATSAPP_HOOK_EVENTS=*" -e WHATSAPP_HOOK_URL=https://httpbin.org/post devlikeapro/whatsapp-http-api
docker run -it -e "WHATSAPP_HOOK_EVENTS=*" -e WHATSAPP_HOOK_URL=https://httpbin.org/post -p 3000:3000 devlikeapro/whatsapp-http-api
```
### Examples
@@ -26,7 +26,7 @@ to the container. WAHA stores authentication information in the directory and re
The full command would be:
```bash
docker run --rm -d -v `pwd`/.sessions:/app/.sessions -p 127.0.0.1:3000:3000/tcp --name whatsapp-http-api devlikeapro/whatsapp-http-api
docker run --rm -d -v `pwd`/.sessions:/app/.sessions -p 3000:3000/tcp --name whatsapp-http-api devlikeapro/whatsapp-http-api
```
### Remote storage ![](/images/versions/soon.png)
@@ -69,11 +69,26 @@ The response:
In order to stop a new session - call `POST /api/sessions/stop`
{{< alert icon="👉" text="The stop request does not log out the account. Manually remove the session storage." />}}
{{< alert icon="👉" text="The stop request does not log out the account by default. Set 'logout' field to 'true'." />}}
```json
{
"name": "default"
"name": "default",
"logout": true
}
```
### Logout
In order to log out the session - call `POST /api/sessions/logout`
{{< alert icon="👉" text="You must stop session first." />}}
```json
{
"name": "default",
"logout": true
}
```
@@ -72,9 +72,7 @@ Here's the available features:
- Swagger panel authentication ![](/images/versions/plus.png)
### Updates
- Bug fixes and updates come as soon as they are implemented. ![](/images/versions/plus.png)
- One month delay in bug fixes and updates. ![](/images/versions/core.png)
- Bug fixes and updates come as soon as they are implemented. ![](/images/versions/core.png)![](/images/versions/plus.png)
### Groups ![](/images/versions/core.png)
- Create a group
@@ -95,7 +95,7 @@ docker tag devlikeapro/whatsapp-http-api-plus:arm devlikeapro/whatsapp-http-api
Run WhatsApp HTTP API:
```bash
docker run -it --rm -p 127.0.0.1:3000:3000/tcp --name whatsapp-http-api devlikeapro/whatsapp-http-api
docker run -it --rm -p 3000:3000/tcp --name whatsapp-http-api devlikeapro/whatsapp-http-api
# It prints logs and the last line must be
# WhatsApp HTTP API is running on: http://[::1]:3000
+1 -1
View File
@@ -1 +1 @@
{"openapi":"3.0.0","paths":{"/api/sessions/start":{"post":{"operationId":"SessionsController_start","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SessionStartRequest"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SessionDTO"}}}}},"tags":["sessions"],"security":[{"api_key":[]}]}},"/api/sessions/stop":{"post":{"operationId":"SessionsController_stop","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SessionStopRequest"}}}},"responses":{"201":{"description":""}},"tags":["sessions"],"security":[{"api_key":[]}]}},"/api/sessions":{"get":{"operationId":"SessionsController_list","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/SessionDTO"}}}}}},"tags":["sessions"],"security":[{"api_key":[]}]}},"/api/checkNumberStatus":{"get":{"operationId":"ChattingController_checkNumberStatus","summary":"Check number status","parameters":[{"name":"phone","required":true,"in":"query","schema":{"type":"string"}},{"name":"session","required":true,"in":"query","schema":{"default":"default","type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WANumberExistResult"}}}}},"tags":["chatting"],"security":[{"api_key":[]}]}},"/api/sendContactVcard":{"post":{"operationId":"ChattingController_sendContactVcard","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageContactVcardRequest"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["chatting"],"security":[{"api_key":[]}]}},"/api/sendText":{"get":{"operationId":"ChattingController_sendTextGet","summary":"Send a text message","parameters":[{"name":"phone","required":true,"in":"query","schema":{"type":"string"}},{"name":"text","required":true,"in":"query","schema":{"type":"string"}},{"name":"session","required":true,"in":"query","schema":{"default":"default","type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["chatting"],"security":[{"api_key":[]}]},"post":{"operationId":"ChattingController_sendText","summary":"Send a text message","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageTextRequest"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WAMessage"}}}}},"tags":["chatting"],"security":[{"api_key":[]}]}},"/api/sendTextButtons":{"post":{"operationId":"ChattingController_sendTextButtons","summary":"Send a text message with buttons","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageTextButtonsRequest"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["chatting"],"security":[{"api_key":[]}]}},"/api/sendLocation":{"post":{"operationId":"ChattingController_sendLocation","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageLocationRequest"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["chatting"],"security":[{"api_key":[]}]}},"/api/sendLinkPreview":{"post":{"operationId":"ChattingController_sendLinkPreview","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageLinkPreviewRequest"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["chatting"],"security":[{"api_key":[]}]}},"/api/sendImage":{"post":{"operationId":"ChattingController_sendImage","summary":"Send an image. Either from an URL or base64 data - look at the request schemas for details.","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageImageRequest"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["chatting"],"security":[{"api_key":[]}]}},"/api/sendFile":{"post":{"operationId":"ChattingController_sendFile","summary":"Send a file. Either from an URL or base64 data - look at the request schemas for details.","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageFileRequest"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["chatting"],"security":[{"api_key":[]}]}},"/api/sendVoice":{"post":{"operationId":"ChattingController_sendVoice","summary":"Send an voice message. Either from an URL or basLine truncated
{"openapi":"3.0.0","paths":{"/api/sessions/start":{"post":{"operationId":"SessionsController_start","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SessionStartRequest"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SessionDTO"}}}}},"tags":["sessions"],"security":[{"api_key":[]}]}},"/api/sessions/stop":{"post":{"operationId":"SessionsController_stop","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SessionStopRequest"}}}},"responses":{"201":{"description":""}},"tags":["sessions"],"security":[{"api_key":[]}]}},"/api/sessions":{"get":{"operationId":"SessionsController_list","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/SessionDTO"}}}}}},"tags":["sessions"],"security":[{"api_key":[]}]}},"/api/checkNumberStatus":{"get":{"operationId":"ChattingController_checkNumberStatus","summary":"Check number status","parameters":[{"name":"phone","required":true,"in":"query","schema":{"type":"string"}},{"name":"session","required":true,"in":"query","schema":{"default":"default","type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WANumberExistResult"}}}}},"tags":["chatting"],"security":[{"api_key":[]}]}},"/api/sendContactVcard":{"post":{"operationId":"ChattingController_sendContactVcard","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageContactVcardRequest"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["chatting"],"security":[{"api_key":[]}]}},"/api/sendText":{"get":{"operationId":"ChattingController_sendTextGet","summary":"Send a text message","parameters":[{"name":"phone","required":true,"in":"query","schema":{"type":"string"}},{"name":"text","required":true,"in":"query","schema":{"type":"string"}},{"name":"session","required":true,"in":"query","schema":{"default":"default","type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["chatting"],"security":[{"api_key":[]}]},"post":{"operationId":"ChattingController_sendText","summary":"Send a text message","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageTextRequest"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WAMessage"}}}}},"tags":["chatting"],"security":[{"api_key":[]}]}},"/api/sendTextButtons":{"post":{"operationId":"ChattingController_sendTextButtons","summary":"Send a text message with buttons","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageTextButtonsRequest"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["chatting"],"security":[{"api_key":[]}]}},"/api/sendLocation":{"post":{"operationId":"ChattingController_sendLocation","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageLocationRequest"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["chatting"],"security":[{"api_key":[]}]}},"/api/sendLinkPreview":{"post":{"operationId":"ChattingController_sendLinkPreview","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageLinkPreviewRequest"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["chatting"],"security":[{"api_key":[]}]}},"/api/sendImage":{"post":{"operationId":"ChattingController_sendImage","summary":"Send an image. Either from an URL or base64 data - look at the request schemas for details.","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageImageRequest"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["chatting"],"security":[{"api_key":[]}]}},"/api/sendFile":{"post":{"operationId":"ChattingController_sendFile","summary":"Send a file. Either from an URL or base64 data - look at the request schemas for details.","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageFileRequest"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["chatting"],"security":[{"api_key":[]}]}},"/api/sendVoice":{"post":{"operationId":"ChattingController_sendVoice","summary":"Send an voice message. Either from an URL or basLine truncated
+1
View File
@@ -1,2 +1,3 @@
flask~=1.0
requests~=2.0
markupsafe==2.0.1
+19 -2
View File
@@ -25,6 +25,17 @@ def send_message(chat_id, text):
)
response.raise_for_status()
def send_seen(chat_id, message_id, participant):
response = requests.post(
"http://localhost:3000/api/sendSeen",
json={
"session": "default",
"chatId": chat_id,
"messageId": message_id,
"participant": participant,
},
)
response.raise_for_status()
@app.route("/")
def whatsapp_echo():
@@ -48,7 +59,13 @@ def whatsapp_webhook():
return "No files in the message"
# Number in format 791111111@c.us
from_ = payload["from"]
chat_id = payload["from"]
# Message ID - false_11111111111@c.us_AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
message_id = payload['id']
# For groups - who sent the message
participant = payload.get('participant')
# IMPORTANT - Always send seen before sending new message
send_seen(chat_id=chat_id, message_id=message_id, participant=participant)
# Download the file and download it to the current folder
client_url = payload["mediaUrl"]
@@ -61,7 +78,7 @@ def whatsapp_webhook():
# Send a text back via WhatsApp HTTP API
text = f"We have downloaded file here: {path}"
print(text)
send_message(chat_id=from_, text=text)
send_message(chat_id=chat_id, text=text)
# Send OK back
return "OK"
+38 -3
View File
@@ -24,6 +24,31 @@ def send_message(chat_id, text):
)
response.raise_for_status()
def reply(chat_id, message_id, text):
response = requests.post(
"http://localhost:3000/api/reply",
json={
"chatId": chat_id,
"text": text,
"reply_to": message_id,
"session": "default",
},
)
response.raise_for_status()
def send_seen(chat_id, message_id, participant):
response = requests.post(
"http://localhost:3000/api/sendSeen",
json={
"session": "default",
"chatId": chat_id,
"messageId": message_id,
"participant": participant,
},
)
response.raise_for_status()
@app.route("/")
def whatsapp_echo():
@@ -45,9 +70,19 @@ def whatsapp_webhook():
payload = data["payload"]
# The text
text = payload["body"]
# Number in format 1231231231@c.us
from_ = payload["from"]
# Number in format 1231231231@c.us or @g.us for group
chat_id = payload["from"]
# Message ID - false_11111111111@c.us_AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
message_id = payload['id']
# For groups - who sent the message
participant = payload.get('participant')
# IMPORTANT - Always send seen before sending new message
send_seen(chat_id=chat_id, message_id=message_id, participant=participant)
# Send a text back via WhatsApp HTTP API
send_message(chat_id=from_, text=text)
send_message(chat_id=chat_id, text=text)
# OR reply on the message
# reply(chat_id=chat_id, message_id=message_id, text=text)
# Send OK back
return "OK"
+10 -2
View File
@@ -22,6 +22,8 @@
"test:e2e": "jest --config ./test/jest-e2e.json"
},
"dependencies": {
"@adiwajshing/baileys": "file:../WhiskeySockets-Baileys",
"@adiwajshing/keyed-db": "^0.2.4",
"@nestjs/common": "^9.0.9",
"@nestjs/config": "^0.5.0",
"@nestjs/core": "^9.0.9",
@@ -29,12 +31,18 @@
"@nestjs/platform-express": "^9.0.9",
"@nestjs/serve-static": "^2.1.3",
"@nestjs/swagger": "^6.0.5",
"@types/lodash": "^4.14.194",
"@types/ws": "^8.5.4",
"class-validator": "^0.12.2",
"del": "^6.0.0",
"express-basic-auth": "^1.2.1",
"file-type": "16.5.4",
"link-preview-js": "^3.0.4",
"lodash": "^4.17.21",
"mime-types": "^2.1.27",
"passport": "^0.6.0",
"passport-headerapikey": "^1.2.2",
"qrcode": "^1.5.1",
"qrcode-terminal": "^0.12.0",
"reflect-metadata": "^0.1.13",
"request": "^2.88.2",
@@ -42,8 +50,8 @@
"rimraf": "^3.0.2",
"rxjs": "^7.1.0",
"swagger-ui-express": "^4.1.4",
"venom-bot": "^4.3.7",
"whatsapp-web.js": "https://github.com/saifkhan7865/whatsapp-web.js"
"venom-bot": "^5.0.1",
"whatsapp-web.js": "https://github.com/pedroslopez/whatsapp-web.js"
},
"devDependencies": {
"@nestjs/cli": "^9.0.0",
+129 -112
View File
@@ -1,134 +1,151 @@
import {Body, Controller, Get, Post, Put, Query} from '@nestjs/common';
import {ApiOperation, ApiSecurity, ApiTags} from "@nestjs/swagger";
import { Body, Controller, Get, Post, Put, Query } from '@nestjs/common';
import { ApiOperation, ApiSecurity, ApiTags } from '@nestjs/swagger';
import {
ChatRequest,
CheckNumberStatusQuery,
MessageContactVcardRequest,
MessageFileRequest,
MessageImageRequest,
MessageLinkPreviewRequest,
MessageLocationRequest,
MessageReactionRequest,
MessageReplyRequest,
MessageTextButtonsRequest,
MessageTextQuery,
MessageTextRequest,
MessageVoiceRequest
} from "../structures/chatting.dto";
import {WAMessage, WANumberExistResult} from "../structures/responses.dto";
import {SessionManager} from "../core/abc/manager.abc";
ChatRequest,
CheckNumberStatusQuery,
GetMessageQuery,
MessageContactVcardRequest,
MessageFileRequest,
MessageImageRequest,
MessageLinkPreviewRequest,
MessageLocationRequest,
MessageReactionRequest,
MessageReplyRequest,
MessageTextButtonsRequest,
MessageTextQuery,
MessageTextRequest,
MessageVoiceRequest,
SendSeenRequest,
} from '../structures/chatting.dto';
import { WAMessage, WANumberExistResult } from '../structures/responses.dto';
import { SessionManager } from '../core/abc/manager.abc';
@ApiSecurity('api_key')
@Controller('api')
@ApiTags('chatting')
export class ChattingController {
constructor(private manager: SessionManager) {
}
constructor(private manager: SessionManager) {}
@Get('/checkNumberStatus')
@ApiOperation({summary: 'Check number status'})
async checkNumberStatus(@Query() request: CheckNumberStatusQuery): Promise<WANumberExistResult> {
const whatsapp = this.manager.getSession(request.session)
return whatsapp.checkNumberStatus(request)
}
@Get('/checkNumberStatus')
@ApiOperation({ summary: 'Check number status' })
async checkNumberStatus(
@Query() request: CheckNumberStatusQuery,
): Promise<WANumberExistResult> {
const whatsapp = this.manager.getSession(request.session);
return whatsapp.checkNumberStatus(request);
}
@Post('/sendContactVcard')
sendContactVcard(@Body() request: MessageContactVcardRequest) {
const whatsapp = this.manager.getSession(request.session)
return whatsapp.sendContactVCard(request)
}
@Post('/sendContactVcard')
sendContactVcard(@Body() request: MessageContactVcardRequest) {
const whatsapp = this.manager.getSession(request.session);
return whatsapp.sendContactVCard(request);
}
@Get('/sendText')
@ApiOperation({summary: 'Send a text message'})
sendTextGet(
@Query() query: MessageTextQuery,
) {
const whatsapp = this.manager.getSession(query.session)
const msg = new MessageTextRequest()
msg.chatId = query.phone
msg.text = query.text
return whatsapp.sendText(msg)
}
@Get('/sendText')
@ApiOperation({ summary: 'Send a text message' })
sendTextGet(@Query() query: MessageTextQuery) {
const whatsapp = this.manager.getSession(query.session);
const msg = new MessageTextRequest();
msg.chatId = query.phone;
msg.text = query.text;
return whatsapp.sendText(msg);
}
@Post('/sendText')
@ApiOperation({summary: 'Send a text message'})
sendText(@Body() request: MessageTextRequest): Promise<WAMessage> {
const whatsapp = this.manager.getSession(request.session)
return whatsapp.sendText(request)
}
@Post('/sendText')
@ApiOperation({ summary: 'Send a text message' })
sendText(@Body() request: MessageTextRequest): Promise<WAMessage> {
const whatsapp = this.manager.getSession(request.session);
return whatsapp.sendText(request);
}
@Post('/sendTextButtons')
@ApiOperation({summary: 'Send a text message with buttons'})
sendTextButtons(@Body() request: MessageTextButtonsRequest) {
const whatsapp = this.manager.getSession(request.session)
return whatsapp.sendTextButtons(request)
}
@Post('/sendTextButtons')
@ApiOperation({ summary: 'Send a text message with buttons' })
sendTextButtons(@Body() request: MessageTextButtonsRequest) {
const whatsapp = this.manager.getSession(request.session);
return whatsapp.sendTextButtons(request);
}
@Post('/sendLocation')
sendLocation(@Body() request: MessageLocationRequest) {
const whatsapp = this.manager.getSession(request.session)
return whatsapp.sendLocation(request)
}
@Post('/sendLocation')
sendLocation(@Body() request: MessageLocationRequest) {
const whatsapp = this.manager.getSession(request.session);
return whatsapp.sendLocation(request);
}
@Post('/sendLinkPreview')
sendLinkPreview(@Body() request: MessageLinkPreviewRequest) {
const whatsapp = this.manager.getSession(request.session)
return whatsapp.sendLinkPreview(request)
}
@Post('/sendLinkPreview')
sendLinkPreview(@Body() request: MessageLinkPreviewRequest) {
const whatsapp = this.manager.getSession(request.session);
return whatsapp.sendLinkPreview(request);
}
@Post('/sendImage')
@ApiOperation({summary: "Send an image. Either from an URL or base64 data - look at the request schemas for details."})
sendImage(@Body() request: MessageImageRequest) {
const whatsapp = this.manager.getSession(request.session)
return whatsapp.sendImage(request)
}
@Post('/sendImage')
@ApiOperation({
summary:
'Send an image. Either from an URL or base64 data - look at the request schemas for details.',
})
sendImage(@Body() request: MessageImageRequest) {
const whatsapp = this.manager.getSession(request.session);
return whatsapp.sendImage(request);
}
@Post('/sendFile')
@ApiOperation({summary: "Send a file. Either from an URL or base64 data - look at the request schemas for details."})
sendFile(@Body() request: MessageFileRequest) {
const whatsapp = this.manager.getSession(request.session)
return whatsapp.sendFile(request)
}
@Post('/sendFile')
@ApiOperation({
summary:
'Send a file. Either from an URL or base64 data - look at the request schemas for details.',
})
sendFile(@Body() request: MessageFileRequest) {
const whatsapp = this.manager.getSession(request.session);
return whatsapp.sendFile(request);
}
@Post('/sendVoice')
@ApiOperation({summary: "Send an voice message. Either from an URL or base64 data - look at the request schemas for details."})
sendVoice(@Body() request: MessageVoiceRequest) {
const whatsapp = this.manager.getSession(request.session)
return whatsapp.sendVoice(request)
}
@Post('/sendVoice')
@ApiOperation({
summary:
'Send an voice message. Either from an URL or base64 data - look at the request schemas for details.',
})
sendVoice(@Body() request: MessageVoiceRequest) {
const whatsapp = this.manager.getSession(request.session);
return whatsapp.sendVoice(request);
}
@Post('/reply')
@ApiOperation({summary: 'Reply to a text message'})
reply(@Body() request: MessageReplyRequest) {
const whatsapp = this.manager.getSession(request.session)
return whatsapp.reply(request)
}
@Post('/reply')
@ApiOperation({ summary: 'Reply to a text message' })
reply(@Body() request: MessageReplyRequest) {
const whatsapp = this.manager.getSession(request.session);
return whatsapp.reply(request);
}
@Post('/sendSeen')
sendSeen(@Body() chat: ChatRequest) {
const whatsapp = this.manager.getSession(chat.session)
return whatsapp.sendSeen(chat)
}
@Post('/sendSeen')
sendSeen(@Body() chat: SendSeenRequest) {
const whatsapp = this.manager.getSession(chat.session);
return whatsapp.sendSeen(chat);
}
@Post('/startTyping')
async startTyping(@Body() chat: ChatRequest) {
// It's infinitive action
const whatsapp = this.manager.getSession(chat.session)
await whatsapp.startTyping(chat)
return {result: true}
}
@Post('/startTyping')
async startTyping(@Body() chat: ChatRequest) {
// It's infinitive action
const whatsapp = this.manager.getSession(chat.session);
await whatsapp.startTyping(chat);
return { result: true };
}
@Post('/stopTyping')
async stopTyping(@Body() chat: ChatRequest) {
const whatsapp = this.manager.getSession(chat.session)
await whatsapp.stopTyping(chat)
return {result: true}
}
@Post('/stopTyping')
async stopTyping(@Body() chat: ChatRequest) {
const whatsapp = this.manager.getSession(chat.session);
await whatsapp.stopTyping(chat);
return { result: true };
}
@Put('/reaction')
@ApiOperation({summary: 'React to a message with an emoji'})
setReaction(@Body() request: MessageReactionRequest) {
const whatsapp = this.manager.getSession(request.session)
return whatsapp.setReaction(request)
}
@Put('/reaction')
@ApiOperation({ summary: 'React to a message with an emoji' })
setReaction(@Body() request: MessageReactionRequest) {
const whatsapp = this.manager.getSession(request.session);
return whatsapp.setReaction(request);
}
@Get('/messages')
@ApiOperation({ summary: 'Get messages in a chat' })
getMessages(@Query() query: GetMessageQuery) {
const whatsapp = this.manager.getSession(query.session);
return whatsapp.getMessages(query);
}
}
+61 -51
View File
@@ -1,63 +1,73 @@
import {Body, Controller, Get, Post, Query} from '@nestjs/common';
import {ApiOperation, ApiSecurity, ApiTags} from "@nestjs/swagger";
import {SessionManager} from "../core/abc/manager.abc";
import {ContactQuery, ContactRequest} from "../structures/contacts.dto";
import {SessionQuery} from "../structures/base.dto";
import {CheckNumberStatusQuery} from "../structures/chatting.dto";
import {WANumberExistResult} from "../structures/responses.dto";
import { Body, Controller, Get, Post, Query } from '@nestjs/common';
import { ApiOperation, ApiSecurity, ApiTags } from '@nestjs/swagger';
import { SessionManager } from '../core/abc/manager.abc';
import { ContactQuery, ContactRequest } from '../structures/contacts.dto';
import { SessionQuery } from '../structures/base.dto';
import { CheckNumberStatusQuery } from '../structures/chatting.dto';
import { WANumberExistResult } from '../structures/responses.dto';
@ApiSecurity('api_key')
@Controller('api/contacts')
@ApiTags('contacts')
export class ContactsController {
constructor(private manager: SessionManager) {
}
constructor(private manager: SessionManager) {}
@Get('/')
@ApiOperation({summary: 'Get contact basic info. The method always return result, even if the phone number is not registered in WhatsApp. For that - use /check-exists endpoint below.'})
get(@Query() query: ContactQuery) {
const whatsapp = this.manager.getSession(query.session)
return whatsapp.getContact(query)
}
@Get('/')
@ApiOperation({
summary:
'Get contact basic info. The method always return result, even if the phone number is not registered in WhatsApp. For that - use /check-exists endpoint below.',
})
get(@Query() query: ContactQuery) {
const whatsapp = this.manager.getSession(query.session);
return whatsapp.getContact(query);
}
@Get('/all')
@ApiOperation({summary: 'Get all contacts'})
getAll(@Query() query: SessionQuery) {
const whatsapp = this.manager.getSession(query.session)
return whatsapp.getContacts()
}
@Get('/all')
@ApiOperation({ summary: 'Get all contacts' })
getAll(@Query() query: SessionQuery) {
const whatsapp = this.manager.getSession(query.session);
return whatsapp.getContacts();
}
@Get('/check-exists')
@ApiOperation({summary: 'Check phone number is registered in WhatsApp.'})
async checkExists(@Query() request: CheckNumberStatusQuery): Promise<WANumberExistResult> {
const whatsapp = this.manager.getSession(request.session)
return whatsapp.checkNumberStatus(request)
}
@Get('/check-exists')
@ApiOperation({ summary: 'Check phone number is registered in WhatsApp.' })
async checkExists(
@Query() request: CheckNumberStatusQuery,
): Promise<WANumberExistResult> {
const whatsapp = this.manager.getSession(request.session);
return whatsapp.checkNumberStatus(request);
}
@Get('/about')
@ApiOperation({summary: 'Gets the Contact\'s current "about" info. Returns null if you don\'t have permission to read their status.'})
getAbout(@Query() query: ContactQuery) {
const whatsapp = this.manager.getSession(query.session)
return whatsapp.getContactAbout(query)
}
@Get('/about')
@ApiOperation({
summary:
'Gets the Contact\'s current "about" info. Returns null if you don\'t have permission to read their status.',
})
getAbout(@Query() query: ContactQuery) {
const whatsapp = this.manager.getSession(query.session);
return whatsapp.getContactAbout(query);
}
@Get('/profile-picture')
@ApiOperation({summary: 'Returns the contact\'s profile picture URL, if privacy settings allow it.'})
getProfilePicture(@Query() query: ContactQuery) {
const whatsapp = this.manager.getSession(query.session)
return whatsapp.getContactProfilePicture(query)
}
@Get('/profile-picture')
@ApiOperation({
summary:
"Returns the contact's profile picture URL, if privacy settings allow it.",
})
getProfilePicture(@Query() query: ContactQuery) {
const whatsapp = this.manager.getSession(query.session);
return whatsapp.getContactProfilePicture(query);
}
@Post('/block')
@ApiOperation({summary:"Block contact"})
block(@Body() request: ContactRequest) {
const whatsapp = this.manager.getSession(request.session)
return whatsapp.blockContact(request)
}
@Post('/unblock')
@ApiOperation({summary:"Unblock contact"})
unblock(@Body() request: ContactRequest) {
const whatsapp = this.manager.getSession(request.session)
return whatsapp.unblockContact(request)
}
@Post('/block')
@ApiOperation({ summary: 'Block contact' })
block(@Body() request: ContactRequest) {
const whatsapp = this.manager.getSession(request.session);
return whatsapp.blockContact(request);
}
@Post('/unblock')
@ApiOperation({ summary: 'Unblock contact' })
unblock(@Body() request: ContactRequest) {
const whatsapp = this.manager.getSession(request.session);
return whatsapp.unblockContact(request);
}
}
+35 -35
View File
@@ -1,6 +1,12 @@
import {ArgumentsHost, Catch, ExceptionFilter, HttpException, HttpStatus,} from '@nestjs/common';
import {Request, Response} from 'express';
import {VERSION} from "../version";
import {
ArgumentsHost,
Catch,
ExceptionFilter,
HttpException,
HttpStatus,
} from '@nestjs/common';
import { Request, Response } from 'express';
import { VERSION } from '../version';
/**
* Serializer error to JSON.
@@ -8,42 +14,36 @@ import {VERSION} from "../version";
* Credits: https://stackoverflow.com/a/72707578/6753144
*/
export function serializeError(err: unknown) {
const properties = Object.getOwnPropertyNames(err)
// getOwnPropertyNames does not get 'name', like "ReferenceError"
properties.push('name')
return JSON.parse(JSON.stringify(err, properties))
const properties = Object.getOwnPropertyNames(err);
// getOwnPropertyNames does not get 'name', like "ReferenceError"
properties.push('name');
return JSON.parse(JSON.stringify(err, properties));
}
@Catch()
export class AllExceptionsFilter implements ExceptionFilter {
catch(exception: HttpException | Error, host: ArgumentsHost): void {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
const request = ctx.getRequest<Request>();
if (exception instanceof HttpException) {
response
.status(exception.getStatus())
.json(exception.getResponse());
return
}
const httpStatus = HttpStatus.INTERNAL_SERVER_ERROR
response
.status(httpStatus)
.json({
statusCode: httpStatus,
timestamp: new Date().toISOString(),
exception: serializeError(exception),
request: {
path: request.url,
method: request.method,
body: request.body,
query: request.query,
},
version: VERSION,
});
catch(exception: HttpException | Error, host: ArgumentsHost): void {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
const request = ctx.getRequest<Request>();
if (exception instanceof HttpException) {
response.status(exception.getStatus()).json(exception.getResponse());
return;
}
const httpStatus = HttpStatus.INTERNAL_SERVER_ERROR;
response.status(httpStatus).json({
statusCode: httpStatus,
timestamp: new Date().toISOString(),
exception: serializeError(exception),
request: {
path: request.url,
method: request.method,
body: request.body,
query: request.query,
},
version: VERSION,
});
}
}
+153 -145
View File
@@ -1,165 +1,173 @@
import {Body, Controller, Delete, Get, Param, Post, Put} from '@nestjs/common';
import {ApiOperation, ApiSecurity, ApiTags} from "@nestjs/swagger";
import {SessionManager} from "../core/abc/manager.abc";
import {CreateGroupRequest, DescriptionRequest, ParticipantsRequest, SubjectRequest} from "../structures/groups.dto";
import {WhatsappSession} from "../core/abc/session.abc";
import {SessionApiParam, SessionParam} from "./helpers";
import {
Body,
Controller,
Delete,
Get,
Param,
Post,
Put,
} from '@nestjs/common';
import { ApiOperation, ApiSecurity, ApiTags } from '@nestjs/swagger';
import { SessionManager } from '../core/abc/manager.abc';
import {
CreateGroupRequest,
DescriptionRequest,
ParticipantsRequest,
SubjectRequest,
} from '../structures/groups.dto';
import { WhatsappSession } from '../core/abc/session.abc';
import { SessionApiParam, SessionParam } from './helpers';
@ApiSecurity('api_key')
@Controller('api/:session/groups')
@ApiTags('groups')
export class GroupsController {
constructor(private manager: SessionManager) {
}
constructor(private manager: SessionManager) {}
@Post('')
@SessionApiParam
@ApiOperation({summary: "Create a new group."})
createGroup(
@SessionParam session: WhatsappSession,
@Body() request: CreateGroupRequest,
) {
return session.createGroup(request)
}
@Post('')
@SessionApiParam
@ApiOperation({ summary: 'Create a new group.' })
createGroup(
@SessionParam session: WhatsappSession,
@Body() request: CreateGroupRequest,
) {
return session.createGroup(request);
}
@Get('')
@SessionApiParam
@ApiOperation({summary: "Get all groups."})
getGroups(
@SessionParam session: WhatsappSession,
) {
return session.getGroups()
}
@Get('')
@SessionApiParam
@ApiOperation({ summary: 'Get all groups.' })
getGroups(@SessionParam session: WhatsappSession) {
return session.getGroups();
}
@Get(':id')
@SessionApiParam
@ApiOperation({summary: "Get the group."})
getGroup(
@SessionParam session: WhatsappSession,
@Param('id') id: string,
) {
return session.getGroup(id)
}
@Get(':id')
@SessionApiParam
@ApiOperation({ summary: 'Get the group.' })
getGroup(@SessionParam session: WhatsappSession, @Param('id') id: string) {
return session.getGroup(id);
}
@Delete(':id')
@SessionApiParam
@ApiOperation({summary: "Delete the group."})
deleteGroup(
@SessionParam session: WhatsappSession,
@Param('id') id: string,
) {
return session.deleteGroup(id)
}
@Delete(':id')
@SessionApiParam
@ApiOperation({ summary: 'Delete the group.' })
deleteGroup(@SessionParam session: WhatsappSession, @Param('id') id: string) {
return session.deleteGroup(id);
}
@Post(':id/leave')
@SessionApiParam
@ApiOperation({summary: "Leave the group."})
leaveGroup(
@SessionParam session: WhatsappSession,
@Param('id') id: string,
) {
return session.leaveGroup(id)
}
@Post(':id/leave')
@SessionApiParam
@ApiOperation({ summary: 'Leave the group.' })
leaveGroup(@SessionParam session: WhatsappSession, @Param('id') id: string) {
return session.leaveGroup(id);
}
@Put(':id/description')
@ApiOperation({
summary: "Updates the group description.\n" +
"Returns true if the subject was properly updated. This can return false if the user does not have the necessary permissions.\n"
})
@SessionApiParam
setDescription(
@SessionParam session: WhatsappSession,
@Param('id') id: string,
@Body() request: DescriptionRequest
) {
return session.setDescription(id, request.description)
}
@Put(':id/description')
@ApiOperation({
summary:
'Updates the group description.\n' +
'Returns true if the subject was properly updated. This can return false if the user does not have the necessary permissions.\n',
})
@SessionApiParam
setDescription(
@SessionParam session: WhatsappSession,
@Param('id') id: string,
@Body() request: DescriptionRequest,
) {
return session.setDescription(id, request.description);
}
@Put(':id/subject')
@SessionApiParam
@ApiOperation({
summary: "Updates the group subject.\n" +
"Returns true if the subject was properly updated. This can return false if the user does not have the necessary permissions.\n"
})
setSubject(
@SessionParam session: WhatsappSession,
@Param('id') id: string,
@Body() request: SubjectRequest
) {
return session.setSubject(id, request.subject)
}
@Put(':id/subject')
@SessionApiParam
@ApiOperation({
summary:
'Updates the group subject.\n' +
'Returns true if the subject was properly updated. This can return false if the user does not have the necessary permissions.\n',
})
setSubject(
@SessionParam session: WhatsappSession,
@Param('id') id: string,
@Body() request: SubjectRequest,
) {
return session.setSubject(id, request.subject);
}
@Get(':id/invite-code')
@SessionApiParam
@ApiOperation({summary: "Gets the invite code for a specific group."})
getInviteCode(
@SessionParam session: WhatsappSession,
@Param('id') id: string,
): Promise<string> {
return session.getInviteCode(id)
}
@Get(':id/invite-code')
@SessionApiParam
@ApiOperation({ summary: 'Gets the invite code for a specific group.' })
getInviteCode(
@SessionParam session: WhatsappSession,
@Param('id') id: string,
): Promise<string> {
return session.getInviteCode(id);
}
@Post(':id/invite-code/revoke')
@SessionApiParam
@ApiOperation({summary: "Invalidates the current group invite code and generates a new one."})
revokeInviteCode(
@SessionParam session: WhatsappSession,
@Param('id') id: string,
): Promise<string> {
return session.revokeInviteCode(id)
}
@Post(':id/invite-code/revoke')
@SessionApiParam
@ApiOperation({
summary:
'Invalidates the current group invite code and generates a new one.',
})
revokeInviteCode(
@SessionParam session: WhatsappSession,
@Param('id') id: string,
): Promise<string> {
return session.revokeInviteCode(id);
}
@Get(':id/participants/')
@SessionApiParam
@ApiOperation({summary: 'Get a list of participants by in the group.'})
getParticipants(
@SessionParam session: WhatsappSession,
@Param('id') id: string,
) {
return session.getParticipants(id)
}
@Get(':id/participants/')
@SessionApiParam
@ApiOperation({ summary: 'Get a list of participants by in the group.' })
getParticipants(
@SessionParam session: WhatsappSession,
@Param('id') id: string,
) {
return session.getParticipants(id);
}
@Post(':id/participants/add')
@SessionApiParam
@ApiOperation({summary: 'Adds a list of participants by ID to the group.'})
addParticipants(
@SessionParam session: WhatsappSession,
@Param('id') id: string,
@Body() request: ParticipantsRequest
) {
return session.addParticipants(id, request)
}
@Post(':id/participants/add')
@SessionApiParam
@ApiOperation({ summary: 'Adds a list of participants by ID to the group.' })
addParticipants(
@SessionParam session: WhatsappSession,
@Param('id') id: string,
@Body() request: ParticipantsRequest,
) {
return session.addParticipants(id, request);
}
@Post(':id/participants/remove')
@SessionApiParam
@ApiOperation({summary: 'Removes a list of participants by ID to the group.'})
removeParticipants(
@SessionParam session: WhatsappSession,
@Param('id') id: string,
@Body() request: ParticipantsRequest
) {
return session.removeParticipants(id, request)
}
@Post(':id/participants/remove')
@SessionApiParam
@ApiOperation({
summary: 'Removes a list of participants by ID to the group.',
})
removeParticipants(
@SessionParam session: WhatsappSession,
@Param('id') id: string,
@Body() request: ParticipantsRequest,
) {
return session.removeParticipants(id, request);
}
@Post(':id/admin/promote')
@SessionApiParam
@ApiOperation({summary: 'Promote participants to admin users.'})
promoteToAdmin(
@SessionParam session: WhatsappSession,
@Param('id') id: string,
@Body() request: ParticipantsRequest
) {
return session.promoteParticipantsToAdmin(id, request)
}
@Post(':id/admin/promote')
@SessionApiParam
@ApiOperation({ summary: 'Promote participants to admin users.' })
promoteToAdmin(
@SessionParam session: WhatsappSession,
@Param('id') id: string,
@Body() request: ParticipantsRequest,
) {
return session.promoteParticipantsToAdmin(id, request);
}
@Post(':id/admin/demote')
@SessionApiParam
@ApiOperation({summary: 'Demotes participants by to regular users.'})
demoteToAdmin(
@SessionParam session: WhatsappSession,
@Param('id') id: string,
@Body() request: ParticipantsRequest
) {
return session.demoteParticipantsToUser(id, request)
}
@Post(':id/admin/demote')
@SessionApiParam
@ApiOperation({ summary: 'Demotes participants by to regular users.' })
demoteToAdmin(
@SessionParam session: WhatsappSession,
@Param('id') id: string,
@Body() request: ParticipantsRequest,
) {
return session.demoteParticipantsToUser(id, request);
}
}
+14 -15
View File
@@ -1,7 +1,7 @@
import {ApiParam} from "@nestjs/swagger";
import {Injectable, Param, PipeTransform} from "@nestjs/common";
import {SessionManager} from "../core/abc/manager.abc";
import {WhatsappSession} from "../core/abc/session.abc";
import { ApiParam } from '@nestjs/swagger';
import { Injectable, Param, PipeTransform } from '@nestjs/common';
import { SessionManager } from '../core/abc/manager.abc';
import { WhatsappSession } from '../core/abc/session.abc';
/**
* Get session name and return Whatsapp session back (if exists)
@@ -10,26 +10,25 @@ import {WhatsappSession} from "../core/abc/session.abc";
*/
@Injectable()
export class SessionPipe implements PipeTransform<WhatsappSession> {
constructor(private manager: SessionManager) {
}
constructor(private manager: SessionManager) {}
async transform(value: any) {
return this.manager.getSession(value)
}
async transform(value: any) {
return this.manager.getSession(value);
}
}
/**
* Decorator for a method that uses SessionPipe above
*/
export const SessionApiParam = ApiParam({
name: 'session',
required: true,
type: "string",
description: "WhatsApp session name",
})
name: 'session',
required: true,
type: 'string',
description: 'WhatsApp session name',
});
/**
* Session param
@SessionParam session: WhatsappSession,
*/
export const SessionParam = Param('session', SessionPipe)
export const SessionParam = Param('session', SessionPipe);
+20 -26
View File
@@ -1,34 +1,28 @@
import {Controller, Get, Query, Res} from '@nestjs/common';
import {ApiSecurity, ApiTags} from "@nestjs/swagger";
import {Readable} from "stream";
import {Response} from 'express';
import {SessionManager} from "../core/abc/manager.abc";
import {SessionQuery} from "../structures/base.dto";
import { Controller, Get, Query, Res } from '@nestjs/common';
import { ApiSecurity, ApiTags } from '@nestjs/swagger';
import { Readable } from 'stream';
import { Response } from 'express';
import { SessionManager } from '../core/abc/manager.abc';
import { SessionQuery } from '../structures/base.dto';
@ApiSecurity('api_key')
@Controller('api')
@ApiTags('screenshot')
export class ScreenshotController {
constructor(private manager: SessionManager) {
}
constructor(private manager: SessionManager) {}
@Get('/screenshot')
async screenshot(
@Res() res: Response,
@Query() sessionQuery: SessionQuery,
) {
const whatsappService = this.manager.getSession(sessionQuery.session)
const buffer = await whatsappService.getScreenshot();
const stream = new Readable();
stream.push(buffer);
stream.push(null);
@Get('/screenshot')
async screenshot(@Res() res: Response, @Query() sessionQuery: SessionQuery) {
const whatsappService = this.manager.getSession(sessionQuery.session);
const buffer = await whatsappService.getScreenshot();
const stream = new Readable();
stream.push(buffer);
stream.push(null);
res.set({
'Content-Type': 'image/png',
'Content-Length': buffer.length,
});
stream.pipe(res)
}
res.set({
'Content-Type': 'image/png',
'Content-Length': buffer.length,
});
stream.pipe(res);
}
}
+40 -18
View File
@@ -1,30 +1,52 @@
import {Body, Controller, Get, Post} from '@nestjs/common';
import {ApiSecurity, ApiTags} from "@nestjs/swagger";
import {SessionDTO, SessionStartRequest, SessionStopRequest} from "../structures/sessions.dto";
import {SessionManager} from "../core/abc/manager.abc";
import { Body, Controller, Get, Post, Query } from '@nestjs/common';
import { ApiOperation, ApiSecurity, ApiTags } from '@nestjs/swagger';
import {
SessionLogoutRequest,
SessionDTO,
SessionStartRequest,
SessionStopRequest,
ListSessionsQuery,
} from '../structures/sessions.dto';
import { SessionManager } from '../core/abc/manager.abc';
import { UnprocessableEntityException } from '@nestjs/common/exceptions/unprocessable-entity.exception';
import { parseBool } from '../helpers';
@ApiSecurity('api_key')
@Controller('api/sessions')
@ApiTags('sessions')
export class SessionsController {
constructor(private manager: SessionManager) {
}
constructor(private manager: SessionManager) {}
@Post('/start/')
start(@Body() request: SessionStartRequest): SessionDTO {
return this.manager.start(request);
}
@Post('/start/')
start(@Body() request: SessionStartRequest): SessionDTO {
return this.manager.start(request)
@Post('/stop/')
@ApiOperation({ summary: 'Stop session' })
async stop(@Body() request: SessionStopRequest): Promise<void> {
await this.manager.stop(request);
if (request.logout) {
await this.manager.logout(request);
}
return;
}
@Post('/stop/')
stop(@Body() request: SessionStopRequest): Promise<void> {
return this.manager.stop(request)
@Post('/logout/')
@ApiOperation({ summary: 'Logout from session.' })
clean(@Body() request: SessionLogoutRequest): Promise<void> {
const session = this.manager.getSession(request.name, false);
if (session) {
throw new UnprocessableEntityException(
`Can not clean running session, please stop it first. You can set 'clean' field to 'True' in stop request so it stops and cleans at the same time.`,
);
}
return this.manager.logout(request);
}
@Get('/')
list(): SessionDTO[] {
return this.manager.getSessions()
}
@Get('/')
list(@Query() query: ListSessionsQuery): SessionDTO[] {
const all = parseBool(query.all);
return this.manager.getSessions(all);
}
}
+7 -9
View File
@@ -1,15 +1,13 @@
import {Controller, Get} from '@nestjs/common';
import {ApiSecurity, ApiTags} from "@nestjs/swagger";
import {VERSION} from "../version";
import { Controller, Get } from '@nestjs/common';
import { ApiSecurity, ApiTags } from '@nestjs/swagger';
import { VERSION } from '../version';
@ApiSecurity('api_key')
@Controller('api/version')
@ApiTags('other')
export class VersionController {
@Get('')
async get() {
return VERSION
}
@Get('')
async get() {
return VERSION;
}
}
+92 -79
View File
@@ -1,90 +1,103 @@
import {Injectable} from "@nestjs/common";
import {ConfigService} from "@nestjs/config";
import {WhatsappEngine} from "./structures/enums.dto";
import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { WhatsappEngine } from './structures/enums.dto';
import { parseBool } from './helpers';
@Injectable()
export class WhatsappConfigService {
public files_uri = '/api/files'
public schema = "http"
public files_uri = '/api/files';
public schema = 'http';
constructor(private configService: ConfigService) {
constructor(private configService: ConfigService) {}
get filesURL(): string {
return `${this.schema}://${this.hostname}:${this.port}${this.files_uri}/`;
}
get hostname(): string {
return this.configService.get('WHATSAPP_API_HOSTNAME', 'localhost');
}
get port(): string {
return this.configService.get('WHATSAPP_API_PORT', '3000');
}
get filesFolder(): string {
return this.configService.get(
'WHATSAPP_FILES_FOLDER',
'/tmp/whatsapp-files',
);
}
get filesLifetime(): number {
return this.configService.get<number>('WHATSAPP_FILES_LIFETIME', 180);
}
get mimetypes(): string[] | null {
const types = this.configService.get('WHATSAPP_FILES_MIMETYPES', '');
return types ? types.split(',') : null;
}
get startSessions(): string[] {
const value: string = this.configService.get('WHATSAPP_START_SESSION', '');
if (!value) {
return [];
}
return value.split(',');
}
get filesURL(): string {
return `${this.schema}://${this.hostname}:${this.port}${this.files_uri}/`
get shouldRestartAllSessions(): boolean {
const value: string = this.configService.get(
'WHATSAPP_RESTART_ALL_SESSIONS',
'false',
);
return parseBool(value);
}
getWebhookUrl(): string | undefined {
return this.get('WHATSAPP_HOOK_URL');
}
getWebhookEvents(): string[] {
const value = this.get('WHATSAPP_HOOK_EVENTS', '');
return value ? value.split(',') : [];
}
getDefaultEngineName(): WhatsappEngine {
const value = this.get('WHATSAPP_DEFAULT_ENGINE', WhatsappEngine.WEBJS);
if (value in WhatsappEngine) {
return WhatsappEngine[value];
}
console.log(
`Unknown WhatsApp default engine, using WEBJS. WHATSAPP_DEFAULT_ENGINE=${value}`,
);
}
get hostname(): string {
return this.configService.get(
'WHATSAPP_API_HOSTNAME',
'localhost',
)
get(name: string, defaultValue = undefined): any {
return this.configService.get(name, defaultValue);
}
getApiKey(): string | undefined {
return this.configService.get('WHATSAPP_API_KEY', '');
}
getSwaggerUsernamePassword(): [string, string] | undefined {
const user = this.configService.get('WHATSAPP_SWAGGER_USERNAME', undefined);
const password = this.configService.get(
'WHATSAPP_SWAGGER_PASSWORD',
undefined,
);
if (!user && !password) {
console.log(
'Please set up both WHATSAPP_SWAGGER_USERNAME and WHATSAPP_SWAGGER_PASSWORD ' +
'to enable swagger authentication.',
);
return undefined;
}
return [user, password];
}
get port(): string {
return this.configService.get(
'WHATSAPP_API_PORT',
'3000',
)
}
get filesFolder(): string {
return this.configService.get(
'WHATSAPP_FILES_FOLDER',
'/tmp/whatsapp-files',
)
}
get filesLifetime(): number {
return this.configService.get<number>(
'WHATSAPP_FILES_LIFETIME',
180,
)
}
get mimetypes(): string[] | null {
const types = this.configService.get('WHATSAPP_FILES_MIMETYPES', "")
return types ? types.split(',') : null
}
get startSession(): string | undefined {
return this.configService.get('WHATSAPP_START_SESSION', undefined)
}
getWebhookUrl(): string | undefined {
return this.get('WHATSAPP_HOOK_URL')
}
getWebhookEvents(): string[] {
const value = this.get('WHATSAPP_HOOK_EVENTS', "")
return value ? value.split(',') : []
}
getDefaultEngineName(): WhatsappEngine {
const value = this.get("WHATSAPP_DEFAULT_ENGINE", WhatsappEngine.WEBJS)
if (value in WhatsappEngine) {
return WhatsappEngine[value]
}
console.log(`Unknown WhatsApp default engine, using WEBJS. WHATSAPP_DEFAULT_ENGINE=${value}`)
}
get(name: string, defaultValue = undefined): any {
return this.configService.get(name, defaultValue)
}
getApiKey(): string | undefined {
return this.configService.get("WHATSAPP_API_KEY", "")
}
getSwaggerUsernamePassword(): [string, string] | undefined {
const user = this.configService.get("WHATSAPP_SWAGGER_USERNAME", undefined)
const password = this.configService.get("WHATSAPP_SWAGGER_PASSWORD", undefined)
if (!user && !password) {
console.log("Please set up both WHATSAPP_SWAGGER_USERNAME and WHATSAPP_SWAGGER_PASSWORD " +
"to enable swagger authentication.")
return undefined
}
return [user, password]
}
getSwaggerAdvancedConfigEnabled(): boolean {
return this.configService.get('WHATSAPP_SWAGGER_CONFIG_ADVANCED', false);
}
}
+11
View File
@@ -0,0 +1,11 @@
export class QR {
private base64: string;
save(base64) {
this.base64 = base64.replace(/^data:image\/png;base64,/, '');
}
get(): Buffer {
return Buffer.from(this.base64, 'base64');
}
}
+24 -16
View File
@@ -1,28 +1,36 @@
import {OnApplicationShutdown} from "@nestjs/common";
import {SessionDTO, SessionStartRequest, SessionStopRequest} from "../../structures/sessions.dto";
import {WhatsappSession} from "./session.abc";
import {WhatsappEngine} from "../../structures/enums.dto";
import {MediaStorage} from "./storage.abc";
import {WebhookConductor} from "./webhooks.abc";
import { OnApplicationShutdown } from '@nestjs/common';
import {
SessionLogoutRequest,
SessionDTO,
SessionStartRequest,
SessionStopRequest,
} from '../../structures/sessions.dto';
import { WhatsappSession } from './session.abc';
import { WhatsappEngine } from '../../structures/enums.dto';
import { MediaStorage } from './storage.abc';
import { WebhookConductor } from './webhooks.abc';
export abstract class SessionManager implements OnApplicationShutdown {
protected abstract getEngine(engine: WhatsappEngine): typeof WhatsappSession;
abstract start(request: SessionStartRequest): SessionDTO
protected abstract get EngineClass(): typeof WhatsappSession;
abstract stop(request: SessionStopRequest): Promise<void>
protected abstract get WebhookConductorClass(): typeof WebhookConductor;
abstract getSession(name: string): WhatsappSession
protected abstract get MediaStorageClass(): typeof MediaStorage;
abstract getSessions(): SessionDTO[]
abstract onApplicationShutdown(signal?: string);
abstract onApplicationShutdown(signal ?: string)
//
// API Methods
//
abstract start(request: SessionStartRequest): SessionDTO;
protected abstract getEngine(engine: WhatsappEngine): typeof WhatsappSession
abstract stop(request: SessionStopRequest): Promise<void>;
protected abstract get EngineClass(): typeof WhatsappSession
abstract logout(request: SessionLogoutRequest): Promise<void>;
protected abstract get WebhookConductorClass(): typeof WebhookConductor
abstract getSession(name: string, error?: boolean): WhatsappSession;
protected abstract get MediaStorageClass(): typeof MediaStorage
abstract getSessions(all: boolean): SessionDTO[];
}
+210 -181
View File
@@ -1,235 +1,264 @@
import {WAEvents, WhatsappStatus} from "../../structures/enums.dto";
import {ConsoleLogger} from "@nestjs/common";
import {
ChatRequest,
CheckNumberStatusQuery,
MessageContactVcardRequest,
MessageFileRequest,
MessageImageRequest,
MessageLinkPreviewRequest,
MessageLocationRequest,
MessageReactionRequest,
MessageReplyRequest,
MessageTextButtonsRequest,
MessageTextRequest,
MessageVoiceRequest
} from "../../structures/chatting.dto";
import {MediaStorage} from "./storage.abc";
import {MessageId} from "whatsapp-web.js";
import {ContactQuery, ContactRequest} from "../../structures/contacts.dto";
import {NotImplementedByEngineError} from "../exceptions";
import {CreateGroupRequest, ParticipantsRequest} from "../../structures/groups.dto";
import * as fs from "fs";
WAEvents,
WhatsappEngine,
WhatsappStatus,
} from '../../structures/enums.dto';
import { ConsoleLogger } from '@nestjs/common';
import {
ChatRequest,
CheckNumberStatusQuery,
GetMessageQuery,
MessageContactVcardRequest,
MessageFileRequest,
MessageImageRequest,
MessageLinkPreviewRequest,
MessageLocationRequest,
MessageReactionRequest,
MessageReplyRequest,
MessageTextButtonsRequest,
MessageTextRequest,
MessageVoiceRequest,
} from '../../structures/chatting.dto';
import { LocalSessionStorage, MediaStorage } from './storage.abc';
import { MessageId } from 'whatsapp-web.js';
import { ContactQuery, ContactRequest } from '../../structures/contacts.dto';
import { NotImplementedByEngineError } from '../exceptions';
import {
CreateGroupRequest,
ParticipantsRequest,
} from '../../structures/groups.dto';
import * as fs from 'fs';
const CHROME_PATH = "/usr/bin/google-chrome-stable"
const CHROMIUM_PATH = "/usr/bin/chromium"
const CHROME_PATH = '/usr/bin/google-chrome-stable';
const CHROMIUM_PATH = '/usr/bin/chromium';
import { EventEmitter } from 'events';
export function getBrowserExecutablePath() {
if (fs.existsSync(CHROME_PATH)) {
return CHROME_PATH
}
return CHROMIUM_PATH
if (fs.existsSync(CHROME_PATH)) {
return CHROME_PATH;
}
return CHROMIUM_PATH;
}
export function ensureSuffix(phone) {
const suffix = '@c.us';
if (phone.includes('@')) {
return phone;
}
return phone + suffix;
}
export enum WAHAInternalEvent {
engine_start = 'engine.start',
}
export interface WhatsAppSessionConfig {
name: string;
storage: MediaStorage;
log: ConsoleLogger;
sessionStorage: LocalSessionStorage;
}
export abstract class WhatsappSession {
public status: WhatsappStatus;
public status: WhatsappStatus;
public events: EventEmitter;
public engine: WhatsappEngine;
public constructor(public name: string, protected storage: MediaStorage, protected log: ConsoleLogger) {
this.name = name
this.status = WhatsappStatus.STARTING
this.log = log
}
public name: string;
protected storage: MediaStorage;
protected log: ConsoleLogger;
protected sessionStorage: LocalSessionStorage;
getBrowserExecutablePath() {
return getBrowserExecutablePath()
}
public constructor({ name, log, sessionStorage }: WhatsAppSessionConfig) {
this.name = name;
this.status = WhatsappStatus.STARTING;
this.log = log;
this.sessionStorage = sessionStorage;
this.events = new EventEmitter();
}
getBrowserArgsForPuppeteer() {
// Run optimized version of Chrome
// References:
// https://github.com/pedroslopez/whatsapp-web.js/issues/1420
// https://www.bannerbear.com/blog/ways-to-speed-up-puppeteer-screenshots/
return [
'--no-sandbox',
'--disable-client-side-phishing-detection',
'--disable-setuid-sandbox',
'--disable-component-update',
'--disable-default-apps',
'--disable-popup-blocking',
'--disable-offer-store-unmasked-wallet-cards',
'--disable-speech-api',
'--hide-scrollbars',
'--mute-audio',
'--disable-extensions',
'--disable-dev-shm-usage',
'--disable-accelerated-2d-canvas',
'--no-first-run',
'--no-default-browser-check',
'--no-pings',
'--password-store=basic',
'--use-mock-keychain',
'--no-zygote',
'--single-process',
'--disable-gpu'
]
}
getBrowserExecutablePath() {
return getBrowserExecutablePath();
}
getBrowserArgsForPuppeteer() {
// Run optimized version of Chrome
// References:
// https://github.com/pedroslopez/whatsapp-web.js/issues/1420
// https://www.bannerbear.com/blog/ways-to-speed-up-puppeteer-screenshots/
return [
'--no-sandbox',
'--disable-client-side-phishing-detection',
'--disable-setuid-sandbox',
'--disable-component-update',
'--disable-default-apps',
'--disable-popup-blocking',
'--disable-offer-store-unmasked-wallet-cards',
'--disable-speech-api',
'--hide-scrollbars',
'--mute-audio',
'--disable-extensions',
'--disable-dev-shm-usage',
'--disable-accelerated-2d-canvas',
'--no-first-run',
'--no-default-browser-check',
'--no-pings',
'--password-store=basic',
'--use-mock-keychain',
'--no-zygote',
'--single-process',
'--disable-gpu',
];
}
/** Start the session */
abstract start()
/** Start the session */
abstract start();
/** Stop the session */
abstract stop(): void
/** Stop the session */
abstract stop(): void;
/** Subscribe the handler to specific hook */
abstract subscribe(hook: WAEvents | string, handler: (message) => void)
/** Subscribe the handler to specific hook */
abstract subscribe(hook: WAEvents | string, handler: (message) => void);
/**
* START - Methods for API
*/
abstract getScreenshot(): Promise<Buffer | string>
/**
* START - Methods for API
*/
abstract getScreenshot(): Promise<Buffer | string>;
abstract checkNumberStatus(request: CheckNumberStatusQuery)
abstract checkNumberStatus(request: CheckNumberStatusQuery);
abstract sendText(request: MessageTextRequest)
abstract sendText(request: MessageTextRequest);
abstract sendContactVCard(request: MessageContactVcardRequest)
abstract sendContactVCard(request: MessageContactVcardRequest);
abstract sendTextButtons(request: MessageTextButtonsRequest)
abstract sendTextButtons(request: MessageTextButtonsRequest);
abstract sendLocation(request: MessageLocationRequest)
abstract sendLocation(request: MessageLocationRequest);
abstract sendLinkPreview(request: MessageLinkPreviewRequest)
abstract sendLinkPreview(request: MessageLinkPreviewRequest);
abstract sendImage(request: MessageImageRequest)
abstract sendImage(request: MessageImageRequest);
abstract sendFile(request: MessageFileRequest)
abstract sendFile(request: MessageFileRequest);
abstract sendVoice(request: MessageVoiceRequest)
abstract sendVoice(request: MessageVoiceRequest);
abstract reply(request: MessageReplyRequest)
abstract reply(request: MessageReplyRequest);
abstract sendSeen(chat: ChatRequest)
abstract sendSeen(chat: ChatRequest);
abstract startTyping(chat: ChatRequest)
abstract startTyping(chat: ChatRequest);
abstract stopTyping(chat: ChatRequest)
abstract stopTyping(chat: ChatRequest);
abstract setReaction(request: MessageReactionRequest)
abstract getMessages(query: GetMessageQuery);
/**
* Contacts methods
*/
public getContact(query: ContactQuery) {
throw new NotImplementedByEngineError()
}
abstract setReaction(request: MessageReactionRequest);
public getContacts() {
throw new NotImplementedByEngineError()
}
/**
* Contacts methods
*/
public getContact(query: ContactQuery) {
throw new NotImplementedByEngineError();
}
public getContactAbout(query: ContactQuery) {
throw new NotImplementedByEngineError()
}
public getContacts() {
throw new NotImplementedByEngineError();
}
public getContactProfilePicture(query: ContactQuery) {
throw new NotImplementedByEngineError()
}
public getContactAbout(query: ContactQuery) {
throw new NotImplementedByEngineError();
}
public blockContact(request: ContactRequest) {
throw new NotImplementedByEngineError()
}
public getContactProfilePicture(query: ContactQuery) {
throw new NotImplementedByEngineError();
}
public unblockContact(request: ContactRequest) {
throw new NotImplementedByEngineError()
}
public blockContact(request: ContactRequest) {
throw new NotImplementedByEngineError();
}
/**
* Group methods
*/
public createGroup(request: CreateGroupRequest) {
throw new NotImplementedByEngineError()
}
public unblockContact(request: ContactRequest) {
throw new NotImplementedByEngineError();
}
public getGroups() {
throw new NotImplementedByEngineError()
}
/**
* Group methods
*/
public createGroup(request: CreateGroupRequest) {
throw new NotImplementedByEngineError();
}
public getGroup(id) {
throw new NotImplementedByEngineError()
}
public getGroups() {
throw new NotImplementedByEngineError();
}
public deleteGroup(id) {
throw new NotImplementedByEngineError()
}
public getGroup(id) {
throw new NotImplementedByEngineError();
}
public leaveGroup(id) {
throw new NotImplementedByEngineError()
}
public deleteGroup(id) {
throw new NotImplementedByEngineError();
}
public setDescription(id, description) {
throw new NotImplementedByEngineError()
}
public leaveGroup(id) {
throw new NotImplementedByEngineError();
}
public setSubject(id, description) {
throw new NotImplementedByEngineError()
}
public setDescription(id, description) {
throw new NotImplementedByEngineError();
}
public getInviteCode(id): Promise<string> {
throw new NotImplementedByEngineError()
}
public setSubject(id, description) {
throw new NotImplementedByEngineError();
}
public revokeInviteCode(id): Promise<string> {
throw new NotImplementedByEngineError()
}
public getInviteCode(id): Promise<string> {
throw new NotImplementedByEngineError();
}
public getParticipants(id) {
throw new NotImplementedByEngineError()
}
public revokeInviteCode(id): Promise<string> {
throw new NotImplementedByEngineError();
}
public addParticipants(id, request: ParticipantsRequest) {
throw new NotImplementedByEngineError()
}
public getParticipants(id) {
throw new NotImplementedByEngineError();
}
public removeParticipants(id, request: ParticipantsRequest) {
throw new NotImplementedByEngineError()
}
public addParticipants(id, request: ParticipantsRequest) {
throw new NotImplementedByEngineError();
}
public promoteParticipantsToAdmin(id, request: ParticipantsRequest) {
throw new NotImplementedByEngineError()
}
public removeParticipants(id, request: ParticipantsRequest) {
throw new NotImplementedByEngineError();
}
public demoteParticipantsToUser(id, request: ParticipantsRequest) {
throw new NotImplementedByEngineError()
}
public promoteParticipantsToAdmin(id, request: ParticipantsRequest) {
throw new NotImplementedByEngineError();
}
public demoteParticipantsToUser(id, request: ParticipantsRequest) {
throw new NotImplementedByEngineError();
}
/**
* END - Methods for API
*/
/**
* END - Methods for API
*/
/**
* Add WhatsApp suffix (@c.us) to the phone number if it doesn't have it yet
* @param phone
*/
protected ensureSuffix(phone) {
const suffix = "@c.us"
if (phone.includes("@")) {
return phone
}
return phone + suffix
}
protected deserializeId(messageId: string): MessageId {
const parts = messageId.split("_")
return {
"fromMe": parts[0] === "true",
"remote": parts[1],
"id": parts[2],
"_serialized": messageId
}
}
/**
* Add WhatsApp suffix (@c.us) to the phone number if it doesn't have it yet
* @param phone
*/
protected ensureSuffix(phone) {
return ensureSuffix(phone);
}
protected deserializeId(messageId: string): MessageId {
const parts = messageId.split('_');
return {
fromMe: parts[0] === 'true',
remote: parts[1],
id: parts[2],
_serialized: messageId,
};
}
}
+21 -1
View File
@@ -1,3 +1,23 @@
export abstract class MediaStorage {
abstract save(messageId: string, mimetype: string, buffer: Buffer): Promise<string>
abstract save(
messageId: string,
mimetype: string,
buffer: Buffer,
): Promise<string>;
}
export abstract class LocalSessionStorage {
public sessionsFolder: string;
protected constructor(public engine: string) {
this.engine = engine;
}
abstract init();
abstract getFolderPath(sessionName: string): string;
abstract clean(sessionName: string);
abstract getAll(): string[];
}
+2 -2
View File
@@ -1,5 +1,5 @@
import {WhatsappSession} from "./session.abc";
import { WhatsappSession } from './session.abc';
export abstract class WebhookConductor {
abstract configure(session: WhatsappSession)
abstract configure(session: WhatsappSession);
}
+49 -50
View File
@@ -1,57 +1,56 @@
import {ConsoleLogger, Module} from '@nestjs/common';
import {ScreenshotController} from "../api/screenshot.controller";
import {ConfigModule} from "@nestjs/config";
import {WhatsappConfigService} from "../config.service";
import {ServeStaticModule} from "@nestjs/serve-static";
import {SessionsController} from "../api/sessions.controller";
import {ChattingController} from "../api/chatting.controller";
import {ContactsController} from "../api/contacts.controller";
import {VersionController} from "../api/version.controller";
import {PassportModule} from "@nestjs/passport";
import {SessionManager} from "./abc/manager.abc";
import {SessionManagerCore} from "./manager.core";
import {GroupsController} from "../api/groups.controller";
import { ConsoleLogger, Module } from '@nestjs/common';
import { ScreenshotController } from '../api/screenshot.controller';
import { ConfigModule } from '@nestjs/config';
import { WhatsappConfigService } from '../config.service';
import { ServeStaticModule } from '@nestjs/serve-static';
import { SessionsController } from '../api/sessions.controller';
import { ChattingController } from '../api/chatting.controller';
import { ContactsController } from '../api/contacts.controller';
import { VersionController } from '../api/version.controller';
import { PassportModule } from '@nestjs/passport';
import { SessionManager } from './abc/manager.abc';
import { SessionManagerCore } from './manager.core';
import { GroupsController } from '../api/groups.controller';
export const IMPORTS = [
ConfigModule.forRoot({
isGlobal: true,
}),
ServeStaticModule.forRootAsync({
imports: [],
extraProviders: [WhatsappConfigService],
inject: [WhatsappConfigService],
useFactory: (config: WhatsappConfigService) => {
return [{
rootPath: config.filesFolder,
serveRoot: config.files_uri,
}]
ConfigModule.forRoot({
isGlobal: true,
}),
ServeStaticModule.forRootAsync({
imports: [],
extraProviders: [WhatsappConfigService],
inject: [WhatsappConfigService],
useFactory: (config: WhatsappConfigService) => {
return [
{
rootPath: config.filesFolder,
serveRoot: config.files_uri,
},
}),
PassportModule,
]
export const CONTROLLERS = [
SessionsController,
ChattingController,
ContactsController,
GroupsController,
ScreenshotController,
VersionController,
]
const PROVIDERS = [
{
provide: SessionManager,
useClass: SessionManagerCore,
];
},
WhatsappConfigService,
ConsoleLogger,
]
}),
PassportModule,
];
export const CONTROLLERS = [
SessionsController,
ChattingController,
ContactsController,
GroupsController,
ScreenshotController,
VersionController,
];
const PROVIDERS = [
{
provide: SessionManager,
useClass: SessionManagerCore,
},
WhatsappConfigService,
ConsoleLogger,
];
@Module({
imports: IMPORTS,
controllers: CONTROLLERS,
providers: PROVIDERS,
imports: IMPORTS,
controllers: CONTROLLERS,
providers: PROVIDERS,
})
export class AppModuleCore {
}
export class AppModuleCore {}
+13 -12
View File
@@ -1,20 +1,21 @@
import {UnprocessableEntityException} from "@nestjs/common";
import { UnprocessableEntityException } from '@nestjs/common';
export const DOCS_URL = "https://waha.devlike.pro/"
export const DOCS_URL = 'https://waha.devlike.pro/';
export class NotImplementedByEngineError extends UnprocessableEntityException {
constructor(msg = "") {
let error = "The method is not implemented by the engine."
if (msg) {
error += ` ${msg}`
}
super(error);
constructor(msg = '') {
let error = 'The method is not implemented by the engine.';
if (msg) {
error += ` ${msg}`;
}
super(error);
}
}
export class AvailableInPlusVersion extends UnprocessableEntityException {
constructor() {
super(`The feature is available only in Plus version. Check this out: ${DOCS_URL}`);
}
constructor() {
super(
`The feature is available only in Plus version. Check this out: ${DOCS_URL}`,
);
}
}
+152 -100
View File
@@ -1,116 +1,168 @@
import {ConsoleLogger, Injectable, NotFoundException, UnprocessableEntityException} from "@nestjs/common";
import {SessionManager} from "./abc/manager.abc";
import {WhatsappSession} from "./abc/session.abc";
import {WhatsappEngine} from "../structures/enums.dto";
import {SessionDTO, SessionStartRequest, SessionStopRequest} from "../structures/sessions.dto";
import {WhatsappConfigService} from "../config.service";
import {WhatsappSessionVenomCore} from "./session.venom.core";
import {WhatsappSessionWebJSCore} from "./session.webjs.core";
import {DOCS_URL} from "./exceptions";
import {WebhookConductorCore} from "./webhooks.core";
import {MediaStorageCore} from "./storage.core";
import {
ConsoleLogger,
Injectable,
NotFoundException,
UnprocessableEntityException,
} from '@nestjs/common';
import { SessionManager } from './abc/manager.abc';
import {
WAHAInternalEvent,
WhatsappSession,
WhatsAppSessionConfig,
} from './abc/session.abc';
import { WhatsappEngine, WhatsappStatus } from '../structures/enums.dto';
import {
SessionLogoutRequest,
SessionDTO,
SessionStartRequest,
SessionStopRequest,
} from '../structures/sessions.dto';
import { WhatsappConfigService } from '../config.service';
import { WhatsappSessionVenomCore } from './session.venom.core';
import { WhatsappSessionWebJSCore } from './session.webjs.core';
import { DOCS_URL } from './exceptions';
import { WebhookConductorCore } from './webhooks.core';
import { MediaStorageCore, SessionStorageCore } from './storage.core';
import { WhatsappSessionNoWebCore } from './session.noweb.core';
import { LocalSessionStorage } from './abc/storage.abc';
export class OnlyDefaultSessionIsAllowed extends UnprocessableEntityException {
constructor() {
super(`WAHA Core support only 'default' session. If you want to run more then one WhatsApp account - please get WAHA PLUS version. Check this out: ${DOCS_URL}`);
}
constructor() {
super(
`WAHA Core support only 'default' session. If you want to run more then one WhatsApp account - please get WAHA PLUS version. Check this out: ${DOCS_URL}`,
);
}
}
@Injectable()
export class SessionManagerCore extends SessionManager {
private session: WhatsappSession;
DEFAULT = "default"
private session: WhatsappSession;
DEFAULT = 'default';
// @ts-ignore
protected MediaStorageClass = MediaStorageCore;
// @ts-ignore
protected WebhookConductorClass = WebhookConductorCore;
protected readonly EngineClass: typeof WhatsappSession;
protected sessionStorage: LocalSessionStorage;
constructor(
private config: WhatsappConfigService,
private log: ConsoleLogger,
) {
super();
this.log.setContext('SessionManager');
this.session = undefined;
const engineName = this.config.getDefaultEngineName();
this.EngineClass = this.getEngine(engineName);
this.sessionStorage = new SessionStorageCore(engineName.toLowerCase());
this.sessionStorage.init();
this.startPredefinedSessions();
}
protected startPredefinedSessions() {
const startSessions = this.config.startSessions;
startSessions.forEach((sessionName) => {
this.start({ name: sessionName });
});
}
protected getEngine(engine: WhatsappEngine): typeof WhatsappSession {
if (engine === WhatsappEngine.WEBJS) {
return WhatsappSessionWebJSCore;
} else if (engine === WhatsappEngine.VENOM) {
return WhatsappSessionVenomCore;
} else if (engine === WhatsappEngine.NOWEB) {
return WhatsappSessionNoWebCore;
} else {
throw new NotFoundException(`Unknown whatsapp engine '${engine}'.`);
}
}
private onlyDefault(name: string) {
if (name !== this.DEFAULT) {
throw new OnlyDefaultSessionIsAllowed();
}
}
async onApplicationShutdown(signal?: string) {
if (!this.session) {
return;
}
await this.stop({ name: this.DEFAULT, logout: false });
}
//
// API Methods
//
start(request: SessionStartRequest): SessionDTO {
this.onlyDefault(request.name);
const name = request.name;
this.log.log(`'${name}' - starting session...`);
const log = new ConsoleLogger(`WhatsappSession - ${name}`);
const storage = new this.MediaStorageClass();
const webhookLog = new ConsoleLogger(`Webhook - ${name}`);
const webhook = new this.WebhookConductorClass(
webhookLog,
this.config.getWebhookUrl(),
this.config.getWebhookEvents(),
);
const sessionConfig: WhatsAppSessionConfig = {
name,
storage,
log,
sessionStorage: this.sessionStorage,
};
// @ts-ignore
protected MediaStorageClass = MediaStorageCore
// @ts-ignore
protected WebhookConductorClass = WebhookConductorCore
protected readonly EngineClass: typeof WhatsappSession;
const session = new this.EngineClass(sessionConfig);
this.session = session;
session.events.on(WAHAInternalEvent.engine_start, () =>
webhook.configure(session),
);
session.start();
return { name: session.name, status: session.status };
}
constructor(
private config: WhatsappConfigService,
private log: ConsoleLogger,
) {
super()
async stop(request: SessionStopRequest): Promise<void> {
this.onlyDefault(request.name);
this.log.setContext('SessionManager')
this.session = undefined
this.EngineClass = this.getEngine(this.config.getDefaultEngineName())
const name = request.name;
this.log.log(`Stopping ${name} session...`);
const session = this.getSession(name);
await session.stop();
this.log.log(`"${name}" has been stopped.`);
this.session = undefined;
}
// Start session from the start
if (config.startSession) {
this.start({name: config.startSession})
}
async logout(request: SessionLogoutRequest) {
this.sessionStorage.clean(request.name);
}
getSession(name: string, error = true): WhatsappSession {
this.onlyDefault(name);
const session = this.session;
if (!session) {
if (error) {
throw new NotFoundException(
`We didn't find a session with name '${name}'. Please start it first by using POST /sessions/start request`,
);
}
return;
}
return session;
}
protected getEngine(engine: WhatsappEngine): typeof WhatsappSession {
if (engine === WhatsappEngine.WEBJS) {
return WhatsappSessionWebJSCore
} else if (engine === WhatsappEngine.VENOM) {
return WhatsappSessionVenomCore
} else {
throw new NotFoundException(`Unknown whatsapp engine '${engine}'.`)
}
getSessions(all: boolean): SessionDTO[] {
if (!this.session) {
return all
? [{ name: this.DEFAULT, status: WhatsappStatus.STOPPED }]
: [];
}
private onlyDefault(name: string) {
if (name !== this.DEFAULT) {
throw new OnlyDefaultSessionIsAllowed()
}
}
getSession(name: string): WhatsappSession {
this.onlyDefault(name)
if (!this.session) {
throw new NotFoundException(
`We didn't find a session with name '${name}'. Please start it first by using POST /sessions/start request`,
);
}
return this.session
}
getSessions(): SessionDTO[] {
return [];
}
async onApplicationShutdown(signal?: string) {
if (!this.session){
return
}
await this.stop({name: this.DEFAULT})
}
start(request: SessionStartRequest): SessionDTO {
this.onlyDefault(request.name)
const name = request.name
this.log.log(`'${name}' - starting session...`)
const log = new ConsoleLogger(`WhatsappSession - ${name}`)
const storage = new this.MediaStorageClass()
const webhookLog = new ConsoleLogger(`Webhook - ${name}`)
const webhook = new this.WebhookConductorClass(
webhookLog,
this.config.getWebhookUrl(),
this.config.getWebhookEvents()
)
// @ts-ignore
const session = new this.EngineClass(name, storage, log)
this.session = session
session.start().then(() => webhook.configure(session))
return {name: session.name, status: session.status}
}
async stop(request: SessionStopRequest): Promise<void> {
const name = request.name
this.log.log(`Stopping ${name} session...`)
const session = this.getSession(name)
await session.stop()
this.log.log(`"${name}" has been stopped.`)
this.session = undefined
}
return [{ name: this.session.name, status: this.session.status }];
}
}
+518
View File
@@ -0,0 +1,518 @@
import {
SECOND,
WAEvents,
WhatsappEngine,
WhatsappStatus,
} from '../structures/enums.dto';
import makeWASocket, {
DisconnectReason,
isJidGroup,
makeInMemoryStore,
useMultiFileAuthState,
} from '@adiwajshing/baileys';
import {
ensureSuffix,
WAHAInternalEvent,
WhatsappSession,
} from './abc/session.abc';
import { WAMessage, WANumberExistResult } from '../structures/responses.dto';
import {
Button,
ChatRequest,
CheckNumberStatusQuery,
GetMessageQuery,
MessageContactVcardRequest,
MessageFileRequest,
MessageImageRequest,
MessageLinkPreviewRequest,
MessageLocationRequest,
MessageReactionRequest,
MessageReplyRequest,
MessageTextButtonsRequest,
MessageTextRequest,
MessageVoiceRequest,
SendSeenRequest,
} from '../structures/chatting.dto';
import {
AvailableInPlusVersion,
NotImplementedByEngineError,
} from './exceptions';
import { ContactQuery, ContactRequest } from '../structures/contacts.dto';
import {
CreateGroupRequest,
ParticipantsRequest,
} from '../structures/groups.dto';
import { QR } from './QR';
import { MediaStorage } from './abc/storage.abc';
import { ConsoleLogger, UnprocessableEntityException } from '@nestjs/common';
import { Message } from 'whatsapp-web.js';
import * as fs from 'fs';
import * as path from 'path';
import * as os from 'os';
// eslint-disable-next-line @typescript-eslint/no-var-requires
const QRCode = require('qrcode');
// eslint-disable-next-line @typescript-eslint/no-var-requires
const logger = require('pino')();
export const BaileysEvents = {
CONNECTION_UPDATE: 'connection.update',
CREDS_UPDATE: 'creds.update',
MESSAGES_UPSERT: 'messages.upsert',
GROUPS_UPSERT: 'groups.upsert',
};
export class WhatsappSessionNoWebCore extends WhatsappSession {
engine = WhatsappEngine.NOWEB;
sock: any;
store: any;
private qr: QR;
authFolder: string;
public constructor(config) {
super(config);
this.qr = new QR();
this.authFolder = this.getAuthFolder();
}
start() {
return this.buildClient();
}
protected getAuthFolder() {
const folder = this.sessionStorage.getFolderPath(this.name);
return fs.mkdtempSync(folder);
}
async makeSocket() {
const { state, saveCreds } = await useMultiFileAuthState(this.authFolder);
const sock: any = makeWASocket({
auth: state,
printQRInTerminal: true,
browser: ['Linux', 'Chrome', '111.0.5563.64'],
logger: logger,
});
sock.ev.on(BaileysEvents.CREDS_UPDATE, saveCreds);
return sock;
}
connectStore() {
if (!this.store) {
this.store = makeInMemoryStore({});
}
this.store.bind(this.sock.ev);
}
async buildClient() {
this.sock = await this.makeSocket();
this.connectStore();
this.sock.ev.on(BaileysEvents.CONNECTION_UPDATE, async (update) => {
const { connection, lastDisconnect, qr } = update;
if (connection === 'connecting') {
this.qr.save('');
this.status = WhatsappStatus.WORKING;
return;
} else if (connection === 'close') {
const shouldReconnect =
lastDisconnect.error?.output?.statusCode !==
DisconnectReason.loggedOut;
this.log.error(
'connection closed due to ',
lastDisconnect.error,
', reconnecting ',
shouldReconnect,
);
this.qr.save('');
this.status = WhatsappStatus.FAILED;
// reconnect if not logged out
if (shouldReconnect) {
setTimeout(() => this.buildClient(), 2 * SECOND);
}
}
// Save QR
if (qr) {
this.status = WhatsappStatus.SCAN_QR_CODE;
QRCode.toDataURL(qr).then((url) => {
this.qr.save(url);
});
}
});
this.events.emit(WAHAInternalEvent.engine_start);
}
stop() {
this.sock.ws.removeAllListeners();
this.log.log('socket connection terminated');
return;
}
/**
* START - Methods for API
*/
async getScreenshot(): Promise<Buffer | string> {
if (this.status === WhatsappStatus.STARTING) {
throw new UnprocessableEntityException(
`The session is starting, please try again after few seconds`,
);
} else if (this.status === WhatsappStatus.SCAN_QR_CODE) {
return Promise.resolve(this.qr.get());
} else if (this.status === WhatsappStatus.WORKING) {
throw new UnprocessableEntityException(
`Can not get screenshot for non chrome based engine.`,
);
} else {
throw new UnprocessableEntityException(`Unknown status - ${this.status}`);
}
}
async checkNumberStatus(
request: CheckNumberStatusQuery,
): Promise<WANumberExistResult> {
const phone = request.phone.split('@')[0];
const [result] = await this.sock.onWhatsApp(phone);
if (!result) {
return { numberExists: false };
}
return { numberExists: result.exists };
}
sendText(request: MessageTextRequest) {
const chatId = this.ensureSuffix(request.chatId);
return this.sock.sendMessage(chatId, { text: request.text });
}
sendTextButtons(request: MessageTextButtonsRequest) {
const buttons = request.buttons.map((button: Button) => {
return {
buttonId: button.id,
buttonText: { displayText: button.text },
type: 1,
};
});
const buttonMessage = {
text: request.title,
buttons: buttons,
headerType: 1,
};
return this.sock.sendMessage(request.chatId, buttonMessage);
}
sendContactVCard(request: MessageContactVcardRequest) {
throw new NotImplementedByEngineError();
}
async reply(request: MessageReplyRequest) {
const { id } = parseMessageId(request.reply_to);
const message = await this.store.loadMessage(toJID(request.chatId), id);
return await this.sock.sendMessage(
request.chatId,
{ text: request.text },
{ quoted: message },
);
}
sendImage(request: MessageImageRequest) {
throw new AvailableInPlusVersion();
}
sendFile(request: MessageFileRequest) {
throw new AvailableInPlusVersion();
}
sendVoice(request: MessageVoiceRequest) {
throw new AvailableInPlusVersion();
}
sendLocation(request: MessageLocationRequest) {
return this.sock.sendMessage(request.chatId, {
location: {
degreesLatitude: request.latitude,
degreesLongitude: request.longitude,
},
});
}
sendLinkPreview(request: MessageLinkPreviewRequest) {
const text = `${request.title}\n${request.url}`;
const chatId = this.ensureSuffix(request.chatId);
return this.sock.sendMessage(chatId, { text: text });
}
async sendSeen(request: SendSeenRequest) {
const key = parseMessageId(request.messageId);
const data = {
remoteJid: key.remoteJid,
id: key.id,
participant: request.participant,
};
return this.sock.readMessages([data]);
}
async startTyping(request: ChatRequest) {
return this.sock.sendPresenceUpdate('composing', request.chatId);
}
async stopTyping(request: ChatRequest) {
return this.sock.sendPresenceUpdate('paused', request.chatId);
}
async getMessages(query: GetMessageQuery) {
throw new NotImplementedByEngineError();
}
async setReaction(request: MessageReactionRequest) {
const key = parseMessageId(request.messageId);
const reactionMessage = {
react: {
text: request.reaction,
key: key,
},
};
return this.sock.sendMessage(key.remoteJid, reactionMessage);
}
/**
* Contacts methods
*/
getContact(query: ContactQuery) {
throw new NotImplementedByEngineError();
}
getContacts() {
throw new NotImplementedByEngineError();
}
public async getContactAbout(query: ContactQuery) {
throw new NotImplementedByEngineError();
}
public async getContactProfilePicture(query: ContactQuery) {
throw new NotImplementedByEngineError();
}
public async blockContact(request: ContactRequest) {
throw new NotImplementedByEngineError();
}
public async unblockContact(request: ContactRequest) {
throw new NotImplementedByEngineError();
}
/**
* Group methods
*/
public createGroup(request: CreateGroupRequest) {
const participants = request.participants.map(getId);
return this.sock.groupCreate(request.name, participants);
}
public async getGroups() {
return await this.sock.groupFetchAllParticipating();
}
public async getGroup(id) {
const groups = await this.sock.groupFetchAllParticipating();
return groups[id];
}
public async deleteGroup(id) {
throw new NotImplementedByEngineError();
}
public async leaveGroup(id) {
return this.sock.groupLeave(id);
}
public async setDescription(id, description) {
return this.sock.groupUpdateDescription(id, description);
}
public async setSubject(id, subject) {
return this.sock.groupUpdateSubject(id, subject);
}
public async getInviteCode(id): Promise<string> {
return this.sock.groupInviteCode(id);
}
public async revokeInviteCode(id): Promise<string> {
await this.sock.groupRevokeInvite(id);
return this.sock.groupInviteCode(id);
}
public async getParticipants(id) {
const groups = await this.sock.groupFetchAllParticipating();
return groups[id].participants;
}
public async addParticipants(id, request: ParticipantsRequest) {
const participants = request.participants.map(getId);
return this.sock.groupParticipantsUpdate(id, participants, 'add');
}
public async removeParticipants(id, request: ParticipantsRequest) {
const participants = request.participants.map(getId);
return this.sock.groupParticipantsUpdate(id, participants, 'remove');
}
public async promoteParticipantsToAdmin(id, request: ParticipantsRequest) {
const participants = request.participants.map(getId);
return this.sock.groupParticipantsUpdate(id, participants, 'promote');
}
public async demoteParticipantsToUser(id, request: ParticipantsRequest) {
const participants = request.participants.map(getId);
return this.sock.groupParticipantsUpdate(id, participants, 'demote');
}
/**
* END - Methods for API
*/
subscribe(event, handler) {
if (event === WAEvents.MESSAGE) {
return this.sock.ev.on(BaileysEvents.MESSAGES_UPSERT, ({ messages }) =>
this.handleIncomingMessages(messages, handler, false),
);
} else if (event === WAEvents.MESSAGE_ANY) {
return this.sock.ev.on(BaileysEvents.MESSAGES_UPSERT, ({ messages }) =>
this.handleIncomingMessages(messages, handler, true),
);
} else if (event === WAEvents.STATE_CHANGE) {
return this.sock.ev.on(BaileysEvents.CONNECTION_UPDATE, handler);
} else if (event === WAEvents.GROUP_JOIN) {
return this.sock.ev.on(BaileysEvents.GROUPS_UPSERT, handler);
} else {
throw new NotImplementedByEngineError(
`Engine does not support webhook event: ${event}`,
);
}
}
private handleIncomingMessages(messages, handler, includeFromMe) {
for (const message of messages) {
// if there is no text or media message
if (!message) return;
if (!message.message) return;
// Do not include my messages
if (!includeFromMe && message.key.fromMe) {
continue;
}
this.processIncomingMessage(message).then(handler);
}
}
private processIncomingMessage(message) {
return this.downloadMedia(message).then(this.toWAMessage);
}
protected toWAMessage(message): Promise<WAMessage> {
const isGroupMessage = Boolean(message.key.participant);
let participant: string;
let to: string;
if (isGroupMessage) {
participant = message.key.participant;
to = message.key.remoteJid;
}
const from = message.key.remoteJid;
const id = buildMessageId(message.key);
let body = message.message.conversation;
if (!body) {
// Some of the messages have no conversation, but instead have text in extendedTextMessage
// https://github.com/devlikeapro/whatsapp-http-api/issues/90
body = message.message.extendedTextMessage?.text;
}
return Promise.resolve({
id: id,
timestamp: message.messageTimestamp,
from: toCusFormat(from),
fromMe: message.key.fromMe,
body: body,
to: toCusFormat(to),
participant: toCusFormat(participant),
// @ts-ignore
hasMedia: Boolean(message.mediaUrl),
// @ts-ignore
mediaUrl: message.mediaUrl,
// @ts-ignore
ack: message.ack,
location: message.location,
vCards: message.vCards,
_data: message,
});
}
protected async downloadMedia(message: Message) {
if (!message.hasMedia) {
return message;
}
// @ts-ignore
message.mediaUrl = await this.storage.save(message.key.id, '', undefined);
return message;
}
}
/**
* Convert from 11111111111@s.whatsapp.net to 11111111111@c.us
*/
function toCusFormat(remoteJid) {
if (isJidGroup(remoteJid)) {
return remoteJid;
}
if (!remoteJid) {
return;
}
const number = remoteJid.split('@')[0];
return ensureSuffix(number);
}
/**
* Convert from 11111111111@c.us to 11111111111@s.whatsapp.net
* @param chatId
*/
function toJID(chatId) {
if (isJidGroup(chatId)) {
return chatId;
}
const number = chatId.split('@')[0];
return number + '@s.whatsapp.net';
}
/**
* Build WAHA message id from engine one
* {id: "AAA", remoteJid: "11111111111@s.whatsapp.net", "fromMe": false}
* false_11111111111@c.us_AA
*/
function buildMessageId({ id, remoteJid, fromMe }) {
const chatId = toCusFormat(remoteJid);
return `${fromMe || false}_${chatId}_${id}`;
}
/**
* Parse message id from WAHA to engine
* false_11111111111@c.us_AAA
* {id: "AAA", remoteJid: "11111111111@s.whatsapp.net", "fromMe": false}
*/
function parseMessageId(messageId) {
const parts = messageId.split('_');
if (parts.length != 3) {
throw new Error(
'Message id be in format false_11111111111@c.us_AAAAAAAAAAAAAAAAAAAA',
);
}
const fromMe = parts[0] == 'true';
const chatId = parts[1];
const remoteJid = toJID(chatId);
const id = parts[2];
return { fromMe: fromMe, id: id, remoteJid: remoteJid };
}
function getId(object) {
return object.id;
}
+255 -217
View File
@@ -1,236 +1,274 @@
import {WhatsappSession} from "./abc/session.abc";
import { WAHAInternalEvent, WhatsappSession } from './abc/session.abc';
import {
Button,
ChatRequest,
CheckNumberStatusQuery,
MessageContactVcardRequest,
MessageFileRequest,
MessageImageRequest,
MessageLinkPreviewRequest,
MessageLocationRequest,
MessageReactionRequest,
MessageReplyRequest,
MessageTextButtonsRequest,
MessageTextRequest
} from "../structures/chatting.dto";
import {WAMessage, WANumberExistResult} from "../structures/responses.dto";
import {create, Message, Whatsapp} from "venom-bot";
import {WAEvents, WhatsappStatus} from "../structures/enums.dto";
import {NotImplementedByEngineError} from "./exceptions";
import {MediaStorage} from "./abc/storage.abc";
import {UnprocessableEntityException} from "@nestjs/common/exceptions/unprocessable-entity.exception";
import {ConsoleLogger} from "@nestjs/common";
import {ContactQuery} from "../structures/contacts.dto";
class QR {
private base64: string;
save(base64) {
this.base64 = base64.replace(/^data:image\/png;base64,/, '');
}
get(): Buffer {
return Buffer.from(this.base64, "base64")
}
}
Button,
ChatRequest,
CheckNumberStatusQuery,
GetMessageQuery,
MessageContactVcardRequest,
MessageFileRequest,
MessageImageRequest,
MessageLinkPreviewRequest,
MessageLocationRequest,
MessageReactionRequest,
MessageReplyRequest,
MessageTextButtonsRequest,
MessageTextRequest,
} from '../structures/chatting.dto';
import { WAMessage, WANumberExistResult } from '../structures/responses.dto';
import { create, Message, Whatsapp } from 'venom-bot';
import {
WAEvents,
WhatsappEngine,
WhatsappStatus,
} from '../structures/enums.dto';
import { NotImplementedByEngineError } from './exceptions';
import { UnprocessableEntityException } from '@nestjs/common/exceptions/unprocessable-entity.exception';
import { QR } from './QR';
export class WhatsappSessionVenomCore extends WhatsappSession {
whatsapp: Whatsapp;
private qr: QR
engine = WhatsappEngine.NOWEB;
public constructor(public name: string, protected storage: MediaStorage, protected log: ConsoleLogger) {
super(name, storage, log);
this.qr = new QR()
whatsapp: Whatsapp;
private qr: QR;
public constructor(config) {
super(config);
this.qr = new QR();
}
protected buildClient() {
return create(this.name, this.getCatchQR(), undefined, {
headless: true,
devtools: false,
debug: false,
logQR: true,
browserArgs: this.getBrowserArgsForPuppeteer(),
autoClose: 60000,
puppeteerOptions: {},
});
}
protected getCatchQR() {
return (base64Qrimg, asciiQR, attempts, urlCode) => {
this.qr.save(base64Qrimg);
this.status = WhatsappStatus.SCAN_QR_CODE;
this.log.debug('Number of attempts to read the qrcode: ', attempts);
this.log.log('Terminal qrcode:');
// Log QR image in console without this.log to make it pretty
console.log(asciiQR);
};
}
async start() {
try {
this.whatsapp = await this.buildClient();
} catch (error) {
this.status = WhatsappStatus.FAILED;
this.log.error(error);
this.qr.save('');
return;
}
protected buildClient() {
return create(this.name,
this.getCatchQR(),
undefined,
{
headless: true,
devtools: false,
useChrome: true,
debug: false,
logQR: true,
browserArgs: this.getBrowserArgsForPuppeteer(),
autoClose: 60000,
puppeteerOptions: {},
multidevice: true,
}
)
this.status = WhatsappStatus.WORKING;
this.events.emit(WAHAInternalEvent.engine_start);
return this;
}
stop() {
return this.whatsapp.close();
}
subscribe(event: WAEvents | string, handler: (message) => void) {
if (event === WAEvents.MESSAGE) {
return this.whatsapp.onMessage((message: Message) =>
this.processIncomingMessage(message).then(handler),
);
} else if (event === WAEvents.MESSAGE_ANY) {
return this.whatsapp.onAnyMessage((message: Message) =>
this.processIncomingMessage(message).then(handler),
);
} else if (event === WAEvents.STATE_CHANGE) {
return this.whatsapp.onStateChange(handler);
} else if (event === WAEvents.MESSAGE_ACK) {
return this.whatsapp.onAck(handler);
} else if (event === WAEvents.GROUP_JOIN) {
return this.whatsapp.onAddedToGroup(handler);
} else {
throw new NotImplementedByEngineError(
`Engine does not support webhook event: ${event}`,
);
}
}
protected getCatchQR() {
return (base64Qrimg, asciiQR, attempts, urlCode) => {
this.qr.save(base64Qrimg)
this.status = WhatsappStatus.SCAN_QR_CODE
this.log.debug('Number of attempts to read the qrcode: ', attempts);
this.log.log('Terminal qrcode:');
// Log QR image in console without this.log to make it pretty
console.log(asciiQR);
};
/**
* START - Methods for API
*/
getScreenshot(): Promise<Buffer | string> {
if (this.status === WhatsappStatus.STARTING) {
throw new UnprocessableEntityException(
`The session is starting, please try again after few seconds`,
);
} else if (this.status === WhatsappStatus.SCAN_QR_CODE) {
return Promise.resolve(this.qr.get());
} else if (this.status === WhatsappStatus.WORKING) {
return this.whatsapp.page.screenshot();
} else {
throw new UnprocessableEntityException(`Unknown status - ${this.status}`);
}
}
async start() {
try {
this.whatsapp = await this.buildClient()
} catch (error) {
this.status = WhatsappStatus.FAILED
this.log.error(error)
this.qr.save("")
return
}
this.status = WhatsappStatus.WORKING
return this
async checkNumberStatus(
request: CheckNumberStatusQuery,
): Promise<WANumberExistResult> {
try {
return await this.whatsapp.checkNumberStatus(
this.ensureSuffix(request.phone),
);
} catch (error) {
// Catch number doesn't exist error and return it as is
if (error.status === 404 && !error.numberExists) {
return error;
}
throw error;
}
}
sendContactVCard(request: MessageContactVcardRequest) {
return this.whatsapp.sendContactVcard(
request.chatId,
request.contactsId,
request.name,
);
}
stop() {
return this.whatsapp.close()
sendText(request: MessageTextRequest) {
return this.whatsapp.sendText(
this.ensureSuffix(request.chatId),
request.text,
);
}
reply(request: MessageReplyRequest) {
return this.whatsapp.reply(request.chatId, request.text, request.reply_to);
}
sendFile(request: MessageFileRequest) {
throw new NotImplementedByEngineError();
}
sendImage(request: MessageImageRequest) {
throw new NotImplementedByEngineError();
}
async sendVoice(request) {
throw new NotImplementedByEngineError();
}
sendLinkPreview(request: MessageLinkPreviewRequest) {
return this.whatsapp.sendLinkPreview(
request.chatId,
request.url,
request.title,
);
}
sendLocation(request: MessageLocationRequest) {
return this.whatsapp.sendLocation(
request.chatId,
String(request.latitude),
String(request.longitude),
request.title,
);
}
sendSeen(chat: ChatRequest) {
return this.whatsapp.markMarkSeenMessage(chat.chatId);
}
sendTextButtons(request: MessageTextButtonsRequest) {
const buttons = request.buttons.map((button: Button) => {
return {
buttonId: button.id,
buttonText: {
displayText: button.text,
},
};
});
return this.whatsapp.sendButtons(
this.ensureSuffix(request.chatId),
request.title,
request.footer,
buttons,
);
}
startTyping(chat: ChatRequest) {
return this.whatsapp.startTyping(chat.chatId, false);
}
stopTyping(chat: ChatRequest) {
return;
// The method is not available yet in new venom-bot
// return this.whatsapp.stopTyping(chat.chatId);
}
async getMessages(query: GetMessageQuery) {
const messages = await this.whatsapp.getAllMessagesInChat(
query.chatId,
true,
false,
);
// Go over messages, download media, and convert to right format.
const result = [];
for (const [count, message] of messages.entries()) {
if (count > query.limit) {
// Have enough in the list, stop processing
break;
}
result.push(await this.processIncomingMessage(message));
}
return result;
}
subscribe(event: WAEvents | string, handler: (message) => void) {
if (event === WAEvents.MESSAGE) {
return this.whatsapp.onMessage((message: Message) => this.processIncomingMessage(message).then(handler))
} else if (event === WAEvents.MESSAGE_ANY) {
return this.whatsapp.onAnyMessage((message: Message) => this.processIncomingMessage(message).then(handler))
} else if (event === WAEvents.STATE_CHANGE) {
return this.whatsapp.onStateChange(handler)
} else if (event === WAEvents.MESSAGE_ACK) {
return this.whatsapp.onAck(handler)
} else if (event === WAEvents.GROUP_JOIN) {
return this.whatsapp.onAddedToGroup(handler)
} else {
throw new NotImplementedByEngineError(`Engine does not support webhook event: ${event}`)
}
setReaction(request: MessageReactionRequest) {
throw new NotImplementedByEngineError();
}
/**
* END - Methods for API
*/
protected async downloadAndDecryptMedia(message: Message) {
if (!message.isMMS || !message.isMedia) {
return message;
}
// @ts-ignore
message.mediaUrl = await this.storage.save(message.id, '', undefined);
return message;
}
private processIncomingMessage(message: Message) {
return this.downloadAndDecryptMedia(message).then(this.toWAMessage);
}
/**
* START - Methods for API
*/
getScreenshot(): Promise<Buffer | string> {
if (this.status === WhatsappStatus.STARTING) {
throw new UnprocessableEntityException(`The session is starting, please try again after few seconds`);
} else if (this.status === WhatsappStatus.SCAN_QR_CODE) {
return Promise.resolve(this.qr.get())
} else if (this.status === WhatsappStatus.WORKING) {
return this.whatsapp.page.screenshot()
} else {
throw new UnprocessableEntityException(`Unknown status - ${this.status}`);
}
}
async checkNumberStatus(request: CheckNumberStatusQuery): Promise<WANumberExistResult> {
try {
return await this.whatsapp.checkNumberStatus(this.ensureSuffix(request.phone))
} catch (error) {
// Catch number doesn't exist error and return it as is
if (error.status === 404 && !error.numberExists){
return error
}
throw error
}
}
sendContactVCard(request: MessageContactVcardRequest) {
return this.whatsapp.sendContactVcard(request.chatId, request.contactsId, request.name)
}
sendText(request: MessageTextRequest) {
return this.whatsapp.sendText(this.ensureSuffix(request.chatId), request.text)
}
reply(request: MessageReplyRequest) {
return this.whatsapp.reply(request.chatId, request.text, request.reply_to)
}
sendFile(request: MessageFileRequest) {
throw new NotImplementedByEngineError()
}
sendImage(request: MessageImageRequest) {
throw new NotImplementedByEngineError()
}
async sendVoice(request) {
throw new NotImplementedByEngineError()
}
sendLinkPreview(request: MessageLinkPreviewRequest) {
return this.whatsapp.sendLinkPreview(request.chatId, request.url, request.title)
}
sendLocation(request: MessageLocationRequest) {
return this.whatsapp.sendLocation(request.chatId, String(request.latitude), String(request.longitude), request.title)
}
sendSeen(chat: ChatRequest) {
return this.whatsapp.sendSeen(chat.chatId)
}
sendTextButtons(request: MessageTextButtonsRequest) {
const buttons = request.buttons.map((button: Button) => {
return {
buttonId: button.id,
buttonText: {
displayText: button.body
}
}
})
return this.whatsapp.sendButtons(this.ensureSuffix(request.chatId), request.title, buttons, request.text)
}
startTyping(chat: ChatRequest) {
return this.whatsapp.startTyping(chat.chatId)
}
stopTyping(chat: ChatRequest) {
return this.whatsapp.stopTyping(chat.chatId)
}
setReaction(request: MessageReactionRequest) {
throw new NotImplementedByEngineError()
}
/**
* END - Methods for API
*/
protected async downloadAndDecryptMedia(message: Message) {
if (!message.isMMS || !message.isMedia) {
return message
}
// @ts-ignore
message.mediaUrl = await this.storage.save(message.id, "", undefined)
return message
}
private processIncomingMessage(message: Message) {
return this.downloadAndDecryptMedia(message).then(this.toWAMessage)
}
protected toWAMessage(message: Message): Promise<WAMessage> {
// @ts-ignore
return Promise.resolve({
id: message.id,
timestamp: message.timestamp,
from: message.from,
fromMe: message.fromMe,
to: message.to,
body: message.body,
// @ts-ignore
hasMedia: Boolean(message.mediaUrl),
// @ts-ignore
mediaUrl: message.mediaUrl,
// @ts-ignore
ack: message.ack,
location: undefined,
vCards: undefined,
_data: message,
})
}
protected toWAMessage(message: Message): Promise<WAMessage> {
// @ts-ignore
return Promise.resolve({
id: message.id,
timestamp: message.timestamp,
from: message.from,
fromMe: message.fromMe,
to: message.to,
body: message.body,
// @ts-ignore
hasMedia: Boolean(message.mediaUrl),
// @ts-ignore
mediaUrl: message.mediaUrl,
// @ts-ignore
ack: message.ack,
location: undefined,
vCards: undefined,
_data: message,
});
}
}
+406 -319
View File
@@ -1,329 +1,416 @@
import {UnprocessableEntityException} from "@nestjs/common/exceptions/unprocessable-entity.exception";
import {Buttons, Chat, Client, Contact, Events, GroupChat, Location, Message} from "whatsapp-web.js";
import {Message as MessageInstance} from "whatsapp-web.js/src/structures"
import {WAEvents, WhatsappStatus} from "../structures/enums.dto";
import {WhatsappSession} from "./abc/session.abc";
import {WAMessage, WANumberExistResult} from "../structures/responses.dto";
import { UnprocessableEntityException } from '@nestjs/common/exceptions/unprocessable-entity.exception';
import {
ChatRequest,
CheckNumberStatusQuery,
MessageContactVcardRequest,
MessageFileRequest,
MessageImageRequest,
MessageLinkPreviewRequest,
MessageLocationRequest,
MessageReactionRequest,
MessageReplyRequest,
MessageTextButtonsRequest,
MessageTextRequest,
MessageVoiceRequest
} from "../structures/chatting.dto";
import {AvailableInPlusVersion, NotImplementedByEngineError} from "./exceptions";
import {ContactQuery, ContactRequest} from "../structures/contacts.dto";
import {CreateGroupRequest, ParticipantsRequest} from "../structures/groups.dto";
Buttons,
Chat,
Client,
Contact,
Events,
GroupChat,
Location,
Message,
} from 'whatsapp-web.js';
import { Message as MessageInstance } from 'whatsapp-web.js/src/structures';
import {
WAEvents,
WhatsappEngine,
WhatsappStatus,
} from '../structures/enums.dto';
import { WAHAInternalEvent, WhatsappSession } from './abc/session.abc';
import { WAMessage, WANumberExistResult } from '../structures/responses.dto';
import {
Button,
ChatRequest,
CheckNumberStatusQuery,
GetMessageQuery,
MessageContactVcardRequest,
MessageFileRequest,
MessageImageRequest,
MessageLinkPreviewRequest,
MessageLocationRequest,
MessageReactionRequest,
MessageReplyRequest,
MessageTextButtonsRequest,
MessageTextRequest,
MessageVoiceRequest,
SendSeenRequest,
} from '../structures/chatting.dto';
import {
AvailableInPlusVersion,
NotImplementedByEngineError,
} from './exceptions';
import { ContactQuery, ContactRequest } from '../structures/contacts.dto';
import {
CreateGroupRequest,
ParticipantsRequest,
} from '../structures/groups.dto';
// eslint-disable-next-line @typescript-eslint/no-var-requires
const qrcode = require('qrcode-terminal');
export class WhatsappSessionWebJSCore extends WhatsappSession {
whatsapp: Client;
engine = WhatsappEngine.NOWEB;
protected buildClient() {
return new Client({
puppeteer: {
headless: true,
executablePath: this.getBrowserExecutablePath(),
args: this.getBrowserArgsForPuppeteer(),
}
});
}
async start() {
this.whatsapp = this.buildClient()
this.whatsapp.initialize().catch(error => {
this.status = WhatsappStatus.FAILED
this.log.error(error)
return
});
// Connect events
this.whatsapp.on(Events.QR_RECEIVED, (qr) => {
qrcode.generate(qr, {small: true});
this.status = WhatsappStatus.SCAN_QR_CODE
});
this.whatsapp.on(Events.AUTHENTICATED, () => {
this.status = WhatsappStatus.WORKING
this.log.log(`Session '${this.name}' has been authenticated!`)
});
return this
}
stop() {
return this.whatsapp.pupPage.close()
}
/**
* START - Methods for API
*/
async getScreenshot(): Promise<Buffer | string> {
if (this.status === WhatsappStatus.FAILED) {
throw new UnprocessableEntityException(`The session under FAILED status. Please try to restart it.`);
}
return await this.whatsapp.pupPage.screenshot()
}
async checkNumberStatus(request: CheckNumberStatusQuery): Promise<WANumberExistResult> {
const exist = await this.whatsapp.isRegisteredUser(this.ensureSuffix(request.phone))
return {numberExists: exist}
}
sendText(request: MessageTextRequest) {
return this.whatsapp.sendMessage(this.ensureSuffix(request.chatId), request.text)
}
sendTextButtons(request: MessageTextButtonsRequest) {
const message = new Buttons("", request.buttons, request.title, request.text)
return this.whatsapp.sendMessage(this.ensureSuffix(request.chatId), message)
}
sendContactVCard(request: MessageContactVcardRequest) {
throw new NotImplementedByEngineError()
}
reply(request: MessageReplyRequest) {
const options = {
quotedMessageId: request.reply_to,
};
return this.whatsapp.sendMessage(request.chatId, request.text, options)
}
sendImage(request: MessageImageRequest) {
throw new AvailableInPlusVersion()
}
sendFile(request: MessageFileRequest) {
throw new AvailableInPlusVersion()
}
sendVoice(request: MessageVoiceRequest) {
throw new AvailableInPlusVersion()
}
async sendLocation(request: MessageLocationRequest) {
const location = new Location(
request.latitude,
request.longitude,
request.title,
)
return this.whatsapp.sendMessage(request.chatId, location)
}
sendLinkPreview(request: MessageLinkPreviewRequest) {
throw new NotImplementedByEngineError()
}
async sendSeen(request: ChatRequest) {
const chat: Chat = await this.whatsapp.getChatById(request.chatId)
await chat.sendSeen()
}
async startTyping(request: ChatRequest) {
const chat: Chat = await this.whatsapp.getChatById(request.chatId)
await chat.sendStateTyping()
}
async stopTyping(request: ChatRequest) {
const chat: Chat = await this.whatsapp.getChatById(request.chatId)
await chat.clearState()
}
async setReaction(request: MessageReactionRequest) {
const messageId = this.deserializeId(request.messageId)
console.log(messageId)
// Recreate instance to react on it
const message = new MessageInstance(this.whatsapp)
message.id = messageId
message._data = {id: messageId}
return message.react(request.reaction)
}
/**
* Contacts methods
*/
getContact(query: ContactQuery) {
return this.whatsapp.getContactById(this.ensureSuffix(query.contactId)).then(this.toWAContact)
}
getContacts() {
return this.whatsapp.getContacts().then(contacts => contacts.map(this.toWAContact))
}
public async getContactAbout(query: ContactQuery) {
const contact = await this.whatsapp.getContactById(this.ensureSuffix(query.contactId))
return {"about": await contact.getAbout()}
}
public async getContactProfilePicture(query: ContactQuery) {
const contact = await this.whatsapp.getContactById(this.ensureSuffix(query.contactId))
return {"profilePictureURL": await contact.getProfilePicUrl()}
}
public async blockContact(request: ContactRequest) {
const contact = await this.whatsapp.getContactById(this.ensureSuffix(request.contactId))
await contact.block()
}
public async unblockContact(request: ContactRequest) {
const contact = await this.whatsapp.getContactById(this.ensureSuffix(request.contactId))
await contact.unblock()
}
/**
* Group methods
*/
public createGroup(request: CreateGroupRequest) {
const participantIds = request.participants.map(participant => participant.id)
return this.whatsapp.createGroup(request.name, participantIds)
}
public getGroups() {
return this.whatsapp.getChats().then(chats => chats.filter(chat => chat.isGroup))
}
public getGroup(id) {
return this.whatsapp.getChatById(id)
}
public async deleteGroup(id) {
const groupChat = await this.whatsapp.getChatById(id) as GroupChat
return groupChat.delete()
}
public async leaveGroup(id) {
const groupChat = await this.whatsapp.getChatById(id) as GroupChat
return groupChat.leave()
}
public async setDescription(id, description) {
const groupChat = await this.whatsapp.getChatById(id) as GroupChat
return groupChat.setDescription(description)
}
public async setSubject(id, subject) {
const groupChat = await this.whatsapp.getChatById(id) as GroupChat
return groupChat.setSubject(subject)
}
public async getInviteCode(id): Promise<string> {
const groupChat = await this.whatsapp.getChatById(id) as GroupChat
return groupChat.getInviteCode()
}
public async revokeInviteCode(id): Promise<string> {
const groupChat = await this.whatsapp.getChatById(id) as GroupChat
await groupChat.revokeInvite()
return groupChat.getInviteCode()
}
public async getParticipants(id) {
const groupChat = await this.whatsapp.getChatById(id) as GroupChat
return groupChat.participants
}
public async addParticipants(id, request: ParticipantsRequest) {
const groupChat = await this.whatsapp.getChatById(id) as GroupChat
const participantIds = request.participants.map(participant => participant.id)
return groupChat.addParticipants(participantIds)
}
public async removeParticipants(id, request: ParticipantsRequest) {
const groupChat = await this.whatsapp.getChatById(id) as GroupChat
const participantIds = request.participants.map(participant => participant.id)
return groupChat.removeParticipants(participantIds)
}
public async promoteParticipantsToAdmin(id, request: ParticipantsRequest) {
const groupChat = await this.whatsapp.getChatById(id) as GroupChat
const participantIds = request.participants.map(participant => participant.id)
return groupChat.promoteParticipants(participantIds)
}
public async demoteParticipantsToUser(id, request: ParticipantsRequest) {
const groupChat = await this.whatsapp.getChatById(id) as GroupChat
const participantIds = request.participants.map(participant => participant.id)
return groupChat.demoteParticipants(participantIds)
}
/**
* END - Methods for API
*/
subscribe(event, handler) {
if (event === WAEvents.MESSAGE) {
this.whatsapp.on(Events.MESSAGE_RECEIVED, (message) => this.processIncomingMessage(message).then(handler))
} else if (event === WAEvents.MESSAGE_ANY) {
this.whatsapp.on(Events.MESSAGE_CREATE, (message) => this.processIncomingMessage(message).then(handler))
} else if (event === WAEvents.STATE_CHANGE) {
this.whatsapp.on(Events.STATE_CHANGED, handler)
} else if (event === WAEvents.MESSAGE_ACK) {
// We do not download media here
this.whatsapp.on(Events.MESSAGE_ACK, (message) => this.toWAMessage(message).then(handler))
} else if (event === WAEvents.GROUP_JOIN) {
this.whatsapp.on(Events.GROUP_JOIN, handler)
} else if (event === WAEvents.GROUP_LEAVE) {
this.whatsapp.on(Events.GROUP_LEAVE, handler)
} else {
throw new NotImplementedByEngineError(`Engine does not support webhook event: ${event}`)
}
}
private processIncomingMessage(message: Message) {
return this.downloadMedia(message).then(this.toWAMessage)
}
protected toWAMessage(message: Message): Promise<WAMessage> {
// @ts-ignore
return Promise.resolve({
id: message.id._serialized,
timestamp: message.timestamp,
from: message.from,
fromMe: message.fromMe,
to: message.to,
body: message.body,
// @ts-ignore
hasMedia: Boolean(message.mediaUrl),
// @ts-ignore
mediaUrl: message.mediaUrl,
// @ts-ignore
ack: message.ack,
location: message.location,
vCards: message.vCards,
_data: message.rawData,
})
}
protected toWAContact(contact: Contact) {
// @ts-ignore
contact.id = contact.id._serialized
return contact
}
protected async downloadMedia(message: Message) {
if (!message.hasMedia) {
return message
}
// @ts-ignore
message.mediaUrl = await this.storage.save(message.id._serialized, "", undefined)
return message
whatsapp: Client;
protected buildClient() {
return new Client({
puppeteer: {
headless: true,
executablePath: this.getBrowserExecutablePath(),
args: this.getBrowserArgsForPuppeteer(),
},
});
}
async start() {
this.whatsapp = this.buildClient();
this.whatsapp.initialize().catch((error) => {
this.status = WhatsappStatus.FAILED;
this.log.error(error);
return;
});
// Connect events
this.whatsapp.on(Events.QR_RECEIVED, (qr) => {
qrcode.generate(qr, { small: true });
this.status = WhatsappStatus.SCAN_QR_CODE;
});
this.whatsapp.on(Events.AUTHENTICATED, () => {
this.status = WhatsappStatus.WORKING;
this.log.log(`Session '${this.name}' has been authenticated!`);
});
this.events.emit(WAHAInternalEvent.engine_start);
return this;
}
stop() {
return this.whatsapp.destroy();
}
/**
* START - Methods for API
*/
async getScreenshot(): Promise<Buffer | string> {
if (this.status === WhatsappStatus.FAILED) {
throw new UnprocessableEntityException(
`The session under FAILED status. Please try to restart it.`,
);
}
return await this.whatsapp.pupPage.screenshot();
}
async checkNumberStatus(
request: CheckNumberStatusQuery,
): Promise<WANumberExistResult> {
const exist = await this.whatsapp.isRegisteredUser(
this.ensureSuffix(request.phone),
);
return { numberExists: exist };
}
sendText(request: MessageTextRequest) {
return this.whatsapp.sendMessage(
this.ensureSuffix(request.chatId),
request.text,
);
}
sendTextButtons(request: MessageTextButtonsRequest) {
const buttons = request.buttons.map((button: Button) => {
return {
id: button.id,
body: button.text,
};
});
const message = new Buttons('', buttons, request.title, request.footer);
return this.whatsapp.sendMessage(
this.ensureSuffix(request.chatId),
message,
);
}
sendContactVCard(request: MessageContactVcardRequest) {
throw new NotImplementedByEngineError();
}
reply(request: MessageReplyRequest) {
const options = {
quotedMessageId: request.reply_to,
};
return this.whatsapp.sendMessage(request.chatId, request.text, options);
}
sendImage(request: MessageImageRequest) {
throw new AvailableInPlusVersion();
}
sendFile(request: MessageFileRequest) {
throw new AvailableInPlusVersion();
}
sendVoice(request: MessageVoiceRequest) {
throw new AvailableInPlusVersion();
}
async sendLocation(request: MessageLocationRequest) {
const location = new Location(
request.latitude,
request.longitude,
request.title,
);
return this.whatsapp.sendMessage(request.chatId, location);
}
sendLinkPreview(request: MessageLinkPreviewRequest) {
throw new NotImplementedByEngineError();
}
async sendSeen(request: SendSeenRequest) {
const chat: Chat = await this.whatsapp.getChatById(request.chatId);
await chat.sendSeen();
}
async startTyping(request: ChatRequest) {
const chat: Chat = await this.whatsapp.getChatById(request.chatId);
await chat.sendStateTyping();
}
async stopTyping(request: ChatRequest) {
const chat: Chat = await this.whatsapp.getChatById(request.chatId);
await chat.clearState();
}
async getMessages(query: GetMessageQuery) {
const chat: Chat = await this.whatsapp.getChatById(
this.ensureSuffix(query.chatId),
);
const messages = await chat.fetchMessages({
limit: query.limit,
});
// Go over messages, download media, and convert to right format.
const result = [];
for (const message of messages) {
result.push(await this.processIncomingMessage(message));
}
return result;
}
async setReaction(request: MessageReactionRequest) {
const messageId = this.deserializeId(request.messageId);
// Recreate instance to react on it
const message = new MessageInstance(this.whatsapp);
message.id = messageId;
message._data = { id: messageId };
return message.react(request.reaction);
}
/**
* Contacts methods
*/
getContact(query: ContactQuery) {
return this.whatsapp
.getContactById(this.ensureSuffix(query.contactId))
.then(this.toWAContact);
}
getContacts() {
return this.whatsapp
.getContacts()
.then((contacts) => contacts.map(this.toWAContact));
}
public async getContactAbout(query: ContactQuery) {
const contact = await this.whatsapp.getContactById(
this.ensureSuffix(query.contactId),
);
return { about: await contact.getAbout() };
}
public async getContactProfilePicture(query: ContactQuery) {
const contact = await this.whatsapp.getContactById(
this.ensureSuffix(query.contactId),
);
return { profilePictureURL: await contact.getProfilePicUrl() };
}
public async blockContact(request: ContactRequest) {
const contact = await this.whatsapp.getContactById(
this.ensureSuffix(request.contactId),
);
await contact.block();
}
public async unblockContact(request: ContactRequest) {
const contact = await this.whatsapp.getContactById(
this.ensureSuffix(request.contactId),
);
await contact.unblock();
}
/**
* Group methods
*/
public createGroup(request: CreateGroupRequest) {
const participantIds = request.participants.map(
(participant) => participant.id,
);
return this.whatsapp.createGroup(request.name, participantIds);
}
public getGroups() {
return this.whatsapp
.getChats()
.then((chats) => chats.filter((chat) => chat.isGroup));
}
public getGroup(id) {
return this.whatsapp.getChatById(id);
}
public async deleteGroup(id) {
const groupChat = (await this.whatsapp.getChatById(id)) as GroupChat;
return groupChat.delete();
}
public async leaveGroup(id) {
const groupChat = (await this.whatsapp.getChatById(id)) as GroupChat;
return groupChat.leave();
}
public async setDescription(id, description) {
const groupChat = (await this.whatsapp.getChatById(id)) as GroupChat;
return groupChat.setDescription(description);
}
public async setSubject(id, subject) {
const groupChat = (await this.whatsapp.getChatById(id)) as GroupChat;
return groupChat.setSubject(subject);
}
public async getInviteCode(id): Promise<string> {
const groupChat = (await this.whatsapp.getChatById(id)) as GroupChat;
return groupChat.getInviteCode();
}
public async revokeInviteCode(id): Promise<string> {
const groupChat = (await this.whatsapp.getChatById(id)) as GroupChat;
await groupChat.revokeInvite();
return groupChat.getInviteCode();
}
public async getParticipants(id) {
const groupChat = (await this.whatsapp.getChatById(id)) as GroupChat;
return groupChat.participants;
}
public async addParticipants(id, request: ParticipantsRequest) {
const groupChat = (await this.whatsapp.getChatById(id)) as GroupChat;
const participantIds = request.participants.map(
(participant) => participant.id,
);
return groupChat.addParticipants(participantIds);
}
public async removeParticipants(id, request: ParticipantsRequest) {
const groupChat = (await this.whatsapp.getChatById(id)) as GroupChat;
const participantIds = request.participants.map(
(participant) => participant.id,
);
return groupChat.removeParticipants(participantIds);
}
public async promoteParticipantsToAdmin(id, request: ParticipantsRequest) {
const groupChat = (await this.whatsapp.getChatById(id)) as GroupChat;
const participantIds = request.participants.map(
(participant) => participant.id,
);
return groupChat.promoteParticipants(participantIds);
}
public async demoteParticipantsToUser(id, request: ParticipantsRequest) {
const groupChat = (await this.whatsapp.getChatById(id)) as GroupChat;
const participantIds = request.participants.map(
(participant) => participant.id,
);
return groupChat.demoteParticipants(participantIds);
}
/**
* END - Methods for API
*/
subscribe(event, handler) {
if (event === WAEvents.MESSAGE) {
this.whatsapp.on(Events.MESSAGE_RECEIVED, (message) =>
this.processIncomingMessage(message).then(handler),
);
} else if (event === WAEvents.MESSAGE_ANY) {
this.whatsapp.on(Events.MESSAGE_CREATE, (message) =>
this.processIncomingMessage(message).then(handler),
);
} else if (event === WAEvents.STATE_CHANGE) {
this.whatsapp.on(Events.STATE_CHANGED, handler);
} else if (event === WAEvents.MESSAGE_ACK) {
// We do not download media here
this.whatsapp.on(Events.MESSAGE_ACK, (message) =>
this.toWAMessage(message).then(handler),
);
} else if (event === WAEvents.GROUP_JOIN) {
this.whatsapp.on(Events.GROUP_JOIN, handler);
} else if (event === WAEvents.GROUP_LEAVE) {
this.whatsapp.on(Events.GROUP_LEAVE, handler);
} else {
throw new NotImplementedByEngineError(
`Engine does not support webhook event: ${event}`,
);
}
}
private processIncomingMessage(message: Message) {
return this.downloadMedia(message).then(this.toWAMessage);
}
protected toWAMessage(message: Message): Promise<WAMessage> {
// @ts-ignore
return Promise.resolve({
id: message.id._serialized,
timestamp: message.timestamp,
from: message.from,
fromMe: message.fromMe,
to: message.to,
body: message.body,
// @ts-ignore
hasMedia: Boolean(message.mediaUrl),
// @ts-ignore
mediaUrl: message.mediaUrl,
// @ts-ignore
ack: message.ack,
location: message.location,
vCards: message.vCards,
_data: message.rawData,
});
}
protected toWAContact(contact: Contact) {
// @ts-ignore
contact.id = contact.id._serialized;
return contact;
}
protected async downloadMedia(message: Message) {
if (!message.hasMedia) {
return message;
}
// @ts-ignore
message.mediaUrl = await this.storage.save(
message.id._serialized,
'',
undefined,
);
return message;
}
}
+47 -5
View File
@@ -1,8 +1,50 @@
import {DOCS_URL} from "./exceptions";
import {MediaStorage} from "./abc/storage.abc";
import { DOCS_URL } from './exceptions';
import { LocalSessionStorage, MediaStorage } from './abc/storage.abc';
import * as path from 'path';
import * as fs from 'fs';
import * as os from 'os';
export class MediaStorageCore implements MediaStorage {
async save(messageId: string, mimetype: string, buffer: Buffer): Promise<string>{
return Promise.resolve(`Media attachment's available only in WAHA Plus version. ${DOCS_URL}`)
}
async save(
messageId: string,
mimetype: string,
buffer: Buffer,
): Promise<string> {
return Promise.resolve(
`Media attachment's available only in WAHA Plus version. ${DOCS_URL}`,
);
}
}
export class SessionStorageCore extends LocalSessionStorage {
public sessionsFolder: string;
constructor(engine: string) {
super(engine);
this.sessionsFolder = path.join(os.tmpdir(), 'waha-');
}
get engineFolder() {
return path.join(this.sessionsFolder, this.engine);
}
init() {
fs.mkdirSync(this.engineFolder, { recursive: true });
}
getFolderPath(name: string): string {
return path.join(this.engineFolder, name);
}
clean(sessionName: string) {
const folder = this.getFolderPath(sessionName);
fs.rmSync(folder, { recursive: true });
}
getAll(): string[] {
return fs
.readdirSync(this.engineFolder, { withFileTypes: true })
.filter((dirent) => dirent.isDirectory())
.map((dirent) => dirent.name);
}
}
+58 -36
View File
@@ -1,44 +1,66 @@
import {DocumentBuilder, SwaggerModule} from "@nestjs/swagger";
import {INestApplication} from "@nestjs/common";
import {VERSION} from "../version";
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
import { INestApplication } from '@nestjs/common';
import { VERSION } from '../version';
import { WhatsappConfigService } from '../config.service';
export class SwaggerModuleCore {
configure(app: INestApplication) {
this.setUpAuth(app)
configure(app: INestApplication) {
this.setUpAuth(app);
const builder = new DocumentBuilder();
const options = new DocumentBuilder()
.setTitle('WAHA - WhatsApp HTTP API')
.setDescription('WhatsApp HTTP API that you can configure in a click!')
.setExternalDoc("Documentation", "https://waha.devlike.pro/")
.setVersion(VERSION.version)
.addTag('sessions', 'Control WhatsApp sessions')
.addTag('screenshot', 'Get screenshot of WhatsApp and show QR code')
.addTag('chatting', 'Chat methods')
.addTag(
'contacts',
`Contacts methods.<br>
builder
.setTitle('WAHA - WhatsApp HTTP API')
.setDescription('WhatsApp HTTP API that you can configure in a click!')
.setExternalDoc('Documentation', 'https://waha.devlike.pro/')
.setVersion(VERSION.version)
.addTag('sessions', 'Control WhatsApp sessions')
.addTag('screenshot', 'Get screenshot of WhatsApp and show QR code')
.addTag('chatting', 'Chat methods')
.addTag(
'contacts',
`Contacts methods.<br>
Use phone number (without +) or phone number and \`@c.us\` at the end as \`contactId\`.<br>
'E.g: \`12312312310\` OR \`12312312310@c.us\`<br>`
)
.addTag(
'groups',
`Groups methods.<br>`
)
.addTag('other', 'Other methods')
.addApiKey({
type: 'apiKey',
description: 'Your secret api key',
name: 'X-Api-Key'
}
)
.build();
const document = SwaggerModule.createDocument(app, options);
SwaggerModule.setup('', app, document);
'E.g: \`12312312310\` OR \`12312312310@c.us\`<br>`,
)
.addTag('groups', `Groups methods.<br>`)
.addTag('other', 'Other methods')
.addApiKey({
type: 'apiKey',
description: 'Your secret api key',
name: 'X-Api-Key',
});
const config = app.get(WhatsappConfigService);
if (config.getSwaggerAdvancedConfigEnabled()) {
builder.addServer('{protocol}://{host}:{port}/{baseUrl}', '', {
protocol: {
default: 'http',
enum: ['http', 'https'],
description: 'The protocol used to access the server.',
},
host: {
default: config.hostname,
description: 'The hostname or IP address of the server.',
},
port: {
default: config.port,
description:
'The port number on which the server is listening for requests',
},
baseUrl: {
default: '',
description:
'The base URL path for all API endpoints. This can be used to group related endpoints together under a common path.',
},
});
}
protected setUpAuth(app: INestApplication) {
return undefined
}
const options = builder.build();
const document = SwaggerModule.createDocument(app, options);
SwaggerModule.setup('', app, document);
}
protected setUpAuth(app: INestApplication) {
return undefined;
}
}
+72 -65
View File
@@ -1,76 +1,83 @@
import {WhatsappSession} from "./abc/session.abc";
import {ConsoleLogger} from "@nestjs/common";
import {WAEvents} from "../structures/enums.dto";
import {WAWebhook} from "../structures/responses.dto";
import {NotImplementedByEngineError} from "./exceptions";
import {WebhookConductor} from "./abc/webhooks.abc";
import { WAHAInternalEvent, WhatsappSession } from './abc/session.abc';
import { ConsoleLogger } from '@nestjs/common';
import { WAEvents } from '../structures/enums.dto';
import { WAWebhook } from '../structures/responses.dto';
import { NotImplementedByEngineError } from './exceptions';
import { WebhookConductor } from './abc/webhooks.abc';
import request = require('request');
export class WebhookConductorCore implements WebhookConductor {
constructor(protected log: ConsoleLogger, private readonly url, private readonly events: WAEvents[] | string[]) {
this.url = url
this.events = this.getSuitableEvents(events)
this.log = log
constructor(
protected log: ConsoleLogger,
private readonly url,
private readonly events: WAEvents[] | string[],
) {
this.url = url;
this.events = this.getSuitableEvents(events);
this.log = log;
}
private getSuitableEvents(events: WAEvents[] | string[]) {
const allEvents = Object.values(WAEvents);
// Enable all events if * in the events
// @ts-ignore
if (events.includes('*')) {
return allEvents;
}
private getSuitableEvents(events: WAEvents[] | string[]) {
const allEvents = Object.values(WAEvents)
// Get only known events, log and ignore others
const rightEvents = [];
for (const event of events) {
// @ts-ignore
if (!allEvents.includes(event)) {
this.log.error(`Unknown event for webhook: '${event}'`);
continue;
}
rightEvents.push(event);
}
return rightEvents;
}
// Enable all events if * in the events
// @ts-ignore
if (events.includes("*")) {
return allEvents
public configure(session: WhatsappSession) {
this.log.log('Configuring webhooks...');
for (const event of this.events) {
try {
session.subscribe(event, (data: any) =>
this.callWebhook(event, session, data, this.url),
);
} catch (error) {
if (error instanceof NotImplementedByEngineError) {
this.log.error(error);
} else {
throw error;
}
// Get only known events, log and ignore others
const rightEvents = []
for (const event of events) {
// @ts-ignore
if (!allEvents.includes(event)) {
this.log.error(`Unknown event for webhook: '${event}'`)
continue
}
rightEvents.push(event)
}
return rightEvents
}
this.log.log(`Event '${event}' is enabled for url: ${this.url}`);
}
this.log.log('Webhooks were configured.');
}
public configure(session: WhatsappSession) {
this.log.log('Configuring webhooks...')
for (const event of this.events) {
try {
session.subscribe(event, (data: any) => this.callWebhook(event, session, data, this.url))
} catch (error) {
if (error instanceof NotImplementedByEngineError) {
this.log.error(error)
} else {
throw error
}
}
this.log.log(`Event '${event}' is enabled for url: ${this.url}`)
}
this.log.log('Webhooks were configured.')
}
public callWebhook(event, session: WhatsappSession, data: any, url) {
const json: WAWebhook = {
event: event,
session: session.name,
payload: data,
engine: session.engine,
};
this.log.log(`Sending POST to ${url}...`);
this.log.debug(`POST DATA: ${JSON.stringify(json)}`);
this.post(json, url);
}
public callWebhook(event, session: WhatsappSession, data: any, url) {
const json: WAWebhook = {event: event, session: session.name, payload: data}
this.log.log(`Sending POST to ${url}...`)
this.log.debug(`POST DATA: ${JSON.stringify(json)}`)
this.post(json, url)
}
protected post(json, url) {
request.post(
url,
{json: json},
(error, res, body) => {
if (error) {
this.log.error(error)
return
}
this.log.log(`POST request was sent with status code: ${res.statusCode}`)
this.log.verbose(`Response: ${JSON.stringify(body)}`)
})
}
protected post(json, url) {
request.post(url, { json: json }, (error, res, body) => {
if (error) {
this.log.error(error);
return;
}
this.log.log(`POST request was sent with status code: ${res.statusCode}`);
this.log.verbose(`Response: ${JSON.stringify(body)}`);
});
}
}
+20
View File
@@ -0,0 +1,20 @@
export function parseBool(value: any): boolean {
if (!value) {
return false;
}
if (value.toLowerCase() === 'true' || value.toLowerCase() === '1') {
return true;
}
if (value.toLowerCase() === 'false' || value.toLowerCase() === '0') {
return false;
}
console.error(
'parseBool got an unexpected value:',
value,
'(accepted values : "true", "false")',
);
throw new Error(
'Error: parseBool got unexpected value - use "true" or "false" values',
);
}
+41 -37
View File
@@ -1,49 +1,53 @@
import {NestFactory} from '@nestjs/core';
import {WhatsappConfigService} from "./config.service";
import {AllExceptionsFilter} from "./api/exception.filter";
import {getWAHAVersion, VERSION, WAHAVersion} from "./version";
import {AppModuleCore} from "./core/app.module.core";
import {SwaggerModuleCore} from "./core/swagger.module.core";
import {json, urlencoded} from "express";
import { NestFactory } from '@nestjs/core';
import { WhatsappConfigService } from './config.service';
import { AllExceptionsFilter } from './api/exception.filter';
import { getWAHAVersion, VERSION, WAHAVersion } from './version';
import { AppModuleCore } from './core/app.module.core';
import { SwaggerModuleCore } from './core/swagger.module.core';
import { json, urlencoded } from 'express';
async function loadModules(): Promise<[typeof AppModuleCore, typeof SwaggerModuleCore]> {
const version = getWAHAVersion()
console.log(`WAHA (WhatsApp HTTP API) - Running ${version} version...`)
async function loadModules(): Promise<
[typeof AppModuleCore, typeof SwaggerModuleCore]
> {
const version = getWAHAVersion();
console.log(`WAHA (WhatsApp HTTP API) - Running ${version} version...`);
if (version === WAHAVersion.CORE) {
const {AppModuleCore} = await import("./core/app.module.core")
const {SwaggerModuleCore} = await import("./core/swagger.module.core")
return [AppModuleCore, SwaggerModuleCore]
}
// Ignore if it's core version - there's no plus module
// @ts-ignore
const {AppModulePlus} = await import("./plus/app.module.plus")
// @ts-ignore
const {SwaggerModulePlus} = await import("./plus/swagger.module.plus")
return [AppModulePlus, SwaggerModulePlus]
if (version === WAHAVersion.CORE) {
const { AppModuleCore } = await import('./core/app.module.core');
const { SwaggerModuleCore } = await import('./core/swagger.module.core');
return [AppModuleCore, SwaggerModuleCore];
}
// Ignore if it's core version - there's no plus module
// @ts-ignore
const { AppModulePlus } = await import('./plus/app.module.plus');
// @ts-ignore
const { SwaggerModulePlus } = await import('./plus/swagger.module.plus');
return [AppModulePlus, SwaggerModulePlus];
}
async function bootstrap() {
const [AppModule, SwaggerModule] = await loadModules()
const app = await NestFactory.create(AppModule, {
logger: process.env.DEBUG != undefined ? ['log', 'debug', 'error', 'verbose', 'warn'] :
['log', 'error', 'warn'],
});
const [AppModule, SwaggerModule] = await loadModules();
const app = await NestFactory.create(AppModule, {
logger:
process.env.DEBUG != undefined
? ['log', 'debug', 'error', 'verbose', 'warn']
: ['log', 'error', 'warn'],
});
app.enableShutdownHooks();
app.useGlobalFilters(new AllExceptionsFilter());
app.enableShutdownHooks();
app.useGlobalFilters(new AllExceptionsFilter());
// Allow to send big body - for images and attachments
app.use(json({limit: '50mb'}));
app.use(urlencoded({limit: '50mb'}));
// Allow to send big body - for images and attachments
app.use(json({ limit: '50mb' }));
app.use(urlencoded({ limit: '50mb', extended: false }));
// Configure swagger
const swagger = new SwaggerModule()
swagger.configure(app)
// Configure swagger
const swagger = new SwaggerModule();
swagger.configure(app);
const config = app.get(WhatsappConfigService);
await app.listen(config.port);
console.log(`WhatsApp HTTP API is running on: ${await app.getUrl()}`);
const config = app.get(WhatsappConfigService);
await app.listen(config.port);
console.log(`WhatsApp HTTP API is running on: ${await app.getUrl()}`);
}
bootstrap();
+5 -5
View File
@@ -1,13 +1,13 @@
import {IsNotEmpty} from "class-validator";
import { IsNotEmpty } from 'class-validator';
export const WHATSAPP_DEFAULT_SESSION_NAME = "default"
export const WHATSAPP_DEFAULT_SESSION_NAME = 'default';
/**
* Queries
*/
export class SessionQuery {
@IsNotEmpty()
session: string = WHATSAPP_DEFAULT_SESSION_NAME;
@IsNotEmpty()
session: string = WHATSAPP_DEFAULT_SESSION_NAME;
}
/**
@@ -15,5 +15,5 @@ export class SessionQuery {
*/
export class SessionBaseRequest {
session = WHATSAPP_DEFAULT_SESSION_NAME
session = WHATSAPP_DEFAULT_SESSION_NAME;
}
+145 -112
View File
@@ -1,188 +1,221 @@
import {IsNotEmpty, IsString} from "class-validator";
import {ApiExtraModels, ApiProperty, getSchemaPath} from "@nestjs/swagger";
import {SessionBaseRequest, SessionQuery} from "./base.dto";
import { IsNotEmpty, IsString, IsOptional, IsNumber } from 'class-validator';
import { ApiExtraModels, ApiProperty, getSchemaPath } from '@nestjs/swagger';
import { SessionBaseRequest, SessionQuery } from './base.dto';
/**
* Queries
*/
export class CheckNumberStatusQuery extends SessionQuery {
@IsString()
phone: string
@IsString()
phone: string;
}
export class MessageTextQuery extends SessionQuery {
@IsString()
phone: string
@IsString()
text: string
@IsString()
phone: string;
@IsString()
text: string;
}
export class ChatQuery extends SessionQuery {
@ApiProperty({
example: '11111111111@c.us',
})
chatId: string;
}
export class GetMessageQuery extends ChatQuery {
@IsNumber()
limit: number;
}
/**
* Requests
*/
export class ChatRequest extends SessionBaseRequest {
@ApiProperty({
example: '11111111111@c.us',
})
chatId: string
@ApiProperty({
example: '11111111111@c.us',
})
chatId: string;
}
export class MessageRequest extends SessionBaseRequest{
@ApiProperty({
example: "false_11111111111@c.us_AAAAAAAAAAAAAAAAAAAA",
})
messageId: string
export class SendSeenRequest extends ChatRequest {
@ApiProperty({
example: 'false_11111111111@c.us_AAAAAAAAAAAAAAAAAAAA',
required: false,
description:
"NOWEB engine only - it's important to mark ALL messages as seen",
})
messageId?: string;
@ApiProperty({
example: '11111111111@c.us',
required: false,
description:
'NOWEB engine only - the ID of the user that sent the message (undefined for individual chats)',
})
participant?: string;
}
export class MessageRequest extends SessionBaseRequest {
@ApiProperty({
example: 'false_11111111111@c.us_AAAAAAAAAAAAAAAAAAAA',
})
messageId: string;
}
export class MessageContactVcardRequest extends ChatRequest {
contactsId: string;
name: string
contactsId: string;
name: string;
}
export class MessageTextRequest extends ChatRequest {
text = "Hi there!"
text = 'Hi there!';
}
export class Button {
id: string
body: string
id: string;
text: string;
}
export class MessageTextButtonsRequest extends ChatRequest {
title: string;
text: string;
buttons?: Array<Button>;
title: string;
footer?: string;
buttons?: Array<Button>;
}
export class MessageReplyRequest extends ChatRequest {
text = "Reply text"
@ApiProperty({
example: 'false_11111111111@c.us_AAAAAAAAAAAAAAAAAAAA',
})
reply_to: string;
text = 'Reply text';
@ApiProperty({
example: 'false_11111111111@c.us_AAAAAAAAAAAAAAAAAAAA',
})
reply_to: string;
}
export class MessageLocationRequest extends ChatRequest {
latitude: number;
longitude: number;
title: string;
latitude: number;
longitude: number;
title: string;
}
export class RemoteFile {
@ApiProperty({
description: "MIME type of the attachment.",
example: 'image/jpeg',
})
mimetype: string
@ApiProperty({
description: 'MIME type of the attachment.',
example: 'image/jpeg',
})
mimetype: string;
@ApiProperty({
description: "Document file name. Value can be null",
example: 'filename.jpg',
})
filename?: string | null
@ApiProperty({
description: 'Document file name. Value can be null',
example: 'filename.jpg',
})
filename?: string | null;
@ApiProperty({
example: 'https://github.com/devlikeapro/whatsapp-http-api/raw/core/examples/dev.likeapro.jpg',
})
url: string
@ApiProperty({
example:
'https://github.com/devlikeapro/whatsapp-http-api/raw/core/examples/dev.likeapro.jpg',
})
url: string;
}
export class BinaryFile {
@ApiProperty({
description: "MIME type of the attachment.",
example: 'image/jpeg',
})
mimetype: string
@ApiProperty({
description: 'MIME type of the attachment.',
example: 'image/jpeg',
})
mimetype: string;
@ApiProperty({
description: "Document file name. Optional",
example: 'filename.jpeg',
})
filename?: string | null
@ApiProperty({
description: 'Document file name. Optional',
example: 'filename.jpeg',
})
filename?: string | null;
@ApiProperty({
description: "Base64-encoded data of the file",
example: '/9j/4AAQSkZJRgABAQAAAQABAAD/4gIoSUNDX1BST0ZJTEUAAQEAAAIYAAAAAAQwAABtbnRyUkdCIFhZWiAAAAAAAAAAAAAAAABhY3NwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAA9tYAAQAAAADTLQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAlkZXNjAAAA8AAAAHRyWFlaAAABZAAAABRnWFlaAAABeAAAABRiWFlaAAABjAAAABRyVFJDAAABoAAAAChnVFJDAAABoAAAAChiVFJDAAABoAAAACh3dHB0AAAByAAAABRjcHJ0AAAB3AAAADxtbHVjAAAAAAAAAAEAAAAMZW5VUwAAAFgAAAAcAHMAUgBHAEIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFhZWiAAAAAAAABvogAAOPUAAAOQWFlaIAAAAAAAAGKZAAC3hQAAGNpYWVogAAAAAAAAJKAAAA+EAAC2z3BhcmEAAAAAAAQAAAACZmYAAPKnAAANWQAAE9AAAApbAAAAAAAAAABYWVogAAAAAAAA9tYAAQAAAADTLW1sdWMAAAAAAAAAAQAAAAxlblVTAAAAIAAAABwARwBvAG8AZwBsAGUAIABJAG4AYwAuACAAMgAwADEANv/bAEMABgQFBgUEBgYFBgcHBggKEAoKCQkKFA4PDBAXFBgYFxQWFhodJR8aGyMcFhYgLCAjJicpKikZHy0wLSgwJSgpKP/bAEMBBwcHCggKEwoKEygaFhooKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKP/AABEIADAAyAMBIgACEQEDEQH/xAAbAAABBQEBAAAAAAAAAAAAAAAAAwQFBgcCAf/EADwQAAEDBAECAwQGBwkBAAAAAAECAwQABQYRIRIxB0FRExQiYRUlcXOBkQgnMjNCdbEWJENSdJKhssHh/8QAFAEBAAAAAAAAAAAAAAAAAAAAAP/EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAMAwEAAhEDEQA/AMg8UfEO755kUqVMlPJtocUIkILIbab3x8PYqI5JPO/lqqVuk90boFN0bpPdTrOLXR7DJGUobZ+iGJYhLWXR1h0gEDp76+Ic0ENujdJ7o3QKbo3Se6N0Etjh+v7f98mtT3WUY4fr+3/fJrU90Cm6N1INWOc7jj18Qlv6PZfEdaisdXWda+H05HNcSLNcY9njXV6KtFukrLbL5I6VqG9gc7/hPl5UDLdG6kLtZJ1qgWyZMS2li4tF6OUrCiUjXceXcVF7oFN0bpPdG6BTdG6T3RugU3Ruk90boFN0bpPdG6BTde7pLdG6C04Rl1wxe7sPx33DDKwJEYqJQ4jz48iB2NFVbdFBi26N0nuvCdgj1GqDX7PguKY/h1ryLxMuN0bVd0lyBa7WhPtlND/EWpXAB2DrjgjueBZr+jGW/wBGq7qw2Vc3rYu/NKUm5NpQ60vpRtJKeFDWjv5/Ko7x8t0y+454e5HZ4z8q0LsbUQrYbK0tOo7pVrseSPtSfSvHLLc7N+ipc0XaBIhOSb62+yiQgoUtspQArR50SD39KCFRh2IYjYbVO8SJ13Xc7qwJUe02kNpW0wf2VurXwCeeB6efOvL9hOKi1WLLLBdLk9hky4JgT0yEoEuAvudkDpV8PPb07740zxhyq8xoOO5HjmO2G8Y7PtrPTLkWpMpbLg3ttau6QPIHz6h5VlmXZhmN88PnY8/HbfbcZXLbUp2JbPdULe0ekA70o6Sd6B4FBB+LOHnBM3m2ZDrj8NKUPxX1626yobB44JB6gdelPPEPDYWH45ihekyV5DdYnv0uMrp9nHaV+7AGt9R89n+E1q2LY+14xYdgdwlLQZWOy/o28KWoAqhoHtEqO/klKftWr0rFvFfKzmef3e8pJ92dd9nFT5JYR8KBry2Bv7SaCHxw/X9v++TWqbrJ8cP1/b/vk1qm6DRref1GXb+cN/0RURdLMY3h5Y7x9Iznfe5LrXujjm2Gukq+JCfInXP2mpS3H9RV2/nDf/VFGQH9SuKf6+R/VygcZ5HfmYx4dxojS3pD0AobbQNqUo9GgBUBltkteNssW1ya7LyXQXLaYKSxFB7IJ1tS+3AP/m9GXl9uxPHsCenW1x/3i3Kacmtr05EaISFFseatlJ9dJNZxlWNKxK7Q5zbxuNllOJlRZwPX7wnqCiFHzXrv69/XQSM+z4riYjxcwk3WTeXWkvOQ7aEARUq5AWpXdWvIf/TH5bYIMK12y949MdmWW5BYaL6Ql1pxPdtYHG+D+R+03vxUv97t99FwtVmsVwsk9lt6NOdt4fUv4RsKWPP035EVTrjdciv8Ozw8gt8K12F2ehLbkaH7uOtXCiN9/hUTvWt0HKJXhnHbQ1IlZNKf6R1usNIShKtc6BAJG6a5xjjOPy4DlvmGba7jHTKiPKT0qKD5KHqNj86t2a3e/YrlDthw6ww4ENgISw6IHt3ZO0glXUQd8kj145pDxqXPVGw5V4HTcjbyZI6QnTm0dXA4HPkKDjKccwbD7yIt8u93lKW0lxMSGykuoBH7S16CdHyA54qCzjHINrttsvePTXJtiuSFFlbqdONrT3Qrt6H8iKmvHKBMXnftm4clTbkRhKVoZUQogHYBA5pLNWl2Lwoxyx3BJaub7709TCuFtNnqA6h5b6h/z6UC+RYdiuNuwXb5kj8aPJioeTGba9rJWo9yABpKO2iRyd1GWrG7FKYvF+kXSU1iMBxLbb/sv7xJWQNISkjg7Ou3mPmQ58bgn6fsqtDq+h443+KqVtDSr34KXCBbkKem225pmPMoG1ltSeFADv3P+00DeLYsYyqFP/sXJurN1hMmQYNySnb7Y7lCk+fbj5j13VDCtjYrRPBWM7CyCZf5ra2bXbYTyn33ElKdkD4dnueCdfKs4UvqUVa6eo716b8qDvdFJ7ooMW3Rurt4seHF5wDI5caZEeVay4oxJoQS263v4fi7BQHBB538tGqJ1UFzw/xKy7DobkTHL5IhxHFFRZ6UOICj3ISsEJP2U1vWeZPe7fKg3e+TZsSU+JLzby+oKcAAB7caAGgNDjtVW6qOqgtuI+IOVYe2tvHL5LhMrV1KZSQtsn16FAjfz1XmXeIGU5g2hvJL5LnMoV1pZUQlsK9ehICd8nnXnVT6qOqgnrFlV7sEK5Q7Nc5EONcW/ZS22lAB1OiNH8FKHHrUNukuqjqoJbHD9fQPvk1qnVWUY2fr6B98mtS3QP0XW4otq7Yia4m2OOB5cUAdKnBrSvXfA/Kh263F63sW56a4u3R1FbMYgdLajvZHnzs/nTHdG6B9LulwnR4sedNckR4iPZxm1gaZRx8I19g7+ldM3i6M2hdobnu/RCle09zUApCVb3tOxtPPPHz9aj90boJyw5bkmOslixXuRFjEkiOoJcbST30lQOvwpvesgvV/WF3+6yZ+t9KHCAhG+/SkaAqL3RugszOfZmxbU29nJJaYqU9CT0pLqU+gc11fjvdREy7XK4oit3Oe/MRFT7Nj2xBLaOON9z2HJphujdBq3iZ4gXmNl7pxHJvq5UZkaYKHmgvR6tbBAPbeqzKbMl3CW7Luct6bMe/ePPK6lK9B8h8hTRAShOkAJHoK63QP7jdbjdXW3btNcmOtNhptTgAKUDska8hs1zbLncLRORNs05+DMQOkOtHun/KodiPkaZbo3QTt/wAvyXI2UsX68vSoySFewSlLbaiOxUEgb/GobqpPdG6BTqoqyYFh9xy28x48aO6IQWDIklJCG0b5581EcACig//Z',
})
data: string
@ApiProperty({
description: 'Base64-encoded data of the file',
example:
'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',
})
data: string;
}
@ApiExtraModels(BinaryFile, RemoteFile)
class FileRequest extends ChatRequest {
@ApiProperty({
oneOf: [
{$ref: getSchemaPath(BinaryFile)},
{$ref: getSchemaPath(RemoteFile)},
],
})
file: BinaryFile | RemoteFile
@ApiProperty({
oneOf: [
{ $ref: getSchemaPath(BinaryFile) },
{ $ref: getSchemaPath(RemoteFile) },
],
})
file: BinaryFile | RemoteFile;
}
export class MessageImageRequest extends FileRequest {
caption: string;
caption: string;
}
export class MessageFileRequest extends FileRequest {
caption: string;
}
export class VoiceBinaryFile {
@ApiProperty({
description: "MIME type of the attachment.",
})
mimetype = "audio/ogg; codecs=opus"
@ApiProperty({
description: 'MIME type of the attachment.',
})
mimetype = 'audio/ogg; codecs=opus';
@ApiProperty({
description: "Document file name. Optional",
})
filename = "voice-message.mp3"
@ApiProperty({
description: 'Document file name. Optional',
})
filename = 'voice-message.mp3';
@ApiProperty({
description: "Base64-encoded data of the file",
example: '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 Line truncated
})
data: string
@ApiProperty({
description: 'Base64-encoded data of the file',
example:
'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 Line truncated
})
data: string;
}
export class VoiceRemoteFile {
@ApiProperty({
description: "MIME type of the attachment.",
})
mimetype = "audio/ogg; codecs=opus"
@ApiProperty({
description: 'MIME type of the attachment.',
})
mimetype = 'audio/ogg; codecs=opus';
@ApiProperty({
description: "Document file name. Optional",
})
filename = "voice-message.mp3"
@ApiProperty({
example: 'https://github.com/devlikeapro/whatsapp-http-api/raw/core/examples/dev.likeapro.mp3',
})
url: string
@ApiProperty({
description: 'Document file name. Optional',
})
filename = 'voice-message.mp3';
@ApiProperty({
example:
'https://github.com/devlikeapro/whatsapp-http-api/raw/core/examples/dev.likeapro.mp3',
})
url: string;
}
@ApiExtraModels(VoiceBinaryFile, VoiceRemoteFile)
export class MessageVoiceRequest extends ChatRequest {
@ApiProperty({
oneOf: [
{$ref: getSchemaPath(VoiceBinaryFile)},
{$ref: getSchemaPath(VoiceRemoteFile)},
],
})
file: VoiceBinaryFile | VoiceRemoteFile
@ApiProperty({
oneOf: [
{ $ref: getSchemaPath(VoiceBinaryFile) },
{ $ref: getSchemaPath(VoiceRemoteFile) },
],
})
file: VoiceBinaryFile | VoiceRemoteFile;
}
export class MessageLinkPreviewRequest extends ChatRequest {
url: string;
title: string;
url: string;
title: string;
}
export class MessageReactionRequest extends MessageRequest {
@ApiProperty({
description: "Emoji to react with. Send an empty string to remove the reaction",
example: '👍',
})
reaction: string
@ApiProperty({
description:
'Emoji to react with. Send an empty string to remove the reaction',
example: '👍',
})
reaction: string;
}
+6 -8
View File
@@ -1,22 +1,20 @@
import {IsString} from "class-validator";
import {SessionBaseRequest, SessionQuery} from "./base.dto";
import { IsString } from 'class-validator';
import { SessionBaseRequest, SessionQuery } from './base.dto';
/**
* Queries
*/
export class ContactQuery extends SessionQuery {
@IsString()
contactId: string
@IsString()
contactId: string;
}
/**
* Requests
*/
export class ContactRequest extends SessionBaseRequest {
@IsString()
contactId: string
@IsString()
contactId: string;
}
+14 -12
View File
@@ -1,22 +1,24 @@
export const SECOND = 1000;
export enum WAEvents {
MESSAGE = "message",
MESSAGE_ANY = "message.any",
MESSAGE_ACK = "message.ack",
STATE_CHANGE = "state.change",
GROUP_JOIN = "group.join",
GROUP_LEAVE = "group.leave",
MESSAGE = 'message',
MESSAGE_ANY = 'message.any',
MESSAGE_ACK = 'message.ack',
STATE_CHANGE = 'state.change',
GROUP_JOIN = 'group.join',
GROUP_LEAVE = 'group.leave',
}
export enum WhatsappStatus {
STARTING = "STARTING",
SCAN_QR_CODE = "SCAN_QR_CODE",
WORKING = "WORKING",
FAILED = "FAILED",
STOPPED = 'STOPPED',
STARTING = 'STARTING',
SCAN_QR_CODE = 'SCAN_QR_CODE',
WORKING = 'WORKING',
FAILED = 'FAILED',
}
export enum WhatsappEngine {
VENOM = "VENOM",
WEBJS = "WEBJS"
VENOM = 'VENOM',
WEBJS = 'WEBJS',
NOWEB = 'NOWEB',
}
+13 -14
View File
@@ -1,40 +1,39 @@
import {IsArray, IsString} from "class-validator";
import { IsArray, IsString } from 'class-validator';
/**
* Structures
*/
export class Participant {
@IsString()
id: string
@IsString()
id: string;
}
/**
* Queries
*/
/**
* Requests
*/
export class ParticipantsRequest {
@IsArray()
participants: Array<Participant>
@IsArray()
participants: Array<Participant>;
}
export class DescriptionRequest {
@IsString()
description: string
@IsString()
description: string;
}
export class SubjectRequest {
@IsString()
subject: string
@IsString()
subject: string;
}
export class CreateGroupRequest {
@IsString()
name: string
@IsString()
name: string;
@IsArray()
participants: Array<Participant>
@IsArray()
participants: Array<Participant>;
}
+110 -101
View File
@@ -1,139 +1,148 @@
import {ApiProperty} from "@nestjs/swagger";
import {WAEvents} from "./enums.dto";
import { ApiProperty } from '@nestjs/swagger';
import { WAEvents, WhatsappEngine } from './enums.dto';
export class WANumberExistResult {
numberExists: boolean
numberExists: boolean;
}
export class WAGroupNotification {
@ApiProperty({
description: "ID that represents the groupNotification",
})
id: any
@ApiProperty({
description: 'ID that represents the groupNotification',
})
id: any;
@ApiProperty({
description: "Unix timestamp for when the groupNotification was created",
})
timestamp: number
@ApiProperty({
description: 'Unix timestamp for when the groupNotification was created',
})
timestamp: number;
@ApiProperty({
description: "ID for the Chat that this groupNotification was sent for",
})
chatId: string
@ApiProperty({
description: 'ID for the Chat that this groupNotification was sent for',
})
chatId: string;
@ApiProperty({
description: 'ContactId for the user that produced the GroupNotification',
})
author: string;
@ApiProperty({
description: "ContactId for the user that produced the GroupNotification",
})
author: string
@ApiProperty({
description: 'Extra content',
})
body: string;
@ApiProperty({
description: "Extra content",
})
body: string
@ApiProperty({
description: "Contact IDs for the users that were affected by this GroupNotification",
})
recipientIds: string[]
@ApiProperty({
description:
'Contact IDs for the users that were affected by this GroupNotification',
})
recipientIds: string[];
}
export enum WAMessageAck {
ACK_ERROR = -1,
ACK_PENDING = 0,
ACK_SERVER = 1,
ACK_DEVICE = 2,
ACK_READ = 3,
ACK_PLAYED = 4,
ACK_ERROR = -1,
ACK_PENDING = 0,
ACK_SERVER = 1,
ACK_DEVICE = 2,
ACK_READ = 3,
ACK_PLAYED = 4,
}
export class WALocation {
description?: string | null
latitude: string
longitude: string
description?: string | null;
latitude: string;
longitude: string;
}
export class WAMessage {
@ApiProperty({
description: "Message ID",
example: "false_11111111111@c.us_AAAAAAAAAAAAAAAAAAAA"
})
id: string
@ApiProperty({
description: 'Message ID',
example: 'false_11111111111@c.us_AAAAAAAAAAAAAAAAAAAA',
})
id: string;
/** */
@ApiProperty({
description: "Unix timestamp for when the message was created",
example: 1666943582
})
timestamp: number
/** */
@ApiProperty({
description: 'Unix timestamp for when the message was created',
example: 1666943582,
})
timestamp: number;
@ApiProperty({
description: "ID for the Chat that this message was sent to, except if the message was sent by the current user "
})
from: string
@ApiProperty({
description:
'ID for the Chat that this message was sent to, except if the message was sent by the current user ',
})
from: string;
@ApiProperty({
description: "Indicates if the message was sent by the current user"
})
fromMe: boolean
@ApiProperty({
description: 'Indicates if the message was sent by the current user',
})
fromMe: boolean;
@ApiProperty({
description: `
@ApiProperty({
description: `
* ID for who this message is for.
* If the message is sent by the current user, it will be the Chat to which the message is being sent.
* If the message is sent by another user, it will be the ID for the current user.
`
})
to: string
`,
})
to: string;
@ApiProperty({
description: "Message content"
})
body: string
@ApiProperty({
description: 'For groups - participant who sent the message',
})
participant: string;
@ApiProperty({
description: "Indicates if the message has media available for download"
})
hasMedia: boolean
@ApiProperty({
description: 'Message content',
})
body: string;
@ApiProperty({
description: "The URL for the media in the message if any",
example: "http://localhost:3000/api/files/false_11111111111@c.us_AAAAAAAAAAAAAAAAAAAA.oga"
})
mediaUrl: string
@ApiProperty({
description: 'Indicates if the message has media available for download',
})
hasMedia: boolean;
@ApiProperty({
description: "ACK status for the message"
})
ack: WAMessageAck
@ApiProperty({
description: 'The URL for the media in the message if any',
example:
'http://localhost:3000/api/files/false_11111111111@c.us_AAAAAAAAAAAAAAAAAAAA.oga',
})
mediaUrl: string;
@ApiProperty({
description: "If the message was sent to a group, this field will contain the user that sent the message."
})
author?: string;
@ApiProperty({
description: 'ACK status for the message',
})
ack: WAMessageAck;
@ApiProperty({
description: "Location information contained in the message, if the message is type \"location\""
})
location?: WALocation
@ApiProperty({
description:
'If the message was sent to a group, this field will contain the user that sent the message.',
})
author?: string;
@ApiProperty({
description: "List of vCards contained in the message."
})
vCards?: string[]
@ApiProperty({
description:
'Location information contained in the message, if the message is type "location"',
})
location?: WALocation;
/** Returns message in a raw format */
@ApiProperty({
description: "Message in a raw format that we get from WhatsApp. May be changed anytime, use it with caution! It depends a lot on the underlying backend."
})
_data?: any
@ApiProperty({
description: 'List of vCards contained in the message.',
})
vCards?: string[];
/** Returns message in a raw format */
@ApiProperty({
description:
'Message in a raw format that we get from WhatsApp. May be changed anytime, use it with caution! It depends a lot on the underlying backend.',
})
_data?: any;
}
export class WAWebhook {
event: WAEvents
session: string
// eslint-disable-next-line @typescript-eslint/ban-types
payload: WAMessage | WAGroupNotification | object
event: WAEvents;
session: string;
engine: WhatsappEngine;
// eslint-disable-next-line @typescript-eslint/ban-types
payload: WAMessage | WAGroupNotification | object;
}
+31 -5
View File
@@ -1,14 +1,40 @@
import {WhatsappStatus} from "./enums.dto";
import { WhatsappStatus } from './enums.dto';
import { ApiProperty } from '@nestjs/swagger';
/**
* Queries
*/
export class ListSessionsQuery {
@ApiProperty({
example: false,
required: false,
description:
'Return all sessions, including those that are in the STOPPED state.',
})
all: boolean;
}
/**
* Requests
*/
export class SessionStartRequest {
name = "default"
name = 'default';
}
export class SessionStopRequest {
name = "default"
name = 'default';
@ApiProperty({
example: false,
required: false,
description: 'Stop and logout from the session.',
})
logout = false;
}
export class SessionLogoutRequest {
name = 'default';
}
export class SessionDTO {
name = "default"
status: WhatsappStatus
name = 'default';
status: WhatsappStatus;
}
+19 -19
View File
@@ -1,34 +1,34 @@
// eslint-disable-next-line @typescript-eslint/no-var-requires
import {getBrowserExecutablePath} from "./core/abc/session.abc";
import { getBrowserExecutablePath } from './core/abc/session.abc';
// eslint-disable-next-line @typescript-eslint/no-var-requires
const fs = require('fs')
const fs = require('fs');
// eslint-disable-next-line @typescript-eslint/no-var-requires
const path = require('path');
export enum WAHAVersion {
PLUS = "PLUS",
CORE = "CORE",
PLUS = 'PLUS',
CORE = 'CORE',
}
export function getWAHAVersion(): WAHAVersion {
// force core version if env variables set
const waha_version = process.env.WAHA_VERSION;
if (waha_version && waha_version === WAHAVersion.CORE) {
return WAHAVersion.CORE
}
// force core version if env variables set
const waha_version = process.env.WAHA_VERSION;
if (waha_version && waha_version === WAHAVersion.CORE) {
return WAHAVersion.CORE;
}
// Check the plus directory exists
const plusExists = fs.existsSync(`${__dirname}/plus`)
if (plusExists) {
return WAHAVersion.PLUS
}
// Check the plus directory exists
const plusExists = fs.existsSync(`${__dirname}/plus`);
if (plusExists) {
return WAHAVersion.PLUS;
}
return WAHAVersion.CORE
return WAHAVersion.CORE;
}
export const VERSION = {
version: "2023.3.25",
tier: getWAHAVersion(),
browser: getBrowserExecutablePath()
}
version: '2023.6.1',
tier: getWAHAVersion(),
browser: getBrowserExecutablePath(),
};
+924 -137
View File
File diff suppressed because it is too large. Load diff