Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
906b03804f | ||
|
|
1ed7a61bea | ||
|
|
ad6ae7ab05 | ||
|
|
5e2de1d47d | ||
|
|
591ca51104 | ||
|
|
b210841158 | ||
|
|
a223a3985b | ||
|
|
8baa6a73cd | ||
|
|
508efac2ea | ||
|
|
b6eed9e228 | ||
|
|
b62cad07f0 | ||
|
|
6d552d27ac | ||
|
|
39d0f2a060 | ||
|
|
0b03abee91 | ||
|
|
b4fb69d0ee | ||
|
|
5b9ed029b3 | ||
|
|
c29397371b | ||
|
|
fe47e27788 | ||
|
|
a14c702c79 | ||
|
|
fe4246036d | ||
|
|
074063b814 | ||
|
|
0271a8b721 | ||
|
|
08617383be | ||
|
|
3840459b0a | ||
|
|
aee7ac28bc | ||
|
|
9e6cd39b2d | ||
|
|
8898429046 | ||
|
|
a74def6dbd | ||
|
|
9f783cd35e | ||
|
|
9d2f061d81 | ||
|
|
a6045b2d54 | ||
|
|
6142b2b564 | ||
|
|
ed53ccb919 | ||
|
|
1ab45c6cfd | ||
|
|
c2fdb090e1 | ||
|
|
a10b47eb7e | ||
|
|
7b234e2e37 | ||
|
|
90d45a4698 | ||
|
|
5e40b70e19 | ||
|
|
a27d0c3efb | ||
|
|
46ed506b35 | ||
|
|
0ad813a1fe | ||
|
|
e57e42f47a | ||
|
|
cc58157d37 | ||
|
|
9b5bb0a339 | ||
|
|
84ebbea287 | ||
|
|
1c2021f54b | ||
|
|
98a7f00d7d | ||
|
|
60d97d0e67 | ||
|
|
03f4a07cbe | ||
|
|
3fbe526793 | ||
|
|
a892f03382 | ||
|
|
8db69f6e69 | ||
|
|
f9037ff9fc | ||
|
|
366ddffa2b | ||
|
|
b8468588de | ||
|
|
62dd6f7f4a | ||
|
|
858202f1bb | ||
|
|
db7900dc26 | ||
|
|
aa87e437dc | ||
|
|
741e5e86f8 | ||
|
|
390a171891 | ||
|
|
1fc5e2b47c | ||
|
|
8926dc7cc6 | ||
|
|
5c3d3c0d70 | ||
|
|
41b29cbf60 | ||
|
|
44463c199a | ||
|
|
9071adcdcc | ||
|
|
21cd92ca13 | ||
|
|
d8a3e8dc43 | ||
|
|
3f5707952c | ||
|
|
38a9590be0 | ||
|
|
a7ce117b87 | ||
|
|
c3b24a243c | ||
|
|
cb2c11df43 | ||
|
|
98f88dcf68 | ||
|
|
9fb12e0e23 | ||
|
|
0ca106f16b | ||
|
|
33210c6374 | ||
|
|
045ab67f5d | ||
|
|
19259c93ea | ||
|
|
9b6784a018 | ||
|
|
2fe3706e9c | ||
|
|
a4e310a7f8 | ||
|
|
802da9f3b2 | ||
|
|
0692979b19 | ||
|
|
fb8b146bc4 | ||
|
|
fdbaa43ec9 | ||
|
|
4a807530c1 | ||
|
|
84784faa1e | ||
|
|
bd40978dee | ||
|
|
d4d94ffe0e | ||
|
|
d984382a33 | ||
|
|
02d05812d2 | ||
|
|
1dbf568bac | ||
|
|
6bc3aed348 | ||
|
|
c1e8e0f09c | ||
|
|
8f8bcfc474 | ||
|
|
b386df982d | ||
|
|
fa846ddf9b | ||
|
|
16685ed8ea | ||
|
|
4addccbd55 | ||
|
|
e9a20c4326 | ||
|
|
f073aeef13 | ||
|
|
eb08739eea | ||
|
|
aff3b01e39 | ||
|
|
2d64b26e86 | ||
|
|
7b7bd02503 | ||
|
|
7904516c97 | ||
|
|
6e0bafdd89 | ||
|
|
2b1e605e5f | ||
|
|
f454310a09 | ||
|
|
b6579b5b96 | ||
|
|
4ef06d7b92 | ||
|
|
e0c594a71b | ||
|
|
a48d386d8b | ||
|
|
d592858a90 |
No files matched your search
@@ -1,33 +1,36 @@
|
||||
module.exports = {
|
||||
parser: "@typescript-eslint/parser",
|
||||
parser: '@typescript-eslint/parser',
|
||||
parserOptions: {
|
||||
project: "tsconfig.json",
|
||||
sourceType: "module"
|
||||
project: 'tsconfig.json',
|
||||
sourceType: 'module',
|
||||
},
|
||||
plugins: [
|
||||
"@typescript-eslint/eslint-plugin",
|
||||
"simple-import-sort"
|
||||
],
|
||||
plugins: ['@typescript-eslint/eslint-plugin', 'simple-import-sort'],
|
||||
extends: [
|
||||
"plugin:@typescript-eslint/eslint-recommended",
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"prettier",
|
||||
"prettier/@typescript-eslint"
|
||||
'plugin:@typescript-eslint/eslint-recommended',
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'prettier',
|
||||
'prettier/@typescript-eslint',
|
||||
],
|
||||
root: true,
|
||||
env: {
|
||||
node: true,
|
||||
jest: 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",
|
||||
}
|
||||
'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',
|
||||
'@typescript-eslint/no-unused-vars': 'off',
|
||||
'lines-between-class-members': [
|
||||
'error',
|
||||
'always',
|
||||
{ exceptAfterSingleLine: true },
|
||||
],
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,61 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: '[BUG][WEBJS] - '
|
||||
labels: bug
|
||||
assignees: ''
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
|
||||
> A clear and concise description of what the bug is. Feel free to remove
|
||||
> sections that you don't feel to make the text shorter!
|
||||
|
||||
**Version**
|
||||
|
||||
```json
|
||||
{
|
||||
"version": "2023.11.1",
|
||||
"engine": "NOWEB",
|
||||
"tier": "PLUS",
|
||||
"browser": "/usr/bin/google-chrome-stable"
|
||||
}
|
||||
```
|
||||
|
||||
> You can get the WAHA version by calling `GET /api/version` Try to update to
|
||||
> [the latest version](https://github.com/devlikeapro/whatsapp-http-api/releases)
|
||||
> before creating an issue!
|
||||
|
||||
**To Reproduce** Steps to reproduce the behavior:
|
||||
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
**Expected behavior**
|
||||
|
||||
> A clear and concise description of what you expected to happen.
|
||||
|
||||
**Engine** I'm using **WEBJS** engine.
|
||||
|
||||
> If you set `WHATSAPP_DEFAULT_ENGINE` explicitly to
|
||||
> [one of the engine](https://waha.devlike.pro/docs/how-to/engines/) - please
|
||||
> provide that information here and in the issue title.
|
||||
|
||||
**Requests - Responses**
|
||||
|
||||
> Please attach all sent requests, responses that you sent.
|
||||
|
||||
**Docker Logs**
|
||||
|
||||
> Collect and attach related docker logs if you have any
|
||||
> https://waha.devlike.pro/docs/how-to/deploy/#viewing-the-logs
|
||||
|
||||
**Screenshots**
|
||||
|
||||
> If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Additional context**
|
||||
|
||||
> Add any other context about the problem here.
|
||||
@@ -0,0 +1,24 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: '[Feature Request]'
|
||||
labels: ''
|
||||
assignees: ''
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.** I'm always
|
||||
frustrated when [...]
|
||||
|
||||
> A clear and concise description of what the problem is. Ex.
|
||||
|
||||
**Describe the solution you'd like**
|
||||
|
||||
> A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
|
||||
> A clear and concise description of any alternative solutions or features
|
||||
> you've considered.
|
||||
|
||||
**Additional context** Add any other context or screenshots about the feature
|
||||
request here.
|
||||
@@ -0,0 +1,15 @@
|
||||
ignore:
|
||||
teams:
|
||||
- no-labels
|
||||
users:
|
||||
- allburov
|
||||
teamLabels:
|
||||
- team: waha-patrons-pro
|
||||
label: patron:PRO
|
||||
comment: '[](https://waha.devlike.pro/docs/how-to/plus-version/#tiers)'
|
||||
- team: waha-patrons-advanced
|
||||
label: patron:ADVANCED
|
||||
comment: '[](https://waha.devlike.pro/docs/how-to/plus-version/#tiers)'
|
||||
- team: waha-patrons-plus
|
||||
label: patron:PLUS
|
||||
comment: '[](https://waha.devlike.pro/docs/how-to/plus-version/#tiers)'
|
||||
@@ -3,42 +3,43 @@ name: Release
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "*"
|
||||
- '*'
|
||||
|
||||
jobs:
|
||||
docker-build:
|
||||
runs-on: ${{ matrix.runner }}
|
||||
name: ${{ matrix.platform }} - ${{ matrix.browser }} - ${{ matrix.tag }}
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
# Chromium
|
||||
- runner: "buildjet-4vcpu-ubuntu-2204"
|
||||
tag: "latest"
|
||||
platform: "amd64"
|
||||
browser: "chromium"
|
||||
- runner: "buildjet-4vcpu-ubuntu-2204-arm"
|
||||
tag: "arm"
|
||||
platform: "linux/arm64"
|
||||
browser: "chromium"
|
||||
- runner: 'buildjet-4vcpu-ubuntu-2204'
|
||||
tag: 'latest'
|
||||
platform: 'amd64'
|
||||
browser: 'chromium'
|
||||
- runner: 'buildjet-4vcpu-ubuntu-2204-arm'
|
||||
tag: 'arm'
|
||||
platform: 'linux/arm64'
|
||||
browser: 'chromium'
|
||||
# Chrome
|
||||
- runner: "buildjet-4vcpu-ubuntu-2204"
|
||||
tag: "chrome"
|
||||
platform: "amd64"
|
||||
browser: "chrome"
|
||||
- runner: 'buildjet-4vcpu-ubuntu-2204'
|
||||
tag: 'chrome'
|
||||
platform: 'amd64'
|
||||
browser: 'chrome'
|
||||
# Chrome is not supported on arm64
|
||||
# - runner: "buildjet-4vcpu-ubuntu-2204-arm"
|
||||
# tag: "chrome-arm"
|
||||
# platform: "linux/arm64"
|
||||
# browser: "chrome"
|
||||
# - runner: "buildjet-4vcpu-ubuntu-2204-arm"
|
||||
# tag: "chrome-arm"
|
||||
# platform: "linux/arm64"
|
||||
# browser: "chrome"
|
||||
# No browser
|
||||
- runner: "buildjet-4vcpu-ubuntu-2204"
|
||||
tag: "noweb"
|
||||
platform: "amd64"
|
||||
browser: "none"
|
||||
- runner: "buildjet-4vcpu-ubuntu-2204-arm"
|
||||
tag: "noweb-arm"
|
||||
platform: "linux/arm64"
|
||||
browser: "none"
|
||||
- runner: 'buildjet-4vcpu-ubuntu-2204'
|
||||
tag: 'noweb'
|
||||
platform: 'amd64'
|
||||
browser: 'none'
|
||||
- runner: 'buildjet-4vcpu-ubuntu-2204-arm'
|
||||
tag: 'noweb-arm'
|
||||
platform: 'linux/arm64'
|
||||
browser: 'none'
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
|
||||
@@ -1,13 +1,25 @@
|
||||
repos:
|
||||
- repo: https://github.com/pre-commit/mirrors-prettier
|
||||
rev: 'v2.7.1'
|
||||
- repo: local
|
||||
hooks:
|
||||
- id: prettier
|
||||
types_or: [ javascript, ts ]
|
||||
- id: lint
|
||||
name: lint
|
||||
language: system
|
||||
entry: bash -c '$HOME/.nvm/nvm-exec npm run lint'
|
||||
pass_filenames: false
|
||||
files: |
|
||||
(?x)^(
|
||||
src/.*|
|
||||
)$
|
||||
|
||||
- repo: https://github.com/pre-commit/mirrors-prettier
|
||||
rev: 'v3.1.0'
|
||||
hooks:
|
||||
- id: prettier
|
||||
exclude: |
|
||||
(?x)^(
|
||||
docs/.*|
|
||||
README.md|
|
||||
)$
|
||||
- repo: https://github.com/Lucas-C/pre-commit-hooks-nodejs
|
||||
rev: v1.1.2
|
||||
hooks:
|
||||
|
||||
@@ -1,4 +1,13 @@
|
||||
{
|
||||
"singleQuote": true,
|
||||
"trailingComma": "all"
|
||||
}
|
||||
"trailingComma": "all",
|
||||
"endOfLine": "auto",
|
||||
"arrowParens": "always",
|
||||
"bracketSpacing": true,
|
||||
"useTabs": false,
|
||||
"tabWidth": 2,
|
||||
"semi": true,
|
||||
"quoteProps": "as-needed",
|
||||
"proseWrap": "always",
|
||||
"printWidth": 80
|
||||
}
|
||||
@@ -1,2 +1,4 @@
|
||||
# Changelog
|
||||
You can find changelog on [the changelog page](https://waha.devlike.pro/docs/help/changelog/)
|
||||
|
||||
You can find changelog on
|
||||
[the changelog page](https://waha.devlike.pro/docs/help/changelog/)
|
||||
@@ -5,16 +5,21 @@ FROM node:20-bullseye as build
|
||||
ENV PUPPETEER_SKIP_DOWNLOAD=True
|
||||
|
||||
# npm packages
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y openssh-client
|
||||
RUN apt install openssh-client
|
||||
RUN mkdir -p -m 0700 ~/.ssh && ssh-keyscan github.com >> ~/.ssh/known_hosts
|
||||
|
||||
WORKDIR /src
|
||||
COPY package.json .
|
||||
COPY yarn.lock .
|
||||
RUN yarn set version 3.6.3
|
||||
RUN yarn install
|
||||
RUN --mount=type=ssh yarn install
|
||||
|
||||
# App
|
||||
WORKDIR /src
|
||||
ADD . /src
|
||||
RUN yarn install
|
||||
RUN --mount=type=ssh yarn install
|
||||
RUN yarn build && find ./dist -name "*.d.ts" -delete
|
||||
|
||||
#
|
||||
|
||||
@@ -12,6 +12,13 @@ build-all: build build-chrome build-noweb
|
||||
build-plus:
|
||||
docker build . -t devlikeapro/whatsapp-http-api-plus
|
||||
|
||||
build-ssh:
|
||||
# check IMAGE provided
|
||||
@[ "${IMAGE}" ] || ( echo "Add APP: make build-ssh image=devlikeapro/whatsapp-http-api"; exit 1 );
|
||||
eval $(ssh-agent) && \
|
||||
ssh-add ~/.ssh/id_rsa && \
|
||||
docker buildx build --ssh default=${SSH_AUTH_SOCK} . -t ${IMAGE} --build-arg USE_BROWSER=none
|
||||
|
||||
stop:
|
||||
docker stop whatsapp-http-api
|
||||
|
||||
@@ -28,7 +35,7 @@ update-swagger:
|
||||
wget http://localhost:3000/-json -O ./docs/site/static/swagger/openapi.json
|
||||
|
||||
update-dependencies:
|
||||
. ${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 whatsapp-web.js@https://github.com/devlikeapro/whatsapp-web.js#main-fork
|
||||
. ${NVM_DIR}/nvm.sh && nvm exec yarn up @adiwajshing/baileys@github:WhiskeySockets/Baileys
|
||||
. ${NVM_DIR}/nvm.sh && nvm exec yarn up venom-bot
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ services:
|
||||
image: devlikeapro/whatsapp-http-api
|
||||
container_name: whatsapp-http-api
|
||||
ports:
|
||||
- "3000:3000/tcp"
|
||||
- '3000:3000/tcp'
|
||||
environment:
|
||||
# Environment variables from https://waha.devlike.pro/docs/how-to/config/
|
||||
- WHATSAPP_HOOK_URL=https://httpbin.org/post
|
||||
@@ -16,9 +16,9 @@ services:
|
||||
image: devlikeapro/whatsapp-http-api-plus
|
||||
container_name: whatsapp-http-api-plus
|
||||
ports:
|
||||
- "3000:3000/tcp"
|
||||
- '3000:3000/tcp'
|
||||
volumes:
|
||||
- "./.sessions:/app/.sessions"
|
||||
- './.sessions:/app/.sessions'
|
||||
environment:
|
||||
# Environment variables from https://waha.devlike.pro/docs/how-to/config/
|
||||
- WHATSAPP_RESTART_ALL_SESSIONS=True
|
||||
@@ -30,3 +30,15 @@ services:
|
||||
- WHATSAPP_SWAGGER_PASSWORD=123
|
||||
- WHATSAPP_API_HOSTNAME=localhost
|
||||
restart: always
|
||||
|
||||
mongodb:
|
||||
image: mongo
|
||||
container_name: mongodb
|
||||
ports:
|
||||
- '27017:27017/tcp'
|
||||
volumes:
|
||||
- mongodb_data:/data/db
|
||||
restart: always
|
||||
|
||||
volumes:
|
||||
mongodb_data: {}
|
||||
@@ -77,6 +77,12 @@
|
||||
url = "/docs/how-to/plus-version"
|
||||
parent = "waha-plus"
|
||||
|
||||
[[main]]
|
||||
name = "Patron Portal ->"
|
||||
weight = 20
|
||||
url = "https://portal.devlike.pro/"
|
||||
parent = "waha-plus"
|
||||
|
||||
[[main]]
|
||||
name = "Get on Patreon ->"
|
||||
weight = 50
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
title : "WAHA"
|
||||
description: "WAHA - WhatsApp HTTP API that you can run in a click!"
|
||||
lead: "WAHA - WhatsApp HTTP API that you can run in a click!"
|
||||
lead: "WAHA - <b>WhatsApp HTTP API</b> 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
|
||||
|
||||
@@ -38,10 +38,17 @@ The following environment variables can be used to configure the WAHA.
|
||||
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.
|
||||
- Also read more about [Sessions ->]({{< relref "/docs/how-to/sessions" >}})
|
||||
- Also read more about [Session Storages on the dedicated page ->]({{< relref "/docs/how-to/storages#sessions" >}})
|
||||
|
||||
### 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.
|
||||
- `WHATSAPP_SWAGGER_ENABLED=false` - disables Swagger documentation. Enabled by default. Available in **WAHA Plus** only.
|
||||
- `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. Available in **WAHA Plus** only.
|
||||
|
||||
Read more about security settings for Swagger and API on [**Security page** ->]({{< relref "/docs/how-to/security" >}}).
|
||||
|
||||
### Proxy
|
||||
#### Global proxy configuration
|
||||
@@ -61,24 +68,59 @@ Read more about it on [**Session page** ->]({{< relref "/docs/how-to/sessions#co
|
||||
Keep in mind that session's proxy configuration takes precedence over proxy configuration set by environment variables!
|
||||
|
||||
|
||||
### Security 
|
||||
### Security
|
||||
<b>Security is available in [WAHA Plus ]({{< relref "/docs/how-to/plus-version" >}}) only.</b>
|
||||
|
||||
- `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.
|
||||
|
||||
Read more about security settings for Swagger and API on [**Security page** ->]({{< relref "/docs/how-to/security" >}}).
|
||||
|
||||
### Files 
|
||||
### Files
|
||||
<b>Files configuration is available in [WAHA Plus ]({{< relref "/docs/how-to/plus-version" >}}) only.</b>
|
||||
|
||||
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_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`). See usage below.
|
||||
- `WHATSAPP_DOWNLOAD_MEDIA=true` - this variable can be used to **completely** disable downloading media files. By default, all files are downloaded.
|
||||
Set this variable to `WHATSAPP_DOWNLOAD_MEDIA=false` to disable downloading media files.
|
||||
- Under the hood, it sets `WHATSAPP_FILES_MIMETYPES=mimetype/ignore-all-media` to ignore all media files.
|
||||
- `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`.
|
||||
- Set this variable to `0` to disable the file lifetime.
|
||||
- `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`.
|
||||
- The folder must be mounted to the host machine to keep the files between container restarts. [ Read more about how to persist files ->]({{< relref "/docs/how-to/storages#media" >}})
|
||||
- Also read more about [Media Storages on the dedicated page ->]({{< relref "/docs/how-to/storages#media" >}})
|
||||
|
||||
💡 Even if WAHA doesn't process the message media because of `WHATSAPP_FILES_MIMETYPES` or `WHATSAPP_DOWNLOAD_MEDIA`
|
||||
you'll get a webhook event with `hasMedia: True` field, but with no `media.url`.
|
||||
```json
|
||||
{
|
||||
"event": "message",
|
||||
"session": "default",
|
||||
"payload": {
|
||||
"hasMedia": true,
|
||||
"media": {
|
||||
"url": null,
|
||||
"mimetype": "video/mp4",
|
||||
"filename": null
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Health Check
|
||||
<b>Health check is available in [WAHA Plus ]({{< relref "/docs/how-to/plus-version" >}}) only.</b>
|
||||
|
||||
The following environment variables can be used to configure the [Health Check ->]({{< relref "/docs/how-to/other" >}}):
|
||||
- `WHATSAPP_HEALTH_MEDIA_FILES_THRESHOLD_MB` - the threshold in MB for the media files storage. The default value is `100`.
|
||||
- `WHATSAPP_HEALTH_SESSIONS_FILES_THRESHOLD_MB` - the threshold in MB for the sessions files storage. The default value is `100`.
|
||||
- `WHATSAPP_HEALTH_MONGODB_TIMEOUT` - the timeout in milliseconds for the MongoDB health check. The default value is `5000`.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -138,6 +180,13 @@ comma-separated list of mimetypes:
|
||||
WHATSAPP_FILES_MIMETYPES=audio,image/png,image/gif
|
||||
```
|
||||
|
||||
#### Disable Downloading Media Files
|
||||
To disable downloading media files, set the `WHATSAPP_DOWNLOAD_MEDIA` environment variable to `false`:
|
||||
|
||||
```
|
||||
WHATSAPP_DOWNLOAD_MEDIA=false
|
||||
```
|
||||
|
||||
#### Setting the File Lifetime
|
||||
|
||||
To set the time (in seconds) after which files will be removed to free up space, set the `WHATSAPP_FILES_LIFETIME`
|
||||
|
||||
@@ -63,13 +63,26 @@ Get contact
|
||||
|
||||
If you want to check if phone number is registered in WhatsApp (even if the number is not in your contact list) - use
|
||||
this endpoint for that.
|
||||
- `GET /api/contacts/check-exists?phone=11231231231&session=default`
|
||||
```bash
|
||||
GET /api/contacts/check-exists?phone=11231231231&session=default
|
||||
```
|
||||
It returns `numberExists` field with `true` or `false` value and `chatId` field with chat ID of the number (if exists).
|
||||
|
||||
```json
|
||||
{
|
||||
"numberExists": true
|
||||
"numberExists": true,
|
||||
"chatId": "123123123@c.us"
|
||||
}
|
||||
```
|
||||
**Note for Brazilian Phone Numbers**
|
||||
|
||||
You should use the `GET /api/contacts/check-exists` endpoint **before sending a message to a new phone number**
|
||||
to get the correct chatId because of the additional 9-digit number added after 2012.
|
||||
|
||||
Read more about
|
||||
[error sending text to half of Brazilian numbers (every number registered before 2012) ->](https://github.com/devlikeapro/whatsapp-http-api/issues/238)
|
||||
|
||||
It's fine to send the response to `chatId` for incoming messages, though - the payload already has the correct `chatId`.
|
||||
|
||||
### Get "about" contact
|
||||
|
||||
|
||||
@@ -49,6 +49,16 @@ Not running Chromium saves you CPU and Memory, so you can run more instances on
|
||||
⚠ Read the article before using **NOWEB** engine
|
||||
[How to avoid blocking ->]({{< relref " /docs/overview/how-to-avoid-blocking" >}}).
|
||||
|
||||
### MOBILE (experimental)
|
||||
{{< alert icon="👉" text="<b>Experimental engine!</b><br/>Please contact us on <a href='https://www.patreon.com/wa_http_api' target='_blank'>Patreon -></a> or <a href='https://boosty.to/wa-http-api' target='_blank'>Boosty -></a> if you're interested in using it.<br>Available for <a href='https://waha.devlike.pro/docs/how-to/plus-version/#tiers' target='_blank'>PRO</a> subscribers only" />}}
|
||||
|
||||
- `WHATSAPP_DEFAULT_ENGINE=MOBILE`
|
||||
- [https://github.com/WhiskeySockets/Baileys](https://github.com/WhiskeySockets/Baileys)
|
||||
|
||||
**MOBILE** engine **does not require a browser and a mobile device** to work with WhatsApp, it does so directly using a WebSocket.
|
||||
You don't have to use a mobile device to run this engine.
|
||||
|
||||
|
||||
### VENOM
|
||||
|
||||
- `WHATSAPP_DEFAULT_ENGINE=VENOM`
|
||||
@@ -68,7 +78,7 @@ For **x86** processors use following images:
|
||||
|
||||
For **ARM** processors use following images:
|
||||
- `devlikeapro/whatsapp-http-api:arm` - **Chromium**
|
||||
- `devlikeapro/whatsapp-http-api:arm-noweb`- **no browser installed**
|
||||
- `devlikeapro/whatsapp-http-api:noweb-arm`- **no browser installed**
|
||||
|
||||
{{< alert icon="💡" text="Chrome version is not available in ARM" />}}
|
||||
|
||||
@@ -98,20 +108,22 @@ it's the engine WAHA runs by default.
|
||||
If you find any inconsistency with actual endpoints -
|
||||
please [create an issue](https://github.com/devlikeapro/whatsapp-http-api/issues/new?title=Error+in+engine+features )
|
||||
|
||||
| | WEBJS | NOWEB | VENOM |
|
||||
|-------------------------------------------|:-----:|:-----:|:-----:|
|
||||
| **Session** | | | |
|
||||
| `POST /api/sessions/start` | ✔️ | ✔️ | ✔️ |
|
||||
| `POST /api/sessions/stop` | ✔️ | ✔️ | ✔️ |
|
||||
| `POST /api/sessions/logout` | ✔️ | ✔️ | ✔️ |
|
||||
| `GET /api/sessions/` | ✔️ | ✔️ | ✔️ |
|
||||
| `GET /api/sessions/{session}/me` | ✔️ | ✔️ | ➖ |
|
||||
| **Authentication** | | | |
|
||||
| `POST /api/{session}/auth/qr` | ✔️ | ✔️ | ➖ |
|
||||
| `POST /api/{session}/auth/request-code` | ➖ | ✔️ | ➖ |
|
||||
| `POST /api/{session}/auth/authorize-code` | ➖ | ✔️ | ➖ |
|
||||
| **Screenshot** | | | |
|
||||
| `POST /api/screenshot` | ✔️ | ➖ | ✔️ |
|
||||
| | WEBJS | NOWEB | VENOM |
|
||||
|-------------------------------------------------------------------|:-----:|:-------------------------------------------------------------------------:|:-----:|
|
||||
| **Session** | | | |
|
||||
| `POST /api/sessions/start` | ✔️ | ✔️ | ✔️ |
|
||||
| `POST /api/sessions/stop` | ✔️ | ✔️ | ✔️ |
|
||||
| `POST /api/sessions/logout` | ✔️ | ✔️ | ✔️ |
|
||||
| `GET /api/sessions/` | ✔️ | ✔️ | ✔️ |
|
||||
| `GET /api/sessions/{session}/me` | ✔️ | ✔️ | ➖ |
|
||||
| **Authentication** | | | |
|
||||
| `POST /api/{session}/auth/qr` | ✔️ | ✔️ | ✔️ |
|
||||
| `POST /api/{session}/auth/request-code` | ➖ | ✔️ | ➖ |
|
||||
| `POST /api/{session}/auth/authorize-code` | ➖ | ➖️<br>[#113](https://github.com/devlikeapro/whatsapp-http-api/issues/113) | ➖ |
|
||||
| `GET /api/{session}/auth/captcha` <br>Only in **MOBILE** engine | ➖ | ➖ | ➖ |
|
||||
| `POST /api/{session}/auth/captcha` <br>Only in **MOBILE** engine | ➖ | ➖ | ➖ |
|
||||
| **Screenshot** | | | |
|
||||
| `POST /api/screenshot` | ✔️ | ➖ | ✔️ |
|
||||
|
||||
| **Chatting** | WEBJS | NOWEB | VENOM |
|
||||
|------------------------------------------------------|:-----:|:-----:|:-----:|
|
||||
@@ -160,6 +172,7 @@ please [create an issue](https://github.com/devlikeapro/whatsapp-http-api/issues
|
||||
| `GET /api/{session}/groups` | ✔️ | ✔️ | ➖ |
|
||||
| `GET /api/{session}/groups/{id}` | ✔️ | ✔️ | ➖ |
|
||||
| `DELETE /api/{session}/groups/{id}` | ✔️ | ➖ | ➖ |
|
||||
| `GET /api/{session}/groups/{id}/settings/security/info-admin-only` | ✔️ | ➖ | ➖ |
|
||||
| `PUT /api/{session}/groups/{id}/settings/security/info-admin-only` | ✔️ | ➖ | ➖ |
|
||||
| `POST /api/{session}/groups/{id}/leave` | ✔️ | ✔️ | ➖ |
|
||||
| `PUT /api/{session}/groups/{id}/description` | ✔️ | ✔️ | ➖ |
|
||||
@@ -175,12 +188,13 @@ please [create an issue](https://github.com/devlikeapro/whatsapp-http-api/issues
|
||||
| | WEBJS | NOWEB | VENOM |
|
||||
|---------------------------------------------------|:-----:|:-----:|:-----:|
|
||||
| **Presence** | | | |
|
||||
| `POST /api/{session}/presence` | ➖ | ✔️ | ➖ |
|
||||
| `POST /api/{session}/presence` | ✔️ | ✔️ | ➖ |
|
||||
| `GET /api/{session}/presence` | ➖ | ✔️ | ➖ |
|
||||
| `GET /api/{session}/presence/{chatId}` | ➖ | ✔️ | ➖ |
|
||||
| `POST /api/{session}/presence/{chatId}/subscribe` | ➖ | ✔️ | ➖ |
|
||||
| **Other** | | | |
|
||||
| `POST /api/version` | ➖ | ✔️ | ➖ |
|
||||
| `GET /api/version` | ➖ | ✔️ | ➖ |
|
||||
| `GET /health`  | ✔️ | ✔️ | ✔️ |
|
||||
|
||||
| **Webhooks** | WEBJS | NOWEB | VENOM |
|
||||
|-----------------------------------------------------|:-----:|:-----:|:-----:|
|
||||
|
||||
@@ -90,7 +90,12 @@ Request:
|
||||
### Settings
|
||||
#### Security for group info
|
||||
Updates the group settings to only allow admins to edit group info (title, description, photo).
|
||||
|
||||
`GET /api/{session}/groups/{groupId}/settings/security/info-admin-only`
|
||||
```json
|
||||
{
|
||||
"adminsOnly": true
|
||||
}
|
||||
```
|
||||
`PUT /api/{session}/groups/{groupId}/settings/security/info-admin-only`
|
||||
|
||||
The request doesn't require any request's body and
|
||||
|
||||
@@ -0,0 +1,192 @@
|
||||
---
|
||||
title: "Other"
|
||||
description: "Other features and API"
|
||||
lead: ""
|
||||
date: 2020-10-06T08:48:45+00:00
|
||||
lastmod: 2020-10-06T08:48:45+00:00
|
||||
draft: false
|
||||
images: [ ]
|
||||
weight: 802
|
||||
---
|
||||
|
||||
This page provides useful information about other features and API that are not covered in the other sections.
|
||||
|
||||
## Health Check
|
||||
<b>Health check is available in [WAHA Plus ]({{< relref "/docs/how-to/plus-version" >}}) only.</b>
|
||||
|
||||
The health check endpoint is used to determine the health of the service.
|
||||
|
||||
```
|
||||
GET /health
|
||||
```
|
||||
|
||||
It returns a **200 OK** status code if the service is healthy.
|
||||
|
||||
The response format:
|
||||
```json
|
||||
{
|
||||
"status": "ok",
|
||||
"info": {
|
||||
"metric1": {
|
||||
"field": "value"
|
||||
},
|
||||
"metric2": {
|
||||
"field": "value"
|
||||
}
|
||||
},
|
||||
"error": {},
|
||||
"details": {}
|
||||
}
|
||||
```
|
||||
|
||||
Where:
|
||||
- `status`: `'error' | 'ok' | 'shutting_down'` - If any health indicator failed the status will be `'error'`. If the app is shutting down but still accepting HTTP requests, the health check will have the `'shutting_down'` status.
|
||||
- `info`: Object containing information of each health indicator which is of status `'up'`, or in other words "healthy".
|
||||
- `error`: Object containing information of each health indicator which is of status `'down'`, or in other words "unhealthy".
|
||||
- `details`: Object containing detailed information of each health indicator.
|
||||
|
||||
### Health Check Indicators
|
||||
Few things we check in the health check:
|
||||
- Media files storage space - `mediaFiles.space`
|
||||
- Sessions files storage space - `sessionsFiles.space`
|
||||
- MongoDB connection - `mongodb`
|
||||
|
||||
### Configuration
|
||||
The following environment variables can be used to configure the health check:
|
||||
- `WHATSAPP_HEALTH_MEDIA_FILES_THRESHOLD_MB` - the threshold in MB for the media files storage. The default value is `100`.
|
||||
- `WHATSAPP_HEALTH_SESSIONS_FILES_THRESHOLD_MB` - the threshold in MB for the sessions files storage. The default value is `100`.
|
||||
- `WHATSAPP_HEALTH_MONGODB_TIMEOUT` - the timeout in milliseconds for the MongoDB health check. The default value is `5000`.
|
||||
|
||||
### Examples
|
||||
**Healthy response** when you use [Local Storage]({{< relref "/docs/how-to/storages#sessions" >}}) for session authentication:
|
||||
|
||||
**200 OK**
|
||||
```json
|
||||
{
|
||||
"status": "ok",
|
||||
"info": {
|
||||
"mediaFiles.space": {
|
||||
"status": "up",
|
||||
"path": "/tmp/whatsapp-files",
|
||||
"diskPath": "/",
|
||||
"free": 132979355648,
|
||||
"threshold": 104857600
|
||||
},
|
||||
"sessionsFiles.space": {
|
||||
"status": "up",
|
||||
"path": "/app/.sessions",
|
||||
"diskPath": "/",
|
||||
"free": 132979355648,
|
||||
"threshold": 104857600
|
||||
}
|
||||
},
|
||||
"error": {},
|
||||
"details": {
|
||||
"mediaFiles.space": {
|
||||
"status": "up",
|
||||
"path": "/tmp/whatsapp-files",
|
||||
"diskPath": "/",
|
||||
"free": 132979355648,
|
||||
"threshold": 104857600
|
||||
},
|
||||
"sessionsFiles.space": {
|
||||
"status": "up",
|
||||
"path": "/app/.sessions",
|
||||
"diskPath": "/",
|
||||
"free": 132979355648,
|
||||
"threshold": 104857600
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Healthy response** when you use [MongoDB Storage]({{< relref "/docs/how-to/storages#sessions" >}}) for session authentication:
|
||||
|
||||
**200 OK**
|
||||
```json
|
||||
{
|
||||
"status": "ok",
|
||||
"info": {
|
||||
"mediaFiles.space": {
|
||||
"status": "up",
|
||||
"path": "/tmp/whatsapp-files",
|
||||
"diskPath": "/",
|
||||
"free": 132977496064,
|
||||
"threshold": 104857600
|
||||
},
|
||||
"mongodb": {
|
||||
"status": "up",
|
||||
"message": "Up and running"
|
||||
}
|
||||
},
|
||||
"error": {},
|
||||
"details": {
|
||||
"mediaFiles.space": {
|
||||
"status": "up",
|
||||
"path": "/tmp/whatsapp-files",
|
||||
"diskPath": "/",
|
||||
"free": 132977496064,
|
||||
"threshold": 104857600
|
||||
},
|
||||
"mongodb": {
|
||||
"status": "up",
|
||||
"message": "Up and running"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Unhealthy response example**
|
||||
|
||||
**503 Service Unavailable**
|
||||
```json
|
||||
{
|
||||
"status": "error",
|
||||
"info": {
|
||||
"mediaFiles.space": {
|
||||
"status": "up",
|
||||
"path": "/tmp/whatsapp-files",
|
||||
"diskPath": "/",
|
||||
"free": 132976623616,
|
||||
"threshold": 104857600
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"mongodb": {
|
||||
"status": "down",
|
||||
"error": "Timeout"
|
||||
}
|
||||
},
|
||||
"details": {
|
||||
"mediaFiles.space": {
|
||||
"status": "up",
|
||||
"path": "/tmp/whatsapp-files",
|
||||
"diskPath": "/",
|
||||
"free": 132976623616,
|
||||
"threshold": 104857600
|
||||
},
|
||||
"mongodb": {
|
||||
"status": "down",
|
||||
"error": "Timeout"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
## Get version
|
||||
Returns the version of the installed docker image.
|
||||
```
|
||||
GET /api/version
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"version": "2024.2.3",
|
||||
"engine": "NOWEB",
|
||||
"tier": "PLUS",
|
||||
"browser": "/usr/bin/google-chrome-stable"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -49,17 +49,35 @@ 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
|
||||
### Tiers
|
||||
|
||||
The Plus additional features are:
|
||||
The more you support the project - the more you get!
|
||||
|
||||
| | Core | Plus | Advanced | Pro |
|
||||
|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:---------|---------|:--------:|:-------:|
|
||||
| Messages limit | **∞** | **∞** | **∞** | **∞** |
|
||||
| [Send and receive text messages ->]({{< relref "/docs/how-to/send-messages" >}}) | ✔️ | ✔️ | ✔️ | ✔️ |
|
||||
| No license checks on installed instances | ✔️ | ✔️ | ✔️ | ✔️ |
|
||||
| [Sessions limit ->]({{< relref "/docs/how-to/sessions" >}}) | **1** | **∞** | **∞** | **∞** |
|
||||
| [Send and receive files ->]({{< relref "/docs/how-to/send-messages" >}}) | ➖️ | ✔️ | ✔️ | ✔️ |
|
||||
| [Built-in Security ->]({{< relref "/docs/how-to/security" >}}) | ➖️ | ✔️ | ✔️ | ✔️ |
|
||||
| [**Webhook retries** →]({{< relref "/docs/how-to/receive-messages#retries" >}}) | ➖️ | ✔️ | ✔️ | ✔️ |
|
||||
| [MongoDB support ->]({{< relref "/docs/how-to/storages" >}}) | ➖️ | ✔️ | ✔️ | ✔️ |
|
||||
| Access to **WAHA Plus**  docker image with a personal key. | ➖️ | ✔️ | ✔️ | ✔️ |
|
||||
| Special badge ([](https://waha.devlike.pro/docs/how-to/plus-version/#tiers)) and labels (`patron:TIER`) on [project's issues, PRs and Discussion](https://github.com/devlikeapro/whatsapp-http-api) to highlight your support to the project! | | ✔️ | ✔️ | ✔️ |
|
||||
| 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. | ➖️ | ➖️ | ➖ | ✔️ |
|
||||
| Access to an [experimental MOBILE engine]({{< relref "/docs/how-to/plus-version#tiers" >}})!. | ➖️ | ➖️ | ➖ | ✔️ |
|
||||
| **Donation** | **FREE** | **$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="If you stay subscribed - the donation amount will never change for you and always be $19 to get monthly updates!" />}}
|
||||
|
||||
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?
|
||||
|
||||
@@ -77,25 +95,6 @@ What is the difference between **WAHA Plus** and **other SaaS solutions** for Wh
|
||||
|
||||
## Get Plus 
|
||||
|
||||
The more you support the project - the more you get!
|
||||
|
||||
Here's available tiers for our supporters:
|
||||
|
||||
| | Plus | Advanced | Pro |
|
||||
|-------------------------------------------------------------------------------------|---------|:--------:|:-------:|
|
||||
| Access to WAHA Plus  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. | ➖️ | ➖ | ✔️ |
|
||||
| Personal key to Docker Hub. | ➖️ | ➖ | ✔️ |
|
||||
| **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="If you stay subscribed - the donation amount will never change for you and always be $19 to get monthly updates!" />}}
|
||||
|
||||
### Patreon
|
||||
<p align="center">
|
||||
@@ -127,8 +126,9 @@ Boosty ->
|
||||
|
||||
Go ahead and get your own WhatsApp HTTP API!
|
||||
|
||||
### Install Plus
|
||||
After you get the password, use it to login and download docker image:
|
||||
## Install Plus
|
||||
After you get the password, get your login to Docker Hub in [Patron Portal ->](https://portal.devlike.pro/)
|
||||
and run the commands:
|
||||
```bash
|
||||
docker login -u devlikeapro -p {KEY}
|
||||
docker pull devlikeapro/whatsapp-http-api-plus
|
||||
|
||||
@@ -27,7 +27,8 @@ See the list of engines [**that support the feature ->**]({{< relref "/docs/how-
|
||||
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
|
||||
```
|
||||
POST /api/{session}/presence
|
||||
{
|
||||
"chatId": "111111111@c.us",
|
||||
"presence": "typing"
|
||||
@@ -35,7 +36,8 @@ Start typing to a chat (you can use `POST /startTyping` instead)
|
||||
```
|
||||
|
||||
Clear "typing" state (you can use `POST /stopTyping` instead)
|
||||
```json
|
||||
```
|
||||
POST /api/{session}/presence
|
||||
{
|
||||
"chatId": "111111111@c.us",
|
||||
"presence": "paused"
|
||||
@@ -43,7 +45,8 @@ Clear "typing" state (you can use `POST /stopTyping` instead)
|
||||
```
|
||||
|
||||
Set global "online", all contacts will see it
|
||||
```json
|
||||
```
|
||||
POST /api/{session}/presence
|
||||
{
|
||||
"presence": "online"
|
||||
}
|
||||
@@ -53,7 +56,8 @@ Set global "online", all contacts will see it
|
||||
to the device.
|
||||
If you would like to receive said notifications - you need to mark a session's presence as `offline`.
|
||||
|
||||
```json
|
||||
```
|
||||
POST /api/{session}/presence
|
||||
{
|
||||
"presence": "offline"
|
||||
}
|
||||
|
||||
@@ -154,10 +154,10 @@ Receive events when server or recipient gets the message, read or played it.
|
||||
|
||||
## Files 
|
||||
|
||||
When people send you files - images, voice messages, and documents - WAHA saves it in the file storage.
|
||||
In your application you must download it and use it as you want to. You can find the URL in `mediaUrl` field
|
||||
When people send you media - images, voice messages, and documents - WAHA saves it in the file storage.
|
||||
In your application you must download it and use it as you want to. You can find the URL in `media.url` field.
|
||||
|
||||
For example, you can get the webhook like this with `mediaUrl` value (we've skipped other fields):
|
||||
For example, you can get the webhook like this with `media` value (we've skipped other fields):
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -167,12 +167,35 @@ For example, you can get the webhook like this with `mediaUrl` value (we've skip
|
||||
"id": "true_11111111111@c.us_AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
|
||||
"timestamp": 1667561485,
|
||||
"from": "11111111111@c.us",
|
||||
"mediaUrl": "http://localhost:3000/api/files/true_11111111111@c.us_AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA.jpg"
|
||||
"hasMedia": true,
|
||||
"media": {
|
||||
"url": "http://localhost:3000/api/files/true_11111111111@c.us_AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA.jpg",
|
||||
"mimetype": "image/jpeg",
|
||||
"filename": null
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
Then you can use the link to download the file `http://localhost:3000/api/files/true_11111111111@c.us_AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA.jpg`.
|
||||
|
||||
For documents (file attachments) there's also `filename` field with the original file name.
|
||||
```json
|
||||
{
|
||||
"event": "message",
|
||||
"session": "default",
|
||||
"payload": {
|
||||
"id": "true_11111111111@c.us_AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
|
||||
"timestamp": 1667561485,
|
||||
"from": "11111111111@c.us",
|
||||
"media": {
|
||||
"url": "http://localhost:3000/api/files/true_11111111111@c.us_AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA.pdf",
|
||||
"filename": "some-file.pdf",
|
||||
"mimetype": "application/pdf"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
To configure the url you can use environment variables `WHATSAPP_API_HOSTNAME` and `WHATSAPP_API_PORT`.
|
||||
|
||||
By default, WAHA download all files that the account receive.
|
||||
|
||||
@@ -16,6 +16,7 @@ Either protect the API with [Api Key](https://www.fortinet.com/resources/cybergl
|
||||
firewalls.
|
||||
|
||||
## Swagger Security 
|
||||
### Username and password
|
||||
If you want to hide the project Swagger panel under the password - run the following command to hide under `admin/admin`
|
||||
login and password.
|
||||
|
||||
@@ -29,6 +30,12 @@ Open http://localhost:3000/ and enter `admin / admin` in the inputs:
|
||||
|
||||
{{< alert icon="👉" text="Protecting Swagger under the password does not protect your API from other request! Use both techniques to protect your API and Swagger!" />}}
|
||||
|
||||
### Disable Swagger
|
||||
You also can hide swagger completely by setting `WHATSAPP_SWAGGER_ENABLED=false` environment variable.
|
||||
|
||||
{{< alert icon="👉" text="Disabling Swagger does not protect the API, please use API security as well" />}}
|
||||
|
||||
|
||||
## API security 
|
||||
|
||||
You can protect the API by requiring Api Key in a request's headers.
|
||||
|
||||
@@ -15,18 +15,34 @@ If you haven't yet - please follow the steps from [**Quick Start →**]({{< relr
|
||||
|
||||
## Fields
|
||||
There are common fields that you can find in almost all requests:
|
||||
- `session` - a session name from which account you're sending the message. We use `default` in the examples.
|
||||
- Core  version supports only `default` session.
|
||||
- Plus  allows you to run multiple sessions inside one container to save your memory and CPU resources!
|
||||
```json
|
||||
{
|
||||
"session": "default",
|
||||
"chatId": "12132132130@c.us",
|
||||
"file": "..."
|
||||
}
|
||||
```
|
||||
|
||||
### session
|
||||
`session` - a session name from which account you're sending the message. We use `default` in the examples.
|
||||
- Core  version supports only `default` session.
|
||||
- Plus  allows you to run multiple sessions inside one container to save your memory and CPU resources!
|
||||
|
||||
Read more about [multiple sessions →]({{< relref "/docs/how-to/sessions" >}})
|
||||
|
||||
- `chatId` - it's a phone number or Group identifier where you're sending the message.
|
||||
- **Phone numbers** accounts use international phone number without + at the start and add `@c.us` at the end.
|
||||
For phone number `12132132131` the `chatId` is `12132132131@c.us`.
|
||||
- **Groups** use random number with `@g.us` at the end.
|
||||
### chatId
|
||||
`chatId` - it's a phone number or Group identifier where you're sending the message.
|
||||
- **Phone numbers** accounts use international phone number without + at the start and add `@c.us` at the end.
|
||||
For phone number `12132132131` the `chatId` is `12132132131@c.us`.
|
||||
- **Groups** use random number with `@g.us` at the end.
|
||||
|
||||
> To get the actual `chatId` for **Brazilian phone number** - use `chatId` field from
|
||||
> [Check phone number exists ->]({{< relref "/docs/how-to/contacts#check-phone-number-exists" >}})
|
||||
> response.
|
||||
|
||||
### file
|
||||
When sending media (images, voice, files) you can either use:
|
||||
- `file.data` field with base64 encoded file (https://base64.guru/converter/encode/file)
|
||||
- `file.data` field [with base64 encoded file](https://base64.guru/converter/encode/file)
|
||||
- `file.url` field with public available URL for that file
|
||||
|
||||
## Endpoints
|
||||
|
||||
@@ -128,6 +128,32 @@ In order to log out the session - call `POST /api/sessions/logout`
|
||||
"logout": true
|
||||
}
|
||||
```
|
||||
### Get screenshot
|
||||
Get screenshot of the session's screen.
|
||||
|
||||
```bash
|
||||
GET /api/screenshot?session=default
|
||||
```
|
||||
|
||||
#### Get screenshot in Base64
|
||||
You can get screenshot in base64 format by adding `Accept: application/json` header to the request.
|
||||
|
||||
```bash
|
||||
GET /api/screenshot?session=default
|
||||
Accept: application/json
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"mimetype": "image/png",
|
||||
"data": "base64-encoded-data"
|
||||
}
|
||||
```
|
||||
|
||||
You can change it in Swagger by clicking on **Media Type** dropdown and selecting **application/json**:
|
||||
|
||||

|
||||
|
||||
|
||||
### Get me
|
||||
|
||||
@@ -160,6 +186,55 @@ GET /api/{session}/auth/qr
|
||||
```
|
||||
You'll get QR image that you can scan and get authenticated
|
||||
|
||||
#### QR formats
|
||||
You can get QR in different formats:
|
||||
1. **binary image** - `GET /api/{session}/auth/qr`
|
||||
2. **base64 image** - `GET /api/{session}/auth/qr` and set `Accept: application/json` header
|
||||
3. **raw** - `GET /api/{session}/auth/qr?format=raw`
|
||||
|
||||
Here's detailed information about each format:
|
||||
|
||||
1. **binary image**, binary image - **default** format, you'll get image in response
|
||||
```bash
|
||||
# Get image - binary
|
||||
GET /api/{session}/auth/qr
|
||||
|
||||
# OR
|
||||
GET /api/{session}/auth/qr?format=image
|
||||
|
||||
# OR specify Accept header as well
|
||||
GET /api/{session}/auth/qr?format=image
|
||||
Accept: image/png
|
||||
```
|
||||
|
||||
2. **base64 image** - you'll get image in base64 format in response if you set `Accept: application/json` header.
|
||||
```bash
|
||||
GET /api/{session}/auth/qr?format=image
|
||||
Accept: application/json
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"mimetype": "image/png",
|
||||
"data": "base64-encoded-data"
|
||||
}
|
||||
```
|
||||
|
||||
You can change it in Swagger by clicking on **Media Type** dropdown and selecting **application/json**:
|
||||
|
||||

|
||||
|
||||
3. **raw** - you'll get raw data in response, you can use it to generate QR code on your side
|
||||
```bash
|
||||
GET /api/{session}/auth/qr?format=raw
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"value": "value-that-you-need-to-use-to-generate-qr-code"
|
||||
}
|
||||
```
|
||||
|
||||
### Get pairing code
|
||||
See the list of engines [**that support the features ->**]({{< relref "/docs/how-to/engines#features" >}}).
|
||||
|
||||
@@ -223,28 +298,8 @@ configure autostart options so when the docker container restarts - it restores
|
||||
|
||||
### 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 
|
||||
|
||||
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
|
||||
If you want to save your session and do not scan QR code everytime when you launch WAHA -
|
||||
[connect the session storage to the container ->]({{< relref "/docs/how-to/storages#sessions" >}})
|
||||
|
||||
### Autostart
|
||||
If you don't want to call `POST /api/sessions/start` for every session each time when the container restart -
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
---
|
||||
title: "Storages"
|
||||
description: "Storages"
|
||||
lead: ""
|
||||
date: 2020-10-06T08:48:45+00:00
|
||||
lastmod: 2020-10-06T08:48:45+00:00
|
||||
draft: false
|
||||
images: [ ]
|
||||
weight: 700
|
||||
---
|
||||
There are several storages that are used by the WAHA, below you can find the list of them and the way to configure them.
|
||||
1. [Sessions](#sessions) - used to store the session data, such as authentication information, configuration, and other data that is required to keep the session alive and connected to WhatsApp.
|
||||
2. [Media](#media) - used to store the media files, such as images, videos, and other files that are received from the WhatsApp instance.
|
||||
|
||||
<b>Storages are available in [WAHA Plus ]({{< relref "/docs/how-to/plus-version" >}}) only.</b>
|
||||
|
||||
## Sessions
|
||||
The "Sessions" storage is used to store the session data, such as authentication information, configuration,
|
||||
and other data that is required to keep the session alive and connected to WhatsApp.
|
||||
|
||||
If you want to save your session and do not scan QR code everytime when you launch WAHA -
|
||||
connect the session storage to the container.
|
||||
|
||||
For the session storage, you can use the following options:
|
||||
1. [Local (files)](#local-files) - the default option, stores the session data in the local storage using files.
|
||||
2. [MongoDB](#mongodb) - stores the session data in the MongoDB database.
|
||||
|
||||
The following table shows the compatibility of the session storage with the engines:
|
||||
|
||||
| | WEBJS | NOWEB | VENOM |
|
||||
|---------------|:-----:|:-----:|:-----:|
|
||||
| Local (files) | ✔️ | ✔️ | ✔️ |
|
||||
| MongoDB | ✔️ | ✔️ | ➖ |
|
||||
|
||||
## Sessions - Local (files)
|
||||
By default, the WAHA uses the **local storage (files)** to store the session data.
|
||||
|
||||
{{< alert icon="💡" text="It's <b>well tested solution</b> even for production" />}}
|
||||
|
||||
In order to use the local storage and save the session data between the container restarts,
|
||||
you need to mount the volume to the `/app/.sessions` directory using the `-v` option in `docker run` command:
|
||||
|
||||
```bash
|
||||
-v /path/to/on/host/.sessions:/app/.sessions
|
||||
```
|
||||
|
||||
The full command to run the WAHA with the local storage and save the session data
|
||||
in the current directory and `.sessions` directory:
|
||||
```bash
|
||||
docker run -v `pwd`/.sessions:/app/.sessions -p 3000:3000/tcp devlikeapro/whatsapp-http-api-plus
|
||||
```
|
||||
This is the only action you need to do to use the local storage - all session data will be available between the container restarts.
|
||||
|
||||
### How it works
|
||||
In the host machine, the session data will be stored in the current directory in the `.sessions` directory.
|
||||
|
||||
Under the hood, the WAHA stores the session data in the following directory structure:
|
||||
```
|
||||
.sessions/{engine}/{sessionName}/...
|
||||
```
|
||||
when you "logout" the session using [POST /api/sessions/logout]({{< relref "/docs/how-to/sessions#logout">}}) or providing `logout: True` in [POST /api/sessions/stop]({{< relref "/docs/how-to/sessions#stop">}})
|
||||
it removes the directory with the session data.
|
||||
|
||||
### Health Check
|
||||
The [WAHA Plus ]({{< relref "/docs/how-to/plus-version" >}}) provides [the health check endpoint]({{< relref "/docs/how-to/other" >}}) that checks the local storage.
|
||||
|
||||
## Sessions - MongoDB
|
||||
If you want to use the MongoDB to store the session data, you need to:
|
||||
1. Start the MongoDB server (using docker or any other way). You can either start your own MongoDB server or use the one of cloud providers, like [MongoDB Atlas](https://www.mongodb.com/atlas/database).
|
||||
2. Set `WHATSAPP_SESSIONS_MONGO_URL=mongodb://user:password@host:port/` environment variable to connect to the MongoDB server.
|
||||
|
||||
|
||||
**We recommend using your own MongoDB server as close as possible to WAHA server** for the best performance and security reasons.
|
||||
|
||||
{{< alert icon="💡" text="For <b>WEBJS</b> engine it can take up to 1 minute to save credentials in Mongo Database" />}}
|
||||
|
||||
### Example
|
||||
First, you need to start MongoDB server:
|
||||
```bash
|
||||
docker run -d -p 27017:27017 --name mongodb mongo
|
||||
```
|
||||
|
||||
Then, you need to run the WAHA with the `WHATSAPP_SESSIONS_MONGO_URL` environment variable (please note using `--network host` option as well)
|
||||
```bash
|
||||
docker run -e WHATSAPP_SESSIONS_MONGO_URL=mongodb://localhost:27017/ --network host devlikeapro/whatsapp-http-api-plus
|
||||
```
|
||||
|
||||
This is the only action you need to do to use the MongoDB storage -
|
||||
all session authentication data will be stored in the MongoDB database.
|
||||
|
||||
### How it works
|
||||
When you start a session, it stores the session data in the MongoDB database in two databases:
|
||||
1. `waha_{engine}` - it saves the session configuration in `sessions` collection with `name: {sessionName}` field.
|
||||
2. `waha_{engine}_{sessionName}` - it saves the WhatsApp authentication data and other session data. Each engine saves different data in this database.
|
||||
|
||||
When you "logout" the session using [POST /api/sessions/logout]({{< relref "/docs/how-to/sessions#logout">}}) or providing `logout: True` in [POST /api/sessions/stop]({{< relref "/docs/how-to/sessions#stop">}}) it removes those databases or elements from them.
|
||||
|
||||
For dealing and troubleshooting with the MongoDB, we recommend using [MongoDB Compass](https://www.mongodb.com/products/tools/compass).
|
||||
|
||||

|
||||
|
||||
### MongoDB Atlas
|
||||
If you use the [MongoDB Atlas](https://www.mongodb.com/atlas/database) you must grant
|
||||
`Atlas Admin` role in **Security => Database Access** before you connect to the database.
|
||||
|
||||
For production please consider running the MongoDB server close to the WAHA server for the best performance and security reasons.
|
||||
|
||||
### Health Check
|
||||
The [WAHA Plus ]({{< relref "/docs/how-to/plus-version" >}}) provides [the health check endpoint]({{< relref "/docs/how-to/other" >}}) that checks the MongoDB connection.
|
||||
|
||||
## Media
|
||||
When your WhatsApp instance receives media files, it stores them in the media storage.
|
||||
|
||||
You can use the following options to store the media files:
|
||||
1. [Local](#local) - the default option, stores the media files in the local storage using files.
|
||||
2. [S3](#s3) - stores the media files in the S3 storage.
|
||||
|
||||
Read more about [available configuration options ->]({{<relref "/docs/how-to/config#files">}}).
|
||||
|
||||
The following table shows the compatibility of the storage with the engines:
|
||||
|
||||
| | WEBJS | NOWEB | VENOM |
|
||||
|-------|:-----:|:-----:|:-----:|
|
||||
| Local | ✔️ | ✔️ | ✔️ |
|
||||
| S3 | 🕒 | 🕒 | ➖ |
|
||||
|
||||
## Media - Local
|
||||
By default, the WAHA uses the **local file storage** to store the media files and those files has a short lifetime (180 seconds).
|
||||
So it's your app responsibility to download and store them in a safe persistent place during this time.
|
||||
|
||||
### Save media files between the container restarts
|
||||
If you want to use the local storage and **save the media files between the container restarts for a long time** - you need to:
|
||||
1. Specify a dedicated folder to store the media files using `WHATSAPP_FILES_FOLDER=/app/.media` environment variable
|
||||
2. Disable automatic media files cleanup using `WHATSAPP_FILES_LIFETIME=0` environment variable
|
||||
3. Connect the volume to the specified folder using the `-v /path/to/files/on/host:/app/.media` option in `docker run` command to the folder specified in `WHATSAPP_FILES_FOLDER` environment variable.
|
||||
|
||||
Read more about [available configuration options ->]({{<relref "/docs/how-to/config#files">}}).
|
||||
|
||||
Here's all the steps in one command:
|
||||
```bash
|
||||
docker run -v /path/to/on/host/.media:/app/.media -e WHATSAPP_FILES_FOLDER=/app/.media -e WHATSAPP_FILES_LIFETIME=0 -p 3000:3000/tcp devlikeapro/whatsapp-http-api-plus
|
||||
```
|
||||
|
||||
### Health Check
|
||||
The [WAHA Plus ]({{< relref "/docs/how-to/plus-version" >}}) provides [the health check endpoint]({{< relref "/docs/how-to/other" >}}) that checks the local storage.
|
||||
|
||||
## Media - S3
|
||||
If you're interested in using the S3 storage or any other cloud storage (like [self-hosted S3 - Minio](https://min.io/)),
|
||||
please create an issue or vote for the S3 issue in [the GitHub repository](https://github.com/devlikeapro/whatsapp-http-api/issues?q=is%3Aissue+is%3Aopen+S3).
|
||||
|
After Width: | Height: | Size: 72 KiB |
@@ -263,7 +263,7 @@ revokes a previously sent message.
|
||||
|
||||
```json
|
||||
{
|
||||
"event": "message.ack",
|
||||
"event": "message.revoked",
|
||||
"session": "default",
|
||||
"payload": {
|
||||
"before": {
|
||||
|
||||
@@ -19,9 +19,58 @@ If you wish to get and read Release Notes - read them on one of our platforms, *
|
||||
|
||||
You even can **subscribe to get new updates** there!
|
||||
|
||||
## 2024.2
|
||||
- Add support for [MongoDB as storage for Session data]({{< relref "/docs/how-to/storages" >}})
|
||||
- Support persistent file storage for media files - [now you can save media files between container restarts]({{< relref "/docs/how-to/storages#media" >}})
|
||||
- If you set `WHATSAPP_FILES_LIFETIME=0` environment variable - media files will be never deleted.
|
||||
- Add `GET /api/health` endpoint to [check the health of the service](https://waha.devlike.pro/docs/how-to/other/)
|
||||
|
||||
## 2024.1
|
||||
- Implement [Patron Portal](https://portal.devlike.pro/) where you can get your personal API key and manage your perks.
|
||||
- Read more on [Patreon ->](https://www.patreon.com/posts/waha-patron-97637416)
|
||||
- Read more on [Boosty ->](https://boosty.to/wa-http-api/posts/8319079f-dac1-4179-b954-fcc559097c76)
|
||||
|
||||
## 2024.2
|
||||
- Listen for browser disconnected and page close events in **WEBJS** engine [#262](https://github.com/devlikeapro/whatsapp-http-api/issues/262)
|
||||
|
||||
## 2023.12
|
||||
December 2023
|
||||
- Add `chatId` field to `GET /api/contacts/check-exists` to help get the right `chatId` for Brazilian numbers.
|
||||
Read more about
|
||||
[error sending text to half of Brazilian numbers (every number registered before 2012) ->](https://github.com/devlikeapro/whatsapp-http-api/issues/238)
|
||||
|
||||
## 2023.11
|
||||
November 2023
|
||||
- Add different formats for QR code:
|
||||
1. **binary image** - `GET /api/{session}/auth/qr`
|
||||
2. **base64 image** - `GET /api/{session}/auth/qr` and set `Accept: application/json` header
|
||||
3. **raw** - `GET /api/{session}/auth/qr?format=raw`
|
||||
- Add different formats for the screenshot:
|
||||
1. **binary image** - `GET /api/{session}/screenshot`
|
||||
2. **base64 image** - `GET /api/{session}/screenshot` and set `Accept: application/json` header
|
||||
- Add `WHATSAPP_SWAGGER_ENABLED=false` so you can hide Swagger documentation (available only in Plus version). [#185](https://github.com/devlikeapro/whatsapp-http-api/issues/185)
|
||||
- Add dedicated `media` field in webhook payload - now you can know which media is attached to the message.
|
||||
```json
|
||||
{
|
||||
"event": "message",
|
||||
"session": "default",
|
||||
"payload": {
|
||||
"hasMedia": true,
|
||||
"media": {
|
||||
"url": "http://localhost:3000/api/files/true_11111111111@c.us_AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA.pdf",
|
||||
"mimetype": "application/pdf",
|
||||
"filename": "file.pdf"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
- Add `WHATSAPP_DOWNLOAD_MEDIA` environment variable to control media download. Available only in Plus version.
|
||||
- `WHATSAPP_DOWNLOAD_MEDIA=True` - download media
|
||||
- `WHATSAPP_DOWNLOAD_MEDIA=False` - do not download media at all.
|
||||
- Add `filename` field for document (file attachments) - the original filename of the document.
|
||||
- Add `/api/sendVideo` endpoint to **NOWEB** and **WEBJS** engines. Available only in Plus version.
|
||||
- Add security settings for edit group info `GET /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.10
|
||||
October 2023
|
||||
|
||||
@@ -14,6 +14,7 @@ toc: true
|
||||
---
|
||||
|
||||
## How much CPU and Memory does WhatsApp HTTP API consume?
|
||||
### WEBJS
|
||||
|
||||
The benchmark has been made on **Intel(R) Core(TM) i7-10510U CPU @ 1.80GHz**.
|
||||
It may differ from case to case, it depends on usage pattern - how many messages you get, how many send, etc.
|
||||
@@ -26,4 +27,10 @@ It may differ from case to case, it depends on usage pattern - how many messages
|
||||
The nature of WhatsApp HTTP API and its underlying engines - is to run **real** WhatsApp Web version in Chrome and
|
||||
communicate with it to prevent blocking from WhatsApp. It's the reason why it's so demanding on resources.
|
||||
|
||||
### NOWEB
|
||||
If you're looking for less resource demanded engine - [have a look at **NOWEB** engine ->]({{< relref "/docs/how-to/engines" >}})
|
||||
|
||||
The quote from the user:
|
||||
> The server has 2 CPU and 8GB of memory.
|
||||
> Today we have 85 session in this instance.
|
||||
|
||||
@@ -27,6 +27,9 @@ When developing a bot for WhatsApp, you **should never initiate a conversation**
|
||||
Instead, your bot **should only reply** to messages that it receives.
|
||||
This will prevent your bot from being flagged as spam by WhatsApp's users and algorithms.
|
||||
|
||||
> You can use a short link http://wa.me/7911111111?text=Hi
|
||||
> so a user can click on it and start the dialog with the bot first
|
||||
|
||||
### 2. Avoid Spamming and Sending Unnecessary Content
|
||||
|
||||
Sending too many messages or sending content that the user did not request can also lead to your bot being blocked. Make
|
||||
@@ -56,3 +59,34 @@ Here's a recommended process to follow:
|
||||
|
||||
By following these steps, you can ensure that your bot processes messages in a way that's compliant with WhatsApp's
|
||||
guidelines and reduces the risk of being blocked.
|
||||
|
||||
## How to not get Banned?
|
||||
|
||||
WhatsApp knows that it's uncommon for someone to send so many messages or bulk messages to people they've never talked to each other before so it is considered spam/marketing junk pretty fast. There are some tips before sending a message to WhatsApp
|
||||
|
||||
**Dos and Don'ts:**
|
||||
|
||||
1. Important: Do NOT send messages which get you a report. As long as you don't get reports from users who you sent a message, your account will be mostly fine.
|
||||
2. Having real content, a survey that the person agreed with is different from a marketing message on a Saturday night.
|
||||
3. Send messages written in different ways, you could make a script that places spacebars randomly on your string AND includes the person's (first) name
|
||||
4. Never use fixed times, always go for sending the first message, wait a random time between 30 ~ and 60 seconds and then send the second message
|
||||
5. Always try to group contacts by their area code, WhatsApp expects a regular person to talk mostly with contacts that are within the same area of your phone number.
|
||||
6. Have a profile picture, this is not related to WhatsApp Bots Catcher® but sending a new message for someone not having a picture/name/status will elevate your chances of being manually tagged as spam.
|
||||
7. Send "seen" confirmation to messages or disable it on WhatsApp
|
||||
8. Avoid sending links that were previously marked as spam on WhatsApp or non-HTTPS. A URL shortener is a good idea
|
||||
9. IMPORTANT: It's terrible if you send messages 24/7 without giving it some time to wait. Random delays between messages are not enough, send a reasonable amount of messages keeping in mind your conversion rate, For example: for one hour send a maximum of 4 messages per contact that have replied to your message, and stop sending messages for one hour, then start again. Again, don't send messages without stopping for a while between every "package"
|
||||
10. Send only one short message when starting a conversation, one should not send long texts or multiple messages without the user consenting
|
||||
|
||||
**Have in mind:**
|
||||
|
||||
1. For every one you send a message that doesn't have your number on their contact list asked if it's spam, being tagged as spam a few times (5 - 10) will get you banned
|
||||
2. WhatsApp records every movement you do, you can even check the logs when sending simple support email, it contains all kinds of information, this said: act as human as possible
|
||||
3. Try to engage in conversations, as long as you send a message and the person doesn't automatically block you it'll be quite okay. People constantly talking to you and adding you to their contact list will make your number stronger against a ban
|
||||
4. Think about that like a points system, you start with zero points (negative if your device was previously blacklisted), if you reach below zero you are out, if you engage in conversations you get a point, if you are tagged as spam you lose some points, if you are blocked you may lose more points
|
||||
5. Finally, If your content is spam, it doesn't matter if you are using a broadcast list, group, or direct contact, you will still be banned
|
||||
|
||||
As API, we say all that's left to do right now is to agree with WhatsApp's policy, not send spam messages, and always wait for the other person to contact you first.
|
||||
|
||||
You could make it by sending an SMS to the person with a link to start a chat on WhatsApp with you by link [https://wa.me/12132132131?text=Hi](https://wa.me/12132132131?text=Hi).
|
||||
|
||||
|
||||
@@ -1,79 +1,104 @@
|
||||
{{ define "main" }}
|
||||
<section class="section container-fluid mt-n3 pb-3">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-lg-12 text-center">
|
||||
<h1 class="mt-0">{{ .Title }}</h1>
|
||||
<section class='section container-fluid mt-n3 pb-3'>
|
||||
<div class='row justify-content-center'>
|
||||
<div class='col-lg-12 text-center'>
|
||||
<h1 class='mt-0'>{{ .Title }}</h1>
|
||||
</div>
|
||||
<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 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>
|
||||
<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>
|
||||
<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>
|
||||
</section>
|
||||
{{ end }}
|
||||
|
||||
{{ define "sidebar-prefooter" }}
|
||||
<section class="section section-sm">
|
||||
<div class="container">
|
||||
<div class="row justify-content-center text-center">
|
||||
<div class="col-lg-5">
|
||||
<h2 class="h4">It's free!</h2>
|
||||
<section class='section section-sm'>
|
||||
<div class='container'>
|
||||
<div class='row justify-content-center text-center'>
|
||||
<div class='col-lg-5'>
|
||||
<img src='/images/icons/free.png' alt='Free' style='width: 5rem'>
|
||||
<h2 class='h4'>It's free!</h2>
|
||||
<p>
|
||||
<b>WAHA Core</b> version is always free!
|
||||
<a href='https://waha.devlike.pro/docs/how-to/plus-version/#tiers' target='_blank'>
|
||||
<b>WAHA Core</b>
|
||||
</a>
|
||||
version is always free!
|
||||
<br>
|
||||
Even <b>WAHA Plus</b> version have no licence expiration!
|
||||
Even
|
||||
<a href='https://waha.devlike.pro/docs/how-to/plus-version/#tiers' target='_blank'>
|
||||
<b>WAHA Plus</b>
|
||||
</a>
|
||||
version have no licence expiration!
|
||||
</p>
|
||||
</div>
|
||||
<div class="col-lg-5">
|
||||
<h2 class="h4">Language agnostic</h2>
|
||||
<div class='col-lg-5'>
|
||||
<img src='/images/icons/api.png' alt='API' style='width: 5rem'>
|
||||
<h2 class='h4'>Language agnostic</h2>
|
||||
<p>
|
||||
Use your own favorite language - Python, JavaScript, PHP, C#, Clojure, or PowerShell.
|
||||
<br/>
|
||||
<small class="text-muted">
|
||||
<br />
|
||||
<small class='text-muted'>
|
||||
PowerShell, really? Are you insane?! Choose another language, dude.
|
||||
</small>
|
||||
</p>
|
||||
</div>
|
||||
<div class="col-lg-5">
|
||||
<h2 class="h4">Well documented</h2>
|
||||
<p>Documentation has a lof examples and how-to guides.</p>
|
||||
<div class='col-lg-5'>
|
||||
<img src='/images/icons/document.png' alt='Document' style='width: 5rem'>
|
||||
<h2 class='h4'>Well documented</h2>
|
||||
<p>
|
||||
<a href='https://waha.devlike.pro/docs/overview/introduction/' target='_blank'>
|
||||
Documentation
|
||||
</a>
|
||||
has a lof examples and how-to guides.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-center text-center">
|
||||
<div class="col-lg-5">
|
||||
<h2 class="h4">Self hosted</h2>
|
||||
<div class='row justify-content-center text-center'>
|
||||
<div class='col-lg-5'>
|
||||
<img src='/images/icons/servers.png' alt='Servers' style='width: 5rem'>
|
||||
<h2 class='h4'>Self-hosted solution</h2>
|
||||
<p>
|
||||
No strings to suspicious SaaS WhatsApp HTTP API solutions. Install it on your own server!
|
||||
<a href='https://waha.devlike.pro/docs/how-to/plus-version/#why-waha' target='_blank'>
|
||||
No strings to suspicious SaaS WhatsApp HTTP API solutions.
|
||||
</a>
|
||||
Install it on your own server!
|
||||
</p>
|
||||
</div>
|
||||
<div class="col-lg-5">
|
||||
<h2 class="h4">Easy installation</h2>
|
||||
<div class='col-lg-5'>
|
||||
<img src='/images/icons/easy.png' alt='Easy' style='width: 5rem'>
|
||||
<h2 class='h4'>Quick installation</h2>
|
||||
<p>
|
||||
You need to install <a href="https://docs.docker.com/engine/install/">Docker</a> and can run the API in a
|
||||
click!
|
||||
<br/>
|
||||
<small class="text-muted">
|
||||
You need to install Docker and
|
||||
<a href='https://waha.devlike.pro/docs/how-to/install/' target='_blank'>
|
||||
run the API in a click!
|
||||
</a>
|
||||
<br />
|
||||
<small class='text-muted'>
|
||||
It's actually "a command", not a click.
|
||||
</small>
|
||||
</p>
|
||||
</div>
|
||||
<div class="col-lg-5">
|
||||
<h2 class="h4">No blocking</h2>
|
||||
<div class='col-lg-5'>
|
||||
<img src='/images/icons/checked.png' alt='Checked' style='width: 5rem'>
|
||||
<h2 class='h4'>No blocking</h2>
|
||||
<p>
|
||||
Under the hood it runs a real instance of Whatsapp Web to avoid getting blocked.
|
||||
<br/>
|
||||
<small class="text-muted">
|
||||
<a href='https://waha.devlike.pro/docs/how-to/engines/' target='_blank'>
|
||||
Under the hood
|
||||
</a>
|
||||
it runs a real instance of Whatsapp Web to avoid getting blocked.
|
||||
<br />
|
||||
<small class='text-muted'>
|
||||
WhatsApp does not allow bots or unofficial clients on their platform, so this shouldn't be considered
|
||||
totally safe.
|
||||
</small>
|
||||
@@ -85,9 +110,9 @@
|
||||
{{ end }}
|
||||
|
||||
{{ define "sidebar-footer" }}
|
||||
<section class="section section-sm container-fluid">
|
||||
<div class="row justify-content-center text-center">
|
||||
<div class="col-lg-9">
|
||||
<section class='section section-sm container-fluid'>
|
||||
<div class='row justify-content-center text-center'>
|
||||
<div class='col-lg-9'>
|
||||
{{- .Content -}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 7.4 KiB |
|
After Width: | Height: | Size: 36 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 49 KiB |
@@ -1 +1 @@
|
||||
{"openapi":"3.1.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.","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.","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/SessionInfo"}}}}}},"tags":["sessions"],"security":[{"api_key":[]}]}},"/api/sessions/{session}/me":{"get":{"operationId":"SessionController_getMe","summary":"Get information about the authenticated account","parameters":[{"name":"session","required":true,"in":"path","description":"WhatsApp session name","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MeInfo"}}}}},"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":[]}]}},"/apLine truncated
|
||||
{"openapi":"3.1.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","schema":{"default":"default"},"description":"WhatsApp session name"},{"name":"format","required":true,"in":"query","schema":{"default":"image","enum":["image","raw"],"type":"string"}}],"responses":{"200":{"content":{"image/png":{"schema":{"type":"string","format":"binary"}},"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/Base64File"},{"$ref":"#/components/schemas/QRCodeValue"}]}}},"description":""}},"tags":["auth"],"security":[{"api_key":[]}]}},"/api/{session}/auth/request-code":{"post":{"operationId":"AuthController_requestCode","summary":"Request authentication code.","parameters":[{"name":"session","required":true,"in":"path","schema":{"default":"default"},"description":"WhatsApp session name"}],"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.","parameters":[{"name":"session","required":true,"in":"path","schema":{"default":"default"},"description":"WhatsApp session name"}],"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/SessionInfo"}}}}}},"tags":["sessions"],"security":[{"api_key":[]}]}},"/api/sessions/{session}/me":{"get":{"operationId":"SessionController_getMe","summary":"Get information about the authenticated account","parameters":[{"name":"session","required":true,"in":"path","schema":{"default":"default"},"description":"WhatsApp session name"}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MeInfo"}}}}},"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":""}},"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,Line truncated
|
||||
@@ -9,10 +9,11 @@ We assume that you have installed software:
|
||||
|
||||
### Download and start image
|
||||
|
||||
First of all, you must run WhatsApp HTTP API locally (which under the hood it runs real WhatsApp Web instance and expose
|
||||
HTTP API for interaction).
|
||||
First of all, you must run WhatsApp HTTP API locally (which under the hood it
|
||||
runs real WhatsApp Web instance and expose HTTP API for interaction).
|
||||
|
||||
Here are the steps from [Quick Start](https://waha.devlike.pro/docs/overview/quick-start/):
|
||||
Here are the steps from
|
||||
[Quick Start](https://waha.devlike.pro/docs/overview/quick-start/):
|
||||
|
||||
Download and start WhatsApp HTTP API docker container
|
||||
|
||||
@@ -28,9 +29,11 @@ docker run -it --rm --network=host -e WHATSAPP_HOOK_URL=http://localhost:5000/bo
|
||||
|
||||
#### Download image - ARM
|
||||
|
||||
If you're using ARM (like Apple Silicon, Apple M1, etc.) - use following commands to download the image
|
||||
If you're using ARM (like Apple Silicon, Apple M1, etc.) - use following
|
||||
commands to download the image
|
||||
|
||||
 For Core version the command is
|
||||
|
||||
```bash
|
||||
# Download the image
|
||||
docker pull devlikeapro/whatsapp-http-api:arm
|
||||
@@ -47,12 +50,16 @@ docker run -it --rm --network=host -e WHATSAPP_HOOK_URL=http://localhost:5000/bo
|
||||
|
||||
2. Open Swagger API in the browser http://localhost:3000/
|
||||
3. Start session and scan QR code in swagger
|
||||
1. Find `POST /api/session/start` request press **Try it out** and **Execute** it with `default` session name
|
||||
2. Find `GET /api/screenshot` and execute it - it must show QR code
|
||||
3. Scan QR code on your mobile WhatsApp application (that installed on your phone)
|
||||
4. Execute `GET /api/screenshot` once again - it must show the screenshot from WhatsApp Web.
|
||||
4. Send test text message - find `POST /api/sendText` and execute it with payload (change `12132132130` in the `chatId`
|
||||
to phone number that is registered in WhatsApp).
|
||||
1. Find `POST /api/session/start` request press **Try it out** and
|
||||
**Execute** it with `default` session name
|
||||
2. Find `GET /api/screenshot` and execute it - it must show QR code
|
||||
3. Scan QR code on your mobile WhatsApp application (that installed on your
|
||||
phone)
|
||||
4. Execute `GET /api/screenshot` once again - it must show the screenshot
|
||||
from WhatsApp Web.
|
||||
4. Send test text message - find `POST /api/sendText` and execute it with
|
||||
payload (change `12132132130` in the `chatId` to phone number that is
|
||||
registered in WhatsApp).
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -66,9 +73,9 @@ If you see **Hi there!** message then you're ready to run bots!
|
||||
|
||||
## WhatsApp Echo Bot
|
||||
|
||||
The WhatsApp Echo Bot is a sample flask webhook server application that echoes back to you whatever you send it. It can
|
||||
serve
|
||||
as a basic reference for how to set up webhooks and reply to incoming messages.
|
||||
The WhatsApp Echo Bot is a sample flask webhook server application that echoes
|
||||
back to you whatever you send it. It can serve as a basic reference for how to
|
||||
set up webhooks and reply to incoming messages.
|
||||
|
||||
```bash
|
||||
# Clone the git repository with example
|
||||
@@ -81,16 +88,19 @@ python -mpip install -r requirements.txt
|
||||
FLASK_APP=whatsapp_echo_bot.py flask run
|
||||
```
|
||||
|
||||
Open http://127.0.0.1:5000/bot - if you see **WhatsApp Echo Bot is ready!** then the bot is ready to receive message!
|
||||
Open http://127.0.0.1:5000/bot - if you see **WhatsApp Echo Bot is ready!** then
|
||||
the bot is ready to receive message!
|
||||
|
||||
Send message to the WhatsApp (that you used to scan QR code) and it'll echo text back to you!
|
||||
Send message to the WhatsApp (that you used to scan QR code) and it'll echo text
|
||||
back to you!
|
||||
|
||||
## WhatsApp Download Files Bot
|
||||
|
||||
The WhatsApp Download Image Bot downloads all files people send to your WhatsApp and log the path for the file.
|
||||
The WhatsApp Download Image Bot downloads all files people send to your WhatsApp
|
||||
and log the path for the file.
|
||||
|
||||
**The bot works only with WAHA Plus version** available with donations.
|
||||
Visit [read more about difference between Core and Plus versions](https://waha.devlike.pro/docs/how-to/plus-version/).
|
||||
**The bot works only with WAHA Plus version** available with donations. Visit
|
||||
[read more about difference between Core and Plus versions](https://waha.devlike.pro/docs/how-to/plus-version/).
|
||||
|
||||
Download **WAHA Plus** version:
|
||||
|
||||
@@ -114,5 +124,5 @@ python -mpip install -r requirements.txt
|
||||
FLASK_APP=whatsapp_download_files_bot.py flask run
|
||||
```
|
||||
|
||||
Open http://127.0.0.1:5000/bot - if you see **WhatsApp Download Files Bot!** then the bot is ready to receive message
|
||||
with files!
|
||||
Open http://127.0.0.1:5000/bot - if you see **WhatsApp Download Files Bot!**
|
||||
then the bot is ready to receive message with files!
|
||||
@@ -1,4 +1,6 @@
|
||||
import random
|
||||
from pprint import pprint
|
||||
from time import sleep
|
||||
|
||||
import requests
|
||||
from flask import Flask
|
||||
@@ -49,6 +51,30 @@ def send_seen(chat_id, message_id, participant):
|
||||
)
|
||||
response.raise_for_status()
|
||||
|
||||
def start_typing(chat_id):
|
||||
response = requests.post(
|
||||
"http://localhost:3000/api/startTyping",
|
||||
json={
|
||||
"session": "default",
|
||||
"chatId": chat_id,
|
||||
},
|
||||
)
|
||||
response.raise_for_status()
|
||||
|
||||
def stop_typing(chat_id):
|
||||
response = requests.post(
|
||||
"http://localhost:3000/api/stopTyping",
|
||||
json={
|
||||
"session": "default",
|
||||
"chatId": chat_id,
|
||||
},
|
||||
)
|
||||
response.raise_for_status()
|
||||
|
||||
def typing(chat_id, seconds):
|
||||
start_typing(chat_id=chat_id)
|
||||
sleep(seconds)
|
||||
stop_typing(chat_id=chat_id)
|
||||
|
||||
@app.route("/")
|
||||
def whatsapp_echo():
|
||||
@@ -69,7 +95,12 @@ def whatsapp_webhook():
|
||||
# Payload that we've got
|
||||
payload = data["payload"]
|
||||
# The text
|
||||
text = payload["body"]
|
||||
text = payload.get("body")
|
||||
if not text:
|
||||
# We can't process non-text messages yet
|
||||
print("No text in message")
|
||||
print(payload)
|
||||
return "OK"
|
||||
# Number in format 1231231231@c.us or @g.us for group
|
||||
chat_id = payload["from"]
|
||||
# Message ID - false_11111111111@c.us_AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
@@ -79,9 +110,13 @@ def whatsapp_webhook():
|
||||
# IMPORTANT - Always send seen before sending new message
|
||||
send_seen(chat_id=chat_id, message_id=message_id, participant=participant)
|
||||
|
||||
|
||||
# Send a text back via WhatsApp HTTP API
|
||||
typing(chat_id=chat_id, seconds=random.random() * 3)
|
||||
send_message(chat_id=chat_id, text=text)
|
||||
|
||||
# OR reply on the message
|
||||
typing(chat_id=chat_id, seconds=random.random() * 3)
|
||||
reply(chat_id=chat_id, message_id=message_id, text=text)
|
||||
|
||||
# Send OK back
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
"collection": "@nestjs/schematics",
|
||||
"sourceRoot": "src",
|
||||
"compilerOptions": {
|
||||
"plugins": [
|
||||
"@nestjs/swagger"
|
||||
]
|
||||
"plugins": ["@nestjs/swagger"]
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,7 @@
|
||||
"start:debug": "nest start --debug --watch",
|
||||
"start:prod": "node --unhandled-rejections=none dist/main",
|
||||
"start:prod-exit": "node dist/main",
|
||||
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
|
||||
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix --max-warnings=0",
|
||||
"test": "jest",
|
||||
"test:watch": "jest --watch",
|
||||
"test:cov": "jest --coverage",
|
||||
@@ -24,6 +24,7 @@
|
||||
"dependencies": {
|
||||
"@adiwajshing/baileys": "github:WhiskeySockets/Baileys",
|
||||
"@adiwajshing/keyed-db": "^0.2.4",
|
||||
"@nestjs/axios": "^3.0.2",
|
||||
"@nestjs/common": "^9.0.9",
|
||||
"@nestjs/config": "^0.5.0",
|
||||
"@nestjs/core": "^9.0.9",
|
||||
@@ -31,8 +32,10 @@
|
||||
"@nestjs/platform-express": "^9.0.9",
|
||||
"@nestjs/serve-static": "^2.1.3",
|
||||
"@nestjs/swagger": "^7.1.11",
|
||||
"@nestjs/terminus": "^10.2.3",
|
||||
"@types/lodash": "^4.14.194",
|
||||
"@types/ws": "^8.5.4",
|
||||
"check-disk-space": "^3.4.0",
|
||||
"class-validator": "^0.12.2",
|
||||
"del": "^6.0.0",
|
||||
"express-basic-auth": "^1.2.1",
|
||||
@@ -42,6 +45,7 @@
|
||||
"link-preview-js": "^3.0.4",
|
||||
"lodash": "^4.17.21",
|
||||
"mime-types": "^2.1.27",
|
||||
"mongodb": "^6.3.0",
|
||||
"passport": "^0.6.0",
|
||||
"passport-headerapikey": "^1.2.2",
|
||||
"qrcode": "^1.5.1",
|
||||
@@ -53,7 +57,7 @@
|
||||
"rxjs": "^7.1.0",
|
||||
"swagger-ui-express": "^4.1.4",
|
||||
"venom-bot": "5.0.1",
|
||||
"whatsapp-web.js": "https://github.com/pedroslopez/whatsapp-web.js"
|
||||
"whatsapp-web.js": "https://github.com/devlikeapro/whatsapp-web.js#main-fork"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nestjs/cli": "^9.0.0",
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
import {
|
||||
CallHandler,
|
||||
ExecutionContext,
|
||||
Injectable,
|
||||
NestInterceptor,
|
||||
StreamableFile,
|
||||
} from '@nestjs/common';
|
||||
import { Request, Response } from 'express';
|
||||
import { Observable } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
|
||||
import { Base64File } from '../structures/files.dto';
|
||||
|
||||
@Injectable()
|
||||
/**
|
||||
* Interceptor to convert Buffer response to Base64File or StreamableFile
|
||||
* based on Accept header.
|
||||
* It should go together with @ApiFileAcceptHeader in controller method
|
||||
* So we have proper Swagger documentation
|
||||
*/
|
||||
export class BufferResponseInterceptor<T>
|
||||
implements NestInterceptor<Buffer, Base64File | StreamableFile>
|
||||
{
|
||||
intercept(
|
||||
context: ExecutionContext,
|
||||
next: CallHandler,
|
||||
): Observable<Base64File | StreamableFile> {
|
||||
const ctx = context.switchToHttp();
|
||||
const request = ctx.getRequest<Request>();
|
||||
const response = ctx.getResponse<Response>();
|
||||
return next.handle().pipe(map(processBuffer));
|
||||
|
||||
function processBuffer(buffer: Buffer) {
|
||||
// Check buffer is Buffer
|
||||
if (!Buffer.isBuffer(buffer)) {
|
||||
return buffer;
|
||||
}
|
||||
|
||||
const accept = request.headers['accept'];
|
||||
if (accept == 'application/json') {
|
||||
return {
|
||||
mimetype: 'image/png',
|
||||
data: buffer.toString('base64'),
|
||||
};
|
||||
}
|
||||
const file = new StreamableFile(buffer);
|
||||
response.set({
|
||||
'Content-Type': 'image/png',
|
||||
'Content-Length': buffer.length,
|
||||
});
|
||||
return file;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,28 @@
|
||||
import { Body, Controller, Get, Post, Res } from '@nestjs/common';
|
||||
import {
|
||||
Body,
|
||||
Controller,
|
||||
Get,
|
||||
Post,
|
||||
Query,
|
||||
UseInterceptors,
|
||||
} 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 { WhatsappSession } from '../core/abc/session.abc';
|
||||
import { OTPRequest, RequestCodeRequest } from '../structures/auth.dto';
|
||||
import { SessionApiParam, SessionParam } from './helpers';
|
||||
import {
|
||||
CaptchaBody,
|
||||
OTPRequest,
|
||||
QRCodeFormat,
|
||||
QRCodeQuery,
|
||||
QRCodeValue,
|
||||
RequestCodeRequest,
|
||||
} from '../structures/auth.dto';
|
||||
import { WAHASessionStatus } from '../structures/enums.dto';
|
||||
import { UnprocessableEntityException } from '@nestjs/common/exceptions/unprocessable-entity.exception';
|
||||
import { Readable } from 'stream';
|
||||
import { Response } from 'express';
|
||||
import { Base64File } from '../structures/files.dto';
|
||||
import { BufferResponseInterceptor } from './BufferResponseInterceptor';
|
||||
import { ApiFileAcceptHeader, SessionApiParam, SessionParam } from './helpers';
|
||||
|
||||
@ApiSecurity('api_key')
|
||||
@Controller('api/:session/auth')
|
||||
@@ -17,26 +31,25 @@ 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) {
|
||||
@SessionApiParam
|
||||
@ApiFileAcceptHeader(Base64File, QRCodeValue)
|
||||
@UseInterceptors(new BufferResponseInterceptor())
|
||||
async getQR(
|
||||
@SessionParam session: WhatsappSession,
|
||||
@Query() query: QRCodeQuery,
|
||||
): Promise<Buffer | QRCodeValue> {
|
||||
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);
|
||||
const qr = session.getQR();
|
||||
if (query.format == QRCodeFormat.RAW) {
|
||||
return { value: qr.raw };
|
||||
}
|
||||
return qr.get();
|
||||
}
|
||||
|
||||
@Post('request-code')
|
||||
@@ -48,7 +61,7 @@ class AuthController {
|
||||
@SessionParam session: WhatsappSession,
|
||||
@Body() request: RequestCodeRequest,
|
||||
) {
|
||||
return session.requestCode(request.phoneNumber, request.method);
|
||||
return session.requestCode(request.phoneNumber, request.method, request);
|
||||
}
|
||||
|
||||
@Post('authorize-code')
|
||||
@@ -62,6 +75,30 @@ class AuthController {
|
||||
) {
|
||||
return session.authorizeCode(request.code);
|
||||
}
|
||||
|
||||
@Get('captcha')
|
||||
@SessionApiParam
|
||||
@ApiFileAcceptHeader()
|
||||
@UseInterceptors(new BufferResponseInterceptor())
|
||||
@ApiOperation({
|
||||
summary: 'Get captcha image.',
|
||||
})
|
||||
async getCaptcha(@SessionParam session: WhatsappSession): Promise<Buffer> {
|
||||
const image = await session.getCaptcha();
|
||||
return image.get();
|
||||
}
|
||||
|
||||
@Post('captcha')
|
||||
@SessionApiParam
|
||||
@ApiOperation({
|
||||
summary: 'Enter captcha code.',
|
||||
})
|
||||
saveCaptcha(
|
||||
@SessionParam session: WhatsappSession,
|
||||
@Body() body: CaptchaBody,
|
||||
) {
|
||||
return session.saveCaptcha(body.code);
|
||||
}
|
||||
}
|
||||
|
||||
export { AuthController };
|
||||
@@ -3,9 +3,9 @@ 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';
|
||||
import { GetChatMessagesQuery } from '../structures/chats.dto';
|
||||
import { SessionApiParam, SessionParam } from './helpers';
|
||||
|
||||
@ApiSecurity('api_key')
|
||||
@Controller('api/:session/chats')
|
||||
|
||||
@@ -67,6 +67,7 @@ export class ContactsController {
|
||||
const whatsapp = this.manager.getSession(request.session);
|
||||
return whatsapp.blockContact(request);
|
||||
}
|
||||
|
||||
@Post('/unblock')
|
||||
@ApiOperation({ summary: 'Unblock contact' })
|
||||
unblock(@Body() request: ContactRequest) {
|
||||
|
||||
@@ -23,16 +23,42 @@ export function serializeError(err: unknown) {
|
||||
|
||||
@Catch()
|
||||
export class AllExceptionsFilter implements ExceptionFilter {
|
||||
catch(exception: HttpException | Error, host: ArgumentsHost): void {
|
||||
catch(exception: any | Error, host: ArgumentsHost): void {
|
||||
const ctx = host.switchToHttp();
|
||||
const response = ctx.getResponse<Response>();
|
||||
const request = ctx.getRequest<Request>();
|
||||
|
||||
/**
|
||||
* If file not found - we get weird 500 error
|
||||
* So we convert that exception manually to 404 and send appropriate JSON response
|
||||
* @issue https://github.com/devlikeapro/whatsapp-http-api/issues/134
|
||||
* @solution https://github.com/nestjs/serve-static/issues/139#issuecomment-612429557
|
||||
*/
|
||||
if (exception.code === 'ENOENT') {
|
||||
response.status(HttpStatus.NOT_FOUND).json({
|
||||
error: {
|
||||
code: 404,
|
||||
key: 'FILE_NOT_FOUND',
|
||||
message: 'File not found',
|
||||
details: 'File not found or no longer available',
|
||||
},
|
||||
});
|
||||
response.send();
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* If it's HttpException - pass it as is
|
||||
*/
|
||||
if (exception instanceof HttpException) {
|
||||
response.status(exception.getStatus()).json(exception.getResponse());
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* If it's not HttpException - pass it as 500 error
|
||||
* And send JSON response with error details
|
||||
*/
|
||||
const httpStatus = HttpStatus.INTERNAL_SERVER_ERROR;
|
||||
response.status(httpStatus).json({
|
||||
statusCode: httpStatus,
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
CreateGroupRequest,
|
||||
DescriptionRequest,
|
||||
ParticipantsRequest,
|
||||
SettingsSecurityChangeInfo,
|
||||
SubjectRequest,
|
||||
} from '../structures/groups.dto';
|
||||
import { SessionApiParam, SessionParam } from './helpers';
|
||||
@@ -62,6 +63,19 @@ export class GroupsController {
|
||||
return session.setInfoAdminsOnly(id, true);
|
||||
}
|
||||
|
||||
@Get(':id/settings/security/info-admin-only')
|
||||
@SessionApiParam
|
||||
@ApiOperation({
|
||||
summary:
|
||||
'Gets the group settings to only allow admins to edit group info (title, description, photo).',
|
||||
})
|
||||
getInfoAdminOnly(
|
||||
@SessionParam session: WhatsappSession,
|
||||
@Param('id') id: string,
|
||||
): Promise<SettingsSecurityChangeInfo> {
|
||||
return session.getInfoAdminsOnly(id);
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
@SessionApiParam
|
||||
@ApiOperation({ summary: 'Delete the group.' })
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import { Controller, Get } from '@nestjs/common';
|
||||
import { ApiTags } from '@nestjs/swagger';
|
||||
import { HealthCheck } from '@nestjs/terminus';
|
||||
|
||||
import { WAHAHealthCheckService } from '../core/abc/WAHAHealthCheckService';
|
||||
|
||||
@Controller('health')
|
||||
@ApiTags('other')
|
||||
export class HealthController {
|
||||
constructor(private wahaHealth: WAHAHealthCheckService) {}
|
||||
|
||||
@Get()
|
||||
@HealthCheck()
|
||||
async check() {
|
||||
return this.wahaHealth.check();
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,19 @@
|
||||
import { Injectable, Param, PipeTransform } from '@nestjs/common';
|
||||
import { ApiParam } from '@nestjs/swagger';
|
||||
import {
|
||||
applyDecorators,
|
||||
Injectable,
|
||||
Param,
|
||||
PipeTransform,
|
||||
} from '@nestjs/common';
|
||||
import {
|
||||
ApiExtraModels,
|
||||
ApiParam,
|
||||
ApiResponse,
|
||||
getSchemaPath,
|
||||
} from '@nestjs/swagger';
|
||||
|
||||
import { SessionManager } from '../core/abc/manager.abc';
|
||||
import { WhatsappSession } from '../core/abc/session.abc';
|
||||
import { Base64File } from '../structures/files.dto';
|
||||
|
||||
/**
|
||||
* Get session name and return Whatsapp session back (if exists)
|
||||
@@ -25,6 +36,9 @@ export const SessionApiParam = ApiParam({
|
||||
name: 'session',
|
||||
required: true,
|
||||
type: 'string',
|
||||
schema: {
|
||||
default: 'default',
|
||||
},
|
||||
description: 'WhatsApp session name',
|
||||
});
|
||||
|
||||
@@ -33,3 +47,34 @@ export const SessionApiParam = ApiParam({
|
||||
@SessionParam session: WhatsappSession,
|
||||
*/
|
||||
export const SessionParam = Param('session', SessionPipe);
|
||||
|
||||
function getRefSchemaPaths(models) {
|
||||
return models.map((model) => {
|
||||
return { $ref: getSchemaPath(model) };
|
||||
});
|
||||
}
|
||||
|
||||
export function ApiFileAcceptHeader(...models) {
|
||||
models = models.length ? models : [Base64File];
|
||||
return applyDecorators(
|
||||
// Add extra models, otherwise it'll give a error
|
||||
// $ref not found
|
||||
ApiExtraModels(...models),
|
||||
ApiResponse({
|
||||
status: 200,
|
||||
content: {
|
||||
'image/png': {
|
||||
schema: {
|
||||
type: 'string',
|
||||
format: 'binary',
|
||||
},
|
||||
},
|
||||
'application/json': {
|
||||
schema: {
|
||||
oneOf: getRefSchemaPaths(models),
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
}
|
||||
@@ -10,12 +10,12 @@ import { ApiOperation, ApiSecurity, ApiTags } from '@nestjs/swagger';
|
||||
|
||||
import { SessionManager } from '../core/abc/manager.abc';
|
||||
import { WhatsappSession } from '../core/abc/session.abc';
|
||||
import { WAHAPresenceStatus } from '../structures/enums.dto';
|
||||
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')
|
||||
|
||||
@@ -1,10 +1,18 @@
|
||||
import { Controller, Get, Query, Res } from '@nestjs/common';
|
||||
import {
|
||||
Controller,
|
||||
Get,
|
||||
Query,
|
||||
Res,
|
||||
StreamableFile,
|
||||
UseInterceptors,
|
||||
} from '@nestjs/common';
|
||||
import { ApiSecurity, ApiTags } from '@nestjs/swagger';
|
||||
import { Response } from 'express';
|
||||
import { Readable } from 'stream';
|
||||
|
||||
import { SessionManager } from '../core/abc/manager.abc';
|
||||
import { SessionQuery } from '../structures/base.dto';
|
||||
import { BufferResponseInterceptor } from './BufferResponseInterceptor';
|
||||
import { ApiFileAcceptHeader } from './helpers';
|
||||
|
||||
@ApiSecurity('api_key')
|
||||
@Controller('api')
|
||||
@@ -13,17 +21,13 @@ export class ScreenshotController {
|
||||
constructor(private manager: SessionManager) {}
|
||||
|
||||
@Get('/screenshot')
|
||||
async screenshot(@Res() res: Response, @Query() sessionQuery: SessionQuery) {
|
||||
@UseInterceptors(new BufferResponseInterceptor())
|
||||
@ApiFileAcceptHeader()
|
||||
async screenshot(
|
||||
@Res({ passthrough: true }) res: Response,
|
||||
@Query() sessionQuery: SessionQuery,
|
||||
) {
|
||||
const whatsappService = this.manager.getSession(sessionQuery.session);
|
||||
const buffer = await whatsappService.getScreenshot();
|
||||
const stream = new Readable();
|
||||
stream.push(buffer);
|
||||
stream.push(null);
|
||||
|
||||
res.set({
|
||||
'Content-Type': 'image/png',
|
||||
'Content-Length': buffer.length,
|
||||
});
|
||||
stream.pipe(res);
|
||||
return await whatsappService.getScreenshot();
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
import { Body, Controller, Get, Post, Query } from '@nestjs/common';
|
||||
import { UnprocessableEntityException } from '@nestjs/common/exceptions/unprocessable-entity.exception';
|
||||
import {
|
||||
ApiExtraModels,
|
||||
ApiOperation,
|
||||
@@ -8,6 +7,7 @@ import {
|
||||
} from '@nestjs/swagger';
|
||||
|
||||
import { SessionManager } from '../core/abc/manager.abc';
|
||||
import { WhatsappSession } from '../core/abc/session.abc';
|
||||
import { parseBool } from '../helpers';
|
||||
import {
|
||||
ListSessionsQuery,
|
||||
@@ -19,9 +19,6 @@ import {
|
||||
SessionStopRequest,
|
||||
} from '../structures/sessions.dto';
|
||||
import { SessionApiParam, SessionParam } from './helpers';
|
||||
import { WhatsappSession } from '../core/abc/session.abc';
|
||||
import { WAHAChatPresences } from '../structures/presence.dto';
|
||||
import { WAHAWebhook } from '../structures/webhooks.dto';
|
||||
|
||||
@ApiSecurity('api_key')
|
||||
@Controller('api/sessions')
|
||||
@@ -32,9 +29,9 @@ class SessionsController {
|
||||
@Post('/start/')
|
||||
async start(@Body() request: SessionStartRequest): Promise<SessionDTO> {
|
||||
const result = await this.manager.start(request);
|
||||
await this.manager.sessionStorage.configRepository.save(
|
||||
await this.manager.sessionConfigRepository.save(
|
||||
request.name,
|
||||
request.config,
|
||||
request.config || null,
|
||||
);
|
||||
return result;
|
||||
}
|
||||
@@ -71,7 +68,7 @@ class SessionController {
|
||||
@Get('me')
|
||||
@SessionApiParam
|
||||
@ApiOperation({ summary: 'Get information about the authenticated account' })
|
||||
getMe(@SessionParam session: WhatsappSession): Promise<MeInfo> {
|
||||
getMe(@SessionParam session: WhatsappSession): Promise<MeInfo | null> {
|
||||
return session.getSessionMeInfo();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,13 +11,13 @@ 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';
|
||||
import { SessionApiParam, SessionParam } from './helpers';
|
||||
|
||||
@ApiSecurity('api_key')
|
||||
@Controller('api/:session/status')
|
||||
|
||||
@@ -36,9 +36,17 @@ export class WhatsappConfigService {
|
||||
return this.configService.get<number>('WHATSAPP_FILES_LIFETIME', 180);
|
||||
}
|
||||
|
||||
get mimetypes(): string[] | null {
|
||||
get mimetypes(): string[] {
|
||||
if (!this.shouldDownloadMedia) {
|
||||
return ['mimetype/ignore-all-media'];
|
||||
}
|
||||
const types = this.configService.get('WHATSAPP_FILES_MIMETYPES', '');
|
||||
return types ? types.split(',') : null;
|
||||
return types ? types.split(',') : [];
|
||||
}
|
||||
|
||||
get shouldDownloadMedia(): boolean {
|
||||
const value = this.configService.get('WHATSAPP_DOWNLOAD_MEDIA', 'true');
|
||||
return parseBool(value);
|
||||
}
|
||||
|
||||
get startSessions(): string[] {
|
||||
@@ -100,6 +108,10 @@ export class WhatsappConfigService {
|
||||
return value ? value.split(',') : [];
|
||||
}
|
||||
|
||||
getSessionMongoUrl(): string | undefined {
|
||||
return this.configService.get('WHATSAPP_SESSIONS_MONGO_URL', undefined);
|
||||
}
|
||||
|
||||
getDefaultEngineName(): WAHAEngine {
|
||||
const value = getEngineName();
|
||||
if (value in WAHAEngine) {
|
||||
@@ -111,7 +123,7 @@ export class WhatsappConfigService {
|
||||
return WAHAEngine.WEBJS;
|
||||
}
|
||||
|
||||
get(name: string, defaultValue = undefined): any {
|
||||
get(name: string, defaultValue: any = undefined): any {
|
||||
return this.configService.get(name, defaultValue);
|
||||
}
|
||||
|
||||
@@ -119,6 +131,11 @@ export class WhatsappConfigService {
|
||||
return this.configService.get('WHATSAPP_API_KEY', '');
|
||||
}
|
||||
|
||||
getSwaggerEnabled(): boolean {
|
||||
const value = this.configService.get('WHATSAPP_SWAGGER_ENABLED', 'true');
|
||||
return parseBool(value);
|
||||
}
|
||||
|
||||
getSwaggerUsernamePassword(): [string, string] | undefined {
|
||||
const user = this.configService.get('WHATSAPP_SWAGGER_USERNAME', undefined);
|
||||
const password = this.configService.get(
|
||||
@@ -138,4 +155,25 @@ export class WhatsappConfigService {
|
||||
getSwaggerAdvancedConfigEnabled(): boolean {
|
||||
return this.configService.get('WHATSAPP_SWAGGER_CONFIG_ADVANCED', false);
|
||||
}
|
||||
|
||||
getHealthMediaFilesThreshold(): number {
|
||||
return this.configService.get<number>(
|
||||
'WHATSAPP_HEALTH_MEDIA_FILES_THRESHOLD_MB',
|
||||
100,
|
||||
);
|
||||
}
|
||||
|
||||
getHealthSessionFilesThreshold(): number {
|
||||
return this.configService.get<number>(
|
||||
'WHATSAPP_HEALTH_SESSION_FILES_THRESHOLD_MB',
|
||||
100,
|
||||
);
|
||||
}
|
||||
|
||||
getHealthMongoTimeout(): number {
|
||||
return this.configService.get<number>(
|
||||
'WHATSAPP_HEALTH_MONGO_TIMEOUT_MS',
|
||||
3000,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,10 @@
|
||||
export class QR {
|
||||
private base64: string;
|
||||
public raw?: string;
|
||||
|
||||
save(base64) {
|
||||
save(base64: string, raw?: string) {
|
||||
this.base64 = base64.replace(/^data:image\/png;base64,/, '');
|
||||
this.raw = raw;
|
||||
}
|
||||
|
||||
get(): Buffer {
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export abstract class DataStore {}
|
||||
@@ -0,0 +1,18 @@
|
||||
import { ConsoleLogger, Injectable } from '@nestjs/common';
|
||||
import { DiskHealthIndicator, HealthCheckService } from '@nestjs/terminus';
|
||||
import type { HealthCheckResult } from '@nestjs/terminus/dist/health-check/health-check-result.interface';
|
||||
|
||||
import { WhatsappConfigService } from '../../config.service';
|
||||
import { SessionManager } from './manager.abc';
|
||||
|
||||
@Injectable()
|
||||
export abstract class WAHAHealthCheckService {
|
||||
constructor(
|
||||
protected sessionManager: SessionManager,
|
||||
protected health: HealthCheckService,
|
||||
protected log: ConsoleLogger,
|
||||
protected config: WhatsappConfigService,
|
||||
) {}
|
||||
|
||||
abstract check(): Promise<HealthCheckResult>;
|
||||
}
|
||||
@@ -8,12 +8,15 @@ import {
|
||||
SessionStartRequest,
|
||||
SessionStopRequest,
|
||||
} from '../../structures/sessions.dto';
|
||||
import { ISessionAuthRepository } from '../storage/ISessionAuthRepository';
|
||||
import { ISessionConfigRepository } from '../storage/ISessionConfigRepository';
|
||||
import { WhatsappSession } from './session.abc';
|
||||
import { LocalSessionStorage, MediaStorage } from './storage.abc';
|
||||
import { WebhookConductor } from './webhooks.abc';
|
||||
|
||||
export abstract class SessionManager implements OnApplicationShutdown {
|
||||
public sessionStorage: LocalSessionStorage;
|
||||
public store: any;
|
||||
public sessionAuthRepository: ISessionAuthRepository;
|
||||
public sessionConfigRepository: ISessionConfigRepository;
|
||||
|
||||
protected abstract getEngine(engine: WAHAEngine): typeof WhatsappSession;
|
||||
|
||||
@@ -21,8 +24,6 @@ export abstract class SessionManager implements OnApplicationShutdown {
|
||||
|
||||
protected abstract get WebhookConductorClass(): typeof WebhookConductor;
|
||||
|
||||
protected abstract get MediaStorageClass(): typeof MediaStorage;
|
||||
|
||||
abstract onApplicationShutdown(signal?: string);
|
||||
|
||||
//
|
||||
@@ -34,7 +35,7 @@ export abstract class SessionManager implements OnApplicationShutdown {
|
||||
|
||||
abstract logout(request: SessionLogoutRequest): Promise<void>;
|
||||
|
||||
abstract getSession(name: string, error?: boolean): WhatsappSession;
|
||||
abstract getSession(name: string): WhatsappSession;
|
||||
|
||||
abstract getSessions(all: boolean): Promise<SessionInfo[]>;
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
abstract class MediaStorage {
|
||||
abstract save(
|
||||
messageId: string,
|
||||
mimetype: string,
|
||||
buffer: Buffer,
|
||||
): Promise<string>;
|
||||
}
|
||||
|
||||
interface IEngineMediaProcessor<Message> {
|
||||
hasMedia(message: Message): boolean;
|
||||
|
||||
getFilename(message: Message): string | null;
|
||||
|
||||
getMimetype(message: Message): string;
|
||||
|
||||
getMessageId(message: Message): string;
|
||||
|
||||
getMediaBuffer(message: Message): Promise<Buffer | null>;
|
||||
}
|
||||
|
||||
interface MediaManager {
|
||||
processMedia<Message>(
|
||||
processor: IEngineMediaProcessor<Message>,
|
||||
message: Message,
|
||||
): Promise<Message>;
|
||||
}
|
||||
|
||||
export { IEngineMediaProcessor, MediaManager, MediaStorage };
|
||||
@@ -29,6 +29,7 @@ import {
|
||||
import {
|
||||
CreateGroupRequest,
|
||||
ParticipantsRequest,
|
||||
SettingsSecurityChangeInfo,
|
||||
} from '../../structures/groups.dto';
|
||||
import { WAHAChatPresences } from '../../structures/presence.dto';
|
||||
import {
|
||||
@@ -36,9 +37,6 @@ 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,
|
||||
@@ -46,6 +44,10 @@ import {
|
||||
VoiceStatus,
|
||||
} from '../../structures/status.dto';
|
||||
import { WASessionStatusBody } from '../../structures/webhooks.dto';
|
||||
import { NotImplementedByEngineError } from '../exceptions';
|
||||
import { QR } from '../QR';
|
||||
import { DataStore } from './DataStore';
|
||||
import { MediaManager } from './media.abc';
|
||||
|
||||
const CHROME_PATH = '/usr/bin/google-chrome-stable';
|
||||
const CHROMIUM_PATH = '/usr/bin/chromium';
|
||||
@@ -72,11 +74,11 @@ export enum WAHAInternalEvent {
|
||||
|
||||
export interface SessionParams {
|
||||
name: string;
|
||||
storage: MediaStorage;
|
||||
mediaManager: MediaManager;
|
||||
log: ConsoleLogger;
|
||||
sessionStorage: LocalSessionStorage;
|
||||
sessionStore: DataStore;
|
||||
proxyConfig?: ProxyConfig;
|
||||
sessionConfig: SessionConfig;
|
||||
sessionConfig?: SessionConfig;
|
||||
}
|
||||
|
||||
export abstract class WhatsappSession {
|
||||
@@ -84,28 +86,28 @@ export abstract class WhatsappSession {
|
||||
public engine: WAHAEngine;
|
||||
|
||||
public name: string;
|
||||
protected storage: MediaStorage;
|
||||
protected mediaManager: MediaManager;
|
||||
protected log: ConsoleLogger;
|
||||
protected sessionStorage: LocalSessionStorage;
|
||||
protected sessionStore: DataStore;
|
||||
protected proxyConfig?: ProxyConfig;
|
||||
public sessionConfig: SessionConfig;
|
||||
public sessionConfig?: SessionConfig;
|
||||
|
||||
private _status: WAHASessionStatus;
|
||||
|
||||
public constructor({
|
||||
name,
|
||||
log,
|
||||
sessionStorage,
|
||||
sessionStore,
|
||||
proxyConfig,
|
||||
storage,
|
||||
mediaManager,
|
||||
sessionConfig,
|
||||
}: SessionParams) {
|
||||
this.events = new EventEmitter();
|
||||
this.name = name;
|
||||
this.proxyConfig = proxyConfig;
|
||||
this.log = log;
|
||||
this.sessionStorage = sessionStorage;
|
||||
this.storage = storage;
|
||||
this.sessionStore = sessionStore;
|
||||
this.mediaManager = mediaManager;
|
||||
this.sessionConfig = sessionConfig;
|
||||
}
|
||||
|
||||
@@ -192,11 +194,11 @@ export abstract class WhatsappSession {
|
||||
* Auth methods
|
||||
*/
|
||||
|
||||
public getQR(): Promise<Buffer> {
|
||||
public getQR(): QR {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public requestCode(phoneNumber: string, method: string) {
|
||||
public requestCode(phoneNumber: string, method: string, params?: any) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
@@ -204,12 +206,20 @@ export abstract class WhatsappSession {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
abstract getScreenshot(): Promise<Buffer | string>;
|
||||
abstract getScreenshot(): Promise<Buffer>;
|
||||
|
||||
public getSessionMeInfo(): Promise<MeInfo | null> {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public getCaptcha(): Promise<QR> {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public saveCaptcha(code: string) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
/**
|
||||
* Other methods
|
||||
*/
|
||||
@@ -320,6 +330,10 @@ export abstract class WhatsappSession {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public getInfoAdminsOnly(id): Promise<SettingsSecurityChangeInfo> {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public setInfoAdminsOnly(id, value) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
import { SessionConfig } from '../../structures/sessions.dto';
|
||||
|
||||
abstract class MediaStorage {
|
||||
abstract save(
|
||||
messageId: string,
|
||||
mimetype: string,
|
||||
buffer: Buffer,
|
||||
): Promise<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(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;
|
||||
}
|
||||
|
||||
export { LocalSessionStorage, MediaStorage, SessionConfigRepository };
|
||||
@@ -2,23 +2,27 @@ import { ConsoleLogger, Module } from '@nestjs/common';
|
||||
import { ConfigModule } from '@nestjs/config';
|
||||
import { PassportModule } from '@nestjs/passport';
|
||||
import { ServeStaticModule } from '@nestjs/serve-static';
|
||||
import { TerminusModule } from '@nestjs/terminus';
|
||||
|
||||
import { AuthController } from '../api/auth.controller';
|
||||
import { ChatsController } from '../api/chats.controller';
|
||||
import { ChattingController } from '../api/chatting.controller';
|
||||
import { ContactsController } from '../api/contacts.controller';
|
||||
import { GroupsController } from '../api/groups.controller';
|
||||
import { HealthController } from '../api/health.controller';
|
||||
import { PresenceController } from '../api/presence.controller';
|
||||
import { ScreenshotController } from '../api/screenshot.controller';
|
||||
import {
|
||||
SessionController,
|
||||
SessionsController,
|
||||
} from '../api/sessions.controller';
|
||||
import { StatusController } from '../api/status.controller';
|
||||
import { VersionController } from '../api/version.controller';
|
||||
import { WhatsappConfigService } from '../config.service';
|
||||
import { SessionManager } from './abc/manager.abc';
|
||||
import { WAHAHealthCheckService } from './abc/WAHAHealthCheckService';
|
||||
import { WAHAHealthCheckServiceCore } from './health/WAHAHealthCheckServiceCore';
|
||||
import { SessionManagerCore } from './manager.core';
|
||||
import { AuthController } from '../api/auth.controller';
|
||||
import { ChatsController } from '../api/chats.controller';
|
||||
import { StatusController } from '../api/status.controller';
|
||||
|
||||
export const IMPORTS = [
|
||||
ConfigModule.forRoot({
|
||||
@@ -38,6 +42,7 @@ export const IMPORTS = [
|
||||
},
|
||||
}),
|
||||
PassportModule,
|
||||
TerminusModule,
|
||||
];
|
||||
export const CONTROLLERS = [
|
||||
AuthController,
|
||||
@@ -51,12 +56,17 @@ export const CONTROLLERS = [
|
||||
PresenceController,
|
||||
ScreenshotController,
|
||||
VersionController,
|
||||
HealthController,
|
||||
];
|
||||
const PROVIDERS = [
|
||||
{
|
||||
provide: SessionManager,
|
||||
useClass: SessionManagerCore,
|
||||
},
|
||||
{
|
||||
provide: WAHAHealthCheckService,
|
||||
useClass: WAHAHealthCheckServiceCore,
|
||||
},
|
||||
WhatsappConfigService,
|
||||
ConsoleLogger,
|
||||
];
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import { useMultiFileAuthState } from '@adiwajshing/baileys';
|
||||
import { Store } from 'whatsapp-web.js';
|
||||
|
||||
import { DataStore } from '../../abc/DataStore';
|
||||
import { LocalStore } from '../../storage/LocalStore';
|
||||
|
||||
export class NowebAuthFactoryCore {
|
||||
buildAuth(store: DataStore, name: string) {
|
||||
if (store instanceof LocalStore) return this.buildLocalAuth(store, name);
|
||||
throw new Error(`Unsupported store type '${store.constructor.name}'`);
|
||||
}
|
||||
|
||||
protected async buildLocalAuth(store: LocalStore, name: string) {
|
||||
await store.init(name);
|
||||
const authFolder = store.getSessionDirectory(name);
|
||||
const { state, saveCreds } = await useMultiFileAuthState(authFolder);
|
||||
return { state, saveCreds };
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import makeWASocket, {
|
||||
getKeyAuthor,
|
||||
isJidGroup,
|
||||
jidNormalizedUser,
|
||||
makeCacheableSignalKeyStore,
|
||||
makeInMemoryStore,
|
||||
PresenceData,
|
||||
proto,
|
||||
@@ -13,13 +14,13 @@ import makeWASocket, {
|
||||
WAMessageKey,
|
||||
} from '@adiwajshing/baileys';
|
||||
import { UnprocessableEntityException } from '@nestjs/common';
|
||||
import * as Buffer from 'buffer';
|
||||
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 { flipObject, splitAt } from '../helpers';
|
||||
import { flipObject, splitAt } from '../../../helpers';
|
||||
import {
|
||||
ChatRequest,
|
||||
CheckNumberStatusQuery,
|
||||
@@ -35,8 +36,8 @@ import {
|
||||
MessageVoiceRequest,
|
||||
SendSeenRequest,
|
||||
WANumberExistResult,
|
||||
} from '../structures/chatting.dto';
|
||||
import { ContactQuery, ContactRequest } from '../structures/contacts.dto';
|
||||
} from '../../../structures/chatting.dto';
|
||||
import { ContactQuery, ContactRequest } from '../../../structures/contacts.dto';
|
||||
import {
|
||||
ACK_UNKNOWN,
|
||||
SECOND,
|
||||
@@ -45,34 +46,36 @@ import {
|
||||
WAHAPresenceStatus,
|
||||
WAHASessionStatus,
|
||||
WAMessageAck,
|
||||
} from '../structures/enums.dto';
|
||||
} from '../../../structures/enums.dto';
|
||||
import {
|
||||
CreateGroupRequest,
|
||||
ParticipantsRequest,
|
||||
} from '../structures/groups.dto';
|
||||
} 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';
|
||||
} from '../../../structures/presence.dto';
|
||||
import { WAMessage } from '../../../structures/responses.dto';
|
||||
import { MeInfo } from '../../../structures/sessions.dto';
|
||||
import { BROADCAST_ID, TextStatus } from '../../../structures/status.dto';
|
||||
import {
|
||||
PollVote,
|
||||
PollVotePayload,
|
||||
WAMessageAckBody,
|
||||
} from '../structures/webhooks.dto';
|
||||
} from '../../../structures/webhooks.dto';
|
||||
import { IEngineMediaProcessor } from '../../abc/media.abc';
|
||||
import {
|
||||
ensureSuffix,
|
||||
WAHAInternalEvent,
|
||||
WhatsappSession,
|
||||
} from './abc/session.abc';
|
||||
} from '../../abc/session.abc';
|
||||
import {
|
||||
AvailableInPlusVersion,
|
||||
NotImplementedByEngineError,
|
||||
} from './exceptions';
|
||||
import { createAgentProxy } from './helpers.proxy';
|
||||
import { QR } from './QR';
|
||||
import { MeInfo } from '../structures/sessions.dto';
|
||||
} from '../../exceptions';
|
||||
import { createAgentProxy } from '../../helpers.proxy';
|
||||
import { QR } from '../../QR';
|
||||
import { NowebAuthFactoryCore } from './NowebAuthFactoryCore';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const QRCode = require('qrcode');
|
||||
@@ -100,6 +103,11 @@ const ToEnginePresenceStatus = flipObject(PresenceStatuses);
|
||||
|
||||
export class WhatsappSessionNoWebCore extends WhatsappSession {
|
||||
engine = WAHAEngine.NOWEB;
|
||||
authFactory = new NowebAuthFactoryCore();
|
||||
|
||||
get listenConnectionEventsFromTheStart() {
|
||||
return true;
|
||||
}
|
||||
|
||||
sock: any;
|
||||
store: any;
|
||||
@@ -119,7 +127,11 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
|
||||
return {
|
||||
agent: agent,
|
||||
fetchAgent: agent,
|
||||
auth: state,
|
||||
auth: {
|
||||
creds: state.creds,
|
||||
/** caching makes the store faster to send/recv messages */
|
||||
keys: makeCacheableSignalKeyStore(state.keys, logger),
|
||||
},
|
||||
printQRInTerminal: true,
|
||||
browser: Browsers.macOS('Chrome'),
|
||||
logger: logger,
|
||||
@@ -130,9 +142,10 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
|
||||
}
|
||||
|
||||
async makeSocket() {
|
||||
const authFolder = this.sessionStorage.getFolderPath(this.name);
|
||||
await fs.mkdir(authFolder, { recursive: true });
|
||||
const { state, saveCreds } = await useMultiFileAuthState(authFolder);
|
||||
const { state, saveCreds } = await this.authFactory.buildAuth(
|
||||
this.sessionStore,
|
||||
this.name,
|
||||
);
|
||||
const agent = this.makeAgent();
|
||||
const socketConfig = this.getSocketConfig(agent, state);
|
||||
const sock: any = makeWASocket(socketConfig);
|
||||
@@ -169,8 +182,10 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
|
||||
if (this.isDebugEnabled()) {
|
||||
this.listenEngineEventsInDebugMode();
|
||||
}
|
||||
this.listenConnectionEvents();
|
||||
this.events.emit(WAHAInternalEvent.ENGINE_START);
|
||||
if (this.listenConnectionEventsFromTheStart) {
|
||||
this.listenConnectionEvents();
|
||||
this.events.emit(WAHAInternalEvent.ENGINE_START);
|
||||
}
|
||||
}
|
||||
|
||||
protected async getMessage(
|
||||
@@ -179,7 +194,7 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
|
||||
if (!this.store) {
|
||||
return proto.Message.fromObject({});
|
||||
}
|
||||
const msg = await this.store.loadMessage(key.remoteJid!, key.id!);
|
||||
const msg = await this.store.loadMessage(key.remoteJid, key.id);
|
||||
return msg?.message || undefined;
|
||||
}
|
||||
|
||||
@@ -221,16 +236,16 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
|
||||
if (qr) {
|
||||
this.status = WAHASessionStatus.SCAN_QR_CODE;
|
||||
QRCode.toDataURL(qr).then((url) => {
|
||||
this.qr.save(url);
|
||||
this.qr.save(url, qr);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async stop() {
|
||||
this.sock.ev.removeAllListeners();
|
||||
this.sock.ws.removeAllListeners();
|
||||
this.sock.ws.close();
|
||||
this.sock?.ev?.removeAllListeners();
|
||||
this.sock?.ws?.removeAllListeners();
|
||||
this.sock?.ws?.close();
|
||||
this.status = WAHASessionStatus.STOPPED;
|
||||
return;
|
||||
}
|
||||
@@ -255,13 +270,14 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
|
||||
/**
|
||||
* Auth methods
|
||||
*/
|
||||
public async getQR(): Promise<Buffer> {
|
||||
return Promise.resolve(this.qr.get());
|
||||
public getQR(): QR {
|
||||
return this.qr;
|
||||
}
|
||||
|
||||
public async requestCode(
|
||||
phoneNumber: string,
|
||||
method: string,
|
||||
params?: any,
|
||||
): Promise<PairingCodeResponse> {
|
||||
if (method) {
|
||||
const err = `NOWEB engine doesn't support any 'method', remove it and try again`;
|
||||
@@ -278,7 +294,7 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
|
||||
throw new UnprocessableEntityException(err);
|
||||
}
|
||||
|
||||
this.log.log('Requesting pairing code...');
|
||||
this.log.log(`Requesting pairing code for '${phoneNumber}'...`);
|
||||
const code: string = await this.sock.requestPairingCode(phoneNumber);
|
||||
// show it as ABCD-ABCD
|
||||
const parts = splitAt(code, 4);
|
||||
@@ -287,7 +303,7 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
|
||||
return { code: codeRepr };
|
||||
}
|
||||
|
||||
async getScreenshot(): Promise<Buffer | string> {
|
||||
async getScreenshot(): Promise<Buffer> {
|
||||
if (this.status === WAHASessionStatus.STARTING) {
|
||||
throw new UnprocessableEntityException(
|
||||
`The session is starting, please try again after few seconds`,
|
||||
@@ -311,10 +327,13 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
|
||||
): Promise<WANumberExistResult> {
|
||||
const phone = request.phone.split('@')[0];
|
||||
const [result] = await this.sock.onWhatsApp(phone);
|
||||
if (!result) {
|
||||
if (!result || !result.exists) {
|
||||
return { numberExists: false };
|
||||
}
|
||||
return { numberExists: result.exists };
|
||||
return {
|
||||
numberExists: true,
|
||||
chatId: toCusFormat(result.jid),
|
||||
};
|
||||
}
|
||||
|
||||
sendText(request: MessageTextRequest) {
|
||||
@@ -659,10 +678,10 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
|
||||
body: body,
|
||||
to: toCusFormat(fromToParticipant.to),
|
||||
participant: toCusFormat(fromToParticipant.participant),
|
||||
// @ts-ignore
|
||||
hasMedia: Boolean(message.mediaUrl),
|
||||
// @ts-ignore
|
||||
mediaUrl: message.mediaUrl,
|
||||
// Media
|
||||
hasMedia: Boolean(message.media),
|
||||
media: message.media,
|
||||
mediaUrl: message.media?.url,
|
||||
// @ts-ignore
|
||||
ack: message.ack,
|
||||
// @ts-ignore
|
||||
@@ -811,14 +830,39 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
|
||||
return { id: chatId, presences: presences };
|
||||
}
|
||||
|
||||
protected async downloadMedia(message: Message) {
|
||||
if (!message.hasMedia) {
|
||||
return message;
|
||||
}
|
||||
protected downloadMedia(message) {
|
||||
const processor = new EngineMediaProcessor(this);
|
||||
return this.mediaManager.processMedia(processor, message);
|
||||
}
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
message.mediaUrl = await this.storage.save(message.key.id, '', undefined);
|
||||
return message;
|
||||
export class EngineMediaProcessor implements IEngineMediaProcessor<any> {
|
||||
constructor(public session: WhatsappSessionNoWebCore) {}
|
||||
|
||||
hasMedia(message: any): boolean {
|
||||
const messageType = Object.keys(message.message)[0];
|
||||
const hasMedia =
|
||||
messageType === 'imageMessage' ||
|
||||
messageType == 'audioMessage' ||
|
||||
messageType == 'documentMessage' ||
|
||||
messageType == 'videoMessage';
|
||||
return hasMedia;
|
||||
}
|
||||
|
||||
getMessageId(message: any): string {
|
||||
return '';
|
||||
}
|
||||
|
||||
getMimetype(message: any): string {
|
||||
return '';
|
||||
}
|
||||
|
||||
getMediaBuffer(message: any): Promise<Buffer | null> {
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
getFilename(message: any): string | null {
|
||||
return message.message?.documentMessage?.fileName || null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { UnprocessableEntityException } from '@nestjs/common/exceptions/unprocessable-entity.exception';
|
||||
import * as Buffer from 'buffer';
|
||||
import { create, CreateConfig, Message, Whatsapp } from 'venom-bot';
|
||||
|
||||
import {
|
||||
@@ -14,16 +15,17 @@ import {
|
||||
MessageReplyRequest,
|
||||
MessageTextRequest,
|
||||
WANumberExistResult,
|
||||
} from '../structures/chatting.dto';
|
||||
} from '../../../structures/chatting.dto';
|
||||
import {
|
||||
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 { QR } from './QR';
|
||||
} from '../../../structures/enums.dto';
|
||||
import { WAMessage } from '../../../structures/responses.dto';
|
||||
import { IEngineMediaProcessor } from '../../abc/media.abc';
|
||||
import { WAHAInternalEvent, WhatsappSession } from '../../abc/session.abc';
|
||||
import { NotImplementedByEngineError } from '../../exceptions';
|
||||
import { QR } from '../../QR';
|
||||
|
||||
export class WhatsappSessionVenomCore extends WhatsappSession {
|
||||
engine = WAHAEngine.VENOM;
|
||||
@@ -65,7 +67,7 @@ export class WhatsappSessionVenomCore extends WhatsappSession {
|
||||
|
||||
protected getCatchQR() {
|
||||
return (base64Qrimg, asciiQR, attempts, urlCode) => {
|
||||
this.qr.save(base64Qrimg);
|
||||
this.qr.save(base64Qrimg, urlCode);
|
||||
this.status = WAHASessionStatus.SCAN_QR_CODE;
|
||||
this.log.debug('Number of attempts to read the qrcode: ', attempts);
|
||||
this.log.log('Terminal qrcode:');
|
||||
@@ -124,7 +126,15 @@ export class WhatsappSessionVenomCore extends WhatsappSession {
|
||||
/**
|
||||
* START - Methods for API
|
||||
*/
|
||||
getScreenshot(): Promise<Buffer | string> {
|
||||
|
||||
/**
|
||||
* Auth methods
|
||||
*/
|
||||
public getQR(): QR {
|
||||
return this.qr;
|
||||
}
|
||||
|
||||
getScreenshot(): Promise<Buffer> {
|
||||
if (this.status === WAHASessionStatus.STARTING) {
|
||||
throw new UnprocessableEntityException(
|
||||
`The session is starting, please try again after few seconds`,
|
||||
@@ -246,17 +256,13 @@ export class WhatsappSessionVenomCore extends WhatsappSession {
|
||||
* END - Methods for API
|
||||
*/
|
||||
|
||||
protected async downloadAndDecryptMedia(message: Message) {
|
||||
if (!message.isMMS || !message.isMedia) {
|
||||
return message;
|
||||
}
|
||||
// @ts-ignore
|
||||
message.mediaUrl = await this.storage.save(message.id, '', undefined);
|
||||
return message;
|
||||
protected downloadMedia(message: Message) {
|
||||
const processor = new EngineMediaProcessor(this);
|
||||
return this.mediaManager.processMedia(processor, message);
|
||||
}
|
||||
|
||||
private processIncomingMessage(message: Message) {
|
||||
return this.downloadAndDecryptMedia(message).then(this.toWAMessage);
|
||||
return this.downloadMedia(message).then(this.toWAMessage);
|
||||
}
|
||||
|
||||
protected toWAMessage(message: Message): Promise<WAMessage> {
|
||||
@@ -268,10 +274,13 @@ export class WhatsappSessionVenomCore extends WhatsappSession {
|
||||
fromMe: message.fromMe,
|
||||
to: message.to,
|
||||
body: message.body,
|
||||
// Media
|
||||
// @ts-ignore
|
||||
hasMedia: Boolean(message.mediaUrl),
|
||||
hasMedia: Boolean(message.media),
|
||||
// @ts-ignore
|
||||
mediaUrl: message.mediaUrl,
|
||||
media: message.media,
|
||||
// @ts-ignore
|
||||
mediaUrl: message.media?.url,
|
||||
// @ts-ignore
|
||||
ack: message.ack,
|
||||
location: undefined,
|
||||
@@ -280,3 +289,29 @@ export class WhatsappSessionVenomCore extends WhatsappSession {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class EngineMediaProcessor implements IEngineMediaProcessor<Message> {
|
||||
constructor(public session: WhatsappSessionVenomCore) {}
|
||||
|
||||
hasMedia(message: any): boolean {
|
||||
if (!message.isMMS || !message.isMedia) {
|
||||
return message;
|
||||
}
|
||||
}
|
||||
|
||||
getMessageId(message: any): string {
|
||||
return '';
|
||||
}
|
||||
|
||||
getMimetype(message: any): string {
|
||||
return '';
|
||||
}
|
||||
|
||||
getMediaBuffer(message: any): Promise<Buffer | null> {
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
getFilename(message: Message): string | null {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -24,8 +24,8 @@ import {
|
||||
MessageVoiceRequest,
|
||||
SendSeenRequest,
|
||||
WANumberExistResult,
|
||||
} from '../structures/chatting.dto';
|
||||
import { ContactQuery, ContactRequest } from '../structures/contacts.dto';
|
||||
} from '../../../structures/chatting.dto';
|
||||
import { ContactQuery, ContactRequest } from '../../../structures/contacts.dto';
|
||||
import {
|
||||
ACK_UNKNOWN,
|
||||
WAHAEngine,
|
||||
@@ -33,20 +33,22 @@ import {
|
||||
WAHAPresenceStatus,
|
||||
WAHASessionStatus,
|
||||
WAMessageAck,
|
||||
} from '../structures/enums.dto';
|
||||
} from '../../../structures/enums.dto';
|
||||
import {
|
||||
CreateGroupRequest,
|
||||
ParticipantsRequest,
|
||||
} from '../structures/groups.dto';
|
||||
import { WAMessage } from '../structures/responses.dto';
|
||||
import { WAHAInternalEvent, WhatsappSession } from './abc/session.abc';
|
||||
SettingsSecurityChangeInfo,
|
||||
} from '../../../structures/groups.dto';
|
||||
import { WAMessage } from '../../../structures/responses.dto';
|
||||
import { MeInfo } from '../../../structures/sessions.dto';
|
||||
import { WAMessageRevokedBody } from '../../../structures/webhooks.dto';
|
||||
import { IEngineMediaProcessor } from '../../abc/media.abc';
|
||||
import { WAHAInternalEvent, WhatsappSession } from '../../abc/session.abc';
|
||||
import {
|
||||
AvailableInPlusVersion,
|
||||
NotImplementedByEngineError,
|
||||
} from './exceptions';
|
||||
import { QR } from './QR';
|
||||
import { MeInfo } from '../structures/sessions.dto';
|
||||
import { WAMessageRevokedBody } from '../structures/webhooks.dto';
|
||||
} from '../../exceptions';
|
||||
import { QR } from '../../QR';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const QRCode = require('qrcode');
|
||||
@@ -65,7 +67,7 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
|
||||
this.qr = new QR();
|
||||
}
|
||||
|
||||
protected buildClient() {
|
||||
protected async buildClient() {
|
||||
const clientOptions: ClientOptions = {
|
||||
puppeteer: {
|
||||
headless: true,
|
||||
@@ -96,12 +98,26 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
|
||||
|
||||
async start() {
|
||||
this.status = WAHASessionStatus.STARTING;
|
||||
this.whatsapp = this.buildClient();
|
||||
this.whatsapp.initialize().catch((error) => {
|
||||
this.status = WAHASessionStatus.FAILED;
|
||||
this.log.error(error);
|
||||
return;
|
||||
});
|
||||
this.whatsapp = await this.buildClient();
|
||||
this.whatsapp
|
||||
.initialize()
|
||||
.then(() => {
|
||||
// Listen for browser disconnected event
|
||||
this.whatsapp.pupBrowser.on('disconnected', () => {
|
||||
this.status = WAHASessionStatus.FAILED;
|
||||
this.log.error('The browser has been disconnected');
|
||||
});
|
||||
// Listen for page close event
|
||||
this.whatsapp.pupPage.on('close', () => {
|
||||
this.status = WAHASessionStatus.FAILED;
|
||||
this.log.error('The WhatsApp Web page has been closed');
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
this.status = WAHASessionStatus.FAILED;
|
||||
this.log.error(error);
|
||||
return;
|
||||
});
|
||||
if (this.isDebugEnabled()) {
|
||||
this.listenEngineEventsInDebugMode();
|
||||
}
|
||||
@@ -144,7 +160,7 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
|
||||
this.log.debug('QR received');
|
||||
// Convert to image and save
|
||||
QRCode.toDataURL(qr).then((url) => {
|
||||
this.qr.save(url);
|
||||
this.qr.save(url, qr);
|
||||
});
|
||||
// Print in terminal
|
||||
qrcode.generate(qr, { small: true });
|
||||
@@ -171,32 +187,42 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
|
||||
/**
|
||||
* Auth methods
|
||||
*/
|
||||
public async getQR(): Promise<Buffer> {
|
||||
return Promise.resolve(this.qr.get());
|
||||
public getQR(): QR {
|
||||
return this.qr;
|
||||
}
|
||||
|
||||
async getScreenshot(): Promise<Buffer | string> {
|
||||
async getScreenshot(): Promise<Buffer> {
|
||||
if (this.status === WAHASessionStatus.FAILED) {
|
||||
throw new UnprocessableEntityException(
|
||||
`The session under FAILED status. Please try to restart it.`,
|
||||
);
|
||||
}
|
||||
return await this.whatsapp.pupPage.screenshot();
|
||||
const screenshot = await this.whatsapp.pupPage.screenshot({
|
||||
encoding: 'binary',
|
||||
});
|
||||
return screenshot as Buffer;
|
||||
}
|
||||
|
||||
async checkNumberStatus(
|
||||
request: CheckNumberStatusQuery,
|
||||
): Promise<WANumberExistResult> {
|
||||
const exist = await this.whatsapp.isRegisteredUser(
|
||||
this.ensureSuffix(request.phone),
|
||||
);
|
||||
return { numberExists: exist };
|
||||
const phone = request.phone.split('@')[0];
|
||||
const result = await this.whatsapp.getNumberId(phone);
|
||||
if (!result) {
|
||||
return {
|
||||
numberExists: false,
|
||||
};
|
||||
}
|
||||
return {
|
||||
numberExists: true,
|
||||
chatId: result._serialized,
|
||||
};
|
||||
}
|
||||
|
||||
sendText(request: MessageTextRequest) {
|
||||
const options = {
|
||||
// It's fine to sent just ids instead of Contact object
|
||||
mentions: request.mentions as unknown as Contact[],
|
||||
mentions: request.mentions as unknown as string[],
|
||||
};
|
||||
return this.whatsapp.sendMessage(
|
||||
this.ensureSuffix(request.chatId),
|
||||
@@ -209,7 +235,7 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
|
||||
const options = {
|
||||
quotedMessageId: request.reply_to,
|
||||
// It's fine to sent just ids instead of Contact object
|
||||
mentions: request.mentions as unknown as Contact[],
|
||||
mentions: request.mentions as unknown as string[],
|
||||
};
|
||||
return this.whatsapp.sendMessage(request.chatId, request.text, options);
|
||||
}
|
||||
@@ -348,6 +374,14 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
|
||||
return this.whatsapp.createGroup(request.name, participantIds);
|
||||
}
|
||||
|
||||
public async getInfoAdminsOnly(id): Promise<SettingsSecurityChangeInfo> {
|
||||
const groupChat = (await this.whatsapp.getChatById(id)) as GroupChat;
|
||||
return {
|
||||
// @ts-ignore
|
||||
adminsOnly: groupChat.groupMetadata.restrict,
|
||||
};
|
||||
}
|
||||
|
||||
public async setInfoAdminsOnly(id, value) {
|
||||
const groupChat = (await this.whatsapp.getChatById(id)) as GroupChat;
|
||||
return groupChat.setInfoAdminsOnly(value);
|
||||
@@ -532,10 +566,14 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
|
||||
fromMe: message.fromMe,
|
||||
to: message.to,
|
||||
body: message.body,
|
||||
// Media
|
||||
// @ts-ignore
|
||||
hasMedia: Boolean(message.mediaUrl),
|
||||
hasMedia: Boolean(message.media),
|
||||
// @ts-ignore
|
||||
media: message.media,
|
||||
// @ts-ignore
|
||||
mediaUrl: message.media?.url,
|
||||
// @ts-ignore
|
||||
mediaUrl: message.mediaUrl,
|
||||
ack: message.ack,
|
||||
ackName: WAMessageAck[message.ack] || ACK_UNKNOWN,
|
||||
location: message.location,
|
||||
@@ -550,17 +588,35 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
|
||||
return contact;
|
||||
}
|
||||
|
||||
protected async downloadMedia(message: Message) {
|
||||
protected downloadMedia(message: Message) {
|
||||
const processor = new EngineMediaProcessor();
|
||||
return this.mediaManager.processMedia(processor, message);
|
||||
}
|
||||
}
|
||||
|
||||
export class EngineMediaProcessor implements IEngineMediaProcessor<Message> {
|
||||
hasMedia(message: Message): boolean {
|
||||
if (!message.hasMedia) {
|
||||
return message;
|
||||
return false;
|
||||
}
|
||||
// Can't get media for revoked messages
|
||||
return message.type !== 'revoked';
|
||||
}
|
||||
|
||||
getMessageId(message: Message): string {
|
||||
return '';
|
||||
}
|
||||
|
||||
getMimetype(message: Message): string {
|
||||
return '';
|
||||
}
|
||||
|
||||
getMediaBuffer(message: Message): Promise<Buffer | null> {
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
getFilename(message: Message): string | null {
|
||||
// @ts-ignore
|
||||
message.mediaUrl = await this.storage.save(
|
||||
message.id._serialized,
|
||||
'',
|
||||
undefined,
|
||||
);
|
||||
return message;
|
||||
return message.rawData?.filename || null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { HealthCheckResult } from '@nestjs/terminus';
|
||||
|
||||
import { WAHAHealthCheckService } from '../abc/WAHAHealthCheckService';
|
||||
import { AvailableInPlusVersion } from '../exceptions';
|
||||
|
||||
@Injectable()
|
||||
export class WAHAHealthCheckServiceCore extends WAHAHealthCheckService {
|
||||
check(): Promise<HealthCheckResult> {
|
||||
throw new AvailableInPlusVersion();
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,7 @@ import { WhatsappSession } from './abc/session.abc';
|
||||
export function getProxyConfig(
|
||||
config: WhatsappConfigService,
|
||||
sessions: Record<string, WhatsappSession>,
|
||||
sessionName?: string,
|
||||
sessionName: string,
|
||||
): ProxyConfig | undefined {
|
||||
// Single proxy server configuration
|
||||
if (typeof config.proxyServer === 'string') {
|
||||
@@ -23,7 +23,7 @@ export function getProxyConfig(
|
||||
// Multiple proxy server configuration
|
||||
if (Array.isArray(config.proxyServer)) {
|
||||
const prefix = config.proxyServerIndexPrefix;
|
||||
let indexToUse: number = undefined;
|
||||
let indexToUse: number | undefined = undefined;
|
||||
if (prefix && sessionName.includes(prefix)) {
|
||||
// match numbers at the end of the string
|
||||
const matches = sessionName.match(/\d+$/);
|
||||
@@ -66,8 +66,8 @@ function getAuthenticatedUrl(
|
||||
export function createAgentProxy(proxyConfig: ProxyConfig): Agent | undefined {
|
||||
const url = getAuthenticatedUrl(
|
||||
proxyConfig.server,
|
||||
proxyConfig.username,
|
||||
proxyConfig.password,
|
||||
proxyConfig.username || '',
|
||||
proxyConfig.password || '',
|
||||
);
|
||||
return new HttpsProxyAgent(url);
|
||||
}
|
||||
@@ -18,17 +18,16 @@ import {
|
||||
} from '../structures/sessions.dto';
|
||||
import { WebhookConfig } from '../structures/webhooks.config.dto';
|
||||
import { SessionManager } from './abc/manager.abc';
|
||||
import {
|
||||
SessionParams,
|
||||
WAHAInternalEvent,
|
||||
WhatsappSession,
|
||||
} from './abc/session.abc';
|
||||
import { SessionParams, WhatsappSession } from './abc/session.abc';
|
||||
import { WhatsappSessionNoWebCore } from './engines/noweb/session.noweb.core';
|
||||
import { WhatsappSessionVenomCore } from './engines/venom/session.venom.core';
|
||||
import { WhatsappSessionWebJSCore } from './engines/webjs/session.webjs.core';
|
||||
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 { MediaStorageCore, SessionStorageCore } from './storage.core';
|
||||
import { CoreMediaManager, MediaStorageCore } from './media.core';
|
||||
import { LocalSessionAuthRepository } from './storage/LocalSessionAuthRepository';
|
||||
import { LocalSessionConfigRepository } from './storage/LocalSessionConfigRepository';
|
||||
import { LocalStoreCore } from './storage/LocalStoreCore';
|
||||
import { WebhookConductorCore } from './webhooks.core';
|
||||
|
||||
export class OnlyDefaultSessionIsAllowed extends UnprocessableEntityException {
|
||||
@@ -45,11 +44,9 @@ export function buildLogger(name) {
|
||||
|
||||
@Injectable()
|
||||
export class SessionManagerCore extends SessionManager {
|
||||
private session: WhatsappSession;
|
||||
private session?: WhatsappSession;
|
||||
DEFAULT = 'default';
|
||||
|
||||
// @ts-ignore
|
||||
protected MediaStorageClass = MediaStorageCore;
|
||||
// @ts-ignore
|
||||
protected WebhookConductorClass = WebhookConductorCore;
|
||||
protected readonly EngineClass: typeof WhatsappSession;
|
||||
@@ -64,8 +61,9 @@ export class SessionManagerCore extends SessionManager {
|
||||
this.session = undefined;
|
||||
const engineName = this.config.getDefaultEngineName();
|
||||
this.EngineClass = this.getEngine(engineName);
|
||||
this.sessionStorage = new SessionStorageCore(engineName.toLowerCase());
|
||||
|
||||
this.store = new LocalStoreCore(engineName.toLowerCase());
|
||||
this.sessionAuthRepository = new LocalSessionAuthRepository(this.store);
|
||||
this.sessionConfigRepository = new LocalSessionConfigRepository(this.store);
|
||||
this.startPredefinedSessions();
|
||||
}
|
||||
|
||||
@@ -110,19 +108,22 @@ export class SessionManagerCore extends SessionManager {
|
||||
const name = request.name;
|
||||
this.log.log(`'${name}' - starting session...`);
|
||||
const log = buildLogger(`WhatsappSession - ${name}`);
|
||||
const storage = new this.MediaStorageClass();
|
||||
const mediaManager = new CoreMediaManager(
|
||||
new MediaStorageCore(),
|
||||
this.config.mimetypes,
|
||||
);
|
||||
const webhookLog = buildLogger(`Webhook - ${name}`);
|
||||
const webhook = new this.WebhookConductorClass(webhookLog);
|
||||
const proxyConfig = this.getProxyConfig(request);
|
||||
const sessionConfig: SessionParams = {
|
||||
name,
|
||||
storage,
|
||||
mediaManager,
|
||||
log,
|
||||
sessionStorage: this.sessionStorage,
|
||||
sessionStore: this.store,
|
||||
proxyConfig: proxyConfig,
|
||||
sessionConfig: request.config,
|
||||
};
|
||||
await this.sessionStorage.init(name);
|
||||
await this.sessionAuthRepository.init(name);
|
||||
// @ts-ignore
|
||||
const session = new this.EngineClass(sessionConfig);
|
||||
this.session = session;
|
||||
@@ -149,7 +150,9 @@ export class SessionManagerCore extends SessionManager {
|
||||
webhooks = webhooks.concat(request.config.webhooks);
|
||||
}
|
||||
const globalWebhookConfig = this.config.getWebhookConfig();
|
||||
webhooks.push(globalWebhookConfig);
|
||||
if (globalWebhookConfig) {
|
||||
webhooks.push(globalWebhookConfig);
|
||||
}
|
||||
return webhooks;
|
||||
}
|
||||
|
||||
@@ -162,6 +165,9 @@ export class SessionManagerCore extends SessionManager {
|
||||
if (request.config?.proxy) {
|
||||
return request.config.proxy;
|
||||
}
|
||||
if (!this.session) {
|
||||
return undefined;
|
||||
}
|
||||
const sessions = { [request.name]: this.session };
|
||||
return getProxyConfig(this.config, sessions, request.name);
|
||||
}
|
||||
@@ -178,19 +184,16 @@ export class SessionManagerCore extends SessionManager {
|
||||
}
|
||||
|
||||
async logout(request: SessionLogoutRequest) {
|
||||
await this.sessionStorage.clean(request.name);
|
||||
await this.sessionAuthRepository.clean(request.name);
|
||||
}
|
||||
|
||||
getSession(name: string, error = true): WhatsappSession {
|
||||
getSession(name: string): WhatsappSession {
|
||||
this.onlyDefault(name);
|
||||
const session = this.session;
|
||||
if (!session) {
|
||||
if (error) {
|
||||
throw new NotFoundException(
|
||||
`We didn't find a session with name '${name}'. Please start it first by using POST /sessions/start request`,
|
||||
);
|
||||
}
|
||||
return;
|
||||
throw new NotFoundException(
|
||||
`We didn't find a session with name '${name}'. Please start it first by using POST /sessions/start request`,
|
||||
);
|
||||
}
|
||||
return session;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
import { WAMedia } from '../structures/responses.dto';
|
||||
import {
|
||||
IEngineMediaProcessor,
|
||||
MediaManager,
|
||||
MediaStorage,
|
||||
} from './abc/media.abc';
|
||||
import { DOCS_URL } from './exceptions';
|
||||
|
||||
export class MediaStorageCore implements MediaStorage {
|
||||
async save(
|
||||
messageId: string,
|
||||
mimetype: string,
|
||||
buffer: Buffer,
|
||||
): Promise<string> {
|
||||
return Promise.resolve(
|
||||
`Media attachment's available only in WAHA Plus version. ${DOCS_URL}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class CoreMediaManager implements MediaManager {
|
||||
constructor(
|
||||
private storage: MediaStorage,
|
||||
private mimetypes: string[],
|
||||
) {}
|
||||
|
||||
async processMedia<Message>(
|
||||
processor: IEngineMediaProcessor<Message>,
|
||||
message: Message,
|
||||
): Promise<Message> {
|
||||
if (!processor.hasMedia(message)) {
|
||||
return message;
|
||||
}
|
||||
const mimetype = '';
|
||||
const filename = processor.getFilename(message);
|
||||
const url = await this.storage.save('', mimetype, Buffer.from(''));
|
||||
const media: WAMedia = {
|
||||
mimetype: mimetype,
|
||||
filename: filename,
|
||||
url: url,
|
||||
};
|
||||
|
||||
// @ts-ignore
|
||||
message.media = media;
|
||||
return message;
|
||||
}
|
||||
}
|
||||
@@ -1,111 +0,0 @@
|
||||
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(
|
||||
messageId: string,
|
||||
mimetype: string,
|
||||
buffer: Buffer,
|
||||
): Promise<string> {
|
||||
return Promise.resolve(
|
||||
`Media attachment's available only in WAHA Plus version. ${DOCS_URL}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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-sessions');
|
||||
}
|
||||
|
||||
get engineFolder() {
|
||||
return path.join(this.sessionsFolder, this.engine);
|
||||
}
|
||||
|
||||
async init(sessionName?: string) {
|
||||
const folder = sessionName
|
||||
? this.getFolderPath(sessionName)
|
||||
: this.engineFolder;
|
||||
await fs.mkdir(folder, { recursive: true });
|
||||
}
|
||||
|
||||
getFolderPath(name: string): string {
|
||||
const suffix = crypto.createHash('md5').update(name).digest('hex');
|
||||
return path.join(this.engineFolder, `${name}-${suffix}`);
|
||||
}
|
||||
|
||||
async clean(sessionName: string) {
|
||||
const folder = this.getFolderPath(sessionName);
|
||||
await fs.rm(folder, { recursive: 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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
export abstract class ISessionAuthRepository {
|
||||
abstract init(sessionName?: string): Promise<void>;
|
||||
|
||||
abstract clean(sessionName: string): Promise<void>;
|
||||
|
||||
abstract getAll(): Promise<string[]>;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { SessionConfig } from '../../structures/sessions.dto';
|
||||
|
||||
export abstract class ISessionConfigRepository {
|
||||
abstract save(
|
||||
sessionName: string,
|
||||
config: SessionConfig | null,
|
||||
): Promise<void>;
|
||||
|
||||
abstract get(sessionName: string): Promise<SessionConfig | null>;
|
||||
|
||||
abstract delete(sessionName: string): Promise<void>;
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import * as fs from 'fs/promises';
|
||||
|
||||
import { ISessionAuthRepository } from './ISessionAuthRepository';
|
||||
import { LocalStore } from './LocalStore';
|
||||
|
||||
export class LocalSessionAuthRepository extends ISessionAuthRepository {
|
||||
private store: LocalStore;
|
||||
|
||||
constructor(store: LocalStore) {
|
||||
super();
|
||||
this.store = store;
|
||||
}
|
||||
|
||||
async init(sessionName?: string) {
|
||||
await this.store.init(sessionName);
|
||||
}
|
||||
|
||||
async clean(sessionName: string) {
|
||||
const folder = this.store.getSessionDirectory(sessionName);
|
||||
await fs.rm(folder, { recursive: true });
|
||||
}
|
||||
|
||||
async getAll(): Promise<string[]> {
|
||||
await this.init();
|
||||
const content = await fs.readdir(this.store.getEngineDirectory(), {
|
||||
withFileTypes: true,
|
||||
});
|
||||
return content
|
||||
.filter((dirent) => dirent.isDirectory())
|
||||
.map((dirent) => dirent.name);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
import * as fs from 'fs/promises';
|
||||
import * as path from 'path';
|
||||
|
||||
import { SessionConfig } from '../../structures/sessions.dto';
|
||||
import { ISessionConfigRepository } from './ISessionConfigRepository';
|
||||
import { LocalStore } from './LocalStore';
|
||||
|
||||
export class LocalSessionConfigRepository extends ISessionConfigRepository {
|
||||
FILENAME = '.waha.session.config.json';
|
||||
private store: LocalStore;
|
||||
|
||||
constructor(store: LocalStore) {
|
||||
super();
|
||||
this.store = store;
|
||||
}
|
||||
|
||||
private async fileExists(filepath: string) {
|
||||
try {
|
||||
await fs.access(filepath, fs.constants.F_OK);
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
async get(sessionName: string): Promise<SessionConfig | null> {
|
||||
const filepath = this.getFilePath(sessionName);
|
||||
// Check file exists
|
||||
if (!(await this.fileExists(filepath))) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Try to load config
|
||||
let content;
|
||||
try {
|
||||
content = await fs.readFile(filepath, 'utf-8');
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return JSON.parse(content);
|
||||
}
|
||||
|
||||
async save(sessionName: string, config: SessionConfig | null) {
|
||||
// Create folder if not exist
|
||||
const folder = this.store.getSessionDirectory(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.store.getSessionDirectory(sessionName);
|
||||
return path.join(folder, this.FILENAME);
|
||||
}
|
||||
|
||||
async delete(sessionName: string): Promise<void> {
|
||||
const filepath = this.getFilePath(sessionName);
|
||||
if (!(await this.fileExists(filepath))) {
|
||||
return;
|
||||
}
|
||||
await fs.unlink(filepath);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import { DataStore } from '../abc/DataStore';
|
||||
|
||||
export abstract class LocalStore extends DataStore {
|
||||
abstract init(sessionName?: string): Promise<void>;
|
||||
|
||||
/**
|
||||
* Get the directory where all the engines and sessions are stored
|
||||
*/
|
||||
abstract getBaseDirectory(): string;
|
||||
|
||||
/**
|
||||
* Get the directory where the engine sessions are stored
|
||||
*/
|
||||
abstract getEngineDirectory(): string;
|
||||
|
||||
/**
|
||||
* Get the directory where the session data is stored
|
||||
*/
|
||||
abstract getSessionDirectory(name: string): string;
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
import * as crypto from 'crypto';
|
||||
import * as fs from 'fs/promises';
|
||||
import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
|
||||
import { LocalStore } from './LocalStore';
|
||||
|
||||
export class LocalStoreCore extends LocalStore {
|
||||
protected readonly baseDirectory: string = path.join(
|
||||
os.tmpdir(),
|
||||
'waha-sessions',
|
||||
);
|
||||
|
||||
private readonly engine: string;
|
||||
|
||||
constructor(engine: string) {
|
||||
super();
|
||||
this.engine = engine;
|
||||
}
|
||||
|
||||
async init(sessionName?: string) {
|
||||
await fs.mkdir(this.getEngineDirectory(), { recursive: true });
|
||||
if (sessionName) {
|
||||
await fs.mkdir(this.getSessionDirectory(sessionName), {
|
||||
recursive: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the directory where all the engines and sessions are stored
|
||||
*/
|
||||
getBaseDirectory() {
|
||||
return path.join(this.baseDirectory);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the directory where the engine sessions are stored
|
||||
*/
|
||||
getEngineDirectory() {
|
||||
return path.join(this.baseDirectory, this.engine);
|
||||
}
|
||||
|
||||
getSessionDirectory(name: string): string {
|
||||
return this.getDirectoryPath(name);
|
||||
}
|
||||
|
||||
protected getDirectoryPath(name: string): string {
|
||||
const suffix = crypto.createHash('md5').update(name).digest('hex');
|
||||
return path.join(this.getEngineDirectory(), `${name}-${suffix}`);
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,12 @@
|
||||
import { INestApplication } from '@nestjs/common';
|
||||
import { DocumentBuilder, OpenAPIObject, SwaggerModule } from '@nestjs/swagger';
|
||||
import { DECORATORS } from '@nestjs/swagger/dist/constants';
|
||||
|
||||
import { WhatsappConfigService } from '../config.service';
|
||||
import { VERSION } from '../version';
|
||||
import { DECORATORS } from '@nestjs/swagger/dist/constants';
|
||||
|
||||
export class SwaggerModuleCore {
|
||||
configure(app: INestApplication, webhooks: any[]) {
|
||||
this.setUpAuth(app);
|
||||
const builder = new DocumentBuilder();
|
||||
|
||||
builder
|
||||
@@ -130,8 +129,4 @@ export class SwaggerModuleCore {
|
||||
document.webhooks = webhooks;
|
||||
return document;
|
||||
}
|
||||
|
||||
protected setUpAuth(app: INestApplication) {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
@@ -5,10 +5,9 @@ import { AllExceptionsFilter } from './api/exception.filter';
|
||||
import { WhatsappConfigService } from './config.service';
|
||||
import { AppModuleCore } from './core/app.module.core';
|
||||
import { SwaggerModuleCore } from './core/swagger.module.core';
|
||||
import { getWAHAVersion, VERSION, WAHAVersion } from './version';
|
||||
import { WAHA_WEBHOOKS } from './structures/webhooks.dto';
|
||||
import { LogLevel } from '@nestjs/common';
|
||||
import { getLogLevels } from './helpers';
|
||||
import { WAHA_WEBHOOKS } from './structures/webhooks.dto';
|
||||
import { getWAHAVersion, VERSION, WAHAVersion } from './version';
|
||||
|
||||
async function loadModules(): Promise<
|
||||
[typeof AppModuleCore, typeof SwaggerModuleCore]
|
||||
|
||||
@@ -1,4 +1,16 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { ApiHideProperty, ApiProperty } from '@nestjs/swagger';
|
||||
|
||||
export enum QRCodeFormat {
|
||||
IMAGE = 'image',
|
||||
RAW = 'raw',
|
||||
}
|
||||
export class QRCodeQuery {
|
||||
format: QRCodeFormat = QRCodeFormat.IMAGE;
|
||||
}
|
||||
|
||||
export class QRCodeValue {
|
||||
value: string;
|
||||
}
|
||||
|
||||
export class RequestCodeRequest {
|
||||
@ApiProperty({
|
||||
@@ -6,6 +18,7 @@ export class RequestCodeRequest {
|
||||
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.',
|
||||
@@ -13,12 +26,22 @@ export class RequestCodeRequest {
|
||||
required: false,
|
||||
})
|
||||
method: string;
|
||||
|
||||
@ApiHideProperty()
|
||||
localeLanguage: string;
|
||||
|
||||
@ApiHideProperty()
|
||||
localeCountry: string;
|
||||
}
|
||||
|
||||
export class OTPRequest {
|
||||
code: string;
|
||||
}
|
||||
|
||||
export class CaptchaBody {
|
||||
code: string;
|
||||
}
|
||||
|
||||
export class PairingCodeResponse {
|
||||
code: string;
|
||||
}
|
||||
@@ -1,4 +1,9 @@
|
||||
import { ApiExtraModels, ApiProperty, getSchemaPath } from '@nestjs/swagger';
|
||||
import {
|
||||
ApiExtraModels,
|
||||
ApiHideProperty,
|
||||
ApiProperty,
|
||||
getSchemaPath,
|
||||
} from '@nestjs/swagger';
|
||||
import { IsNumber, IsString } from 'class-validator';
|
||||
|
||||
import { SessionBaseRequest, SessionQuery } from './base.dto';
|
||||
@@ -23,6 +28,7 @@ export class CheckNumberStatusQuery extends SessionQuery {
|
||||
export class MessageTextQuery extends SessionQuery {
|
||||
@IsString()
|
||||
phone: string;
|
||||
|
||||
@IsString()
|
||||
text: string;
|
||||
}
|
||||
@@ -35,6 +41,7 @@ export class ChatQuery extends SessionQuery {
|
||||
export class GetMessageQuery extends ChatQuery {
|
||||
@IsNumber()
|
||||
limit: number;
|
||||
|
||||
@ApiProperty({
|
||||
example: true,
|
||||
required: false,
|
||||
@@ -85,12 +92,7 @@ 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'],
|
||||
})
|
||||
@ApiHideProperty()
|
||||
mentions?: string[];
|
||||
}
|
||||
|
||||
@@ -147,6 +149,7 @@ export class MessageVideoRequest extends ChatRequest {
|
||||
],
|
||||
})
|
||||
file: VideoRemoteFile | VideoBinaryFile;
|
||||
|
||||
caption: string = 'Just watch at this!';
|
||||
}
|
||||
|
||||
@@ -166,6 +169,11 @@ export class MessageReactionRequest extends MessageRequest {
|
||||
|
||||
export class WANumberExistResult {
|
||||
numberExists: boolean;
|
||||
@ApiProperty({
|
||||
example:
|
||||
'Chat id for the phone number. Undefined if the number does not exist',
|
||||
})
|
||||
chatId?: string;
|
||||
}
|
||||
|
||||
export class MessagePoll {
|
||||
@@ -192,6 +200,7 @@ export class MessageDestination {
|
||||
example: 'false_11111111111@c.us_AAAAAAAAAAAAAAAAAAAA',
|
||||
})
|
||||
id: string;
|
||||
|
||||
to: string;
|
||||
from: string;
|
||||
fromMe: boolean;
|
||||
|
||||
@@ -26,7 +26,7 @@ export enum WAHAEngine {
|
||||
VENOM = 'VENOM',
|
||||
WEBJS = 'WEBJS',
|
||||
NOWEB = 'NOWEB',
|
||||
NOWEB_MOBILE = 'NOWEB_MOBILE',
|
||||
MOBILE = 'MOBILE',
|
||||
}
|
||||
|
||||
export enum WAHAPresenceStatus {
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
export class Base64File {
|
||||
mimetype: string;
|
||||
data: string;
|
||||
}
|
||||
|
||||
export class RemoteFile {
|
||||
@ApiProperty({
|
||||
@@ -11,7 +15,7 @@ export class RemoteFile {
|
||||
description: 'Document file name. Value can be null',
|
||||
example: 'filename.jpg',
|
||||
})
|
||||
filename?: string | null;
|
||||
filename?: string;
|
||||
|
||||
@ApiProperty({
|
||||
example:
|
||||
@@ -31,7 +35,7 @@ export class BinaryFile {
|
||||
description: 'Document file name. Optional',
|
||||
example: 'filename.jpeg',
|
||||
})
|
||||
filename?: string | null;
|
||||
filename?: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'Base64-encoded data of the file',
|
||||
|
||||
@@ -8,6 +8,10 @@ export class Participant {
|
||||
id: string;
|
||||
}
|
||||
|
||||
export class SettingsSecurityChangeInfo {
|
||||
adminsOnly: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Queries
|
||||
*/
|
||||
|
||||
@@ -8,6 +8,7 @@ export class WAHAPresenceData {
|
||||
example: '11111111111@c.us',
|
||||
})
|
||||
participant: string;
|
||||
|
||||
lastKnownPresence: WAHAPresenceStatus;
|
||||
@ApiProperty({
|
||||
example: 1686568773,
|
||||
@@ -20,6 +21,7 @@ export class WAHAChatPresences {
|
||||
example: '11111111111@c.us',
|
||||
})
|
||||
id: string;
|
||||
|
||||
presences: WAHAPresenceData[];
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { WAHAEvents } from './enums.dto';
|
||||
import { ApiProperty, ApiPropertyOptions } from '@nestjs/swagger';
|
||||
|
||||
function ChatIdProperty(options = undefined) {
|
||||
function ChatIdProperty(options: ApiPropertyOptions | null = null) {
|
||||
options = options || {};
|
||||
if (!options.example) {
|
||||
options.example = '11111111111@c.us';
|
||||
|
||||
@@ -4,11 +4,32 @@ import { WAMessageAck } from './enums.dto';
|
||||
import { ChatIdProperty, MessageIdProperty } from './properties.dto';
|
||||
|
||||
export class WALocation {
|
||||
description?: string | null;
|
||||
description?: string;
|
||||
latitude: string;
|
||||
longitude: string;
|
||||
}
|
||||
|
||||
export class WAMedia {
|
||||
@ApiProperty({
|
||||
description: 'The URL for the media in the message if any',
|
||||
example:
|
||||
'http://localhost:3000/api/files/false_11111111111@c.us_AAAAAAAAAAAAAAAAAAAA.oga',
|
||||
})
|
||||
url: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'mimetype for the media in the message if any',
|
||||
example: 'audio/jpeg',
|
||||
})
|
||||
mimetype?: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'The original filename in mediaUrl in the message if any',
|
||||
example: 'example.pdf',
|
||||
})
|
||||
filename?: string;
|
||||
}
|
||||
|
||||
export class WAMessage {
|
||||
@MessageIdProperty()
|
||||
id: string;
|
||||
@@ -55,8 +76,12 @@ export class WAMessage {
|
||||
})
|
||||
hasMedia: boolean;
|
||||
|
||||
media?: WAMedia = null;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'The URL for the media in the message if any',
|
||||
description:
|
||||
'Use `media.url` instead! The URL for the media in the message if any',
|
||||
deprecated: true,
|
||||
example:
|
||||
'http://localhost:3000/api/files/false_11111111111@c.us_AAAAAAAAAAAAAAAAAAAA.oga',
|
||||
})
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
|
||||
import { WAHASessionStatus } from './enums.dto';
|
||||
import { WebhookConfig } from './webhooks.config.dto';
|
||||
import { ChatIdProperty } from './properties.dto';
|
||||
import { WebhookConfig } from './webhooks.config.dto';
|
||||
|
||||
/**
|
||||
* Queries
|
||||
@@ -25,10 +25,12 @@ export class ProxyConfig {
|
||||
example: 'localhost:3128',
|
||||
})
|
||||
server: string;
|
||||
|
||||
@ApiProperty({
|
||||
example: null,
|
||||
})
|
||||
username?: string;
|
||||
|
||||
@ApiProperty({
|
||||
example: null,
|
||||
})
|
||||
@@ -65,15 +67,16 @@ export class SessionLogoutRequest {
|
||||
export class SessionDTO {
|
||||
name = 'default';
|
||||
status: WAHASessionStatus;
|
||||
config: SessionConfig;
|
||||
config?: SessionConfig;
|
||||
}
|
||||
|
||||
export class MeInfo {
|
||||
@ChatIdProperty()
|
||||
id: string;
|
||||
|
||||
pushName: string;
|
||||
}
|
||||
|
||||
export class SessionInfo extends SessionDTO {
|
||||
me: MeInfo | null;
|
||||
me?: MeInfo;
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import { ApiExtraModels, ApiProperty, getSchemaPath } from '@nestjs/swagger';
|
||||
|
||||
import {
|
||||
BinaryFile,
|
||||
RemoteFile,
|
||||
@@ -32,6 +33,7 @@ export class ImageStatus extends StatusRequest {
|
||||
],
|
||||
})
|
||||
file: BinaryFile | RemoteFile;
|
||||
|
||||
caption: string;
|
||||
}
|
||||
|
||||
@@ -44,6 +46,7 @@ export class VoiceStatus extends StatusRequest {
|
||||
],
|
||||
})
|
||||
file: VoiceBinaryFile | VoiceRemoteFile;
|
||||
|
||||
backgroundColor = '#38b42f';
|
||||
}
|
||||
|
||||
@@ -56,5 +59,6 @@ export class VideoStatus extends StatusRequest {
|
||||
],
|
||||
})
|
||||
file: BinaryFile | RemoteFile;
|
||||
|
||||
caption: string;
|
||||
}
|
||||
@@ -1,17 +1,17 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
|
||||
import { MessageDestination } from './chatting.dto';
|
||||
import {
|
||||
WAHAEngine,
|
||||
WAHAEvents,
|
||||
WAHASessionStatus,
|
||||
WAMessageAck,
|
||||
} from './enums.dto';
|
||||
import { WAMessage } from './responses.dto';
|
||||
import { MessageDestination } from './chatting.dto';
|
||||
import { ChatIdProperty, MessageIdProperty } from './properties.dto';
|
||||
import { WAHAChatPresences } from './presence.dto';
|
||||
import { MeInfo } from './sessions.dto';
|
||||
import { WAHAEnvironment } from './environment.dto';
|
||||
import { WAHAChatPresences } from './presence.dto';
|
||||
import { ChatIdProperty, MessageIdProperty } from './properties.dto';
|
||||
import { WAMessage } from './responses.dto';
|
||||
import { MeInfo } from './sessions.dto';
|
||||
|
||||
export class WAMessageAckBody {
|
||||
@MessageIdProperty()
|
||||
@@ -84,14 +84,15 @@ export class PollVotePayload {
|
||||
}
|
||||
|
||||
export class WAMessageRevokedBody {
|
||||
after: WAMessage | null;
|
||||
before: WAMessage | null;
|
||||
after?: WAMessage;
|
||||
before?: WAMessage;
|
||||
}
|
||||
export class WASessionStatusBody {
|
||||
@ApiProperty({
|
||||
example: 'default',
|
||||
})
|
||||
name: string;
|
||||
|
||||
status: WAHASessionStatus;
|
||||
}
|
||||
|
||||
@@ -106,7 +107,7 @@ export class WAHAWebhook {
|
||||
})
|
||||
engine: WAHAEngine;
|
||||
|
||||
me: MeInfo | null;
|
||||
me?: MeInfo;
|
||||
|
||||
environment: WAHAEnvironment;
|
||||
|
||||
@@ -122,12 +123,14 @@ class WAHAWebhookSessionStatus extends WAHAWebhook {
|
||||
description: 'The event is triggered when the session status changes.',
|
||||
})
|
||||
event = WAHAEvents.SESSION_STATUS;
|
||||
|
||||
payload: WASessionStatusBody;
|
||||
}
|
||||
|
||||
class WAHAWebhookMessage extends WAHAWebhook {
|
||||
@ApiProperty({ description: 'Incoming message.' })
|
||||
event = WAHAEvents.MESSAGE;
|
||||
|
||||
payload: WAMessage;
|
||||
}
|
||||
|
||||
@@ -136,6 +139,7 @@ class WAHAWebhookMessageAny extends WAHAWebhook {
|
||||
description: 'Fired on all message creations, including your own.',
|
||||
})
|
||||
event = WAHAEvents.MESSAGE_ANY;
|
||||
|
||||
payload: WAMessage;
|
||||
}
|
||||
|
||||
@@ -145,6 +149,7 @@ class WAHAWebhookMessageAck extends WAHAWebhook {
|
||||
'Receive events when server or recipient gets the message, read or played it.',
|
||||
})
|
||||
event = WAHAEvents.MESSAGE_ACK;
|
||||
|
||||
payload: WAMessageAckBody;
|
||||
}
|
||||
|
||||
@@ -155,6 +160,7 @@ class WAHAWebhookMessageRevoked extends WAHAWebhook {
|
||||
'revokes a previously sent message.',
|
||||
})
|
||||
event = WAHAEvents.MESSAGE_REVOKED;
|
||||
|
||||
payload: WAMessageRevokedBody;
|
||||
}
|
||||
|
||||
@@ -163,6 +169,7 @@ class WAHAWebhookStateChange extends WAHAWebhook {
|
||||
description: 'It’s an internal engine’s state, not session status.',
|
||||
})
|
||||
event = WAHAEvents.STATE_CHANGE;
|
||||
|
||||
payload: any;
|
||||
}
|
||||
|
||||
@@ -171,6 +178,7 @@ class WAHAWebhookGroupJoin extends WAHAWebhook {
|
||||
description: 'Some one join a group.',
|
||||
})
|
||||
event = WAHAEvents.GROUP_JOIN;
|
||||
|
||||
payload: any;
|
||||
}
|
||||
|
||||
@@ -179,6 +187,7 @@ class WAHAWebhookGroupLeave extends WAHAWebhook {
|
||||
description: 'Some one left a group.',
|
||||
})
|
||||
event = WAHAEvents.GROUP_LEAVE;
|
||||
|
||||
payload: any;
|
||||
}
|
||||
|
||||
@@ -187,6 +196,7 @@ class WAHAWebhookPresenceUpdate extends WAHAWebhook {
|
||||
description: 'The most recent presence information for a chat.',
|
||||
})
|
||||
event = WAHAEvents.PRESENCE_UPDATE;
|
||||
|
||||
payload: WAHAChatPresences;
|
||||
}
|
||||
|
||||
@@ -195,6 +205,7 @@ class WAHAWebhookPollVote extends WAHAWebhook {
|
||||
description: 'With this event, you receive new votes for the poll sent.',
|
||||
})
|
||||
event = WAHAEvents.POLL_VOTE;
|
||||
|
||||
payload: PollVotePayload;
|
||||
}
|
||||
|
||||
@@ -205,6 +216,7 @@ class WAHAWebhookPollVoteFailed extends WAHAWebhook {
|
||||
'Read more about how to handle such events: https://waha.devlike.pro/docs/how-to/polls/#pollvotefailed',
|
||||
})
|
||||
event = WAHAEvents.POLL_VOTE_FAILED;
|
||||
|
||||
payload: PollVotePayload;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* An errors which gets raised when the timeout
|
||||
* exceeded
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
export class TimeoutError extends Error {}
|
||||
|
||||
/**
|
||||
* Executes a promise in the given timeout. If the promise
|
||||
* does not finish in the given timeout, it will
|
||||
* raise a TimeoutError
|
||||
*
|
||||
* @param {number} ms The timeout in milliseconds
|
||||
* @param {Promise<any>} promise The promise which should get executed
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
export const promiseTimeout = function (
|
||||
ms: number,
|
||||
promise: Promise<any>,
|
||||
): Promise<any> {
|
||||
let timer: NodeJS.Timeout;
|
||||
return Promise.race([
|
||||
promise,
|
||||
new Promise(
|
||||
(_, reject) =>
|
||||
(timer = setTimeout(
|
||||
() => reject(new TimeoutError(`Timed out in ${ms}ms.`)),
|
||||
ms,
|
||||
)),
|
||||
),
|
||||
]).finally(() => clearTimeout(timer));
|
||||
};
|
||||
@@ -33,7 +33,7 @@ export function getEngineName(): string {
|
||||
}
|
||||
|
||||
export const VERSION: WAHAEnvironment = {
|
||||
version: '2023.11.1',
|
||||
version: '2024.3.1',
|
||||
engine: getEngineName(),
|
||||
tier: getWAHAVersion(),
|
||||
browser: getBrowserExecutablePath(),
|
||||
|
||||
@@ -1,9 +1,4 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"test",
|
||||
"dist",
|
||||
"**/*spec.ts"
|
||||
]
|
||||
"exclude": ["node_modules", "test", "dist", "**/*spec.ts"]
|
||||
}
|
||||
@@ -12,6 +12,6 @@
|
||||
"baseUrl": "./",
|
||||
"incremental": true,
|
||||
"resolveJsonModule": true,
|
||||
"skipLibCheck": true,
|
||||
"skipLibCheck": true
|
||||
}
|
||||
}
|
||||