Compare commits

..
96 Commits
Author SHA1 Message Date
allburov abfcbc7397 [core] Update WEBJS
Release / deploy-docker (push) Waiting to run
2023-08-23 16:40:46 +03:00
allburov 96f2982325 [core] Bump to 2023.8.23 2023-08-23 12:15:06 +03:00
allburov 3a521bc06c [core] Update WEBJS engine 2023-08-23 12:14:51 +03:00
allburov 6fbaec0083 [core] Bump to 2023.8.21 2023-08-21 14:25:06 +03:00
allburov cae94c7b5c [core] catch error while handling incoming message NOWEB 2023-08-21 14:24:51 +03:00
allburov bce012f692 [core] Bump version to 2023.8.19
Release / deploy-docker (push) Waiting to run
2023-08-19 12:36:08 +03:00
allburov b23b639a8b [core] Support message.ack for NOWEB, add ackName 2023-08-19 12:35:44 +03:00
allburov 5a628907ed [core] Add pairing auth method for NOWEB, add QR endpoint 2023-08-13 16:08:56 +03:00
allburov 6da479c0d7 [core] Update WEBJS engine
Release / deploy-docker (push) Waiting to run
2023-08-10 10:45:40 +03:00
allburov cbb321df6d [core] Bump to 2023.8.10 2023-08-10 07:27:36 +03:00
allburov 0a7beb6909 [core] Update webjs engine
Fix TypeError: Cannot read properties of undefined (reading 'CallCollection')
2023-08-10 07:27:33 +03:00
allburov 2105094ac3 [core] Bump version to 2023.8.8
Release / deploy-docker (push) Waiting to run
2023-08-08 08:57:31 +03:00
allburov d8fee686f8 [core] Update WEBJS and NOWEB engines 2023-08-08 08:56:23 +03:00
allburov 75fc1fd7e0 [docs] Update swagger (openapi) 2023-08-07 08:06:36 +03:00
allburov e718652491 [docs] Keep only versions as headers 2023-08-07 08:05:01 +03:00
allburov bcb49f2be7 [docs] Fix changelog for stories 2023-08-06 18:52:56 +03:00
allburov dab63e4060 [example] Send message and reply 2023-08-06 18:02:36 +03:00
allburov 3fdacbb6ac [core] set defaultQueryTimeoutMs to undefined 2023-08-01 16:42:16 +07:00
allburov f02b62e620 [plus] Show macos by default 2023-08-01 15:42:59 +07:00
allburov 51acce14a0 [plus] Add session to what's next? 2023-08-01 15:28:26 +07:00
allburov b63f6ac39e [core] Add status endpoints 2023-07-25 17:42:44 +07:00
allburov 9e107a8a76 [core] Update noweb engine 2023-07-23 16:49:21 +07:00
allburov 8c083242d3 [core] add documentation and changelog for info-admin-only 2023-07-21 16:08:04 +07:00
gqlxj1987 2a068d561f [core] Add PUT /api/groups/:id/settings/security/info-admin-only endpoint 2023-07-21 13:24:23 +07:00
allburov ef96fbf8c5 [core] Fix chatId bracket 2023-07-16 16:51:50 +07:00
allburov 2c381a0b9c [core] Add openapi json link 2023-07-16 16:41:37 +07:00
allburov fef2bcb6e2 [core] create directory before saving file
Release / deploy-docker (push) Waiting to run
2023-07-16 16:25:41 +07:00
allburov 2f6ed2fb58 [core] Add chats endpoints
Release / deploy-docker (push) Waiting to run
2023-07-16 16:14:12 +07:00
allburov 4eb23d2c09 [core] Bump version to 2023.7.16 2023-07-16 16:14:08 +07:00
allburov ec3de8dbcf [core] Init folder for a session 2023-07-16 13:30:40 +07:00
allburov f6f09747dc [core] pin node:20-bullseye
Release / deploy-docker (push) Waiting to run
2023-07-14 10:10:28 +07:00
allburov 27a534a605 [core] add set session presence endpoint 2023-07-12 16:46:15 +07:00
allburov 3cbd940843 [core] Add swagger image at the top of quick start 2023-07-07 14:26:23 +07:00
allburov e728eec01a [core] Move tiers
Release / deploy-docker (push) Waiting to run
2023-07-05 10:29:10 +07:00
allburov d06d2b78a8 [core] Move donations to top, add links 2023-07-05 10:27:32 +07:00
allburov 5878a9196e [core] Pin yarn version to 3.6.1 2023-07-03 21:52:32 +07:00
allburov 85dd061f96 [core] ignore .yaml folder 2023-07-03 21:52:29 +07:00
allburov 8381c3e946 [core] Set retention days 5 2023-07-03 11:01:51 +07:00
allburov 6ff8a9705d [core] Add cleanup github script 2023-07-03 10:59:56 +07:00
allburov fb5819b966 [core] Create folder if there's no yet
Release / deploy-docker (push) Waiting to run
2023-06-29 19:05:26 +07:00
allburov 5cc1af3f82 [core] Bump to 2023.7.1 2023-06-29 18:56:12 +07:00
allburov c279de694c [core] Update NOWEB engine 2023-06-29 18:55:20 +07:00
allburov f7a1787f1f [core] Add command to update NOWEB 2023-06-29 18:55:20 +07:00
allburov 5442fd6e93 [core] Update WEBJS engine 2023-06-29 18:55:20 +07:00
allburov b8c8ca7653 [core] Use yarn v2 commands to update package 2023-06-29 18:55:20 +07:00
allburov 5ca7dbc84b [core] Update swagger 2023-06-29 18:38:41 +07:00
allburov d836c9706f [core] Add for-swagger make command 2023-06-29 18:38:34 +07:00
allburov 318be43a4f [core] fix start\stop issue with NOWEB engine 2023-06-29 18:31:36 +07:00
allburov 95bebce731 [core] fix sessions with no config 2023-06-29 18:31:34 +07:00
allburov 27b8c9d058 [core] Make config optional 2023-06-29 18:14:23 +07:00
allburov 10d4d24526 [core] Place Boosty first 2023-06-29 18:07:14 +07:00
allburov b19917bd3d [core] Always logout from session 2023-06-29 18:06:07 +07:00
allburov 4b6774b1db [core] WAHA Plus links last 2023-06-26 11:29:31 +07:00
allburov 360e0a7951 [core] All tiers 2023-06-26 11:28:01 +07:00
allburov 171f516eee [core] Patreon first 2023-06-26 11:17:58 +07:00
allburov 01943f6970 [core] Remove global env.varilabels from webhooks 2023-06-25 14:01:22 +07:00
allburov 953d48ee1f [core] Add links to Quick Start 2023-06-25 14:00:51 +07:00
allburov 2b6028bcb1 [core] Make alert dismissible 2023-06-25 13:51:36 +07:00
allburov 79b7b7d2a2 [core] Adjust headers 2023-06-25 13:34:11 +07:00
allburov 93a6a7b87f [core] Remove youtube video from other pages 2023-06-25 13:14:30 +07:00
allburov 3b499d6af5 [core] Change headers to mobile first 2023-06-23 15:04:22 +07:00
allburov 6481919597 [core] Change swagger title to WAHA 2023-06-23 14:57:17 +07:00
allburov 228f6ec8c5 [core] No one-time 2023-06-23 14:48:03 +07:00
allburov 6723f201b7 [core] Add make start-proxy command 2023-06-23 14:45:22 +07:00
allburov c250bb49f0 [core] Make proxy per session 2023-06-23 14:44:14 +07:00
allburov 3b607662e5 [core] Flexible webhooks 2023-06-22 18:20:27 +07:00
allburov f2d7041cb0 [core] Fix Dockerfile with yarn v2 2023-06-19 13:10:43 +07:00
allburov afc06794c2 [core] No mobile yet 2023-06-19 12:16:52 +07:00
allburov 33eaafcff1 [core] update NOWEB engine, use yarn v2 2023-06-18 17:38:06 +07:00
allburov e5a8743b9a [core] Remove Get on links 2023-06-18 15:17:44 +07:00
allburov 3322c4e2a5 [core] Add eslint fix import plugin 2023-06-16 16:17:31 +07:00
allburov aa39aaf489 [core] You can run, not configure 2023-06-16 15:01:30 +07:00
allburov a238581f44 [core] Add presence event 2023-06-16 15:00:44 +07:00
allburov 00814ef64e [core] Add dedicated webhooks page 2023-06-16 14:56:35 +07:00
allburov 6362d2966b [core] Add active link to docker-compose.yaml file 2023-06-14 12:36:44 +07:00
allburov 4e181a82b6 [core] Use plus image in sessions 2023-06-14 12:35:45 +07:00
allburov 374199905d [core][noweb] Add presence endpoints and webhook 2023-06-14 12:27:39 +07:00
allburov 642079ab1f [core] Add changelog about mentions 2023-06-12 15:31:35 +07:00
allburov 215c9d96b9 [core] Add mentions
Fix #105
2023-06-12 15:27:53 +07:00
allburov 5369eda705 [core] Fix engine field 2023-06-09 16:25:31 +07:00
allburov 5e42bbe1eb [core] ignore webjs cache 2023-06-09 16:25:26 +07:00
allburov 79dd2ee03e [core] bump version to 2023.6.9 2023-06-09 16:25:24 +07:00
allburov 78d3eae44b [core] Fix storage issue 2023-06-09 16:25:22 +07:00
allburovandPierluca Lino a9ee68fe1a [core] Add proxy support
Co-authored-by: Pierluca Lino <pierluca.lino@dontouch.ch>

Co-authored-by: puntolino <pierluca.lino@dontouch.ch>
2023-06-07 17:02:18 +07:00
allburov ee5678ccfb [core] yarn upgrade instead of add 2023-06-07 17:02:18 +07:00
allburov 248a675c1a [core] No core images 2023-06-05 17:14:21 +07:00
allburov 6b1d84cf38 [core] Show core features first 2023-06-05 16:59:52 +07:00
allburov c0977538ff [core] change header 2023-06-03 18:51:23 +07:00
allburov c8727e8b2f [core] Add a link to PLUS 2023-06-03 18:49:01 +07:00
allburov 55a41e19f4 [core] Rename to WAHA Plus 2023-06-03 18:47:09 +07:00
allburov 6096c0b158 [core] Move help's files to overview 2023-06-03 18:44:35 +07:00
allburov 161c6dfd95 [core] Receive message 2023-06-03 18:39:16 +07:00
allburov 078bcf7652 [core] env variables block 2023-06-03 18:33:47 +07:00
allburov 57c6df3970 [core] Adjust sessions how to 2023-06-03 18:27:58 +07:00
allburov 1130ee1b21 [core] Add docker run on index 2023-06-01 13:15:02 +07:00
allburov c9c0a29740 Use 2023 year in changelog 2023-06-01 12:10:37 +07:00
82 changed files with 16689 additions and 10122 deletions

No files matched your search

