Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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
+25
-22
@@ -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
|
||||
|
||||
+16
-4
@@ -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:
|
||||
|
||||
+11
-2
@@ -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
|
||||
}
|
||||
+3
-1
@@ -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/)
|
||||
+7
-2
@@ -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
|
||||
|
||||
|
||||
+3
-3
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -42,6 +42,11 @@ The following environment variables can be used to configure the WAHA.
|
||||
### 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
|
||||
@@ -72,14 +77,34 @@ Keep in mind that session's proxy configuration takes precedence over proxy conf
|
||||
|
||||
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`.
|
||||
- `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`.
|
||||
|
||||
💡 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
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
#### Debug Mode
|
||||
@@ -138,6 +163,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` | ✔️ | ✔️ | ➖ |
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -79,15 +79,18 @@ What is the difference between **WAHA Plus** and **other SaaS solutions** for Wh
|
||||
|
||||
The more you support the project - the more you get!
|
||||
|
||||
### Tiers
|
||||
|
||||
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** |
|
||||
| | Plus | Advanced | Pro |
|
||||
|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------|:--------:|:-------:|
|
||||
| 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** | **$19** | **$39** | **$99** |
|
||||
|
||||
For **$19** donation you get:
|
||||
1. **Secret password** for Docker Hub to download Plus image: `devlikeapro/whatsapp-http-api-plus`.
|
||||
@@ -127,8 +130,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
|
||||
|
||||
@@ -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" >}}).
|
||||
|
||||
@@ -236,7 +311,7 @@ to the container. WAHA stores authentication information in the directory and re
|
||||
The full command would be:
|
||||
|
||||
```bash
|
||||
docker run --rm -d -v `pwd`/.sessions:/app/.sessions -p 3000:3000/tcp --name whatsapp-http-api devlikeapro/whatsapp-http-api-plus
|
||||
docker run -d -v `pwd`/.sessions:/app/.sessions -p 3000:3000/tcp --name whatsapp-http-api devlikeapro/whatsapp-http-api-plus
|
||||
```
|
||||
|
||||
#### Remote storage 
|
||||
|
||||
@@ -19,9 +19,47 @@ 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
|
||||
- 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).
|
||||
|
||||
|
||||
Binary file not shown.
|
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
|
||||
+30
-20
@@ -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
|
||||
|
||||
+1
-3
@@ -2,8 +2,6 @@
|
||||
"collection": "@nestjs/schematics",
|
||||
"sourceRoot": "src",
|
||||
"compilerOptions": {
|
||||
"plugins": [
|
||||
"@nestjs/swagger"
|
||||
]
|
||||
"plugins": ["@nestjs/swagger"]
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -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",
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+57
-20
@@ -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.' })
|
||||
|
||||
+47
-2
@@ -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')
|
||||
@@ -34,7 +31,7 @@ class SessionsController {
|
||||
const result = await this.manager.start(request);
|
||||
await this.manager.sessionStorage.configRepository.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')
|
||||
|
||||
+16
-3
@@ -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[] {
|
||||
@@ -111,7 +119,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 +127,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(
|
||||
|
||||
+3
-1
@@ -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 {
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
SessionStopRequest,
|
||||
} from '../../structures/sessions.dto';
|
||||
import { WhatsappSession } from './session.abc';
|
||||
import { LocalSessionStorage, MediaStorage } from './storage.abc';
|
||||
import { LocalSessionStorage } from './storage.abc';
|
||||
import { WebhookConductor } from './webhooks.abc';
|
||||
|
||||
export abstract class SessionManager implements OnApplicationShutdown {
|
||||
@@ -21,8 +21,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 +32,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 };
|
||||
+27
-12
@@ -3,6 +3,7 @@ import { EventEmitter } from 'events';
|
||||
import * as fs from 'fs';
|
||||
import { MessageId } from 'whatsapp-web.js';
|
||||
|
||||
import { OTPRequest, RequestCodeRequest } from '../../structures/auth.dto';
|
||||
import {
|
||||
ChatRequest,
|
||||
CheckNumberStatusQuery,
|
||||
@@ -29,6 +30,7 @@ import {
|
||||
import {
|
||||
CreateGroupRequest,
|
||||
ParticipantsRequest,
|
||||
SettingsSecurityChangeInfo,
|
||||
} from '../../structures/groups.dto';
|
||||
import { WAHAChatPresences } from '../../structures/presence.dto';
|
||||
import {
|
||||
@@ -36,9 +38,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 +45,10 @@ import {
|
||||
VoiceStatus,
|
||||
} from '../../structures/status.dto';
|
||||
import { WASessionStatusBody } from '../../structures/webhooks.dto';
|
||||
import { NotImplementedByEngineError } from '../exceptions';
|
||||
import { QR } from '../QR';
|
||||
import { MediaManager } from './media.abc';
|
||||
import { LocalSessionStorage } from './storage.abc';
|
||||
|
||||
const CHROME_PATH = '/usr/bin/google-chrome-stable';
|
||||
const CHROMIUM_PATH = '/usr/bin/chromium';
|
||||
@@ -72,11 +75,11 @@ export enum WAHAInternalEvent {
|
||||
|
||||
export interface SessionParams {
|
||||
name: string;
|
||||
storage: MediaStorage;
|
||||
mediaManager: MediaManager;
|
||||
log: ConsoleLogger;
|
||||
sessionStorage: LocalSessionStorage;
|
||||
proxyConfig?: ProxyConfig;
|
||||
sessionConfig: SessionConfig;
|
||||
sessionConfig?: SessionConfig;
|
||||
}
|
||||
|
||||
export abstract class WhatsappSession {
|
||||
@@ -84,11 +87,11 @@ export abstract class WhatsappSession {
|
||||
public engine: WAHAEngine;
|
||||
|
||||
public name: string;
|
||||
protected storage: MediaStorage;
|
||||
protected mediaManager: MediaManager;
|
||||
protected log: ConsoleLogger;
|
||||
protected sessionStorage: LocalSessionStorage;
|
||||
protected proxyConfig?: ProxyConfig;
|
||||
public sessionConfig: SessionConfig;
|
||||
public sessionConfig?: SessionConfig;
|
||||
|
||||
private _status: WAHASessionStatus;
|
||||
|
||||
@@ -97,7 +100,7 @@ export abstract class WhatsappSession {
|
||||
log,
|
||||
sessionStorage,
|
||||
proxyConfig,
|
||||
storage,
|
||||
mediaManager,
|
||||
sessionConfig,
|
||||
}: SessionParams) {
|
||||
this.events = new EventEmitter();
|
||||
@@ -105,7 +108,7 @@ export abstract class WhatsappSession {
|
||||
this.proxyConfig = proxyConfig;
|
||||
this.log = log;
|
||||
this.sessionStorage = sessionStorage;
|
||||
this.storage = storage;
|
||||
this.mediaManager = mediaManager;
|
||||
this.sessionConfig = sessionConfig;
|
||||
}
|
||||
|
||||
@@ -192,11 +195,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 +207,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 +331,10 @@ export abstract class WhatsappSession {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public getInfoAdminsOnly(id): Promise<SettingsSecurityChangeInfo> {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public setInfoAdminsOnly(id, value) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
@@ -1,17 +1,12 @@
|
||||
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 save(
|
||||
sessionName: string,
|
||||
config: SessionConfig | null,
|
||||
): Promise<void>;
|
||||
|
||||
abstract get(sessionName: string): Promise<SessionConfig>;
|
||||
abstract get(sessionName: string): Promise<SessionConfig | null>;
|
||||
}
|
||||
|
||||
abstract class SessionStorage {
|
||||
@@ -37,4 +32,4 @@ abstract class LocalSessionStorage extends SessionStorage {
|
||||
abstract getFolderPath(sessionName: string): string;
|
||||
}
|
||||
|
||||
export { LocalSessionStorage, MediaStorage, SessionConfigRepository };
|
||||
export { LocalSessionStorage, SessionConfigRepository };
|
||||
@@ -3,6 +3,8 @@ import { ConfigModule } from '@nestjs/config';
|
||||
import { PassportModule } from '@nestjs/passport';
|
||||
import { ServeStaticModule } from '@nestjs/serve-static';
|
||||
|
||||
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';
|
||||
@@ -12,13 +14,11 @@ 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 { 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({
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
+19
-19
@@ -18,17 +18,14 @@ 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 { DOCS_URL } from './exceptions';
|
||||
import { getProxyConfig } from './helpers.proxy';
|
||||
import { CoreMediaManager, MediaStorageCore } from './media.core';
|
||||
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 { SessionStorageCore } from './storage.core';
|
||||
import { WebhookConductorCore } from './webhooks.core';
|
||||
|
||||
export class OnlyDefaultSessionIsAllowed extends UnprocessableEntityException {
|
||||
@@ -45,11 +42,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;
|
||||
@@ -110,13 +105,16 @@ 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,
|
||||
proxyConfig: proxyConfig,
|
||||
@@ -149,7 +147,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 +162,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);
|
||||
}
|
||||
@@ -181,16 +184,13 @@ export class SessionManagerCore extends SessionManager {
|
||||
await this.sessionStorage.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;
|
||||
}
|
||||
}
|
||||
@@ -13,11 +13,11 @@ 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 {
|
||||
@@ -55,12 +55,14 @@ import {
|
||||
WAHAPresenceData,
|
||||
} 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';
|
||||
import { IEngineMediaProcessor } from './abc/media.abc';
|
||||
import {
|
||||
ensureSuffix,
|
||||
WAHAInternalEvent,
|
||||
@@ -72,7 +74,6 @@ import {
|
||||
} from './exceptions';
|
||||
import { createAgentProxy } from './helpers.proxy';
|
||||
import { QR } from './QR';
|
||||
import { MeInfo } from '../structures/sessions.dto';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const QRCode = require('qrcode');
|
||||
@@ -100,6 +101,9 @@ const ToEnginePresenceStatus = flipObject(PresenceStatuses);
|
||||
|
||||
export class WhatsappSessionNoWebCore extends WhatsappSession {
|
||||
engine = WAHAEngine.NOWEB;
|
||||
get listenConnectionEventsFromTheStart() {
|
||||
return true;
|
||||
}
|
||||
|
||||
sock: any;
|
||||
store: any;
|
||||
@@ -169,8 +173,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 +185,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,7 +227,7 @@ 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);
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -255,13 +261,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 +285,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 +294,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 +318,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 +669,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 +821,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 {
|
||||
@@ -21,6 +22,7 @@ import {
|
||||
WAHASessionStatus,
|
||||
} 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';
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -37,16 +37,18 @@ import {
|
||||
import {
|
||||
CreateGroupRequest,
|
||||
ParticipantsRequest,
|
||||
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';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const QRCode = require('qrcode');
|
||||
@@ -97,11 +99,25 @@ 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
|
||||
.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;
|
||||
}
|
||||
}
|
||||
@@ -6,22 +6,8 @@ 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';
|
||||
@@ -30,13 +16,13 @@ class LocalSessionConfigRepository extends SessionConfigRepository {
|
||||
super();
|
||||
}
|
||||
|
||||
async get(sessionName: string): Promise<SessionConfig> {
|
||||
async get(sessionName: string): Promise<SessionConfig | null> {
|
||||
const filepath = this.getFilePath(sessionName);
|
||||
// Check file exists
|
||||
try {
|
||||
await fs.access(filepath, fs.constants.F_OK);
|
||||
} catch (error) {
|
||||
return undefined;
|
||||
return null;
|
||||
}
|
||||
|
||||
// Try to load config
|
||||
@@ -44,13 +30,13 @@ class LocalSessionConfigRepository extends SessionConfigRepository {
|
||||
try {
|
||||
content = await fs.readFile(filepath, 'utf-8');
|
||||
} catch (error) {
|
||||
return undefined;
|
||||
return null;
|
||||
}
|
||||
|
||||
return JSON.parse(content);
|
||||
}
|
||||
|
||||
async save(sessionName: string, config: SessionConfig) {
|
||||
async save(sessionName: string, config: SessionConfig | null) {
|
||||
// Create folder if not exist
|
||||
const folder = this.storage.getFolderPath(sessionName);
|
||||
await fs.mkdir(folder, { recursive: true });
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
+2
-3
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -33,7 +33,7 @@ export function getEngineName(): string {
|
||||
}
|
||||
|
||||
export const VERSION: WAHAEnvironment = {
|
||||
version: '2023.11.1',
|
||||
version: '2023.12.3',
|
||||
engine: getEngineName(),
|
||||
tier: getWAHAVersion(),
|
||||
browser: getBrowserExecutablePath(),
|
||||
|
||||
+1
-6
@@ -1,9 +1,4 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"test",
|
||||
"dist",
|
||||
"**/*spec.ts"
|
||||
]
|
||||
"exclude": ["node_modules", "test", "dist", "**/*spec.ts"]
|
||||
}
|
||||
+1
-1
@@ -12,6 +12,6 @@
|
||||
"baseUrl": "./",
|
||||
"incremental": true,
|
||||
"resolveJsonModule": true,
|
||||
"skipLibCheck": true,
|
||||
"skipLibCheck": true
|
||||
}
|
||||
}
|
||||
@@ -13,28 +13,28 @@ __metadata:
|
||||
linkType: hard
|
||||
|
||||
"@adiwajshing/baileys@github:WhiskeySockets/Baileys":
|
||||
version: 6.4.1
|
||||
resolution: "@adiwajshing/baileys@https://github.com/WhiskeySockets/Baileys.git#commit=cd8542ff7c072b5e58d2d3de2396983a5304e5e1"
|
||||
version: 6.6.0
|
||||
resolution: "@adiwajshing/baileys@https://github.com/WhiskeySockets/Baileys.git#commit=daafc122759bd5b54279f82b196e1ce41aec4807"
|
||||
dependencies:
|
||||
"@adiwajshing/keyed-db": ^0.2.4
|
||||
"@hapi/boom": ^9.1.3
|
||||
audio-decode: ^2.1.3
|
||||
axios: ^1.3.3
|
||||
cache-manager: ^5.2.2
|
||||
cache-manager: 4.0.1
|
||||
futoin-hkdf: ^1.5.1
|
||||
libphonenumber-js: ^1.10.20
|
||||
libsignal: "github:adiwajshing/libsignal-node"
|
||||
music-metadata: ^7.12.3
|
||||
node-cache: ^5.1.2
|
||||
pino: ^7.0.0
|
||||
protobufjs: ^6.11.3
|
||||
protobufjs: ^7.2.4
|
||||
uuid: ^9.0.0
|
||||
ws: ^8.13.0
|
||||
peerDependencies:
|
||||
jimp: ^0.16.1
|
||||
link-preview-js: ^3.0.0
|
||||
qrcode-terminal: ^0.12.0
|
||||
sharp: ^0.30.5
|
||||
sharp: ^0.32.2
|
||||
peerDependenciesMeta:
|
||||
jimp:
|
||||
optional: true
|
||||
@@ -44,7 +44,7 @@ __metadata:
|
||||
optional: true
|
||||
sharp:
|
||||
optional: true
|
||||
checksum: 4778b35869210130b938b5fb1d49a5b7263d7aaff734fe3ea4747fb06231ef9ffcf646ec653054e086c08d2a1b8833d167ebb975e0ec93251658dd9a081bd790
|
||||
checksum: 252146db962934c5dd54472922dd6b60725879f1fd1a3809e464fef726738cb6deeab843394f9793ca98f23d74e4a1436bca5f2a8ab7534e0ea668f3a117011a
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -1650,7 +1650,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/long@npm:^4.0.0, @types/long@npm:^4.0.1":
|
||||
"@types/long@npm:^4.0.0":
|
||||
version: 4.0.2
|
||||
resolution: "@types/long@npm:4.0.2"
|
||||
checksum: d16cde7240d834cf44ba1eaec49e78ae3180e724cd667052b194a372f350d024cba8dd3f37b0864931683dab09ca935d52f0c4c1687178af5ada9fc85b0635f4
|
||||
@@ -2798,6 +2798,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"async@npm:3.2.3":
|
||||
version: 3.2.3
|
||||
resolution: "async@npm:3.2.3"
|
||||
checksum: c4bee57ab2249af3dc83ca3ef9acfa8e822c0d5e5aa41bae3eaf7f673648343cd64ecd7d26091ffd357f3f044428b17b5f00098494b6cf8b6b3e9681f0636ca1
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"async@npm:>=0.2.9, async@npm:^3.2.3, async@npm:^3.2.4":
|
||||
version: 3.2.4
|
||||
resolution: "async@npm:3.2.4"
|
||||
@@ -3351,13 +3358,14 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"cache-manager@npm:^5.2.2":
|
||||
version: 5.2.3
|
||||
resolution: "cache-manager@npm:5.2.3"
|
||||
"cache-manager@npm:4.0.1":
|
||||
version: 4.0.1
|
||||
resolution: "cache-manager@npm:4.0.1"
|
||||
dependencies:
|
||||
async: 3.2.3
|
||||
lodash.clonedeep: ^4.5.0
|
||||
lru-cache: ^9.1.2
|
||||
checksum: 2bcd6a385e5575bf9577a209e02c9f5074cd476e6eb08a0f108e45d80c20f4e91fbff163493fe345f166f8ecb232687f20541bc4f42fb2167f8770f9f8b26c51
|
||||
lru-cache: ^7.10.1
|
||||
checksum: 8ca25b1e603111f033d1a3f1463b2a9203e9ea61a0706265a4d4f63d54a8be95f9e79efb04fc43103219becb4903a5db747a346e009e7102767a47e5677219b2
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -5706,7 +5714,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"fluent-ffmpeg@npm:^2.1.2":
|
||||
"fluent-ffmpeg@npm:2.1.2":
|
||||
version: 2.1.2
|
||||
resolution: "fluent-ffmpeg@npm:2.1.2"
|
||||
dependencies:
|
||||
@@ -8063,13 +8071,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"jsqr@npm:^1.3.1":
|
||||
version: 1.4.0
|
||||
resolution: "jsqr@npm:1.4.0"
|
||||
checksum: 7c572971f90c42772e30d152bde63b84edf1164bde80c53942e6b2068ea31caf00ad704aa46cacc9e71645f52dbeddebc6e84ba15e883c678ee93cde690de339
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"keyv@npm:^3.0.0":
|
||||
version: 3.1.0
|
||||
resolution: "keyv@npm:3.1.0"
|
||||
@@ -8437,6 +8438,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"long@npm:^5.0.0":
|
||||
version: 5.2.3
|
||||
resolution: "long@npm:5.2.3"
|
||||
checksum: 885ede7c3de4facccbd2cacc6168bae3a02c3e836159ea4252c87b6e34d40af819824b2d4edce330bfb5c4d6e8ce3ec5864bdcf9473fa1f53a4f8225860e5897
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"longest@npm:^2.0.1":
|
||||
version: 2.0.1
|
||||
resolution: "longest@npm:2.0.1"
|
||||
@@ -8476,7 +8484,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"lru-cache@npm:^7.14.1, lru-cache@npm:^7.7.1":
|
||||
"lru-cache@npm:^7.10.1, lru-cache@npm:^7.14.1, lru-cache@npm:^7.7.1":
|
||||
version: 7.18.3
|
||||
resolution: "lru-cache@npm:7.18.3"
|
||||
checksum: e550d772384709deea3f141af34b6d4fa392e2e418c1498c078de0ee63670f1f46f5eee746e8ef7e69e1c895af0d4224e62ee33e66a543a14763b0f2e74c1356
|
||||
@@ -8490,13 +8498,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"lru-cache@npm:^9.1.2":
|
||||
version: 9.1.2
|
||||
resolution: "lru-cache@npm:9.1.2"
|
||||
checksum: d3415634be3908909081fc4c56371a8d562d9081eba70543d86871b978702fffd0e9e362b83921b27a29ae2b37b90f55675aad770a54ac83bb3e4de5049d4b15
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"lunr@npm:^2.3.9":
|
||||
version: 2.3.9
|
||||
resolution: "lunr@npm:2.3.9"
|
||||
@@ -9240,10 +9241,10 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"node-webpmux@npm:^3.1.0":
|
||||
version: 3.1.8
|
||||
resolution: "node-webpmux@npm:3.1.8"
|
||||
checksum: 96d230f33bf645e93c4e1c16f506571bde91ede320d4a9951ed08a099f5796b68a89a3591815019eee3c385215eaaa6c428008abea65cecabbfbd463b8551b23
|
||||
"node-webpmux@npm:3.1.0":
|
||||
version: 3.1.0
|
||||
resolution: "node-webpmux@npm:3.1.0"
|
||||
checksum: 45041b561d21ab88b81b5eaa1761cf1667dc56118b1ecf1ec1774d5f00ab9fbf473cdada7d5e09577f12dbc765fd22841b81ebeee352ddba5db7b6c5f5980056
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -10084,9 +10085,9 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"protobufjs@npm:^6.11.3":
|
||||
version: 6.11.4
|
||||
resolution: "protobufjs@npm:6.11.4"
|
||||
"protobufjs@npm:^7.2.4":
|
||||
version: 7.2.6
|
||||
resolution: "protobufjs@npm:7.2.6"
|
||||
dependencies:
|
||||
"@protobufjs/aspromise": ^1.1.2
|
||||
"@protobufjs/base64": ^1.1.2
|
||||
@@ -10098,13 +10099,9 @@ __metadata:
|
||||
"@protobufjs/path": ^1.1.2
|
||||
"@protobufjs/pool": ^1.1.0
|
||||
"@protobufjs/utf8": ^1.1.0
|
||||
"@types/long": ^4.0.1
|
||||
"@types/node": ">=13.7.0"
|
||||
long: ^4.0.0
|
||||
bin:
|
||||
pbjs: bin/pbjs
|
||||
pbts: bin/pbts
|
||||
checksum: b2fc6a01897b016c2a7e43a854ab4a3c57080f61be41e552235436e7a730711b8e89e47cb4ae52f0f065b5ab5d5989fc932f390337ce3a8ccf07203415700850
|
||||
long: ^5.0.0
|
||||
checksum: 3c62e48f7d50017ac3b0dcd2a58e617cf858f9fba56a488bd48b9aa3482893a75540052dbcb3c12dfbaab42b1d04964611175faf06bdadcd33a4ebac982a511e
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -13130,17 +13127,16 @@ __metadata:
|
||||
linkType: soft
|
||||
|
||||
"whatsapp-web.js@https://github.com/pedroslopez/whatsapp-web.js":
|
||||
version: 1.22.2-alpha.3
|
||||
resolution: "whatsapp-web.js@https://github.com/pedroslopez/whatsapp-web.js.git#commit=f5555f6aa0d34e5c5b2f6fd5d8e1ee5559528c55"
|
||||
version: 1.23.1-alpha.4
|
||||
resolution: "whatsapp-web.js@https://github.com/pedroslopez/whatsapp-web.js.git#commit=ef2f725bce1bd1afd8491f9de0cd2ed06a6febc3"
|
||||
dependencies:
|
||||
"@pedroslopez/moduleraid": ^5.0.2
|
||||
archiver: ^5.3.1
|
||||
fluent-ffmpeg: ^2.1.2
|
||||
fluent-ffmpeg: 2.1.2
|
||||
fs-extra: ^10.1.0
|
||||
jsqr: ^1.3.1
|
||||
mime: ^3.0.0
|
||||
node-fetch: ^2.6.5
|
||||
node-webpmux: ^3.1.0
|
||||
node-webpmux: 3.1.0
|
||||
puppeteer: ^13.0.0
|
||||
unzipper: ^0.10.11
|
||||
dependenciesMeta:
|
||||
@@ -13150,7 +13146,7 @@ __metadata:
|
||||
optional: true
|
||||
unzipper:
|
||||
optional: true
|
||||
checksum: d4d030cef0955cadfd199684d9176effdac1e4222100ec994c9d91004618b070bd23d01b04060e6fd988e3c1329a8ba0f38d208093a75f78115676eb6f6b4a4b
|
||||
checksum: 36062f9aa97e5d3f2ae50235a1535ae4b67550ea40f875794590cbaed6045b6b5ae56bd3edd79092419018f5509ff8dfb8f03cb894ed76315f9583d546a4f4be
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
||||
Reference in new issue
Block a user