+1
View File
@@ -11,6 +11,7 @@ examples
# compiled output
/dist
/node_modules
.yarn
# Logs
logs
+31 -25
View File
@@ -1,27 +1,33 @@
module.exports = {
parser: '@typescript-eslint/parser',
parserOptions: {
project: 'tsconfig.json',
sourceType: 'module',
},
plugins: ['@typescript-eslint/eslint-plugin'],
extends: [
'plugin:@typescript-eslint/eslint-recommended',
'plugin:@typescript-eslint/recommended',
'prettier',
'prettier/@typescript-eslint',
],
root: true,
env: {
node: true,
jest: true,
},
rules: {
'@typescript-eslint/interface-name-prefix': 'off',
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-explicit-any': 'off',
"@typescript-eslint/ban-ts-ignore": "off",
"@typescript-eslint/ban-ts-comment": "off"
},
parser: "@typescript-eslint/parser",
parserOptions: {
project: "tsconfig.json",
sourceType: "module"
},
plugins: [
"@typescript-eslint/eslint-plugin",
"simple-import-sort"
],
extends: [
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended",
"prettier",
"prettier/@typescript-eslint"
],
root: true,
env: {
node: true,
jest: true
},
rules: {
"simple-import-sort/imports": "error",
"simple-import-sort/exports": "error",
"@typescript-eslint/interface-name-prefix": "off",
"@typescript-eslint/explicit-function-return-type": "off",
"@typescript-eslint/explicit-module-boundary-types": "off",
"@typescript-eslint/no-inferrable-types": "off",
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/ban-ts-ignore": "off",
"@typescript-eslint/ban-ts-comment": "off",
}
};
+18
View File
@@ -0,0 +1,18 @@
# Idea from
# https://stackoverflow.com/a/65539398/6753144
#
OWNER=devlikeapro
REPO=whatsapp-http-api-plus
# list workflows
gh api -X GET /repos/$OWNER/$REPO/actions/workflows | jq '.workflows[] | .name,.id'
# copy the ID of the workflow you want to clear and set it
WORKFLOW_ID=40218610
# list runs
gh api -X GET /repos/$OWNER/$REPO/actions/workflows/$WORKFLOW_ID/runs --paginate | jq '.workflow_runs[] | .id'
# delete runs
gh api -X GET /repos/$OWNER/$REPO/actions/workflows/$WORKFLOW_ID/runs --paginate | jq '.workflow_runs[] | .id' | xargs -I{} gh api -X DELETE /repos/$OWNER/$REPO/actions/runs/{} --silent
+1
View File
@@ -31,6 +31,7 @@ jobs:
with:
path: docs/site/public
name: public
retention-days: 5
deploy-github-pages:
needs: build
+3
View File
@@ -1,9 +1,12 @@
.yarn
example.ts
.*sessions
tokens
files
venv
.wwebjs_auth
test.js
.wwebjs_cache
# compiled output
/dist
+1 -1
View File
@@ -3,7 +3,7 @@ repos:
rev: 'v2.7.1'
hooks:
- id: prettier
types_or: [javascript, ts]
types_or: [ javascript, ts ]
files: |
(?x)^(
src/.*|
+3
View File
@@ -0,0 +1,3 @@
nodeLinker: node-modules
yarnPath: .yarn/releases/yarn-3.6.1.cjs
+6 -13
View File
@@ -1,33 +1,26 @@
#
# Build
#
FROM node:18 as build
FROM node:20-bullseye 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 .
COPY yarn.lock .
RUN yarn set version 3.6.1
RUN yarn install
# App
WORKDIR /src
ADD . /src
RUN npm run build && find ./dist -name "*.d.ts" -delete
RUN yarn install
RUN yarn build && find ./dist -name "*.d.ts" -delete
#
# Final
#
FROM node:18 as release
FROM node:20-bullseye as release
ENV PUPPETEER_SKIP_DOWNLOAD=True
ARG USE_BROWSER=chromium
@@ -66,4 +59,4 @@ COPY --from=build /src/dist ./dist
# Run command, etc
EXPOSE 3000
CMD npm run start:prod
CMD yarn start:prod
+9 -2
View File
@@ -13,9 +13,16 @@ clean: stop
push:
docker push devlikeapro/whatsapp-http-api
for-swagger:
WHATSAPP_SWAGGER_CONFIG_ADVANCED=true npm run start
update-swagger:
wget http://localhost:3000/-json -O ./docs/site/static/swagger/openapi.json
update-dependencies:
. ${NVM_DIR}/nvm.sh && nvm exec yarn add whatsapp-web.js@https://github.com/pedroslopez/whatsapp-web.js
. ${NVM_DIR}/nvm.sh && nvm exec yarn add venom-bot
. ${NVM_DIR}/nvm.sh && nvm exec yarn up whatsapp-web.js@https://github.com/pedroslopez/whatsapp-web.js
. ${NVM_DIR}/nvm.sh && nvm exec yarn up @adiwajshing/baileys@github:WhiskeySockets/Baileys
. ${NVM_DIR}/nvm.sh && nvm exec yarn up venom-bot
start-proxy:
docker run --rm -d --name squid-container -e TZ=UTC -p 3128:3128 ubuntu/squid:5.2-22.04_beta
+1
View File
@@ -75,6 +75,7 @@
- Block/unblock contacts
- Get contact info
- Get profile pictures
- Get presence (online\offline\typing) status
# Quick start
+61 -16
View File
@@ -19,42 +19,87 @@
[[main]]
name = "Documentation"
identifier = "docs"
url = "/docs/overview/introduction/"
# url = "/docs/1.0/overview/introduction/"
weight = 10
#[[main]]
# name = "Blog"
# url = "/blog/"
# weight = 20
[[main]]
name = "Introduction"
weight = 11
url = "/docs/overview/introduction"
parent = "docs"
[[main]]
name = "Get Plus"
weight = 30
identifier = "get-plus"
url = ""
name = "Quick Start"
weight = 12
url = "/docs/overview/introduction"
parent = "docs"
[[main]]
name = "Send messages"
weight = 14
url = "/docs/how-to/send-messages"
parent = "docs"
[[main]]
name = "Receive Messages"
weight = 15
url = "/docs/how-to/receive-messages"
parent = "docs"
[[main]]
name = "How to guides"
weight = 20
url = "/docs/how-to"
parent = "docs"
[[main]]
name = "Swagger ->"
weight = 98
url = "https://waha.devlike.pro/swagger"
parent = "docs"
[[main]]
name = "OpenAPI JSON ->"
weight = 99
url = "https://waha.devlike.pro/swagger/openapi.json"
parent = "docs"
[[main]]
name = "WAHA Plus"
weight = 30
identifier = "waha-plus"
[[main]]
name = "About WAHA Plus"
weight = 40
identifier = "plus-version"
weight = 10
url = "/docs/how-to/plus-version"
parent = "get-plus"
parent = "waha-plus"
[[main]]
name = "Get on Boosty ->"
weight = 50
identifier = "boosty"
url = "https://boosty.to/wa-http-api"
parent = "get-plus"
parent = "waha-plus"
[[main]]
name = "Get on Patreon ->"
weight = 60
identifier = "patreon"
url = "https://patreon.com/wa_http_api"
parent = "get-plus"
parent = "waha-plus"
[[main]]
name = "Examples"
weight = 20
identifier = "examples"
[[main]]
name = "Python ->"
weight = 21
url = "https://github.com/devlikeapro/whatsapp-http-api/tree/core/examples/python"
parent = "examples"
[[social]]
name = "GitHub"
+3 -4
View File
@@ -4,7 +4,7 @@
title = "WAHA"
titleSeparator = "-"
titleAddition = "WhatsApp HTTP API"
description = "WAHA - WhatsApp HTTP API that you can configure in a click!"
description = "WAHA - WhatsApp HTTP API that you can run in a click!"
## Documentation
# docsVersion = "0.3"
@@ -72,9 +72,8 @@ copyRight = "Copyright (c) 2021-2023 Dev Like A Pro"
# Alert
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? <a class=\"alert-link stretched-link\" href=\"https://waha.devlike.pro/docs/how-to/plus-version/\">Support the project -></a>"
alertDismissable = true
alertText = "<a class=\"alert-link stretched-link\" href=\"https://waha.devlike.pro/docs/how-to/plus-version/\">Support the project and get WAHA Plus version! -></a>"
# Edit Page
# repoHost [Github | Gitea | GitLab | Bitbucket | BitbucketServer ] is used for building the edit link based on git hoster
+2 -2
View File
@@ -1,7 +1,7 @@
---
title : "WAHA"
description: "WAHA - WhatsApp HTTP API that you can configure in a click!"
lead: "WAHA - WhatsApp HTTP API that you can configure in a click!"
description: "WAHA - WhatsApp HTTP API that you can run in a click!"
lead: "WAHA - WhatsApp HTTP API that you can run in a click!"
date: 2020-10-06T08:47:36+00:00
lastmod: 2020-10-06T08:47:36+00:00
draft: false
-10
View File
@@ -1,10 +0,0 @@
---
title: "Help"
description: "Help Doks."
lead: ""
date: 2020-10-06T08:49:15+00:00
lastmod: 2020-10-06T08:49:15+00:00
draft: false
images: []
weight: 800
---
@@ -0,0 +1,41 @@
---
title : "Chats"
description: "Chats"
lead: ""
date: 2020-10-06T08:48:45+00:00
lastmod: 2020-10-06T08:48:45+00:00
draft: false
images: []
weight: 126
---
Chats methods.
Parameters in path that you can find in below endpoints:
- `{session}` - use the session name for Whatsapp instance that you created with `POST /api/session/start` endpoint
- `{chatId}` - chat id in format `123123123123@[c.us|g.us]`, `c.us` for direct chats and `g.us` for groups.
## Methods
### Get all chats
Get all chats
`GET /api/{session}/chats`
### Get messages from chat
Get 100 messages from the chat
`GET /api/{session}/chats/{chatId}/messages?limit=100`
Get 100 messages from the chat, skip downloading media (images, files)
`GET /api/{session}/chats/{chatId}/messages?limit=100&downloadMedia=false`
### Delete chat
Use the method to delete chat
`DELETE /api/{session}/chats/{chatId}`
### Clear messages
Use the method to clear all messages from the chat
`DELETE /api/{session}/chats/{chatId}/messages`
@@ -22,17 +22,14 @@ file as described on [How to deploy page ->]({{< relref "/docs/how-to/deploy" >}
## Environment variables
The following environment variables can be used to configure the WAHA:
The following environment variables can be used to configure the WAHA.
### Common
- `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.
### Sessions
- `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
@@ -42,7 +39,48 @@ The following environment variables can be used to configure the WAHA:
- `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.
### Examples
### Swagger
- `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.
### Proxy
#### Global proxy configuration
If you need to use a proxy, you can set the following environment variables:
- `WHATSAPP_PROXY_SERVER=localhost:3128`: Use this variable to set the proxy server in the format `host:port`, without http or https.
- `WHATSAPP_PROXY_SERVER_USERNAME=username` and `WHATSAPP_PROXY_SERVER_PASSWORD=password`: Use these variables to set up credentials for the proxy.
- `WHATSAPP_PROXY_SERVER_LIST=host1.example.com:3138,host2.example.com:3138`: Use this variable to set a comma-separated list of addresses to use, using a round-robin algorithm to choose the server for the session.
- `WHATSAPP_PROXY_SERVER_INDEX_PREFIX=proxy-`: Use this variable to parse the session name for the prefix and find the appropriate session.
For example, if you have set `WHATSAPP_PROXY_SERVER_LIST=host-first:80,host-second:80,host-third:80` and `WHATSAPP_PROXY_SERVER_INDEX_PREFIX=proxy-` and you run `proxy-3` session, the `host-third:80` proxy will be chosen for that session.
This is a way to select a proxy from while you start session.
#### Session proxy configuration
You can configure proxy when you start session by setting `config.proxy` fields.
Read more about it on [**Session page** ->]({{< relref "/docs/how-to/sessions#configure-proxy" >}}).
Keep in mind that session's proxy configuration takes precedence over proxy configuration set by environment variables!
### Security ![](/images/versions/plus.png)
- `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.
### Files ![](/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
#### Debug Mode
@@ -90,20 +128,6 @@ 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
@@ -6,7 +6,7 @@ date: 2020-10-06T08:48:45+00:00
lastmod: 2020-10-06T08:48:45+00:00
draft: false
images: [ ]
weight: 801
weight: 899
---
This page provides useful information to assist you in deploying the project.
@@ -27,7 +27,7 @@ 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.
To get started, you need to clone the [docker-compose.yaml](https://github.com/devlikeapro/whatsapp-http-api/blob/core/docker-compose.yaml) file to your local machine.
Open your terminal and run the following command:
```
@@ -39,7 +39,7 @@ Not running Chromium saves you CPU and Memory, so you can run more instances on
️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" >}}).
⚠ Read the article before using **NOWEB** engine [How to avoid blocking ->]({{< relref "/docs/overview/how-to-avoid-blocking" >}}).
- `WHATSAPP_DEFAULT_ENGINE=NOWEB`
- https://github.com/WhiskeySockets/Baileys
@@ -84,6 +84,17 @@ Request:
}
```
## Settings
### Security for group info
Updates the group settings to only allow admins to edit group info (title, description, photo).
`PUT /api/{session}/groups/{groupId}/settings/security/info-admin-only`
The request doesn't require any request's body and
**can't be used to disable the security setting (allow ANY to edit group info)**.
If you wish to have that ability - please [create an issue](https://github.com/devlikeapro/whatsapp-http-api/issues)
## Participants
### Get participants
Binary file not shown.

Before

Width:  |  Height:  |  Size: 301 KiB

@@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><svg width="145px" height="40px" viewBox="0 0 145 40" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><linearGradient x1="61.5370404%" y1="13%" x2="34.2376801%" y2="129.365079%" id="linearGradient-1"><stop stop-color="#EF7829" offset="0%"/><stop stop-color="#F0692A" offset="28%"/><stop stop-color="#F15E2C" offset="63%"/><stop stop-color="#F15A2C" offset="100%"/></linearGradient></defs><g id="Symbols" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"><g id="Blocks/Header_Unlogged" transform="translate(-15.000000, -15.000000)"><g id="icons/boosty_logo" transform="translate(15.000000, 15.000000)"><g id="Shape"><path d="M138.119363,13.14856 L132.002653,22.63144 L131.397878,13.14856 L122.575597,13.14856 C123.384615,10.366 124.037135,8.13256 124.037135,8.13256 L124.381963,7.01848 L117.6313,7.01848 L117.312997,8.13256 L115.851459,13.14856 L108.660477,13.14856 C102.750663,13.14856 99.4244032,14.8276 98.0503979,18.00352 C97.2546419,15.09952 94.6206897,13.14856 90.5782493,13.14856 C87.2902077,13.135208 84.1128672,14.3300173 81.6551724,16.504 C80.464191,14.45536 78.1061008,13.1512 74.8116711,13.1512 C71.2975199,13.1360778 67.9189855,14.5000837 65.4084881,16.94752 C64.3103448,14.656 61.8541114,13.14856 58.3262599,13.14856 C57.1586378,13.1510938 55.9961737,13.302814 54.867374,13.6 L55.397878,11.77312 C55.4066092,11.7439195 55.4181572,11.7156279 55.4323607,11.68864 L56.7904509,7 L50.066313,7 L45.7798408,21.73384 C45.668435,22.02952 45.5676393,22.32784 45.4801061,22.63144 C45.2902336,23.2752559 45.1623364,23.9355911 45.0981432,24.60352 C44.5384615,28.714 46.3474801,31.816 51.3660477,32.0272 C51.8475451,32.0843575 52.332067,32.1125687 52.8169761,32.11168 C56.2925823,32.0722669 59.6211655,30.7100879 62.1193634,28.3048 C63.1564987,30.6148 65.5676393,32.11168 69.2970822,32.11168 C72.5436452,32.0790306 75.6705362,30.8873894 78.1087533,28.7536 C79.2493369,30.80488 81.5862069,32.11168 85.061008,32.11168 L101.917772,32.11168 C107.143236,32.11168 110.095491,31.08472 111.6313,28.85392 C111.6313,30.98176 112.851459,32.11168 115.941645,32.11168 C118.437666,32.11168 121.777188,31.5652 126.366048,30.43792 L119.687003,39.9208 L126.411141,39.9208 L144.846154,13.14856 L138.119363,13.14856 Z M58.9310345,22.63144 C58.3023873,24.79096 56.2785146,26.53336 54.4217507,26.53336 C52.5649867,26.53336 51.5676393,24.79096 52.1962865,22.63144 C52.8249337,20.47192 54.8488064,18.72688 56.6923077,18.72688 C58.535809,18.72688 59.5596817,20.464 58.9310345,22.63144 Z M75.4164456,22.63144 C74.7877984,24.79096 72.7639257,26.53336 70.9071618,26.53336 C69.0503979,26.53336 68.0769231,24.79096 68.6923077,22.63144 C69.3076923,20.47192 71.3448276,18.72688 73.2015915,18.72688 C75.0583554,18.72688 76.0344828,20.464 75.4164456,22.63144 Z M84.4562334,22.63144 C85.0848806,20.46928 87.1087533,18.72688 88.9522546,18.72688 C90.795756,18.72688 91.8090186,20.46928 91.1803714,22.63144 C90.5517241,24.7936 88.5941645,26.4832 86.7586207,26.536 L86.6074271,26.536 C84.801061,26.4832 83.8355438,24.76192 84.4562334,22.63144 Z M106.127321,25.85752 C105.787798,26.64952 103.132626,26.53072 102.466844,26.54128 L95.928382,26.536 C96.8103436,25.360374 97.4758216,24.0383816 97.8938992,22.63144 C97.9363395,22.48888 97.9734748,22.34896 98.0079576,22.20904 C98.7108753,23.2096 100.201592,24.11248 103.100796,24.68536 C105.864721,25.216 106.3687,25.05496 106.127321,25.85752 Z M112.777188,23.7244 C112.204244,21.49624 109.721485,20.6224 106.241379,20.2 C104.785146,20.02576 103.965517,19.95712 104.148541,19.3288 C104.289125,18.8404 105.018568,18.73744 106.586207,18.73744 L114.230769,18.73744 L112.777188,23.7244 Z M119.496021,23.74552 C119.496021,23.70064 120.151194,21.4804 120.952255,18.72952 L125.217507,18.72952 L125.915119,25.8364 C118.888594,27.328 118.713528,26.92672 119.496021,23.74552 L119.496021,23.74552 Z" fill="#242B2C" fill-rule="nonzero"/><path d="M1.03841146,23.8920635 L7.81508464,0 L18.2158138,0 L16.1119922,7.40740741 C16.0897554,7.44994435 16.0716695,7.49460291 16.0579948,7.54074074 L10.5357227,27.1005291 L15.6903971,27.1005291 C13.537424,32.5848325 11.8524284,36.8846561 10.6354102,40 C1.12771484,39.8920635 -1.53892578,32.9481481 0.795423177,24.7322751 L1.03841146,23.8920635 Z M10.672793,40 L23.2084961,21.6 L17.8897526,21.6 L22.5169141,9.81375661 C30.4586849,10.6603175 34.1824284,17.0285714 31.993457,24.7301587 C29.6424935,33.015873 20.1285677,40 10.8659375,40 L10.672793,40 Z" fill="url(#linearGradient-1)" fill-rule="nonzero"/></g></g></g></g></svg>

After

Width:  |  Height:  |  Size: 4.4 KiB

@@ -1,6 +1,6 @@
---
title : "Plus version"
description: "Plus version"
title : "WAHA Plus"
description: "WAHA Plus"
lead: ""
date: 2020-10-06T08:48:45+00:00
lastmod: 2020-10-06T08:48:45+00:00
@@ -16,38 +16,21 @@ weight: 900
1. Core ![](/images/versions/core.png) the basic version that meet almost 80% people’s needs. 100% free and open source.
2. Plus ![](/images/versions/plus.png) the version with advanced messages, security, and reliability features.
**If you enjoy Core ![](/images/versions/core.png) version and would like to support the project** - you can subscribe on Core level.
It's not expensive, but it supports the project a lot!
- [Boosty ->](https://boosty.to/wa-http-api)
- [Patreon ->](https://patreon.com/wa_http_api)
You support the project and get **WAHA Plus** on
<a href="https://boosty.to/wa-http-api" target="_blank">
<b>
Boosty ->
</b>
</a>
or
<a href="https://patreon.com/wa_http_api" target="_blank">
<b>
Patreon ->
</b>
</a>
.
## Features
The Plus additional features are:
1. [**Send images\files\voices** →]({{< relref "/docs/how-to/send-messages" >}})
2. [**Receive images\files\voices** →]({{< relref "/docs/how-to/receive-messages" >}})
3. [**Webhook retries** →]({{< relref "/docs/how-to/receive-messages#retries" >}})
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. **Priority in support** - on both bugs and features
## Differences
What is the difference between **WAHA Plus** and other SaaS solutions for WhatsApp HTTP API?
| | WAHA Plus | Others |
|--------------------|:-------------------------------------------------:|----------------------------------------------------------------------------------:|
| **Sessions** | ✔️ No limits on **accounts** or **servers**! | ➖ One account for $50/month. |
| **Infrastructure** | ✔️ On-Premise - your server, your infrastructure! | ➖ Insecure cloud solution. |
| **Security** | ✔️ No data leaks! | ➖ You have to give access to customers' data - phone number, name, your messages. |
| **License Term** | ✔️ No license checks! | ➖ One month |
| **Message price** | ✔️ All messages are Free! | ➖ Some solutions have a price for messages - $1/100 messages. |
| **Expenses** | ✔️ $19 **donation**! | ➖ Monthly payment starts from $50 |
## Donations
### Donations
WAHA Plus version is available through **donations** (subscriptions).
@@ -66,29 +49,83 @@ Treat **WAHA Plus** it as an additional episode, behind-the-scenes videos, our n
We donate most of the donations down to the stream to the underlying libraries and tools - JS-community, Node, other libraries authors, personal contributors.
By donating to the **WAHA** you donate to the Open Source world!
### Features
The Plus additional features are:
1. [**Send images\files\voices** →]({{< relref "/docs/how-to/send-messages" >}})
2. [**Receive images\files\voices** →]({{< relref "/docs/how-to/receive-messages" >}})
3. [**Webhook retries** →]({{< relref "/docs/how-to/receive-messages#retries" >}})
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. **Priority in support** - on both bugs and features
### Why WAHA?
What is the difference between **WAHA Plus** and **other SaaS solutions** for WhatsApp HTTP API?
| | WAHA Plus | Others |
|--------------------|:-------------------------------------------------:|----------------------------------------------------------------------------------:|
| **Sessions** | ✔️ No limits on **accounts** or **servers**! | ➖ One account for $50/month. |
| **Infrastructure** | ✔️ On-Premise - your server, your infrastructure! | ➖ Insecure cloud solution. |
| **Security** | ✔️ No data leaks! | ➖ You have to give access to customers' data - phone number, name, your messages. |
| **License Term** | ✔️ No license checks! | ➖ One month |
| **Message price** | ✔️ All messages are Free! | ➖ Some solutions have a price for messages - $1/100 messages. |
| **Expenses** | ✔️ $19 **donation**! | ➖ Monthly payment starts from $50 |
## Get Plus ![](/images/versions/plus.png)
For $19 one-time donation you get:
The more you support the project - the more you get!
Here's available tiers for our supporters:
| | Plus | Advanced | Pro |
|-------------------------------------------------------------------------------------|---------|:--------:|:-------:|
| Access to WAHA Plus ![](/images/versions/plus.png) docker image. | ✔️ | ✔️ | ✔️ |
| An invitation to a private Github repository so you have the WAHA Plus source code. | ➖️ | ✔️ | ✔️️ |
| Access to a Telegram group where we provide support and assistance. | ➖️ | ➖ | ✔️ |
| **Donation** | **$19** | **$39** | **$99** |
For **$19** donation you get:
1. **Secret password** for Docker Hub to download Plus image: `devlikeapro/whatsapp-http-api-plus`.
2. **All updates for one month** for the Plus image.
3. **NO LICENSE CHECKS** on installed instances!
4. **NO LICENSE EXPIRATION** on installed instances! We repeated it twice to make sure that you've got it 😊
{{< alert icon="👉" text="We don't mind if you stay subscribed with us after one month :)" />}}
### Boosty
We use Boosty to get donations (Patreon analogue) - [https://boosty.to/wa-http-api](https://boosty.to/wa-http-api).
Go ahead and get your monthly password to get no license expiration WhatsApp HTTP API!
{{< alert icon="💡" text="If you stay subscribed - the donation amount will never change for you and always be $19 to get monthly updates!" />}}
![](boosty.png)
### Boosty
<p align="center">
<img src="boosty.svg" alt="Patreon" />
<br/>
<br/>
</p>
Support the project and get WAHA Plus version on
<a href="https://boosty.to/wa-http-api" target="_blank">
<b>
Boosty ->
</b>
</a>
Go ahead and get your own WhatsApp HTTP API!
### Patreon
You can support the project and get WAHA Plus version on Patreon: [https://patreon.com/wa_http_api](https://patreon.com/wa_http_api)
<p align="center">
<img src="patreon.png" alt="Patreon" />
</p>
{{< alert icon="💡" text="Please look at Boosty - we prefer it because of lower fees, so we can donate more to the projects!" />}}
Support the project and get WAHA Plus version on
<a href="https://patreon.com/wa_http_api" target="_blank">
<b>
Patreon ->
</b>
</a>
Go ahead and get your own WhatsApp HTTP API!
### Install Plus
@@ -99,3 +136,4 @@ docker pull devlikeapro/whatsapp-http-api-plus
docker logout
```
Then in all commands use **Plus** image `devlikeapro/whatsapp-http-api-plus` instead of Core `devlikeapro/whatsapp-http-api`.
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

@@ -0,0 +1,152 @@
---
title: "Presence"
description: "Presence"
lead: ""
date: 2020-10-06T08:48:45+00:00
lastmod: 2020-10-06T08:48:45+00:00
draft: false
images: [ ]
weight: 801
---
You can get presence information (online, offline with last seen, typing status) for a contact if they share their
presence information.
Possible presence statuses that you can set or get for chats:
- `online`
- `offline`
- `typing`
- `recording`
- `paused` resets the chat presence after you were `typing`
## Endpoints
### Set presence
You can set your global or chat-related presence with `POST /api/{session}/presence` endpoint
Start typing to a chat (you can use `POST /startTyping` instead)
```json
{
"chatId": "111111111@c.us",
"presence": "typing"
}
```
Clear "typing" state (you can use `POST /stopTyping` instead)
```json
{
"chatId": "111111111@c.us",
"presence": "paused"
}
```
Set global "online", all contacts will see it
```json
{
"presence": "online"
}
```
💡 In the multi-device version of WhatsApp - if a desktop client is active, WhatsApp doesn't send push notifications
to the device.
If you would like to receive said notifications - you need to mark a session's presence as `offline`.
```json
{
"presence": "offline"
}
```
### Get all chats presence
{{< alert icon="👉" text="Get presence endpoints and webhook is available only in **NOWEB** engine." />}}
[Read more about engines to choose right for you ->]({{< relref "/docs/how-to/engines" >}})
Here's few notes about fields:
- `chatId` - either contact id (`213213213@c.us`) or group chat id (`1111111111111@g.us`).
- `lastSeen` - contains Unix timestamps indicating when a participant was last online
- `lastKnownPresence` - contains the last known presence status, which can be
`offline`, `online`, `typing`, `recording`, or `paused`
You can get all presence information available for a session by calling `GET /api/{session}/presence/`.
It returns both groups' and personal chats' presence information.
```json
[
{
"id": "2132132130@c.us",
"presences": [
{
"participant": "2132132130@c.us",
"lastKnownPresence": "offline",
"lastSeen": 1686719326
}
]
},
{
"id": "11111111111111111111111@g.us",
"presences": [
{
"participant": "11111111111111111111111@g.us",
"lastKnownPresence": "online",
"lastSeen": null
},
{
"participant": "2132132130@c.us",
"lastKnownPresence": "offline",
"lastSeen": 1686719326
}
]
}
]
```
### Get chat presence
To get presence information for a single chat - call `GET /api/{session}/presence/{chatId}`.
For a group, you'll get participants' statuses.
```json
{
"id": "2132132130@c.us",
"presences": [
{
"participant": "2132132130@c.us",
"lastKnownPresence": "online",
"lastSeen": null
}
]
}
```
### Subscribe to presence
You can subscribe to presence information by calling `POST /api/{session}/presence/{chatId}/subscribe` request
(no body required).
You can get later presence information for the chat with above `GET` endpoints or by listening to `presence.update`
webhook.
## Webhook
You can subscribe to `presence.update` webhook event to get the most recent presence information.
```json
{
"event": "presence.update",
"session": "default",
"payload": {
"id": "111111111111111111@g.us",
"presences": [
{
"participant": "11111111111@c.us",
"lastKnownPresence": "typing",
"lastSeen": null
}
]
},
"engine": "NOWEB"
}
```
@@ -8,65 +8,37 @@ draft: false
images: []
weight: 120
---
We consider that you've run docker container and authenticated the session with QR code.
The project uses [Webhooks](https://en.wikipedia.org/wiki/Webhook) to send the messages and events from WhatsApp to your
application.
If you haven't yet - please follow the steps from [**Quick Start →**]({{< relref "/docs/overview/quick-start" >}}).
Webhooks are a way for two different applications to communicate with each other in real-time.
When a certain event happens in one application, it sends a message to another application through a webhook URL.
The receiving application can then take action based on the information received.
## Webhooks
To receive incoming messages with webhooks,
you first need to set up a webhook URL in your application and pass it to `WHATSAPP_HOOK_URL` environment variable.
This URL is where WhatsApp will send incoming messages and other events -
define which events you want to receive with `WHATSAPP_HOOK_EVENTS` environment variable.
## Webhooks ![](/images/versions/core.png)
You can configure where you want to receive events in environment variables:
- `WHATSAPP_HOOK_URL=https://httpbin.org/post` - to set up a URL for the webhook
- `WHATSAPP_HOOK_EVENTS=message,message.any,state.change,group.join,group.leave` - specify events. DO NOT specify all of
them, it's too heavy payload, choose the right for you.
- `WHATSAPP_HOOK_EVENTS=*` - subscribe to all events. It's not recommended for production, but it's fine for
development.
On the URL that you set via `WHATSAPP_HOOK_URL` you receive JSON-data with following format:
You must use [Webhooks]({{< relref "/docs/how-to/webhooks" >}}) to receive messages from WhatsApp to your application.
Start a new session with configured `message` event in webhooks - call `POST /api/sessions/start` with the payload:
```json
{
"event": "message",
"session": "default",
"engine": "WEBJS",
"payload": {}
"name": "default",
"config": {
"webhooks": [
{
"url": "https://httpbin.org/post",
"events": [
"message"
]
}
]
}
}
```
Where `event` value helps you identify the incoming event with `payload` for that events.
Below the list of all events that WhatsApp HTTP API sends to your.
{{< alert icon="👉" text="If you want to look at a webhook body - use the url `https://httpbin.org/post`" />}}
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 -p 3000:3000 devlikeapro/whatsapp-http-api
```
### Examples
Here's few examples of how to handle webhook in different languages:
1. [Python guide]({{< relref "/docs/examples/python" >}})
**Do you use another language?**
Please create a short guide how to handle webhook and send message after you finish your setup!
You can create a pull request with your favorite language in the [GitHub, in examples folder](https://github.com/devlikeapro/whatsapp-http-api/tree/core/examples).
Just put the code and **README.md** file in the folder - we'll do the rest!
After that WAHA'll send events (see below) about new messages to an endpoint you provided.
## Events ![](/images/versions/core.png)
## Events
Here's examples of message-related events.
Read [Webhooks ->]({{< relref "/docs/how-to/webhooks" >}}) to find how to set them up.
### message
@@ -144,6 +116,9 @@ Fired on all message creations, including your own. The payload is the same as f
```
### message.ack
Receive events when server or recipient gets the message, read or played it.
[See details on Webhooks page ->]({{< relref "webhooks#messageack" >}}).
```json
{
@@ -153,36 +128,6 @@ Fired on all message creations, including your own. The payload is the same as f
}
```
### state.change
```json
{
"event": "state.change",
"session": "default",
"payload": {}
}
```
### group.join
```json
{
"event": "group.join",
"session": "default",
"payload": {}
}
```
### group.leave
```json
{
"event": "group.left",
"session": "default",
"payload": {}
}
```
## Files ![](/images/versions/plus.png)
When people send you files - images, voice messages, and documents - WAHA saves it in the file storage.
@@ -206,17 +151,33 @@ Then you can use the link to download the file `http://localhost:3000/api/files/
To configure the url you can use environment variables `WHATSAPP_API_HOSTNAME` and `WHATSAPP_API_PORT`.
[Read more about file storage configuration and variables ->]({{< relref "config" >}}).
By default, WAHA download all files that the account receive.
If you want to limit files lifetime, specify file types for download or change directory for files -
[read more about file storage configuration and variables ->]({{< relref "config" >}}).
## Webhook retries ![](/images/versions/plus.png)
## Endpoints
**WAHA** retries to reach your webhook URL 15 times and with 2 seconds delay between attempts.
### Get messages
Read messages from the history - use `GET /api/messages` endpoint.
## Get messages
You can read messages from the history by using `GET /api/messages` endpoint.
You also can get messages for chats by using
[**Get messages from chat ->**]({{< relref "/docs/how-to/chats" >}}) endpoint.
{{< alert icon="👉" text="We do not recommend using this method in production, consider using webhooks instead!" />}}
```bash
curl -X 'GET' \
'http://localhost:3000/api/messages?chatId=11111111111%40c.us&limit=1000&session=default' \
-H 'accept: application/json'
```
## Examples
Here's few examples of how to receive messages in different languages:
1. [Python guide ->]({{< relref "/docs/examples/python" >}})
**Do you use another language?**
Please create a short guide how to handle webhook and send message after you finish your setup!
You can create a pull request with your favorite language in the
[GitHub, in examples folder ->](https://github.com/devlikeapro/whatsapp-http-api/tree/core/examples).
@@ -65,3 +65,7 @@ headers = {
requests.get("http://localhost:3000/api/sessions", headers=headers)
```
## Webhook security ![](/images/versions/plus.png)
To make sure that you get a webhook from your WAHA instance - you can use **HMAC authentication**.
Read more about it on [Webhooks page ->]({{< relref "/docs/how-to/webhooks#hmac-authentication" >}})
@@ -8,13 +8,10 @@ draft: false
images: []
weight: 110
---
On your swagger documentation [http://localhost:3000/](http://localhost:3000/)
or in [the example swagger](/swagger) you can find:
1. List of all supported methods - we have not documented all of them on the page
1. Requests JSON schemas
1. Responses JSON schemas
1. **Try it** button - no additional setup to play around with a method
We consider that you've run docker container and authenticated the session with QR code.
If you haven't yet - please follow the steps from [**Quick Start →**]({{< relref "/docs/overview/quick-start" >}}).
## Fields
There are common fields that you can find in almost all requests:
@@ -28,7 +25,13 @@ Read more about [multiple sessions →]({{< relref "/docs/how-to/sessions" >}})
For phone number `12132132131` the `chatId` is `12132132131@c.us`.
- **Groups** use random number with `@g.us` at the end.
## Send text ![](/images/versions/core.png)
When sending media (images, voice, files) you can either use:
- `file.data` field with base64 encoded file (https://base64.guru/converter/encode/file)
- `file.url` field with public available URL for that file
## Endpoints
### Send text ![](/images/versions/core.png)
To send text message - use `POST /api/sendText` with example payload.
```json
{
@@ -37,8 +40,25 @@ To send text message - use `POST /api/sendText` with example payload.
"text": "Hi there!"
}
```
#### Mention contact
If you send a message in a group and want to mention a participant in the message -
use `mentions` field for that in `POST /api/sendText` request.
## Reply on message ![](/images/versions/core.png)
Please note that you MUST mention a number in the text as well in the format `@2132132130` and
also mention it in `mentions` in format `2132132130@c.us`
```json
{
"session": "default",
"chatId": "12132132130@c.us",
"text": "Hi there! @2132132130",
"mentions": [
"2132132130@c.us"
]
}
```
### Reply on message ![](/images/versions/core.png)
To reply on a message - use `POST /api/reply` with example payload.
```json
{
@@ -49,10 +69,10 @@ To reply on a message - use `POST /api/reply` with example payload.
}
```
### Reply files ![](/images/versions/plus-soon.png)
#### Reply files ![](/images/versions/plus-soon.png)
WAHA does not support reply with files (images, voice, etc). If you're interested in it - please create an issue in GitHub.
## Add a reaction ![](/images/versions/core.png)
### Add a reaction ![](/images/versions/core.png)
Use `PUT /api/reaction` method to set reaction to a message.
{{< alert icon="👉" text="Reaction API uses PUT, not POST request! Please make sure you send right request." />}}
@@ -74,14 +94,14 @@ To remove reaction from a message - send empty string in the reaction request.
}
```
## Send image ![](/images/versions/plus.png)
### Send image ![](/images/versions/plus.png)
Use `POST /api/sendImage` to send images.
You can send images in two ways:
1. Provide a URL for the image.
2. Encode the whole file content into base64 and send it in the request body.
### URL
#### URL
```json
{
"session": "default",
@@ -95,7 +115,7 @@ You can send images in two ways:
}
```
### Base64
#### Base64
```json
{
"session": "default",
@@ -109,14 +129,14 @@ You can send images in two ways:
}
```
## Send voice ![](/images/versions/plus.png)
### Send voice ![](/images/versions/plus.png)
Use `POST /api/sendVoice` to send voice messages.
You can send voice messages in two ways:
1. Provide a URL for the voice.
2. Encode the whole file content into base64 and send it in the request body.
### URL
#### URL
```json
{
"session": "default",
@@ -130,7 +150,7 @@ You can send voice messages in two ways:
}
```
### Base64
#### Base64
```json
{
"chatId": "11111111111@c.us",
@@ -142,14 +162,15 @@ You can send voice messages in two ways:
"session": "default"
}
```
## Send file ![](/images/versions/plus.png)
### Send file ![](/images/versions/plus.png)
Use `POST /api/sendFile` to send a file as a document.
You can send files in two ways:
1. Provide a URL for the file.
2. Encode the whole file content into base64 and send it in the request body.
### URL
#### URL
```json
{
"chatId": "11111111111@c.us",
@@ -162,7 +183,7 @@ You can send files in two ways:
}
```
### Base64
#### Base64
```json
{
"session": "default",
@@ -176,3 +197,73 @@ You can send files in two ways:
}
```
### Send status (aka stories)
With [NOWEB engine]({{< relref "/docs/how-to/engines" >}}) you can send statuses (aka stories)!
It is always necessary to inform the list of `contacts` that will have access to the posted status!
#### Send text status
`POST /api/{session}/status/text`
```json
{
"contacts": [
"55xxxxxxxxxxx@c.us"
],
"text": "Have a look! https://waha.devlike.pro/",
"backgroundColor": "#38b42f",
"font": 1
}
```
#### Send image status ![](/images/versions/plus.png)
`POST /api/{session}/status/image`
```json
{
"contacts": [
"55xxxxxxxxxxx@c.us"
],
"file": {
"mimetype": "image/jpeg",
"filename": "filename.jpeg",
"data": "/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"
},
"caption": "string"
}
```
#### Send voice status ![](/images/versions/plus.png)
`POST /api/{session}/status/voice`
```json
{
"contacts": [
"55xxxxxxxxxxx@c.us"
],
"file": {
"mimetype": "audio/mp4",
"filename": "voice-message.mp3",
"data": "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 truncated
},
"backgroundColor": "#38b42f"
}
```
#### Send video status ![](/images/versions/plus.png)
`POST /api/{session}/status/video`
```json
{
"contacts": [
"55xxxxxxxxxxx@c.us"
],
"file": {
"mimetype": "audio/mp4",
"filename": "video-message.mp3",
"data": "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 truncated
},
"backgroundColor": "#38b42f"
}
```
@@ -1,44 +1,15 @@
---
title : "Sessions"
title: "Sessions"
description: "Sessions"
lead: ""
date: 2020-10-06T08:48:45+00:00
lastmod: 2020-10-06T08:48:45+00:00
draft: false
images: []
weight: 200
images: [ ]
weight: 125
---
## Saving session ![](/images/versions/plus.png)
Plus ![](/images/versions/plus.png) version allows you to save "session" state and avoid scanning QR code everytime when
you start a container.
### File storage
If you want to save your session and do not scan QR code everytime when you launch WAHA - connect a local file storage
to the container. WAHA stores authentication information in the directory and reuses it after restart.
[Attach volume](https://docs.docker.com/storage/volumes/) part to the command:
```bash
-v `pwd`/.sessions:/app/.sessions
```
The full command would be:
```bash
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)
If you're interested in using some "remote" storage (like Redis or other Databases) to save sessions - please create an
issue on GitHub.
For instances, it may be useful if you run WAHA in a cluster of servers and do not have shared file storage
## Multiple sessions ![](/images/versions/plus.png)
If you want to save server's CPU and Memory - run multiple sessions inside one docker container!
## Sessions
### Start
@@ -49,6 +20,68 @@ In order to start a new session - call `POST /api/sessions/start`
"name": "default"
}
```
#### Configure webhooks
You can configure webhooks for a session:
```json
{
"name": "default",
"config": {
"webhooks": [
{
"url": "https://httpbin.org/post",
"events": [
"message"
]
}
]
}
}
```
Read more about available options on [**Webhooks page ->**]({{< relref "/docs/how-to/webhooks#hmac-authentication" >}})
The configuration is saved and will be applied if the docker container restarts,
and you set `WHATSAPP_RESTART_ALL_SESSIONS` environment variables.
Read more about it in [Autostart section](#autostart).
#### Configure proxy
You can configure proxy for a session by setting `config.proxy` fields:
- `server` - proxy server address, without `http://` or `https://` prefixes
- `username` and `password` - set this if the proxy requires authentication
**No authentication**
```json
{
"name": "default",
"config": {
"proxy": {
"server": "localhost:3128"
}
}
}
```
**Proxy with authentication**
```json
{
"name": "default",
"config": {
"proxy": {
"server": "localhost:3128",
"username": "username",
"password": "P@ssw0rd"
}
}
}
```
The configuration is saved and will be applied if the docker container restarts,
and you set `WHATSAPP_RESTART_ALL_SESSIONS` environment variables.
Read more about it in [Autostart section](#autostart).
You can configure proxy when for all sessions by set up environment variables.
Read more about it on [**Configuration page** ->]({{< relref "/docs/how-to/config#proxy" >}}).
### List
@@ -65,6 +98,10 @@ The response:
]
```
You can add `?all=true` parameter to the request `GET /api/session?all=True` it'll show you ALL session,
including **STOPPED**,
so you can know which one will be restarted if you set `WHATSAPP_RESTART_ALL_SESSIONS=True` environment variable.
### Stop
In order to stop a new session - call `POST /api/sessions/stop`
@@ -78,7 +115,6 @@ In order to stop a new session - call `POST /api/sessions/stop`
}
```
### Logout
In order to log out the session - call `POST /api/sessions/logout`
@@ -92,3 +128,86 @@ In order to log out the session - call `POST /api/sessions/logout`
}
```
## Authentication
### Get QR
The simplest way to authenticate a new session - get QR code and scan it on your device.
```bash
GET /api/{session}/auth/qr
```
You'll get QR image that you can scan and get authenticated
### Get pairing code
{{< alert icon="👉" text="Available in **NOWEB** engine only." />}}
You can [link a session with phone number](https://faq.whatsapp.com/1324084875126592) - make a request to the endpoint.
```bash
POST /api/{session}/auth/request-code
```
Body example:
```json
{
"phoneNumber": "12132132130"
}
```
You'll get code in the response that you can use on your WhatsApp app to connect the session:
```json
{
"code": "ABCD-ABCD"
}
```
## Advanced sessions ![](/images/versions/plus.png)
With [WAHA Plus version]({{< relref "plus-version" >}}) you can save session state to avoid scanning QR code everytime,
configure autostart options so when the docker container restarts - it restores all previously run sessions!
### Session persistent
#### File storage
If you want to save your session and do not scan QR code everytime when you launch WAHA - connect a local file storage
to the container. WAHA stores authentication information in the directory and reuses it after restart.
[Attach volume](https://docs.docker.com/storage/volumes/) part to the command:
```bash
-v `pwd`/.sessions:/app/.sessions
```
The full command would be:
```bash
docker run --rm -d -v `pwd`/.sessions:/app/.sessions -p 3000:3000/tcp --name whatsapp-http-api devlikeapro/whatsapp-http-api-plus
```
#### Remote storage ![](/images/versions/soon.png)
If you're interested in using some "remote" storage (like Redis or other Databases) to save sessions - please create an
issue on GitHub.
For instances, it may be useful if you run WAHA in a cluster of servers and do not have shared file storage
### Autostart
If you don't want to call `POST /api/sessions/start` for every session each time when the container restart -
you can use set of these environment variables to start sessions for you:
- `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.
### Multiple sessions
If you want to save server's CPU and Memory - run multiple sessions inside one docker container!
[Plus version]({{< relref "plus-version" >}}) supports multiple sessions in one container.
@@ -0,0 +1,392 @@
---
title : "Webhooks"
description: "Webhooks"
lead: ""
date: 2020-10-06T08:48:45+00:00
lastmod: 2020-10-06T08:48:45+00:00
draft: false
images: []
weight: 121
---
The project uses [Webhooks](https://en.wikipedia.org/wiki/Webhook) to send the messages and events from WhatsApp to your
application.
Webhooks are a way for two different applications to communicate with each other in real-time.
When a certain event happens in one application, it sends a message to another application through a webhook URL.
The receiving application can then take action based on the information received.
## Setup webhooks
### Session webhooks
You can define webhooks configuration per session when you start it with `POST /api/sessions/start` request data.
Here's a simple example:
```json
{
"name": "default",
"config": {
"webhooks": [
{
"url": "https://httpbin.org/post",
"events": [
"message"
]
}
]
}
}
```
Here's available configuration options for webhooks
```json
{
"name": "default",
"config": {
"webhooks": [
{
"url": "https://httpbin.org/post",
"events": [
"message"
],
"hmac": {
"key": "your-secret-key"
},
"retries": {
"delaySeconds": 2,
"attempts": 15
},
"customHeaders": [
{
"name": "X-My-Custom-Header",
"value": "Value"
}
]
}
]
}
}
```
### Global webhooks
There's a way how you can configure webhooks for ALL sessions - by settings these environment variables:
- `WHATSAPP_HOOK_URL=https://httpbin.org/post` - to set up a URL for the webhook
- `WHATSAPP_HOOK_EVENTS=message,message.any,state.change` - specify events. Do not specify all of
them, it's too heavy payload, choose the right for you.
- `WHATSAPP_HOOK_EVENTS=*` - subscribe to all events. It's not recommended for production, but it's fine for
development.
That webhook configuration **does not appear** in `session.config` field in `GET /api/sessions/` request.
## Webhook payload
On the URL that you set you'll receive **HTTP POST** request with a JSON string with following format:
```json
{
"event": "message",
"session": "default",
"engine": "WEBJS",
"payload": {
...
}
}
```
Where `event` value helps you identify the incoming event with `payload` for that events.
Below the list of all events that WhatsApp HTTP API sends to your.
{{< alert icon="👉" text="If you want to look at a webhook body - use the url https://httpbin.org/post" />}}
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 -p 3000:3000 devlikeapro/whatsapp-http-api
```
## Events
### message
Incoming message (text/audio/files)
```json
{
"event": "message",
"session": "default",
"engine": "WEBJS",
"payload": {
"id": "true_11111111111@c.us_AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
"timestamp": 1667561485,
"from": "11111111111@c.us",
"fromMe": true,
"to": "11111111111@c.us",
"body": "Hi there!",
"hasMedia": false,
"ack": 1,
"vCards": [],
"_data": {
"id": {
"fromMe": true,
"remote": "11111111111@c.us",
"id": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
"_serialized": "true_11111111111@c.us_AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
},
"body": "Hi there!",
"type": "chat",
"t": 1667561485,
"notifyName": "MyName",
"from": "11111111111@c.us",
"to": "11111111111@c.us",
"self": "in",
"ack": 1,
"isNewMsg": true,
"star": false,
"kicNotified": false,
"recvFresh": true,
"isFromTemplate": false,
"pollInvalidated": false,
"latestEditMsgKey": null,
"latestEditSenderTimestampMs": null,
"broadcast": false,
"mentionedJidList": [],
"isVcardOverMmsDocument": false,
"isForwarded": false,
"hasReaction": false,
"ephemeralOutOfSync": false,
"productHeaderImageRejected": false,
"lastPlaybackProgress": 0,
"isDynamicReplyButtonsMsg": false,
"isMdHistoryMsg": false,
"stickerSentTs": 0,
"isAvatar": false,
"requiresDirectConnection": false,
"pttForwardedFeaturesEnabled": true,
"isEphemeral": false,
"isStatusV3": false,
"links": []
}
}
}
```
### message.any
Fired on all message creations, including your own. The payload is the same as for [message](#message) event.
```json
{
"event": "message.any",
"session": "default",
"engine": "WEBJS",
"payload": {
...
}
}
```
### message.ack
Receive events when server or recipient gets the message, read or played it.
`ackName` field contains message status (`ack` has the same meaning, but show the value in int, but we keep it for backward compatability, they much to each other)
Possible message ack statuses:
- `ackName: ERROR, ack: -1`
- `ackName: PENDING, ack: 0`
- `ackName: SERVER, ack: 1`
- `ackName: DEVICE, ack: 2`
- `ackName: READ, ack: 3`
- `ackName: PLAYED, ack: 4`
The payload may have more fields, it depends on the engine you use, but here's a minimum amount that all engines send:
```json
{
"event": "message.ack",
"session": "default",
"engine": "WEBJS",
"payload": {
"id":"true_11111111111@c.us_4CC5EDD64BC22EBA6D639F2AF571346C",
"from":"11111111111@c.us",
"participant": null,
"fromMe":true,
"ack":3,
"ackName":"READ"
}
}
```
### state.change
It's an internal engine's state, not **session** `status`.
```json
{
"event": "state.change",
"session": "default",
"engine": "WEBJS",
"payload": {
...
}
}
```
### group.join
```json
{
"event": "group.join",
"session": "default",
"engine": "WEBJS",
"payload": {
...
}
}
```
### group.leave
```json
{
"event": "group.left",
"session": "default",
"engine": "WEBJS",
"payload": {
...
}
}
```
### presence.update
- `payload.id` indicates the chat - either direct chat with a contact or a group chat.
- `payload.id.[].participant` - certain participant presence status. For a direct chat there's only one participant.
```json
{
"event": "presence.update",
"session": "default",
"engine": "NOWEB",
"payload": {
"id": "111111111111111111@g.us",
"presences": [
{
"participant": "11111111111@c.us",
"lastKnownPresence": "typing",
"lastSeen": null
}
]
}
}
```
## Webhooks Advanced ![](/images/versions/plus.png)
### HMAC authentication
You can authenticate webhook sender by using [HMAC Authentication](https://www.okta.com/identity-101/hmac/).
1. Define you secret key in `config.hmac.key` field when you start session with `POST /api/sessions/start`:
```json
{
"name": "default",
"config": {
"webhooks": [
{
"url": "https://httpbin.org/post",
"events": [
"message"
],
"hmac": {
"key": "your-secret-key"
}
}
]
}
}
```
2. After that you'll receive all webhooks payload with two additional headers:
- `X-Webhook-Hmac` - message authentication code for the raw **body** in HTTP POST request that send to your endpoint.
- `X-Webhook-Hmac-Algorithm` - `sha512` - algorithm that have been used to create `X-Webhook-Hmac` value.
3. Implement the authentication algorithm by hashing body and using secret key and then verifying it with `X-Webhook-Hmac`
value. Please [check your implementation here ->](https://www.devglan.com/online-tools/hmac-sha256-online)
Here's example for
```
# Full body
{"event":"message","session":"default","engine":"WEBJS"}
# Secret key
my-secret-key
# X-Webhook-Hmac-Algorithm
sha512
# X-Webhook-Hmac
208f8a55dde9e05519e898b10b89bf0d0b3b0fdf11fdbf09b6b90476301b98d8097c462b2b17a6ce93b6b47a136cf2e78a33a63f6752c2c1631777076153fa89
```
### Retries
**WAHA** retries to reach your webhook URL **15 times** with **2 seconds delay** between attempts by default in
[Plus Version →]({{< relref "plus-version" >}})
You can configure those parameters by settings `config.retries` structure when `POST /api/sessions/start`:
```json
{
"name": "default",
"config": {
"webhooks": [
{
"url": "https://httpbin.org/post",
"events": [
"message"
],
"retries": {
"delaySeconds": 2,
"attempts": 15
}
}
]
}
}
```
### Custom Headers
You can send any customer headers by defining `config.webhooks.customHeaders` fields this way:
```json
{
"name": "default",
"config": {
"webhooks": [
{
"url": "https://httpbin.org/post",
"events": [
"message"
],
"customHeaders": [
{
"name": "X-My-Custom-Header",
"value": "Value"
}
]
}
]
}
}
```
## Examples
Here's few examples of how to handle webhook in different languages:
1. [Python guide]({{< relref "/docs/examples/python" >}})
**Do you use another language?**
Please create a short guide how to handle webhook and send message after you finish your setup!
You can create a pull request with your favorite language in the
[GitHub, in examples folder ->](https://github.com/devlikeapro/whatsapp-http-api/tree/core/examples).
@@ -9,11 +9,48 @@ images: [ ]
menu:
docs:
parent: "help"
weight: 600
weight: 199
toc: true
---
## 2023.9
September 2023
- Add dedicated [Get QR](https://waha.devlike.pro/docs/how-to/sessions/#get-qr) endpoint!
- Support [pairing method (NOWEB)](https://waha.devlike.pro/docs/how-to/sessions/#get-pairing-code) - you can connect with a code instead of QR.
- Add string field `ackName: DEVICE|READ|...` in [message.ack payload](https://waha.devlike.pro/docs/how-to/webhooks/#messageack)
- Support `message.ack` event webhook in **NOWEB** engine
## 2021.6 - June 2023
## 2023.8
August 2023
- Added [stories (aka status) endpoints](https://waha.devlike.pro/docs/how-to/send-messages/#send-status-aka-stories) to **NOWEB** engine!
- `POST /api/{session}/status/text|image|voice|video` - choose one that suites your!
- Added [chats endpoints](https://waha.devlike.pro/docs/how-to/chats)
- Get all chats - `GET /api/{session/chats`
- Delete chat - `DELETE /api/{session/chats/{chatId}`
- Get messages from chat - `GET /api/{session/chats/{chatId}/messages?limit=1000?downloadMedia=true`
- Clear messages in the chat `DELETE /api/{session/chats/{chatId}/messages`
- Added `downloadMedia` flag to speed up `GET /api/messages` and `GET /api/{session/chats/{chatId}/messages` performance.
- Added [set presence endpoint](https://waha.devlike.pro/docs/how-to/presence)!
- With `online` presence you can show your status as online [#121](https://github.com/devlikeapro/whatsapp-http-api/issues/121)
- With `offline` presence you can get notifications for your device [#28](https://github.com/devlikeapro/whatsapp-http-api/issues/28)
- Added security settings for edit group info `PUT /api/{session}/groups/{groupId}/settings/security/info-admin-only` - updates the group settings to only allow admins to edit group info (title, description, photo).
## 2023.7
July 2023
- Now session can have their own [Webhooks](https://waha.devlike.pro/docs/how-to/webhooks) -
you can define webhook configuration when you start a session with `POST /api/session/start`!
- Add HMAC authentication for webhooks
- Configure retries
- Add you custom headers
- Added [proxy configuration](https://waha.devlike.pro/docs/how-to/config/#proxy) with supporting proxy authentication.
Thanks **puntolino** for the contribution!
You can control proxy's settings per session with `POST /api/session/start` or globally with environment variables.
- Added [presence information](https://waha.devlike.pro/docs/how-to/presence) - now you can get online status for
a contact by calling endpoints or receiving a webhook event!
- Now you can mention contact in groups by settings `mentions` field in `POST /api/sendText`
[read more about it in Send Messages ->]({{< relref "/docs/how-to/send-messages" >}})
## 2023.6
June 2023
Improvements on session management, restarting sessions and more:
@@ -46,7 +83,8 @@ Improvements on session management, restarting sessions and more:
}
```
## 2021.5 - May 2023
## 2023.5
May 2023
- Added new [NOWEB engine]({{< relref "/docs/how-to/engines" >}}). **NOWEB** engine does not require a browser to work
with
@@ -54,14 +92,16 @@ Improvements on session management, restarting sessions and more:
- 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" >}}).
- ⚠ Read the article before using it [How to avoid blocking ->]({{< relref "/docs/overview/how-to-avoid-blocking" >}}).
## 2021.4 - March 2023
## 2023.4
March 2023
- Add [Groups API]({{< relref "/docs/how-to/groups" >}})
- Use Chromium by default instead of Chrome
## 2021.1 - January 2023
## 2023.1
January 2023
- Added [Contacts API]({{< relref "/docs/how-to/contacts" >}})
- Get all contacts
@@ -71,43 +111,39 @@ Improvements on session management, restarting sessions and more:
- Check number exists (is registered in WhatsApp) - works even if the number is not in the contact list
- Block and unblock contact
## 2022.12 - December 2023
## 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)
**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.
2. Optimize CPU and memory consumption.
### Requests ![](/images/versions/core.png)
**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)
**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)
**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.
### Webhooks ![](/images/versions/core.png)
#### Configuration
**Webhooks** ![](/images/versions/core.png)
Instead of setting each webhook via environment variables - we use two environments variables:
@@ -118,7 +154,7 @@ Instead of setting each webhook via environment variables - we use two environme
|----------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------|
| <pre>WHATSAPP_HOOK_ONMESSAGE=https://httpbin.org/post <br>WHATSAPP_HOOK_ONANYMESSAGE=https://httpbin.org/post </pre> | <pre>WHATSAPP_HOOK_URL=https://httpbin.org/post <br>WHATSAPP_HOOK_EVENTS=message,message.any</pre> |
#### Payload
** Webhook payload**
The data for webhooks are wrapped inside a new `WAWebhook` object with `event` and `payload` fields to help you identify
which handler you should call based on `event`.
@@ -9,7 +9,7 @@ images: [ ]
menu:
docs:
parent: "help"
weight: 630
weight: 130
toc: true
---
@@ -8,7 +8,7 @@ images: [ ]
menu:
docs:
parent: "help"
weight: 610
weight: 120
toc: true
---
@@ -23,58 +23,29 @@ toc: true
{{< alert icon="👉" text="Run WhatsApp HTTP API in less than 5 minutes!" />}}
We will guide you through the necessary steps to successfully send your first text message using WhatsApp HTTP API in
[Quick Start →]({{< relref "quick-start" >}}).
If you find it easier to follow a video tutorial rather than reading through text, we've got you covered!
A 3-minute video tutorial is available to help you navigate WhatsApp HTTP API smoothly.
<div class="d-flex justify-content-center my-4">
<iframe
width="560" height="315"
src="https://www.youtube.com/embed/RFerMyAUPRg"
title="YouTube video player"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
allowfullscreen
></iframe>
</div>
[**Quick Start →**]({{< relref "quick-start" >}}).
## Features
WAHA is distributed in two versions:
- Core ![](/images/versions/core.png) - the basic version that meet almost 80% people's needs. 100% free and [open source](https://github.com/devlikeapro/whatsapp-http-api).
- Plus ![](/images/versions/plus.png) - the version with advanced messages, security, and reliability features. One-time donation, no license expiration, quick updates, read more about [Plus Version →]({{< relref "plus-version" >}})
- Core ![](/images/versions/core.png) - the basic version that meet almost 80% people's needs. 100% free and [open source ->](https://github.com/devlikeapro/whatsapp-http-api).
- Plus ![](/images/versions/plus.png) - the version with advanced messages, security, and reliability features. Donations, no license expiration, quick updates, read more about [Plus Version →]({{< relref "plus-version" >}})
Here's the available features:
### Messages
- Send messages ![](/images/versions/core.png)
- Receive messages ![](/images/versions/core.png)
- Message replies ![](/images/versions/core.png)
- Send location ![](/images/versions/core.png)
- Send buttons ![](/images/versions/core.png)
- Receive location ![](/images/versions/core.png)
- React to messages ![](/images/versions/core.png)
- Send messages
- Receive messages
- Message replies
- Send location
- Send buttons
- Receive location
- React to messages
- Send media (images/documents/files) ![](/images/versions/plus.png)
- Send voice messages ![](/images/versions/plus.png)
- Receive media (images/audio/video/documents) ![](/images/versions/plus.png)
- Receive messages - webhook retries ![](/images/versions/plus.png)
### Sessions
- Multi Device ![](/images/versions/core.png)
- Get the screenshot ![](/images/versions/core.png)
- Session saving (don't have to scan QR on every restart) ![](/images/versions/plus.png)
- Session saving on remote storage (database, redis) ![](/images/versions/plus-soon.png)
- Single WhatsApp account running inside one container ![](/images/versions/core.png)
- Multiple WhatsApp account running inside one container ![](/images/versions/plus.png)
### Security
- API authentication ![](/images/versions/plus.png)
- Swagger panel authentication ![](/images/versions/plus.png)
### Updates
- Bug fixes and updates come as soon as they are implemented. ![](/images/versions/core.png)![](/images/versions/plus.png)
### Groups ![](/images/versions/core.png)
### Groups
- Create a group
- Get invite for group
- Modify group info (subject, description)
@@ -82,8 +53,25 @@ Here's the available features:
- Kick group participants
- Promote/demote group participants
### Contacts ![](/images/versions/core.png)
### Contacts
- Mute/unmute chats
- Block/unblock contacts
- Get contact info
- Get profile pictures
- Get presence (online\offline\typing) status
### Sessions
- Multi Device
- Get the screenshot
- Single WhatsApp account running inside one container
- Multiple WhatsApp account running inside one container ![](/images/versions/plus.png)
- Session saving (don't have to scan QR on every restart) ![](/images/versions/plus.png)
- Session saving on remote storage (database, redis) ![](/images/versions/plus-soon.png)
### Security
- API authentication ![](/images/versions/plus.png)
- Swagger panel authentication ![](/images/versions/plus.png)
### Updates
- Bug fixes and updates come as soon as they are implemented.
@@ -16,6 +16,8 @@ toc: true
On this page you're going to install WAHA, run it, authenticate the client using QR code and send the first text message
to WhatsApp!
![](swagger.png)
## Requirements
Only thing that you must have - installed docker. Please follow the original
@@ -34,7 +36,7 @@ Also Docker makes installation and update processes so simple, just one command!
We will guide you through the necessary steps to successfully send your first text message using WhatsApp HTTP API.
If you find it easier to follow a video tutorial rather than reading through text, we've got you covered!
**If you find it easier to follow a video** tutorial, we've got you covered!
A 3-minute video tutorial is available to help you navigate WhatsApp HTTP API smoothly.
<div class="d-flex justify-content-center my-4">
@@ -181,8 +183,8 @@ curl -d "{\"chatId\": \"${PHONE}@c.us\", \"text\": \"Hello from WhatsApp HTTP AP
```
## What is next?
- Right now you've sent simple text message, but you can send more! [Send messages →]({{< relref "/docs/how-to/send-messages" >}})
- Read how to receive and response to messages [Receive messages →]({{< relref "/docs/how-to/receive-messages" >}})
- [Save session information, so you don't have to scan QR after container restart ->]({{< relref "/docs/how-to/sessions" >}})
- Make sure your [API is properly protected ->]({{< relref "/docs/how-to/security" >}})
+6 -13
View File
@@ -4,29 +4,22 @@
<div class="col-lg-12 text-center">
<h1 class="mt-0">{{ .Title }}</h1>
</div>
<div class="col-lg-9 col-xl-8 text-center">
<div class="col-lg-9 col-xl-9 text-center">
<div class="mb-4">
<img src="/images/logo.png" title="WhatsApp HTTP API" alt="logo" width="100"/>
</div>
<p class="lead">{{ .Params.lead | safeHTML }}</p>
<code>
docker run -it -p 3000:3000/tcp devlikeapro/whatsapp-http-api
</code>
<br/>
<br/>
<a class="btn btn-primary btn-lg px-4 mb-2"
href="/docs/{{ if .Site.Params.options.docsVersioning }}{{ .Site.Params.docsVersion }}/{{ end }}overview/introduction/"
role="button">Get Started</a>
<p class="meta"><a href="https://github.com/devlikeapro/whatsapp-http-api">Source code in GitHub.</a></p>
</div>
</div>
<div>
<div class="d-flex justify-content-center my-4">
<iframe
width="560" height="315"
src="https://www.youtube.com/embed/RFerMyAUPRg"
title="YouTube video player"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
allowfullscreen
></iframe>
</div>
</div>
</section>
{{ end }}
+1 -1
View File
@@ -3,7 +3,7 @@
<html lang="en">
<head>
<meta charset="UTF-8">
<title>devlikeapro/whatsapp-http-api</title>
<title>WAHA - WhatsApp HTTP API</title>
<link href="./swagger-ui.css" rel="stylesheet" type="text/css">
<link href="./favicon-32x32.png" rel="icon" sizes="32x32" type="image/png"/>
<link href="./favicon-16x16.png" rel="icon" sizes="16x16" type="image/png"/>
+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/{session}/auth/qr":{"get":{"operationId":"AuthController_getQR","summary":"Get QR code for pairing WhatsApp Web.","parameters":[{"name":"session","required":true,"in":"path","description":"WhatsApp session name","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["auth"],"security":[{"api_key":[]}]}},"/api/{session}/auth/request-code":{"post":{"operationId":"AuthController_requestCode","summary":"Request authentication code. NOWEB and NOWEB_MOBILE engines only","parameters":[{"name":"session","required":true,"in":"path","description":"WhatsApp session name","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RequestCodeRequest"}}}},"responses":{"201":{"description":""}},"tags":["auth"],"security":[{"api_key":[]}]}},"/api/{session}/auth/authorize-code":{"post":{"operationId":"AuthController_authorizeCode","summary":"Send OTP authentication code. NOWEB_MOBILE engine only","parameters":[{"name":"session","required":true,"in":"path","description":"WhatsApp session name","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OTPRequest"}}}},"responses":{"201":{"description":""}},"tags":["auth"],"security":[{"api_key":[]}]}},"/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","summary":"Stop session","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SessionStopRequest"}}}},"responses":{"201":{"description":""}},"tags":["sessions"],"security":[{"api_key":[]}]}},"/api/sessions/logout":{"post":{"operationId":"SessionsController_clean","summary":"Logout from session.","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SessionLogoutRequest"}}}},"responses":{"201":{"description":""}},"tags":["sessions"],"security":[{"api_key":[]}]}},"/api/sessions":{"get":{"operationId":"SessionsController_list","parameters":[{"name":"all","required":false,"in":"query","example":false,"description":"Return all sessions, including those that are in the STOPPED state.","schema":{"type":"boolean"}}],"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"}}}}},"taLine truncated
+1 -1
View File
@@ -82,7 +82,7 @@ def whatsapp_webhook():
# Send a text back via WhatsApp HTTP API
send_message(chat_id=chat_id, text=text)
# OR reply on the message
# reply(chat_id=chat_id, message_id=message_id, text=text)
reply(chat_id=chat_id, message_id=message_id, text=text)
# Send OK back
return "OK"
+8 -4
View File
@@ -22,7 +22,7 @@
"test:e2e": "jest --config ./test/jest-e2e.json"
},
"dependencies": {
"@adiwajshing/baileys": "file:../WhiskeySockets-Baileys",
"@adiwajshing/baileys": "github:WhiskeySockets/Baileys",
"@adiwajshing/keyed-db": "^0.2.4",
"@nestjs/common": "^9.0.9",
"@nestjs/config": "^0.5.0",
@@ -37,6 +37,8 @@
"del": "^6.0.0",
"express-basic-auth": "^1.2.1",
"file-type": "16.5.4",
"https-proxy-agent": "^7.0.0",
"libphonenumber-js": "^1.10.36",
"link-preview-js": "^3.0.4",
"lodash": "^4.17.21",
"mime-types": "^2.1.27",
@@ -50,7 +52,7 @@
"rimraf": "^3.0.2",
"rxjs": "^7.1.0",
"swagger-ui-express": "^4.1.4",
"venom-bot": "^5.0.1",
"venom-bot": "5.0.1",
"whatsapp-web.js": "https://github.com/pedroslopez/whatsapp-web.js"
},
"devDependencies": {
@@ -66,6 +68,7 @@
"eslint": "7.7.0",
"eslint-config-prettier": "^6.10.0",
"eslint-plugin-import": "^2.20.1",
"eslint-plugin-simple-import-sort": "^10.0.0",
"jest": "26.4.2",
"prettier": "^1.19.1",
"supertest": "^4.0.2",
@@ -73,7 +76,7 @@
"ts-loader": "^6.2.1",
"ts-node": "^10.9.1",
"tsconfig-paths": "^4.1.0",
"typescript": "4.7.4"
"typescript": "^4.8.4"
},
"jest": {
"moduleFileExtensions": [
@@ -88,5 +91,6 @@
},
"coverageDirectory": "../coverage",
"testEnvironment": "node"
}
},
"packageManager": "yarn@3.6.0"
}
+67
View File
@@ -0,0 +1,67 @@
import { Body, Controller, Get, Post, Res } from '@nestjs/common';
import { ApiOperation, ApiSecurity, ApiTags } from '@nestjs/swagger';
import { SessionManager } from '../core/abc/manager.abc';
import { WhatsappSession } from '../core/abc/session.abc';
import { OTPRequest, RequestCodeRequest } from '../structures/auth.dto';
import { SessionApiParam, SessionParam } from './helpers';
import { WAHASessionStatus } from '../structures/enums.dto';
import { UnprocessableEntityException } from '@nestjs/common/exceptions/unprocessable-entity.exception';
import { Readable } from 'stream';
import { Response } from 'express';
@ApiSecurity('api_key')
@Controller('api/:session/auth')
@ApiTags('auth')
class AuthController {
constructor(private manager: SessionManager) {}
@Get('qr')
@SessionApiParam
@ApiOperation({
summary: 'Get QR code for pairing WhatsApp Web.',
})
async getQR(@Res() res: Response, @SessionParam session: WhatsappSession) {
if (session.status != WAHASessionStatus.SCAN_QR_CODE) {
const err = `Can get QR code only in SCAN_QR_CODE status. The current status is '${session.status}'`;
throw new UnprocessableEntityException(err);
}
const buffer = await session.getQR();
const stream = new Readable();
stream.push(buffer);
stream.push(null);
res.set({
'Content-Type': 'image/png',
'Content-Length': buffer.length,
});
stream.pipe(res);
}
@Post('request-code')
@SessionApiParam
@ApiOperation({
summary: 'Request authentication code. NOWEB and NOWEB_MOBILE engines only',
})
requestCode(
@SessionParam session: WhatsappSession,
@Body() request: RequestCodeRequest,
) {
return session.requestCode(request.phoneNumber, request.method);
}
@Post('authorize-code')
@SessionApiParam
@ApiOperation({
summary: 'Send OTP authentication code. NOWEB_MOBILE engine only',
})
authorizeCode(
@SessionParam session: WhatsappSession,
@Body() request: OTPRequest,
) {
return session.authorizeCode(request.code);
}
}
export { AuthController };
+56
View File
@@ -0,0 +1,56 @@
import { Controller, Delete, Get, Param, Post, Query } from '@nestjs/common';
import { ApiOperation, ApiSecurity, ApiTags } from '@nestjs/swagger';
import { SessionManager } from '../core/abc/manager.abc';
import { WhatsappSession } from '../core/abc/session.abc';
import { SessionApiParam, SessionParam } from './helpers';
import { GetChatMessagesQuery } from '../structures/chats.dto';
import { parseBool } from '../helpers';
@ApiSecurity('api_key')
@Controller('api/:session/chats')
@ApiTags('chats')
class ChatsController {
constructor(private manager: SessionManager) {}
@Get('')
@SessionApiParam
@ApiOperation({ summary: 'Get all chats' })
getAllChats(@SessionParam session: WhatsappSession) {
return session.getChats();
}
@Delete(':chatId')
@SessionApiParam
@ApiOperation({ summary: 'Deletes the chat' })
deleteChat(
@SessionParam session: WhatsappSession,
@Param('chatId') chatId: string,
) {
return session.deleteChat(chatId);
}
@Get(':chatId/messages')
@SessionApiParam
@ApiOperation({ summary: 'Gets messages in the chat' })
getChatMessages(
@Query() query: GetChatMessagesQuery,
@SessionParam session: WhatsappSession,
@Param('chatId') chatId: string,
) {
const downloadMedia = parseBool(query.downloadMedia);
return session.getChatMessages(chatId, query.limit, downloadMedia);
}
@Delete(':chatId/messages')
@SessionApiParam
@ApiOperation({ summary: 'Clears all messages from the chat' })
clearMessages(
@SessionParam session: WhatsappSession,
@Param('chatId') chatId: string,
) {
return session.clearMessages(chatId);
}
}
export { ChatsController };
+4 -2
View File
@@ -1,5 +1,7 @@
import { Body, Controller, Get, Post, Put, Query } from '@nestjs/common';
import { ApiOperation, ApiSecurity, ApiTags } from '@nestjs/swagger';
import { SessionManager } from '../core/abc/manager.abc';
import {
ChatRequest,
CheckNumberStatusQuery,
@@ -16,9 +18,9 @@ import {
MessageTextRequest,
MessageVoiceRequest,
SendSeenRequest,
WANumberExistResult,
} from '../structures/chatting.dto';
import { WAMessage, WANumberExistResult } from '../structures/responses.dto';
import { SessionManager } from '../core/abc/manager.abc';
import { WAMessage } from '../structures/responses.dto';
@ApiSecurity('api_key')
@Controller('api')
+6 -3
View File
@@ -1,10 +1,13 @@
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 {
CheckNumberStatusQuery,
WANumberExistResult,
} from '../structures/chatting.dto';
import { ContactQuery, ContactRequest } from '../structures/contacts.dto';
@ApiSecurity('api_key')
@Controller('api/contacts')
+1
View File
@@ -6,6 +6,7 @@ import {
HttpStatus,
} from '@nestjs/common';
import { Request, Response } from 'express';
import { VERSION } from '../version';
/**
+9 -1
View File
@@ -8,14 +8,15 @@ import {
Put,
} from '@nestjs/common';
import { ApiOperation, ApiSecurity, ApiTags } from '@nestjs/swagger';
import { SessionManager } from '../core/abc/manager.abc';
import { WhatsappSession } from '../core/abc/session.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')
@@ -48,6 +49,13 @@ export class GroupsController {
return session.getGroup(id);
}
@Put(':id/settings/security/info-admin-only')
@SessionApiParam
@ApiOperation({ summary: 'Updates the group settings to only allow admins to edit group info (title, description, photo).' })
setGroupAdminOnly(@SessionParam session: WhatsappSession, @Param('id') id: string) {
return session.setInfoAdminsOnly(id, true);
}
@Delete(':id')
@SessionApiParam
@ApiOperation({ summary: 'Delete the group.' })
+2 -1
View File
@@ -1,5 +1,6 @@
import { ApiParam } from '@nestjs/swagger';
import { Injectable, Param, PipeTransform } from '@nestjs/common';
import { ApiParam } from '@nestjs/swagger';
import { SessionManager } from '../core/abc/manager.abc';
import { WhatsappSession } from '../core/abc/session.abc';
+89
View File
@@ -0,0 +1,89 @@
import {
BadRequestException,
Body,
Controller,
Get,
Param,
Post,
} from '@nestjs/common';
import { ApiOperation, ApiSecurity, ApiTags } from '@nestjs/swagger';
import { SessionManager } from '../core/abc/manager.abc';
import { WhatsappSession } from '../core/abc/session.abc';
import {
WAHAChatPresences,
WAHASessionPresence,
} from '../structures/presence.dto';
import { SessionApiParam, SessionParam } from './helpers';
import { WAHAPresenceStatus } from '../structures/enums.dto';
@ApiSecurity('api_key')
@Controller('api/:session/presence')
@ApiTags('presence')
export class PresenceController {
constructor(private manager: SessionManager) {}
@Post('')
@SessionApiParam
@ApiOperation({ summary: 'Set session presence' })
setPresence(
@SessionParam session: WhatsappSession,
@Body() request: WAHASessionPresence,
) {
// Validate request
const presencesWithoutChatId = [
WAHAPresenceStatus.ONLINE,
WAHAPresenceStatus.OFFLINE,
];
const requiresNoChatId = presencesWithoutChatId.includes(request.presence);
const requiresChatId = !requiresNoChatId;
if (requiresNoChatId && request.chatId) {
const msg = {
detail: `'${request.presence}' presence works on the global scope and doesn't require 'chatId' field.`,
};
throw new BadRequestException(msg);
} else if (requiresChatId && !request.chatId) {
const msg = {
detail: `'${request.presence}' presence requires 'chatId' field.`,
};
throw new BadRequestException(msg);
}
return session.setPresence(request.presence, request.chatId);
}
@Get('')
@SessionApiParam
@ApiOperation({ summary: 'Get all subscribed presence information.' })
getPresenceAll(
@SessionParam session: WhatsappSession,
): Promise<WAHAChatPresences[]> {
return session.getPresences();
}
@Get(':chatId')
@SessionApiParam
@ApiOperation({
summary:
"Get the presence for the chat id. If it hasn't been subscribed - it also subscribes to it.",
})
getPresence(
@SessionParam session: WhatsappSession,
@Param('chatId') chatId: string,
): Promise<WAHAChatPresences> {
return session.getPresence(chatId);
}
@Post(':chatId/subscribe')
@SessionApiParam
@ApiOperation({
summary: 'Subscribe to presence events for the chat.',
})
subscribe(
@SessionParam session: WhatsappSession,
@Param('chatId') chatId: string,
): Promise<void> {
return session.subscribePresence(chatId);
}
}
+2 -1
View File
@@ -1,7 +1,8 @@
import { Controller, Get, Query, Res } from '@nestjs/common';
import { ApiSecurity, ApiTags } from '@nestjs/swagger';
import { Readable } from 'stream';
import { Response } from 'express';
import { Readable } from 'stream';
import { SessionManager } from '../core/abc/manager.abc';
import { SessionQuery } from '../structures/base.dto';
+14 -14
View File
@@ -1,15 +1,16 @@
import { Body, Controller, Get, Post, Query } from '@nestjs/common';
import { UnprocessableEntityException } from '@nestjs/common/exceptions/unprocessable-entity.exception';
import { ApiOperation, ApiSecurity, ApiTags } from '@nestjs/swagger';
import { SessionManager } from '../core/abc/manager.abc';
import { parseBool } from '../helpers';
import {
SessionLogoutRequest,
ListSessionsQuery,
SessionDTO,
SessionLogoutRequest,
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')
@@ -18,8 +19,13 @@ export class SessionsController {
constructor(private manager: SessionManager) {}
@Post('/start/')
start(@Body() request: SessionStartRequest): SessionDTO {
return this.manager.start(request);
async start(@Body() request: SessionStartRequest): Promise<SessionDTO> {
const result = await this.manager.start(request);
await this.manager.sessionStorage.configRepository.save(
request.name,
request.config,
);
return result;
}
@Post('/stop/')
@@ -35,17 +41,11 @@ export class SessionsController {
@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(@Query() query: ListSessionsQuery): SessionDTO[] {
async list(@Query() query: ListSessionsQuery): Promise<SessionDTO[]> {
const all = parseBool(query.all);
return this.manager.getSessions(all);
}
+69
View File
@@ -0,0 +1,69 @@
import {
Body,
Controller,
Delete,
Get,
Param,
Post,
Query,
} from '@nestjs/common';
import { ApiOperation, ApiSecurity, ApiTags } from '@nestjs/swagger';
import { SessionManager } from '../core/abc/manager.abc';
import { WhatsappSession } from '../core/abc/session.abc';
import { SessionApiParam, SessionParam } from './helpers';
import {
ImageStatus,
TextStatus,
VideoStatus,
VoiceStatus,
} from '../structures/status.dto';
@ApiSecurity('api_key')
@Controller('api/:session/status')
@ApiTags('status')
class StatusController {
constructor(private manager: SessionManager) {}
@Post('text')
@SessionApiParam
@ApiOperation({ summary: 'Send text status.' })
sendTextStatus(
@SessionParam session: WhatsappSession,
@Body() status: TextStatus,
) {
return session.sendTextStatus(status);
}
@Post('image')
@SessionApiParam
@ApiOperation({ summary: 'Send image status.' })
sendImageStatus(
@SessionParam session: WhatsappSession,
@Body() status: ImageStatus,
) {
return session.sendImageStatus(status);
}
@Post('voice')
@SessionApiParam
@ApiOperation({ summary: 'Send voice status.' })
sendVoiceStatus(
@SessionParam session: WhatsappSession,
@Body() status: VoiceStatus,
) {
return session.sendVoiceStatus(status);
}
@Post('video')
@SessionApiParam
@ApiOperation({ summary: 'Send video status.' })
sendVideoStatus(
@SessionParam session: WhatsappSession,
@Body() status: VideoStatus,
) {
return session.sendVideoStatus(status);
}
}
export { StatusController };
+1
View File
@@ -1,5 +1,6 @@
import { Controller, Get } from '@nestjs/common';
import { ApiSecurity, ApiTags } from '@nestjs/swagger';
import { VERSION } from '../version';
@ApiSecurity('api_key')
+43 -7
View File
@@ -1,7 +1,9 @@
import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { WhatsappEngine } from './structures/enums.dto';
import { parseBool } from './helpers';
import { WAHAEngine } from './structures/enums.dto';
import { WebhookConfig } from './structures/webhooks.config.dto';
@Injectable()
export class WhatsappConfigService {
@@ -54,19 +56,53 @@ export class WhatsappConfigService {
return parseBool(value);
}
getWebhookUrl(): string | undefined {
get proxyServer(): string[] | string | undefined {
const single = this.configService.get('WHATSAPP_PROXY_SERVER', undefined);
const multipleValues = this.configService.get(
'WHATSAPP_PROXY_SERVER_LIST',
undefined,
);
const multiple = multipleValues ? multipleValues.split(',') : undefined;
return single ? single : multiple;
}
get proxyServerIndexPrefix(): string | undefined {
return this.configService.get(
'WHATSAPP_PROXY_SERVER_INDEX_PREFIX',
undefined,
);
}
get proxyServerUsername(): string | undefined {
return this.configService.get('WHATSAPP_PROXY_SERVER_USERNAME', undefined);
}
get proxyServerPassword(): string | undefined {
return this.configService.get('WHATSAPP_PROXY_SERVER_PASSWORD', undefined);
}
getWebhookConfig(): WebhookConfig | undefined {
const url = this.getWebhookUrl();
const events = this.getWebhookEvents();
if (!url || events.length === 0) {
return undefined;
}
return { url: url, events: events };
}
private getWebhookUrl(): string | undefined {
return this.get('WHATSAPP_HOOK_URL');
}
getWebhookEvents(): string[] {
private 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];
getDefaultEngineName(): WAHAEngine {
const value = this.get('WHATSAPP_DEFAULT_ENGINE', WAHAEngine.WEBJS);
if (value in WAHAEngine) {
return WAHAEngine[value];
}
console.log(
`Unknown WhatsApp default engine, using WEBJS. WHATSAPP_DEFAULT_ENGINE=${value}`,
+9 -6
View File
@@ -1,17 +1,20 @@
import { OnApplicationShutdown } from '@nestjs/common';
import { WAHAEngine } from '../../structures/enums.dto';
import {
SessionLogoutRequest,
SessionDTO,
SessionLogoutRequest,
SessionStartRequest,
SessionStopRequest,
} from '../../structures/sessions.dto';
import { WhatsappSession } from './session.abc';
import { WhatsappEngine } from '../../structures/enums.dto';
import { MediaStorage } from './storage.abc';
import { LocalSessionStorage, MediaStorage } from './storage.abc';
import { WebhookConductor } from './webhooks.abc';
export abstract class SessionManager implements OnApplicationShutdown {
protected abstract getEngine(engine: WhatsappEngine): typeof WhatsappSession;
public sessionStorage: LocalSessionStorage;
protected abstract getEngine(engine: WAHAEngine): typeof WhatsappSession;
protected abstract get EngineClass(): typeof WhatsappSession;
@@ -24,7 +27,7 @@ export abstract class SessionManager implements OnApplicationShutdown {
//
// API Methods
//
abstract start(request: SessionStartRequest): SessionDTO;
abstract start(request: SessionStartRequest): Promise<SessionDTO>;
abstract stop(request: SessionStopRequest): Promise<void>;
@@ -32,5 +35,5 @@ export abstract class SessionManager implements OnApplicationShutdown {
abstract getSession(name: string, error?: boolean): WhatsappSession;
abstract getSessions(all: boolean): SessionDTO[];
abstract getSessions(all: boolean): Promise<SessionDTO[]>;
}
+127 -17
View File
@@ -1,9 +1,8 @@
import {
WAEvents,
WhatsappEngine,
WhatsappStatus,
} from '../../structures/enums.dto';
import { ConsoleLogger } from '@nestjs/common';
import { EventEmitter } from 'events';
import * as fs from 'fs';
import { MessageId } from 'whatsapp-web.js';
import {
ChatRequest,
CheckNumberStatusQuery,
@@ -19,19 +18,31 @@ import {
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 {
WAHAEngine,
WAHAEvents,
WAHAPresenceStatus,
WAHASessionStatus,
} from '../../structures/enums.dto';
import {
CreateGroupRequest,
ParticipantsRequest,
} from '../../structures/groups.dto';
import * as fs from 'fs';
import { WAHAChatPresences } from '../../structures/presence.dto';
import { ProxyConfig, SessionConfig } from '../../structures/sessions.dto';
import { NotImplementedByEngineError } from '../exceptions';
import { LocalSessionStorage, MediaStorage } from './storage.abc';
import { OTPRequest, RequestCodeRequest } from '../../structures/auth.dto';
import {
ImageStatus,
TextStatus,
VideoStatus,
VoiceStatus,
} from '../../structures/status.dto';
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)) {
@@ -39,6 +50,7 @@ export function getBrowserExecutablePath() {
}
return CHROMIUM_PATH;
}
export function ensureSuffix(phone) {
const suffix = '@c.us';
if (phone.includes('@')) {
@@ -51,29 +63,43 @@ export enum WAHAInternalEvent {
engine_start = 'engine.start',
}
export interface WhatsAppSessionConfig {
export interface SessionParams {
name: string;
storage: MediaStorage;
log: ConsoleLogger;
sessionStorage: LocalSessionStorage;
proxyConfig?: ProxyConfig;
sessionConfig: SessionConfig;
}
export abstract class WhatsappSession {
public status: WhatsappStatus;
public status: WAHASessionStatus;
public events: EventEmitter;
public engine: WhatsappEngine;
public engine: WAHAEngine;
public name: string;
protected storage: MediaStorage;
protected log: ConsoleLogger;
protected sessionStorage: LocalSessionStorage;
protected proxyConfig?: ProxyConfig;
public sessionConfig: SessionConfig;
public constructor({ name, log, sessionStorage }: WhatsAppSessionConfig) {
public constructor({
name,
log,
sessionStorage,
proxyConfig,
storage,
sessionConfig,
}: SessionParams) {
this.name = name;
this.status = WhatsappStatus.STARTING;
this.proxyConfig = proxyConfig;
this.status = WAHASessionStatus.STARTING;
this.log = log;
this.sessionStorage = sessionStorage;
this.events = new EventEmitter();
this.storage = storage;
this.sessionConfig = sessionConfig;
}
getBrowserExecutablePath() {
@@ -117,13 +143,33 @@ export abstract class WhatsappSession {
abstract stop(): void;
/** Subscribe the handler to specific hook */
abstract subscribe(hook: WAEvents | string, handler: (message) => void);
abstract subscribe(hook: WAHAEvents | string, handler: (message) => void);
/**
* START - Methods for API
*/
/**
* Auth methods
*/
public getQR(): Promise<Buffer> {
throw new NotImplementedByEngineError();
}
public requestCode(phoneNumber: string, method: string) {
throw new NotImplementedByEngineError();
}
public authorizeCode(code: string) {
throw new NotImplementedByEngineError();
}
abstract getScreenshot(): Promise<Buffer | string>;
/**
* Other methods
*/
abstract checkNumberStatus(request: CheckNumberStatusQuery);
abstract sendText(request: MessageTextRequest);
@@ -150,10 +196,35 @@ export abstract class WhatsappSession {
abstract stopTyping(chat: ChatRequest);
abstract getMessages(query: GetMessageQuery);
getMessages(query: GetMessageQuery) {
throw new NotImplementedByEngineError();
}
abstract setReaction(request: MessageReactionRequest);
/**
* Chats methods
*/
public getChats() {
throw new NotImplementedByEngineError();
}
public deleteChat(chatId) {
throw new NotImplementedByEngineError();
}
public getChatMessages(
chatId: string,
limit: number,
downloadMedia: boolean,
) {
throw new NotImplementedByEngineError();
}
public clearMessages(chatId) {
throw new NotImplementedByEngineError();
}
/**
* Contacts methods
*/
@@ -196,6 +267,10 @@ export abstract class WhatsappSession {
throw new NotImplementedByEngineError();
}
public setInfoAdminsOnly(id, value) {
throw new NotImplementedByEngineError();
}
public deleteGroup(id) {
throw new NotImplementedByEngineError();
}
@@ -240,6 +315,41 @@ export abstract class WhatsappSession {
throw new NotImplementedByEngineError();
}
public setPresence(presence: WAHAPresenceStatus, chatId?: string) {
throw new NotImplementedByEngineError();
}
public getPresences(): Promise<WAHAChatPresences[]> {
throw new NotImplementedByEngineError();
}
public getPresence(id: string): Promise<WAHAChatPresences> {
throw new NotImplementedByEngineError();
}
public subscribePresence(id: string): Promise<void> {
throw new NotImplementedByEngineError();
}
/**
* Status methods
*/
public sendTextStatus(status: TextStatus) {
throw new NotImplementedByEngineError();
}
public sendImageStatus(status: ImageStatus) {
throw new NotImplementedByEngineError();
}
public sendVoiceStatus(status: VoiceStatus) {
throw new NotImplementedByEngineError();
}
public sendVideoStatus(status: VideoStatus) {
throw new NotImplementedByEngineError();
}
/**
* END - Methods for API
*/
+25 -8
View File
@@ -1,4 +1,6 @@
export abstract class MediaStorage {
import { SessionConfig } from '../../structures/sessions.dto';
abstract class MediaStorage {
abstract save(
messageId: string,
mimetype: string,
@@ -6,18 +8,33 @@ export abstract class MediaStorage {
): Promise<string>;
}
export abstract class LocalSessionStorage {
public sessionsFolder: string;
abstract class SessionConfigRepository {
abstract save(sessionName: string, config: SessionConfig): Promise<void>;
abstract get(sessionName: string): Promise<SessionConfig>;
}
abstract class SessionStorage {
protected constructor(public engine: string) {
this.engine = engine;
}
abstract init();
abstract init(sessionName?: string): Promise<void>;
abstract clean(sessionName: string): Promise<void>;
abstract getAll(): Promise<string[]>;
//
// Repositories
//
abstract get configRepository(): SessionConfigRepository;
}
abstract class LocalSessionStorage extends SessionStorage {
public sessionsFolder: string;
abstract getFolderPath(sessionName: string): string;
abstract clean(sessionName: string);
abstract getAll(): string[];
}
export { LocalSessionStorage, MediaStorage, SessionConfigRepository };
+16 -1
View File
@@ -1,5 +1,20 @@
import { ConsoleLogger } from '@nestjs/common';
import { WebhookConfig } from '../../structures/webhooks.config.dto';
import { WhatsappSession } from './session.abc';
export abstract class WebhookSender {
protected url: string;
constructor(
protected log: ConsoleLogger,
protected webhookConfig: WebhookConfig,
) {
this.url = webhookConfig.url;
}
abstract send(json);
}
export abstract class WebhookConductor {
abstract configure(session: WhatsappSession);
abstract configure(session: WhatsappSession, webhooks: WebhookConfig[]);
}
+14 -5
View File
@@ -1,16 +1,21 @@
import { ConsoleLogger, Module } from '@nestjs/common';
import { ScreenshotController } from '../api/screenshot.controller';
import { ConfigModule } from '@nestjs/config';
import { WhatsappConfigService } from '../config.service';
import { PassportModule } from '@nestjs/passport';
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 { GroupsController } from '../api/groups.controller';
import { PresenceController } from '../api/presence.controller';
import { ScreenshotController } from '../api/screenshot.controller';
import { SessionsController } from '../api/sessions.controller';
import { VersionController } from '../api/version.controller';
import { PassportModule } from '@nestjs/passport';
import { WhatsappConfigService } from '../config.service';
import { SessionManager } from './abc/manager.abc';
import { SessionManagerCore } from './manager.core';
import { GroupsController } from '../api/groups.controller';
import { AuthController } from '../api/auth.controller';
import { ChatsController } from '../api/chats.controller';
import { StatusController } from '../api/status.controller';
export const IMPORTS = [
ConfigModule.forRoot({
@@ -32,10 +37,14 @@ export const IMPORTS = [
PassportModule,
];
export const CONTROLLERS = [
AuthController,
SessionsController,
ChattingController,
ChatsController,
StatusController,
ContactsController,
GroupsController,
PresenceController,
ScreenshotController,
VersionController,
];
+73
View File
@@ -0,0 +1,73 @@
import { Agent } from 'https';
import { HttpsProxyAgent } from 'https-proxy-agent';
import { URL } from 'url';
import { WhatsappConfigService } from '../config.service';
import { ProxyConfig } from '../structures/sessions.dto';
import { WhatsappSession } from './abc/session.abc';
export function getProxyConfig(
config: WhatsappConfigService,
sessions: Record<string, WhatsappSession>,
sessionName?: string,
): ProxyConfig | undefined {
// Single proxy server configuration
if (typeof config.proxyServer === 'string') {
return {
server: config.proxyServer,
username: config.proxyServerUsername,
password: config.proxyServerPassword,
};
}
// Multiple proxy server configuration
if (Array.isArray(config.proxyServer)) {
const prefix = config.proxyServerIndexPrefix;
let indexToUse: number = undefined;
if (prefix && sessionName.includes(prefix)) {
// match numbers at the end of the string
const matches = sessionName.match(/\d+$/);
indexToUse = matches ? parseInt(matches[0], 10) : undefined;
}
let idx = 0;
idx = Object.keys(sessions).length % config.proxyServer.length;
const index = indexToUse ? indexToUse % config.proxyServer.length : idx;
return {
server: config.proxyServer[index],
username: config.proxyServerUsername,
password: config.proxyServerPassword,
};
}
// No proxy server configuration
return undefined;
}
function getAuthenticatedUrl(
url: string,
username: string,
password: string,
): string {
const hasSchema = url.startsWith('http://') || url.startsWith('https://');
if (!hasSchema) {
url = `http://${url}`;
}
const parsedUrl = new URL(url);
parsedUrl.protocol = parsedUrl.protocol || 'http';
parsedUrl.username = username;
parsedUrl.password = password;
return parsedUrl.toString();
}
/**
* Return https Agent proxy for the config
* @param proxyConfig
*/
export function createAgentProxy(proxyConfig: ProxyConfig): Agent | undefined {
const url = getAuthenticatedUrl(
proxyConfig.server,
proxyConfig.username,
proxyConfig.password,
);
return new HttpsProxyAgent(url);
}
+83 -36
View File
@@ -4,27 +4,30 @@ import {
NotFoundException,
UnprocessableEntityException,
} from '@nestjs/common';
import { SessionManager } from './abc/manager.abc';
import { WhatsappConfigService } from '../config.service';
import { WAHAEngine, WAHASessionStatus } from '../structures/enums.dto';
import {
WAHAInternalEvent,
WhatsappSession,
WhatsAppSessionConfig,
} from './abc/session.abc';
import { WhatsappEngine, WhatsappStatus } from '../structures/enums.dto';
import {
SessionLogoutRequest,
ProxyConfig,
SessionDTO,
SessionLogoutRequest,
SessionStartRequest,
SessionStopRequest,
} from '../structures/sessions.dto';
import { WhatsappConfigService } from '../config.service';
import { WebhookConfig } from '../structures/webhooks.config.dto';
import { SessionManager } from './abc/manager.abc';
import {
SessionParams,
WAHAInternalEvent,
WhatsappSession,
} from './abc/session.abc';
import { DOCS_URL } from './exceptions';
import { getProxyConfig } from './helpers.proxy';
import { WhatsappSessionNoWebCore } from './session.noweb.core';
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';
import { WebhookConductorCore } from './webhooks.core';
export class OnlyDefaultSessionIsAllowed extends UnprocessableEntityException {
constructor() {
@@ -44,7 +47,6 @@ export class SessionManagerCore extends SessionManager {
// @ts-ignore
protected WebhookConductorClass = WebhookConductorCore;
protected readonly EngineClass: typeof WhatsappSession;
protected sessionStorage: LocalSessionStorage;
constructor(
private config: WhatsappConfigService,
@@ -57,7 +59,6 @@ export class SessionManagerCore extends SessionManager {
const engineName = this.config.getDefaultEngineName();
this.EngineClass = this.getEngine(engineName);
this.sessionStorage = new SessionStorageCore(engineName.toLowerCase());
this.sessionStorage.init();
this.startPredefinedSessions();
}
@@ -69,12 +70,12 @@ export class SessionManagerCore extends SessionManager {
});
}
protected getEngine(engine: WhatsappEngine): typeof WhatsappSession {
if (engine === WhatsappEngine.WEBJS) {
protected getEngine(engine: WAHAEngine): typeof WhatsappSession {
if (engine === WAHAEngine.WEBJS) {
return WhatsappSessionWebJSCore;
} else if (engine === WhatsappEngine.VENOM) {
} else if (engine === WAHAEngine.VENOM) {
return WhatsappSessionVenomCore;
} else if (engine === WhatsappEngine.NOWEB) {
} else if (engine === WAHAEngine.NOWEB) {
return WhatsappSessionNoWebCore;
} else {
throw new NotFoundException(`Unknown whatsapp engine '${engine}'.`);
@@ -97,7 +98,7 @@ export class SessionManagerCore extends SessionManager {
//
// API Methods
//
start(request: SessionStartRequest): SessionDTO {
async start(request: SessionStartRequest): Promise<SessionDTO> {
this.onlyDefault(request.name);
const name = request.name;
@@ -105,27 +106,60 @@ export class SessionManagerCore extends SessionManager {
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 = {
const webhook = new this.WebhookConductorClass(webhookLog);
const proxyConfig = this.getProxyConfig(request);
const sessionConfig: SessionParams = {
name,
storage,
log,
sessionStorage: this.sessionStorage,
proxyConfig: proxyConfig,
sessionConfig: request.config,
};
await this.sessionStorage.init(name);
// @ts-ignore
const session = new this.EngineClass(sessionConfig);
this.session = session;
// configure webhooks
const webhooks = this.getWebhooks(request);
session.events.on(WAHAInternalEvent.engine_start, () =>
webhook.configure(session),
webhook.configure(session, webhooks),
);
session.start();
return { name: session.name, status: session.status };
// start session
await session.start();
return {
name: session.name,
status: session.status,
config: session.sessionConfig,
};
}
/**
* Combine per session and global webhooks
*/
private getWebhooks(request: SessionStartRequest) {
let webhooks: WebhookConfig[] = [];
if (request.config?.webhooks) {
webhooks = webhooks.concat(request.config.webhooks);
}
const globalWebhookConfig = this.config.getWebhookConfig();
webhooks.push(globalWebhookConfig);
return webhooks;
}
/**
* Get either session's or global proxy if defined
*/
protected getProxyConfig(
request: SessionStartRequest,
): ProxyConfig | undefined {
if (request.config?.proxy) {
return request.config.proxy;
}
const sessions = { [request.name]: this.session };
return getProxyConfig(this.config, sessions, request.name);
}
async stop(request: SessionStopRequest): Promise<void> {
@@ -140,7 +174,7 @@ export class SessionManagerCore extends SessionManager {
}
async logout(request: SessionLogoutRequest) {
this.sessionStorage.clean(request.name);
await this.sessionStorage.clean(request.name);
}
getSession(name: string, error = true): WhatsappSession {
@@ -157,12 +191,25 @@ export class SessionManagerCore extends SessionManager {
return session;
}
getSessions(all: boolean): SessionDTO[] {
async getSessions(all: boolean): Promise<SessionDTO[]> {
if (!this.session) {
return all
? [{ name: this.DEFAULT, status: WhatsappStatus.STOPPED }]
: [];
if (!all) {
return [];
}
return [
{
name: this.DEFAULT,
status: WAHASessionStatus.STOPPED,
config: undefined,
},
];
}
return [{ name: this.session.name, status: this.session.status }];
return [
{
name: this.session.name,
status: this.session.status,
config: this.session.sessionConfig,
},
];
}
}
+326 -75
View File
@@ -1,27 +1,23 @@
import {
SECOND,
WAEvents,
WhatsappEngine,
WhatsappStatus,
} from '../structures/enums.dto';
import makeWASocket, {
Browsers,
DisconnectReason,
isJidGroup,
makeInMemoryStore,
PresenceData,
useMultiFileAuthState,
} from '@adiwajshing/baileys';
import { UnprocessableEntityException } from '@nestjs/common';
import * as fs from 'fs/promises';
import { Agent } from 'https';
import * as lodash from 'lodash';
import { PairingCodeResponse } from 'src/structures/auth.dto';
import { Message } from 'whatsapp-web.js';
import {
ensureSuffix,
WAHAInternalEvent,
WhatsappSession,
} from './abc/session.abc';
import { WAMessage, WANumberExistResult } from '../structures/responses.dto';
import { flipObject, splitAt } from '../helpers';
import {
Button,
ChatRequest,
CheckNumberStatusQuery,
GetMessageQuery,
MessageContactVcardRequest,
MessageFileRequest,
MessageImageRequest,
@@ -33,23 +29,41 @@ import {
MessageTextRequest,
MessageVoiceRequest,
SendSeenRequest,
WANumberExistResult,
} from '../structures/chatting.dto';
import {
AvailableInPlusVersion,
NotImplementedByEngineError,
} from './exceptions';
import { ContactQuery, ContactRequest } from '../structures/contacts.dto';
import {
ACK_UNKNOWN,
SECOND,
WAHAEngine,
WAHAEvents,
WAHAPresenceStatus,
WAHASessionStatus,
WAMessageAck,
} from '../structures/enums.dto';
import {
CreateGroupRequest,
ParticipantsRequest,
} from '../structures/groups.dto';
import {
WAHAChatPresences,
WAHAPresenceData,
} from '../structures/presence.dto';
import { WAMessage } from '../structures/responses.dto';
import { BROADCAST_ID, TextStatus } from '../structures/status.dto';
import {
ensureSuffix,
WAHAInternalEvent,
WhatsappSession,
} from './abc/session.abc';
import {
AvailableInPlusVersion,
NotImplementedByEngineError,
} from './exceptions';
import { createAgentProxy } from './helpers.proxy';
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';
import { WAMessageAckBody } from '../structures/webhooks.dto';
import { update } from 'lodash';
// eslint-disable-next-line @typescript-eslint/no-var-requires
const QRCode = require('qrcode');
@@ -59,60 +73,100 @@ const logger = require('pino')();
export const BaileysEvents = {
CONNECTION_UPDATE: 'connection.update',
CREDS_UPDATE: 'creds.update',
MESSAGES_UPDATE: 'messages.update',
MESSAGES_UPSERT: 'messages.upsert',
MESSAGE_RECEIPT_UPDATE: 'message-receipt.update',
GROUPS_UPSERT: 'groups.upsert',
PRESENCE_UPDATE: 'presence.update',
};
const PresenceStatuses = {
unavailable: WAHAPresenceStatus.OFFLINE,
available: WAHAPresenceStatus.ONLINE,
composing: WAHAPresenceStatus.TYPING,
recording: WAHAPresenceStatus.RECORDING,
paused: WAHAPresenceStatus.PAUSED,
};
const ToEnginePresenceStatus = flipObject(PresenceStatuses);
export class WhatsappSessionNoWebCore extends WhatsappSession {
engine = WhatsappEngine.NOWEB;
engine = WAHAEngine.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);
getSocketConfig(agent, state) {
return {
agent: agent,
fetchAgent: agent,
auth: state,
printQRInTerminal: true,
browser: Browsers.macOS('Chrome'),
logger: logger,
mobile: false,
defaultQueryTimeoutMs: undefined,
};
}
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,
});
const authFolder = this.sessionStorage.getFolderPath(this.name);
await fs.mkdir(authFolder, { recursive: true });
const { state, saveCreds } = await useMultiFileAuthState(authFolder);
const agent = this.makeAgent();
const socketConfig = this.getSocketConfig(agent, state);
const sock: any = makeWASocket(socketConfig);
sock.ev.on(BaileysEvents.CREDS_UPDATE, saveCreds);
return sock;
}
protected makeAgent(): Agent {
if (!this.proxyConfig) {
return undefined;
}
return createAgentProxy(this.proxyConfig);
}
connectStore() {
this.log.debug(`Connecting store...`);
if (!this.store) {
this.log.debug(`Making a new auth store...`);
this.store = makeInMemoryStore({});
}
this.log.debug(`Binding store to socket...`);
this.store.bind(this.sock.ev);
}
resubscribeToKnownPresences() {
for (const jid in this.store.presences) {
this.sock.presenceSubscribe(jid);
}
}
async buildClient() {
this.sock = await this.makeSocket();
this.connectStore();
this.listenConnectionEvents();
this.events.emit(WAHAInternalEvent.engine_start);
}
protected listenConnectionEvents() {
this.log.debug(`Start listening ${BaileysEvents.CONNECTION_UPDATE}...`);
this.sock.ev.on(BaileysEvents.CONNECTION_UPDATE, async (update) => {
const { connection, lastDisconnect, qr } = update;
if (connection === 'connecting') {
this.qr.save('');
this.status = WhatsappStatus.WORKING;
this.status = WAHASessionStatus.WORKING;
this.resubscribeToKnownPresences();
return;
} else if (connection === 'close') {
const shouldReconnect =
@@ -125,7 +179,7 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
shouldReconnect,
);
this.qr.save('');
this.status = WhatsappStatus.FAILED;
this.status = WAHASessionStatus.FAILED;
// reconnect if not logged out
if (shouldReconnect) {
setTimeout(() => this.buildClient(), 2 * SECOND);
@@ -134,32 +188,68 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
// Save QR
if (qr) {
this.status = WhatsappStatus.SCAN_QR_CODE;
this.status = WAHASessionStatus.SCAN_QR_CODE;
QRCode.toDataURL(qr).then((url) => {
this.qr.save(url);
});
}
});
this.events.emit(WAHAInternalEvent.engine_start);
}
stop() {
this.sock.ev.removeAllListeners();
this.sock.ws.removeAllListeners();
this.log.log('socket connection terminated');
this.sock.ws.close();
return;
}
/**
* START - Methods for API
*/
/**
* Auth methods
*/
public async getQR(): Promise<Buffer> {
return Promise.resolve(this.qr.get());
}
public async requestCode(
phoneNumber: string,
method: string,
): Promise<PairingCodeResponse> {
if (method) {
const err = `NOWEB engine doesn't support any 'method', remove it and try again`;
throw new UnprocessableEntityException(err);
}
if (this.status == WAHASessionStatus.STARTING) {
this.log.debug('Waiting for connection update...');
await this.sock.waitForConnectionUpdate((update) => !!update.qr);
}
if (this.status != WAHASessionStatus.SCAN_QR_CODE) {
const err = `Can request code only in SCAN_QR_CODE status. The current status is ${this.status}`;
throw new UnprocessableEntityException(err);
}
this.log.debug('Requesting pairing code...');
const code: string = await this.sock.requestPairingCode(phoneNumber);
// show it as ABCD-ABCD
const parts = splitAt(code, 4);
const codeRepr = parts.join('-');
this.log.debug(`Your code: ${codeRepr}`);
return { code: codeRepr };
}
async getScreenshot(): Promise<Buffer | string> {
if (this.status === WhatsappStatus.STARTING) {
if (this.status === WAHASessionStatus.STARTING) {
throw new UnprocessableEntityException(
`The session is starting, please try again after few seconds`,
);
} else if (this.status === WhatsappStatus.SCAN_QR_CODE) {
} else if (this.status === WAHASessionStatus.SCAN_QR_CODE) {
return Promise.resolve(this.qr.get());
} else if (this.status === WhatsappStatus.WORKING) {
} else if (this.status === WAHASessionStatus.WORKING) {
throw new UnprocessableEntityException(
`Can not get screenshot for non chrome based engine.`,
);
@@ -168,6 +258,9 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
}
}
/**
* Other methods
*/
async checkNumberStatus(
request: CheckNumberStatusQuery,
): Promise<WANumberExistResult> {
@@ -181,7 +274,11 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
sendText(request: MessageTextRequest) {
const chatId = this.ensureSuffix(request.chatId);
return this.sock.sendMessage(chatId, { text: request.text });
const message = {
text: request.text,
mentions: request.mentions?.map(toJID),
};
return this.sock.sendMessage(chatId, message);
}
sendTextButtons(request: MessageTextButtonsRequest) {
@@ -208,12 +305,17 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
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 },
const quotedMessage = await this.store.loadMessage(
toJID(request.chatId),
id,
);
const message = {
text: request.text,
mentions: request.mentions?.map(toJID),
};
return await this.sock.sendMessage(request.chatId, message, {
quoted: quotedMessage,
});
}
sendImage(request: MessageImageRequest) {
@@ -261,10 +363,6 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
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 = {
@@ -370,23 +468,91 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
return this.sock.groupParticipantsUpdate(id, participants, 'demote');
}
public async setPresence(presence: WAHAPresenceStatus, chatId?: string) {
const enginePresence = ToEnginePresenceStatus[presence];
if (!enginePresence) {
throw new NotImplementedByEngineError(
`NOWEB engine doesn't support '${presence}' presence.`,
);
}
await this.sock.sendPresenceUpdate(enginePresence, chatId);
}
public async getPresences(): Promise<WAHAChatPresences[]> {
const result: WAHAChatPresences[] = [];
for (const remoteJid in this.store.presences) {
const storedPresences = this.store.presences[remoteJid];
result.push(this.toWahaPresences(remoteJid, storedPresences));
}
return result;
}
public async getPresence(chatId: string): Promise<WAHAChatPresences> {
await this.subscribePresence(chatId);
const remoteJid = toJID(chatId);
const storedPresences = this.store.presences[remoteJid];
return this.toWahaPresences(remoteJid, storedPresences || []);
}
public async subscribePresence(chatId: string): Promise<void> {
const remoteJid = toJID(chatId);
if (this.store.presences[remoteJid]) {
return;
}
// Have no info - subscribe to listen for events
await this.sock.presenceSubscribe(remoteJid);
return;
}
/**
* Status methods
*/
public sendTextStatus(status: TextStatus) {
const message = { text: status.text };
const options = {
backgroundColor: status.backgroundColor,
font: status.font,
statusJidList: status.contacts.map(toJID),
};
return this.sock.sendMessage(BROADCAST_ID, message, options);
}
/**
* END - Methods for API
*/
subscribe(event, handler) {
if (event === WAEvents.MESSAGE) {
return this.sock.ev.on(BaileysEvents.MESSAGES_UPSERT, ({ messages }) =>
if (event === WAHAEvents.MESSAGE) {
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 }) =>
} else if (event === WAHAEvents.MESSAGE_ANY) {
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 if (event === WAHAEvents.MESSAGE_ACK) {
// Direct message ack
this.sock.ev.on(BaileysEvents.MESSAGES_UPDATE, (events) => {
events
.filter(isMine)
.map(this.convertMessageUpdateToMessageAck)
.forEach(handler);
});
// Group message ack
this.sock.ev.on(BaileysEvents.MESSAGE_RECEIPT_UPDATE, (events) => {
events
.filter(isMine)
.map(this.convertMessageReceiptUpdateToMessageAck)
.forEach(handler);
});
} else if (event === WAHAEvents.STATE_CHANGE) {
this.sock.ev.on(BaileysEvents.CONNECTION_UPDATE, handler);
} else if (event === WAHAEvents.GROUP_JOIN) {
this.sock.ev.on(BaileysEvents.GROUPS_UPSERT, handler);
} else if (event === WAHAEvents.PRESENCE_UPDATE) {
this.sock.ev.on(BaileysEvents.PRESENCE_UPDATE, (data) =>
handler(this.toWahaPresences(data.id, data.presences)),
);
} else {
throw new NotImplementedByEngineError(
`Engine does not support webhook event: ${event}`,
@@ -408,18 +574,17 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
}
private processIncomingMessage(message) {
return this.downloadMedia(message).then(this.toWAMessage);
return this.downloadMedia(message)
.then(this.toWAMessage)
.catch((error) => {
this.log.error('Failed to process incoming message');
this.log.error(error);
console.trace(error);
});
}
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 destination = getDestination(message);
const id = buildMessageId(message.key);
let body = message.message.conversation;
if (!body) {
@@ -430,22 +595,88 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
return Promise.resolve({
id: id,
timestamp: message.messageTimestamp,
from: toCusFormat(from),
from: toCusFormat(destination.from),
fromMe: message.key.fromMe,
body: body,
to: toCusFormat(to),
participant: toCusFormat(participant),
to: toCusFormat(destination.to),
participant: toCusFormat(destination.participant),
// @ts-ignore
hasMedia: Boolean(message.mediaUrl),
// @ts-ignore
mediaUrl: message.mediaUrl,
// @ts-ignore
ack: message.ack,
// @ts-ignore
ackName: WAMessageAck[message.ack] || ACK_UNKNOWN,
location: message.location,
vCards: message.vCards,
_data: message,
});
}
protected convertMessageUpdateToMessageAck(event): WAMessageAckBody {
const message = event;
const destination = getDestination(message);
const id = buildMessageId(message.key);
const ack = message.update.status - 1;
const body: WAMessageAckBody = {
id: id,
from: toCusFormat(destination.from),
to: toCusFormat(destination.to),
participant: toCusFormat(destination.participant),
fromMe: message.key.fromMe,
ack: ack,
ackName: WAMessageAck[ack] || ACK_UNKNOWN,
};
return body;
}
protected convertMessageReceiptUpdateToMessageAck(event): WAMessageAckBody {
const destination = getDestination(event);
const id = buildMessageId(event.key);
const receipt = event.receipt;
let ack;
if (receipt.receiptTimestamp) {
ack = WAMessageAck.SERVER;
} else if (receipt.playedTimestamp) {
ack = WAMessageAck.PLAYED;
} else if (receipt.readTimestamp) {
ack = WAMessageAck.READ;
}
const body: WAMessageAckBody = {
id: id,
from: toCusFormat(destination.from),
to: toCusFormat(destination.to),
participant: toCusFormat(destination.participant),
fromMe: event.key.fromMe,
ack: ack,
ackName: WAMessageAck[ack] || ACK_UNKNOWN,
};
return body;
}
private toWahaPresences(
remoteJid,
storedPresences: PresenceData[],
): WAHAChatPresences {
const presences: WAHAPresenceData[] = [];
for (const participant in storedPresences) {
const data: PresenceData = storedPresences[participant];
const lastKnownPresence = lodash.get(
PresenceStatuses,
data.lastKnownPresence,
data.lastKnownPresence,
);
const presence: WAHAPresenceData = {
participant: toCusFormat(participant),
// @ts-ignore
lastKnownPresence: lastKnownPresence,
lastSeen: data.lastSeen || null,
};
presences.push(presence);
}
const chatId = toCusFormat(remoteJid);
return { id: chatId, presences: presences };
}
protected async downloadMedia(message: Message) {
if (!message.hasMedia) {
@@ -476,7 +707,7 @@ function toCusFormat(remoteJid) {
* Convert from 11111111111@c.us to 11111111111@s.whatsapp.net
* @param chatId
*/
function toJID(chatId) {
export function toJID(chatId) {
if (isJidGroup(chatId)) {
return chatId;
}
@@ -516,3 +747,23 @@ function parseMessageId(messageId) {
function getId(object) {
return object.id;
}
function isMine(message) {
return message?.key?.fromMe;
}
function getDestination(message) {
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;
return {
from: from,
to: to,
participant: participant,
};
}
+46 -25
View File
@@ -1,4 +1,6 @@
import { WAHAInternalEvent, WhatsappSession } from './abc/session.abc';
import { UnprocessableEntityException } from '@nestjs/common/exceptions/unprocessable-entity.exception';
import { create, CreateConfig, Message, Whatsapp } from 'venom-bot';
import {
Button,
ChatRequest,
@@ -13,20 +15,20 @@ import {
MessageReplyRequest,
MessageTextButtonsRequest,
MessageTextRequest,
WANumberExistResult,
} from '../structures/chatting.dto';
import { WAMessage, WANumberExistResult } from '../structures/responses.dto';
import { create, Message, Whatsapp } from 'venom-bot';
import {
WAEvents,
WhatsappEngine,
WhatsappStatus,
WAHAEngine,
WAHAEvents,
WAHASessionStatus,
} from '../structures/enums.dto';
import { WAMessage } from '../structures/responses.dto';
import { WAHAInternalEvent, WhatsappSession } from './abc/session.abc';
import { NotImplementedByEngineError } from './exceptions';
import { UnprocessableEntityException } from '@nestjs/common/exceptions/unprocessable-entity.exception';
import { QR } from './QR';
export class WhatsappSessionVenomCore extends WhatsappSession {
engine = WhatsappEngine.NOWEB;
engine = WAHAEngine.VENOM;
whatsapp: Whatsapp;
private qr: QR;
@@ -37,7 +39,7 @@ export class WhatsappSessionVenomCore extends WhatsappSession {
}
protected buildClient() {
return create(this.name, this.getCatchQR(), undefined, {
const venomOptions: CreateConfig = {
headless: true,
devtools: false,
debug: false,
@@ -45,13 +47,28 @@ export class WhatsappSessionVenomCore extends WhatsappSession {
browserArgs: this.getBrowserArgsForPuppeteer(),
autoClose: 60000,
puppeteerOptions: {},
});
};
this.addProxyConfig(venomOptions);
return create(this.name, this.getCatchQR(), undefined, venomOptions);
}
protected addProxyConfig(venomOptions: CreateConfig) {
if (this.proxyConfig?.server !== undefined) {
venomOptions.addProxy = [this.proxyConfig?.server];
}
if (
this.proxyConfig?.username !== undefined &&
this.proxyConfig?.password !== undefined
) {
venomOptions.userProxy = this.proxyConfig?.username;
venomOptions.userPass = this.proxyConfig?.password;
}
}
protected getCatchQR() {
return (base64Qrimg, asciiQR, attempts, urlCode) => {
this.qr.save(base64Qrimg);
this.status = WhatsappStatus.SCAN_QR_CODE;
this.status = WAHASessionStatus.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
@@ -63,13 +80,13 @@ export class WhatsappSessionVenomCore extends WhatsappSession {
try {
this.whatsapp = await this.buildClient();
} catch (error) {
this.status = WhatsappStatus.FAILED;
this.status = WAHASessionStatus.FAILED;
this.log.error(error);
this.qr.save('');
return;
}
this.status = WhatsappStatus.WORKING;
this.status = WAHASessionStatus.WORKING;
this.events.emit(WAHAInternalEvent.engine_start);
return this;
}
@@ -78,20 +95,20 @@ export class WhatsappSessionVenomCore extends WhatsappSession {
return this.whatsapp.close();
}
subscribe(event: WAEvents | string, handler: (message) => void) {
if (event === WAEvents.MESSAGE) {
subscribe(event: WAHAEvents | string, handler: (message) => void) {
if (event === WAHAEvents.MESSAGE) {
return this.whatsapp.onMessage((message: Message) =>
this.processIncomingMessage(message).then(handler),
);
} else if (event === WAEvents.MESSAGE_ANY) {
} else if (event === WAHAEvents.MESSAGE_ANY) {
return this.whatsapp.onAnyMessage((message: Message) =>
this.processIncomingMessage(message).then(handler),
);
} else if (event === WAEvents.STATE_CHANGE) {
} else if (event === WAHAEvents.STATE_CHANGE) {
return this.whatsapp.onStateChange(handler);
} else if (event === WAEvents.MESSAGE_ACK) {
} else if (event === WAHAEvents.MESSAGE_ACK) {
return this.whatsapp.onAck(handler);
} else if (event === WAEvents.GROUP_JOIN) {
} else if (event === WAHAEvents.GROUP_JOIN) {
return this.whatsapp.onAddedToGroup(handler);
} else {
throw new NotImplementedByEngineError(
@@ -104,13 +121,13 @@ export class WhatsappSessionVenomCore extends WhatsappSession {
* START - Methods for API
*/
getScreenshot(): Promise<Buffer | string> {
if (this.status === WhatsappStatus.STARTING) {
if (this.status === WAHASessionStatus.STARTING) {
throw new UnprocessableEntityException(
`The session is starting, please try again after few seconds`,
);
} else if (this.status === WhatsappStatus.SCAN_QR_CODE) {
} else if (this.status === WAHASessionStatus.SCAN_QR_CODE) {
return Promise.resolve(this.qr.get());
} else if (this.status === WhatsappStatus.WORKING) {
} else if (this.status === WAHASessionStatus.WORKING) {
return this.whatsapp.page.screenshot();
} else {
throw new UnprocessableEntityException(`Unknown status - ${this.status}`);
@@ -212,16 +229,16 @@ export class WhatsappSessionVenomCore extends WhatsappSession {
// return this.whatsapp.stopTyping(chat.chatId);
}
async getMessages(query: GetMessageQuery) {
async getChatMessages(chatId: string, limit: number) {
const messages = await this.whatsapp.getAllMessagesInChat(
query.chatId,
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) {
if (count > limit) {
// Have enough in the list, stop processing
break;
}
@@ -230,6 +247,10 @@ export class WhatsappSessionVenomCore extends WhatsappSession {
return result;
}
async getMessages(query: GetMessageQuery) {
return this.getChatMessages(query.chatId, query.limit);
}
setReaction(request: MessageReactionRequest) {
throw new NotImplementedByEngineError();
}
+170 -49
View File
@@ -3,6 +3,7 @@ import {
Buttons,
Chat,
Client,
ClientOptions,
Contact,
Events,
GroupChat,
@@ -10,13 +11,7 @@ import {
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,
@@ -33,54 +28,83 @@ import {
MessageTextRequest,
MessageVoiceRequest,
SendSeenRequest,
WANumberExistResult,
} from '../structures/chatting.dto';
import {
AvailableInPlusVersion,
NotImplementedByEngineError,
} from './exceptions';
import { ContactQuery, ContactRequest } from '../structures/contacts.dto';
import {
ACK_UNKNOWN,
WAHAEngine,
WAHAEvents,
WAHAPresenceStatus,
WAHASessionStatus,
WAMessageAck,
} from '../structures/enums.dto';
import {
CreateGroupRequest,
ParticipantsRequest,
} from '../structures/groups.dto';
import { WAMessage } from '../structures/responses.dto';
import { WAHAInternalEvent, WhatsappSession } from './abc/session.abc';
import {
AvailableInPlusVersion,
NotImplementedByEngineError,
} from './exceptions';
import { QR } from './QR';
// eslint-disable-next-line @typescript-eslint/no-var-requires
const QRCode = require('qrcode');
// eslint-disable-next-line @typescript-eslint/no-var-requires
const qrcode = require('qrcode-terminal');
export class WhatsappSessionWebJSCore extends WhatsappSession {
engine = WhatsappEngine.NOWEB;
engine = WAHAEngine.WEBJS;
whatsapp: Client;
protected qr: QR;
public constructor(config) {
super(config);
this.qr = new QR();
}
protected buildClient() {
return new Client({
const clientOptions: ClientOptions = {
puppeteer: {
headless: true,
executablePath: this.getBrowserExecutablePath(),
args: this.getBrowserArgsForPuppeteer(),
},
});
};
this.addProxyConfig(clientOptions);
return new Client(clientOptions);
}
protected addProxyConfig(clientOptions: ClientOptions) {
if (this.proxyConfig?.server !== undefined) {
// push the proxy server to the args
clientOptions.puppeteer.args.push(
`--proxy-server=${this.proxyConfig?.server}`,
);
// Authenticate
if (this.proxyConfig?.username && this.proxyConfig?.password) {
clientOptions.proxyAuthentication = {
username: this.proxyConfig?.username,
password: this.proxyConfig?.password,
};
}
}
}
async start() {
this.whatsapp = this.buildClient();
this.whatsapp.initialize().catch((error) => {
this.status = WhatsappStatus.FAILED;
this.status = WAHASessionStatus.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.listenConnectionEvents();
this.events.emit(WAHAInternalEvent.engine_start);
return this;
}
@@ -89,11 +113,38 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
return this.whatsapp.destroy();
}
protected listenConnectionEvents() {
this.whatsapp.on(Events.QR_RECEIVED, (qr) => {
this.log.debug('QR received');
// Convert to image and save
QRCode.toDataURL(qr).then((url) => {
this.qr.save(url);
});
// Print in terminal
qrcode.generate(qr, { small: true });
this.status = WAHASessionStatus.SCAN_QR_CODE;
});
this.whatsapp.on(Events.AUTHENTICATED, () => {
this.status = WAHASessionStatus.WORKING;
this.qr.save('');
this.log.log(`Session '${this.name}' has been authenticated!`);
});
}
/**
* START - Methods for API
*/
/**
* Auth methods
*/
public async getQR(): Promise<Buffer> {
return Promise.resolve(this.qr.get());
}
async getScreenshot(): Promise<Buffer | string> {
if (this.status === WhatsappStatus.FAILED) {
if (this.status === WAHASessionStatus.FAILED) {
throw new UnprocessableEntityException(
`The session under FAILED status. Please try to restart it.`,
);
@@ -111,9 +162,14 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
}
sendText(request: MessageTextRequest) {
const options = {
// It's fine to sent just ids instead of Contact object
mentions: request.mentions as unknown as Contact[],
};
return this.whatsapp.sendMessage(
this.ensureSuffix(request.chatId),
request.text,
options,
);
}
@@ -138,6 +194,8 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
reply(request: MessageReplyRequest) {
const options = {
quotedMessageId: request.reply_to,
// It's fine to sent just ids instead of Contact object
mentions: request.mentions as unknown as Contact[],
};
return this.whatsapp.sendMessage(request.chatId, request.text, options);
}
@@ -183,18 +241,7 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
}
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;
return this.getChatMessages(query.chatId, query.limit, query.downloadMedia);
}
async setReaction(request: MessageReactionRequest) {
@@ -207,6 +254,39 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
return message.react(request.reaction);
}
/**
* Chats methods
*/
getChats() {
return this.whatsapp.getChats();
}
async getChatMessages(chatId: string, limit: number, downloadMedia: boolean) {
const chat: Chat = await this.whatsapp.getChatById(
this.ensureSuffix(chatId),
);
const messages = await chat.fetchMessages({
limit: limit,
});
// Go over messages, download media, and convert to right format.
const result = [];
for (const message of messages) {
const msg = await this.processIncomingMessage(message, downloadMedia);
result.push(msg);
}
return result;
}
async deleteChat(chatId) {
const chat = await this.whatsapp.getChatById(chatId);
return chat.delete();
}
async clearMessages(chatId) {
const chat = await this.whatsapp.getChatById(chatId);
return chat.clearMessages();
}
/**
* Contacts methods
*/
@@ -260,6 +340,11 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
return this.whatsapp.createGroup(request.name, participantIds);
}
public async setInfoAdminsOnly(id, value) {
const groupChat = (await this.whatsapp.getChatById(id)) as GroupChat;
return groupChat.setInfoAdminsOnly(value);
}
public getGroups() {
return this.whatsapp
.getChats()
@@ -338,29 +423,57 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
return groupChat.demoteParticipants(participantIds);
}
public async setPresence(presence: WAHAPresenceStatus, chatId?: string) {
let chat: Chat;
switch (presence) {
case WAHAPresenceStatus.ONLINE:
await this.whatsapp.sendPresenceAvailable();
break;
case WAHAPresenceStatus.OFFLINE:
await this.whatsapp.sendPresenceUnavailable();
break;
case WAHAPresenceStatus.TYPING:
chat = await this.whatsapp.getChatById(chatId);
await chat.sendStateTyping();
break;
case WAHAPresenceStatus.RECORDING:
chat = await this.whatsapp.getChatById(chatId);
await chat.sendStateRecording();
break;
case WAHAPresenceStatus.PAUSED:
chat = await this.whatsapp.getChatById(chatId);
await chat.clearState();
break;
default:
throw new NotImplementedByEngineError(
`WEBJS engine doesn't support '${presence}' presence.`,
);
}
}
/**
* END - Methods for API
*/
subscribe(event, handler) {
if (event === WAEvents.MESSAGE) {
if (event === WAHAEvents.MESSAGE) {
this.whatsapp.on(Events.MESSAGE_RECEIVED, (message) =>
this.processIncomingMessage(message).then(handler),
);
} else if (event === WAEvents.MESSAGE_ANY) {
} else if (event === WAHAEvents.MESSAGE_ANY) {
this.whatsapp.on(Events.MESSAGE_CREATE, (message) =>
this.processIncomingMessage(message).then(handler),
);
} else if (event === WAEvents.STATE_CHANGE) {
} else if (event === WAHAEvents.STATE_CHANGE) {
this.whatsapp.on(Events.STATE_CHANGED, handler);
} else if (event === WAEvents.MESSAGE_ACK) {
} else if (event === WAHAEvents.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) {
} else if (event === WAHAEvents.GROUP_JOIN) {
this.whatsapp.on(Events.GROUP_JOIN, handler);
} else if (event === WAEvents.GROUP_LEAVE) {
} else if (event === WAHAEvents.GROUP_LEAVE) {
this.whatsapp.on(Events.GROUP_LEAVE, handler);
} else {
throw new NotImplementedByEngineError(
@@ -369,8 +482,16 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
}
}
private processIncomingMessage(message: Message) {
return this.downloadMedia(message).then(this.toWAMessage);
private async processIncomingMessage(message: Message, downloadMedia = true) {
if (downloadMedia) {
try {
message = await this.downloadMedia(message);
} catch (e) {
this.log.error('Failed when tried to download media for a message');
this.log.error(e, e.stack);
}
}
return await this.toWAMessage(message);
}
protected toWAMessage(message: Message): Promise<WAMessage> {
@@ -386,8 +507,8 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
hasMedia: Boolean(message.mediaUrl),
// @ts-ignore
mediaUrl: message.mediaUrl,
// @ts-ignore
ack: message.ack,
ackName: WAMessageAck[message.ack] || ACK_UNKNOWN,
location: message.location,
vCards: message.vCards,
_data: message.rawData,
+74 -13
View File
@@ -1,8 +1,15 @@
import { DOCS_URL } from './exceptions';
import { LocalSessionStorage, MediaStorage } from './abc/storage.abc';
import * as path from 'path';
import * as fs from 'fs';
import * as crypto from 'crypto';
import * as fs from 'fs/promises';
import * as os from 'os';
import * as path from 'path';
import { SessionConfig } from '../structures/sessions.dto';
import {
LocalSessionStorage,
MediaStorage,
SessionConfigRepository,
} from './abc/storage.abc';
import { DOCS_URL } from './exceptions';
export class MediaStorageCore implements MediaStorage {
async save(
@@ -16,35 +23,89 @@ export class MediaStorageCore implements MediaStorage {
}
}
class LocalSessionConfigRepository extends SessionConfigRepository {
FILENAME = '.waha.session.config.json';
constructor(private storage: LocalSessionStorage) {
super();
}
async get(sessionName: string): Promise<SessionConfig> {
const filepath = this.getFilePath(sessionName);
// Check file exists
try {
await fs.access(filepath, fs.constants.F_OK);
} catch (error) {
return undefined;
}
// Try to load config
let content;
try {
content = await fs.readFile(filepath, 'utf-8');
} catch (error) {
return undefined;
}
return JSON.parse(content);
}
async save(sessionName: string, config: SessionConfig) {
// Create folder if not exist
const folder = this.storage.getFolderPath(sessionName);
await fs.mkdir(folder, { recursive: true });
// Save config
const filepath = this.getFilePath(sessionName);
const content = JSON.stringify(config || null);
await fs.writeFile(filepath, content);
}
private getFilePath(sessionName): string {
const folder = this.storage.getFolderPath(sessionName);
return path.join(folder, this.FILENAME);
}
}
export class SessionStorageCore extends LocalSessionStorage {
public sessionsFolder: string;
constructor(engine: string) {
super(engine);
this.sessionsFolder = path.join(os.tmpdir(), 'waha-');
this.sessionsFolder = path.join(os.tmpdir(), 'waha-sessions');
}
get engineFolder() {
return path.join(this.sessionsFolder, this.engine);
}
init() {
fs.mkdirSync(this.engineFolder, { recursive: true });
async init(sessionName?: string) {
const folder = sessionName
? this.getFolderPath(sessionName)
: this.engineFolder;
await fs.mkdir(folder, { recursive: true });
}
getFolderPath(name: string): string {
return path.join(this.engineFolder, name);
const suffix = crypto.createHash('md5').update(name).digest('hex');
return path.join(this.engineFolder, `${name}-${suffix}`);
}
clean(sessionName: string) {
async clean(sessionName: string) {
const folder = this.getFolderPath(sessionName);
fs.rmSync(folder, { recursive: true });
await fs.rm(folder, { recursive: true });
}
getAll(): string[] {
return fs
.readdirSync(this.engineFolder, { withFileTypes: true })
async getAll(): Promise<string[]> {
await this.init();
const content = await fs.readdir(this.engineFolder, {
withFileTypes: true,
});
return content
.filter((dirent) => dirent.isDirectory())
.map((dirent) => dirent.name);
}
get configRepository(): LocalSessionConfigRepository {
return new LocalSessionConfigRepository(this);
}
}
+15 -5
View File
@@ -1,7 +1,8 @@
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
import { INestApplication } from '@nestjs/common';
import { VERSION } from '../version';
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
import { WhatsappConfigService } from '../config.service';
import { VERSION } from '../version';
export class SwaggerModuleCore {
configure(app: INestApplication) {
@@ -10,12 +11,18 @@ export class SwaggerModuleCore {
builder
.setTitle('WAHA - WhatsApp HTTP API')
.setDescription('WhatsApp HTTP API that you can configure in a click!')
.setDescription('WhatsApp HTTP API that you can run in a click!')
.setExternalDoc('Documentation', 'https://waha.devlike.pro/')
.setVersion(VERSION.version)
.addTag('sessions', 'Control WhatsApp sessions')
.addTag('auth', 'Authentication')
.addTag('screenshot', 'Get screenshot of WhatsApp and show QR code')
.addTag('chatting', 'Chat methods')
.addTag('chatting', 'Chatting methods')
.addTag(
'status',
'Status (aka stories) methods. <b>NOWEB</b> engine only!',
)
.addTag('chats', `Chats methods`)
.addTag(
'contacts',
`Contacts methods.<br>
@@ -23,6 +30,7 @@ export class SwaggerModuleCore {
'E.g: \`12312312310\` OR \`12312312310@c.us\`<br>`,
)
.addTag('groups', `Groups methods.<br>`)
.addTag('presence', `Presence information`)
.addTag('other', 'Other methods')
.addApiKey({
type: 'apiKey',
@@ -57,7 +65,9 @@ export class SwaggerModuleCore {
const options = builder.build();
const document = SwaggerModule.createDocument(app, options);
SwaggerModule.setup('', app, document);
SwaggerModule.setup('', app, document, {
customSiteTitle: 'WAHA - WhatsApp HTTP API',
});
}
protected setUpAuth(app: INestApplication) {
+58 -34
View File
@@ -1,24 +1,40 @@
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 { WAHAEvents } from '../structures/enums.dto';
import { WebhookConfig } from '../structures/webhooks.config.dto';
import { WhatsappSession } from './abc/session.abc';
import { WebhookConductor, WebhookSender } from './abc/webhooks.abc';
import { NotImplementedByEngineError } from './exceptions';
import { WebhookConductor } from './abc/webhooks.abc';
import request = require('request');
import { WAWebhook } from '../structures/webhooks.dto';
export class WebhookSenderCore extends WebhookSender {
send(json) {
this.log.log(`Sending POST to ${this.url}...`);
this.log.debug(`POST DATA: ${JSON.stringify(json)}`);
request.post(this.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)}`);
});
}
}
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);
constructor(protected log: ConsoleLogger) {
this.log = log;
}
private getSuitableEvents(events: WAEvents[] | string[]) {
const allEvents = Object.values(WAEvents);
protected buildSender(webhookConfig: WebhookConfig): WebhookSender {
return new WebhookSenderCore(this.log, webhookConfig);
}
private getSuitableEvents(events: WAHAEvents[] | string[]): WAHAEvents[] {
const allEvents = Object.values(WAHAEvents);
// Enable all events if * in the events
// @ts-ignore
@@ -39,12 +55,28 @@ export class WebhookConductorCore implements WebhookConductor {
return rightEvents;
}
public configure(session: WhatsappSession) {
this.log.log('Configuring webhooks...');
for (const event of this.events) {
public configure(session: WhatsappSession, webhooks: WebhookConfig[]) {
for (const webhookConfig of webhooks) {
this.configureSingleWebhook(session, webhookConfig);
}
}
private configureSingleWebhook(
session: WhatsappSession,
webhook: WebhookConfig,
) {
if (!webhook || !webhook.url || webhook.events.length === 0) {
return;
}
const url = webhook.url;
this.log.log(`Configuring webhooks for ${url}...`);
const events = this.getSuitableEvents(webhook.events);
const sender = this.buildSender(webhook);
for (const event of events) {
try {
session.subscribe(event, (data: any) =>
this.callWebhook(event, session, data, this.url),
this.callWebhook(event, session, data, sender),
);
} catch (error) {
if (error instanceof NotImplementedByEngineError) {
@@ -53,31 +85,23 @@ export class WebhookConductorCore implements WebhookConductor {
throw error;
}
}
this.log.log(`Event '${event}' is enabled for url: ${this.url}`);
this.log.log(`Event '${event}' is enabled for url: ${url}`);
}
this.log.log('Webhooks were configured.');
this.log.log(`Webhooks were configured for ${url}.`);
}
public callWebhook(event, session: WhatsappSession, data: any, url) {
public callWebhook(
event,
session: WhatsappSession,
data: any,
sender: WebhookSender,
) {
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);
}
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)}`);
});
sender.send(json);
}
}
+15 -1
View File
@@ -1,4 +1,4 @@
export function parseBool(value: any): boolean {
function parseBool(value: any): boolean {
if (!value) {
return false;
}
@@ -18,3 +18,17 @@ export function parseBool(value: any): boolean {
'Error: parseBool got unexpected value - use "true" or "false" values',
);
}
function flipObject(object) {
return Object.fromEntries(
Object.entries(object).map(([key, value]) => [value, key]),
);
}
function splitAt(str: string, index) {
const fst = [...str];
const snd = fst.splice(index);
return [fst.join(''), snd.join('')];
}
export { flipObject, parseBool, splitAt };
+4 -3
View File
@@ -1,10 +1,11 @@
import { NestFactory } from '@nestjs/core';
import { WhatsappConfigService } from './config.service';
import { json, urlencoded } from 'express';
import { AllExceptionsFilter } from './api/exception.filter';
import { getWAHAVersion, VERSION, WAHAVersion } from './version';
import { WhatsappConfigService } from './config.service';
import { AppModuleCore } from './core/app.module.core';
import { SwaggerModuleCore } from './core/swagger.module.core';
import { json, urlencoded } from 'express';
import { getWAHAVersion, WAHAVersion } from './version';
async function loadModules(): Promise<
[typeof AppModuleCore, typeof SwaggerModuleCore]
+24
View File
@@ -0,0 +1,24 @@
import { ApiProperty } from '@nestjs/swagger';
export class RequestCodeRequest {
@ApiProperty({
description: 'Mobile phone number in international format',
example: '12132132130',
})
phoneNumber: string;
@ApiProperty({
description:
'How would you like to receive the one time code for registration? |sms|voice. Leave empty for Web pairing.',
example: null,
required: false,
})
method: string;
}
export class OTPRequest {
code: string;
}
export class PairingCodeResponse {
code: string;
}
+21
View File
@@ -0,0 +1,21 @@
import { ApiProperty } from '@nestjs/swagger';
import { SessionBaseRequest, SessionQuery } from './base.dto';
/**
* Queries
*/
export class GetChatMessagesQuery extends SessionQuery {
limit: number = 100;
@ApiProperty({
example: true,
required: false,
description: 'Download media for messages',
})
downloadMedia: boolean = true;
}
/**
* Requests
*/
+28 -80
View File
@@ -1,6 +1,13 @@
import { IsNotEmpty, IsString, IsOptional, IsNumber } from 'class-validator';
import { ApiExtraModels, ApiProperty, getSchemaPath } from '@nestjs/swagger';
import { IsNumber, IsString } from 'class-validator';
import { SessionBaseRequest, SessionQuery } from './base.dto';
import {
BinaryFile,
RemoteFile,
VoiceBinaryFile,
VoiceRemoteFile,
} from './files.dto';
/**
* Queries
@@ -27,8 +34,16 @@ export class ChatQuery extends SessionQuery {
export class GetMessageQuery extends ChatQuery {
@IsNumber()
limit: number;
@ApiProperty({
example: true,
required: false,
description: 'Download media for messages',
})
downloadMedia: true;
}
export class GetPresenceQuery extends ChatQuery {}
/**
* Requests
*/
@@ -71,6 +86,13 @@ export class MessageContactVcardRequest extends ChatRequest {
export class MessageTextRequest extends ChatRequest {
text = 'Hi there!';
@ApiProperty({
description:
'Mention contact in the message. ' +
"The message MUST contain '@123456789' text in order to work with 'mentions' field.",
example: ['123456789@c.us'],
})
mentions?: string[];
}
export class Button {
@@ -84,7 +106,7 @@ export class MessageTextButtonsRequest extends ChatRequest {
buttons?: Array<Button>;
}
export class MessageReplyRequest extends ChatRequest {
export class MessageReplyRequest extends MessageTextRequest {
text = 'Reply text';
@ApiProperty({
example: 'false_11111111111@c.us_AAAAAAAAAAAAAAAAAAAA',
@@ -98,47 +120,6 @@ export class MessageLocationRequest extends ChatRequest {
title: string;
}
export class RemoteFile {
@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({
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: 'Document file name. Optional',
example: 'filename.jpeg',
})
filename?: string | null;
@ApiProperty({
description: 'Base64-encoded data of the file',
example:
'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',
})
data: string;
}
@ApiExtraModels(BinaryFile, RemoteFile)
class FileRequest extends ChatRequest {
@ApiProperty({
@@ -158,43 +139,6 @@ export class MessageFileRequest extends FileRequest {
caption: string;
}
export class VoiceBinaryFile {
@ApiProperty({
description: 'MIME type of the attachment.',
})
mimetype = 'audio/ogg; codecs=opus';
@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;
}
export class VoiceRemoteFile {
@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;
}
@ApiExtraModels(VoiceBinaryFile, VoiceRemoteFile)
export class MessageVoiceRequest extends ChatRequest {
@ApiProperty({
@@ -219,3 +163,7 @@ export class MessageReactionRequest extends MessageRequest {
})
reaction: string;
}
export class WANumberExistResult {
numberExists: boolean;
}
+1
View File
@@ -1,4 +1,5 @@
import { IsString } from 'class-validator';
import { SessionBaseRequest, SessionQuery } from './base.dto';
/**
+23 -3
View File
@@ -1,15 +1,16 @@
export const SECOND = 1000;
export enum WAEvents {
export enum WAHAEvents {
MESSAGE = 'message',
MESSAGE_ANY = 'message.any',
MESSAGE_ACK = 'message.ack',
STATE_CHANGE = 'state.change',
GROUP_JOIN = 'group.join',
GROUP_LEAVE = 'group.leave',
PRESENCE_UPDATE = 'presence.update',
}
export enum WhatsappStatus {
export enum WAHASessionStatus {
STOPPED = 'STOPPED',
STARTING = 'STARTING',
SCAN_QR_CODE = 'SCAN_QR_CODE',
@@ -17,8 +18,27 @@ export enum WhatsappStatus {
FAILED = 'FAILED',
}
export enum WhatsappEngine {
export enum WAHAEngine {
VENOM = 'VENOM',
WEBJS = 'WEBJS',
NOWEB = 'NOWEB',
NOWEB_MOBILE = 'NOWEB_MOBILE',
}
export enum WAHAPresenceStatus {
OFFLINE = 'offline',
ONLINE = 'online',
TYPING = 'typing',
RECORDING = 'recording',
PAUSED = 'paused',
}
export enum WAMessageAck {
ERROR = -1,
PENDING = 0,
SERVER = 1,
DEVICE = 2,
READ = 3,
PLAYED = 4,
}
export const ACK_UNKNOWN = 'UNKNOWN';
+79
View File
@@ -0,0 +1,79 @@
import { ApiProperty } from '@nestjs/swagger';
export class RemoteFile {
@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({
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: 'Document file name. Optional',
example: 'filename.jpeg',
})
filename?: string | null;
@ApiProperty({
description: 'Base64-encoded data of the file',
example:
'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',
})
data: string;
}
export class VoiceBinaryFile {
@ApiProperty({
description: 'MIME type of the attachment.',
})
mimetype = 'audio/ogg; codecs=opus';
@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;
}
export class VoiceRemoteFile {
@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;
}
+34
View File
@@ -0,0 +1,34 @@
import { ApiProperty } from '@nestjs/swagger';
import { WAHAPresenceStatus } from './enums.dto';
export class WAHAPresenceData {
@ApiProperty({
description: 'Chat ID - participant or contact id',
example: '11111111111@c.us',
})
participant: string;
lastKnownPresence: WAHAPresenceStatus;
@ApiProperty({
example: 1686568773,
})
lastSeen?: number;
}
export class WAHAChatPresences {
@ApiProperty({
description: 'Chat ID - either group id or contact id',
example: '11111111111@c.us',
})
id: string;
presences: WAHAPresenceData[];
}
export class WAHASessionPresence {
presence: WAHAPresenceStatus;
@ApiProperty({
description: 'Chat ID - either group id or contact id',
example: '11111111111@c.us',
})
chatId: string;
}
+6 -54
View File
@@ -1,51 +1,6 @@
import { ApiProperty } from '@nestjs/swagger';
import { WAEvents, WhatsappEngine } from './enums.dto';
export class WANumberExistResult {
numberExists: boolean;
}
export class WAGroupNotification {
@ApiProperty({
description: 'ID that represents the groupNotification',
})
id: any;
@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: 'ContactId for the user that produced the GroupNotification',
})
author: string;
@ApiProperty({
description: 'Extra content',
})
body: 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,
}
import { WAMessageAck } from './enums.dto';
export class WALocation {
description?: string | null;
@@ -114,6 +69,11 @@ export class WAMessage {
})
ack: WAMessageAck;
@ApiProperty({
description: 'ACK status name for the message',
})
ackName: string;
@ApiProperty({
description:
'If the message was sent to a group, this field will contain the user that sent the message.',
@@ -138,11 +98,3 @@ export class WAMessage {
})
_data?: any;
}
export class WAWebhook {
event: WAEvents;
session: string;
engine: WhatsappEngine;
// eslint-disable-next-line @typescript-eslint/ban-types
payload: WAMessage | WAGroupNotification | object;
}
+30 -2
View File
@@ -1,6 +1,8 @@
import { WhatsappStatus } from './enums.dto';
import { ApiProperty } from '@nestjs/swagger';
import { WAHASessionStatus } from './enums.dto';
import { WebhookConfig } from './webhooks.config.dto';
/**
* Queries
*/
@@ -13,11 +15,36 @@ export class ListSessionsQuery {
})
all: boolean;
}
/**
* Requests
*/
export class ProxyConfig {
@ApiProperty({
example: 'localhost:3128',
})
server: string;
@ApiProperty({
example: null,
})
username?: string;
@ApiProperty({
example: null,
})
password?: string;
}
export class SessionConfig {
webhooks?: WebhookConfig[];
@ApiProperty({
example: null,
})
proxy?: ProxyConfig;
}
export class SessionStartRequest {
name = 'default';
config?: SessionConfig;
}
export class SessionStopRequest {
@@ -36,5 +63,6 @@ export class SessionLogoutRequest {
export class SessionDTO {
name = 'default';
status: WhatsappStatus;
status: WAHASessionStatus;
config: SessionConfig;
}
+60
View File
@@ -0,0 +1,60 @@
import { ApiExtraModels, ApiProperty, getSchemaPath } from '@nestjs/swagger';
import {
BinaryFile,
RemoteFile,
VoiceBinaryFile,
VoiceRemoteFile,
} from './files.dto';
export const BROADCAST_ID = 'status@broadcast';
class StatusRequest {
@ApiProperty({
description:
'it is always necessary to inform the list of contacts that will have access to the posted status',
example: ['55xxxxxxxxxxx@c.us'],
})
contacts = ['55xxxxxxxxxxx@c.us'];
}
export class TextStatus extends StatusRequest {
text = 'Have a look! https://waha.devlike.pro/';
backgroundColor = '#38b42f';
font = 1;
}
@ApiExtraModels(BinaryFile, RemoteFile)
export class ImageStatus extends StatusRequest {
@ApiProperty({
oneOf: [
{ $ref: getSchemaPath(BinaryFile) },
{ $ref: getSchemaPath(RemoteFile) },
],
})
file: BinaryFile | RemoteFile;
caption: string;
}
@ApiExtraModels(VoiceBinaryFile, VoiceRemoteFile)
export class VoiceStatus extends StatusRequest {
@ApiProperty({
oneOf: [
{ $ref: getSchemaPath(VoiceBinaryFile) },
{ $ref: getSchemaPath(VoiceRemoteFile) },
],
})
file: VoiceBinaryFile | VoiceRemoteFile;
backgroundColor = '#38b42f';
}
@ApiExtraModels(BinaryFile, RemoteFile)
export class VideoStatus extends StatusRequest {
@ApiProperty({
oneOf: [
{ $ref: getSchemaPath(BinaryFile) },
{ $ref: getSchemaPath(RemoteFile) },
],
})
file: BinaryFile | RemoteFile;
caption: string;
}
+60
View File
@@ -0,0 +1,60 @@
import { ApiProperty } from '@nestjs/swagger';
export class RetriesConfiguration {
@ApiProperty({
example: 2,
})
delaySeconds: number;
@ApiProperty({
example: 15,
})
attempts: number;
}
export class CustomHeader {
@ApiProperty({
example: 'X-My-Custom-Header',
})
name: string;
@ApiProperty({
example: 'Value',
})
value: string;
}
export class HmacConfiguration {
@ApiProperty({
example: 'your-secret-key',
})
key: string;
}
export class WebhookConfig {
@ApiProperty({
example: 'https://httpbin.org/post',
required: true,
})
url: string;
@ApiProperty({
example: ['message'],
required: true,
})
events: string[];
@ApiProperty({
example: null,
})
hmac?: HmacConfiguration;
@ApiProperty({
example: null,
})
retries?: RetriesConfiguration;
@ApiProperty({
example: null,
})
customHeaders?: CustomHeader[];
}
+59
View File
@@ -0,0 +1,59 @@
import { ApiProperty } from '@nestjs/swagger';
import { WAHAEngine, WAHAEvents, WAMessageAck } from './enums.dto';
import { WAMessage } from './responses.dto';
export class WAMessageAckBody {
@ApiProperty({
description: 'Message ID',
example: 'false_11111111111@c.us_AAAAAAAAAAAAAAAAAAAA',
})
id: string;
from: string;
to: string;
participant: string;
fromMe: boolean;
ack: WAMessageAck;
ackName: string;
}
export class WAGroupPayload {
@ApiProperty({
description: 'ID that represents the groupNotification',
})
id: any;
@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: 'ContactId for the user that produced the GroupNotification',
})
author: string;
@ApiProperty({
description: 'Extra content',
})
body: string;
@ApiProperty({
description:
'Contact IDs for the users that were affected by this GroupNotification',
})
recipientIds: string[];
}
export class WAWebhook {
event: WAHAEvents;
session: string;
engine: WAHAEngine;
// eslint-disable-next-line @typescript-eslint/ban-types
payload: WAMessage | WAGroupPayload | WAMessageAckBody | object;
}
+1 -3
View File
@@ -3,8 +3,6 @@ import { getBrowserExecutablePath } from './core/abc/session.abc';
// eslint-disable-next-line @typescript-eslint/no-var-requires
const fs = require('fs');
// eslint-disable-next-line @typescript-eslint/no-var-requires
const path = require('path');
export enum WAHAVersion {
PLUS = 'PLUS',
@@ -28,7 +26,7 @@ export function getWAHAVersion(): WAHAVersion {
}
export const VERSION = {
version: '2023.6.1',
version: '2023.8.23',
tier: getWAHAVersion(),
browser: getBrowserExecutablePath(),
};
+2 -1
View File
@@ -10,6 +10,7 @@
"sourceMap": true,
"outDir": "./dist",
"baseUrl": "./",
"incremental": true
"incremental": true,
"resolveJsonModule": true,
}
}
+13558 -9311
View File
File diff suppressed because it is too large. Load diff