Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5c666c0dd4 | ||
|
|
3ccc0469c0 | ||
|
|
5ab3244d6a | ||
|
|
05bc8abbee | ||
|
|
8e9056a0cd | ||
|
|
ea75b357a7 | ||
|
|
2b607e9b44 | ||
|
|
57d3528a96 | ||
|
|
660a04aa52 | ||
|
|
d0a408001d | ||
|
|
48391533a6 | ||
|
|
f2113adbbc | ||
|
|
abfcbc7397 | ||
|
|
96f2982325 | ||
|
|
3a521bc06c | ||
|
|
6fbaec0083 | ||
|
|
cae94c7b5c | ||
|
|
bce012f692 | ||
|
|
b23b639a8b | ||
|
|
5a628907ed | ||
|
|
6da479c0d7 | ||
|
|
cbb321df6d | ||
|
|
0a7beb6909 | ||
|
|
2105094ac3 | ||
|
|
d8fee686f8 | ||
|
|
75fc1fd7e0 | ||
|
|
e718652491 | ||
|
|
bcb49f2be7 | ||
|
|
dab63e4060 | ||
|
|
3fdacbb6ac | ||
|
|
f02b62e620 | ||
|
|
51acce14a0 | ||
|
|
b63f6ac39e | ||
|
|
9e107a8a76 | ||
|
|
8c083242d3 | ||
|
|
2a068d561f | ||
|
|
ef96fbf8c5 | ||
|
|
2c381a0b9c | ||
|
|
fef2bcb6e2 | ||
|
|
2f6ed2fb58 | ||
|
|
4eb23d2c09 | ||
|
|
ec3de8dbcf | ||
|
|
f6f09747dc | ||
|
|
27a534a605 | ||
|
|
3cbd940843 | ||
|
|
e728eec01a | ||
|
|
d06d2b78a8 | ||
|
|
5878a9196e | ||
|
|
85dd061f96 | ||
|
|
8381c3e946 | ||
|
|
6ff8a9705d | ||
|
|
fb5819b966 | ||
|
|
5cc1af3f82 | ||
|
|
c279de694c | ||
|
|
f7a1787f1f | ||
|
|
5442fd6e93 | ||
|
|
b8c8ca7653 | ||
|
|
5ca7dbc84b | ||
|
|
d836c9706f | ||
|
|
318be43a4f | ||
|
|
95bebce731 | ||
|
|
27b8c9d058 | ||
|
|
10d4d24526 | ||
|
|
b19917bd3d | ||
|
|
4b6774b1db | ||
|
|
360e0a7951 | ||
|
|
171f516eee | ||
|
|
01943f6970 | ||
|
|
953d48ee1f | ||
|
|
2b6028bcb1 | ||
|
|
79b7b7d2a2 | ||
|
|
93a6a7b87f | ||
|
|
3b499d6af5 | ||
|
|
6481919597 | ||
|
|
228f6ec8c5 | ||
|
|
6723f201b7 | ||
|
|
c250bb49f0 | ||
|
|
3b607662e5 | ||
|
|
f2d7041cb0 | ||
|
|
afc06794c2 | ||
|
|
33eaafcff1 | ||
|
|
e5a8743b9a | ||
|
|
3322c4e2a5 | ||
|
|
aa39aaf489 | ||
|
|
a238581f44 | ||
|
|
00814ef64e | ||
|
|
6362d2966b | ||
|
|
4e181a82b6 | ||
|
|
374199905d | ||
|
|
642079ab1f | ||
|
|
215c9d96b9 | ||
|
|
5369eda705 | ||
|
|
5e42bbe1eb | ||
|
|
79dd2ee03e | ||
|
|
78d3eae44b | ||
|
|
a9ee68fe1a | ||
|
|
ee5678ccfb | ||
|
|
248a675c1a | ||
|
|
6b1d84cf38 | ||
|
|
c0977538ff | ||
|
|
c8727e8b2f | ||
|
|
55a41e19f4 | ||
|
|
6096c0b158 | ||
|
|
161c6dfd95 | ||
|
|
078bcf7652 | ||
|
|
57c6df3970 | ||
|
|
1130ee1b21 | ||
|
|
c9c0a29740 | ||
|
|
e60d972bff | ||
|
|
3c4a139f52 | ||
|
|
bf615efa96 | ||
|
|
2af3940be7 | ||
|
|
64e145909e | ||
|
|
e90f34cc96 | ||
|
|
a23cf2dd26 | ||
|
|
7b623d0faa | ||
|
|
14788b1d53 | ||
|
|
e1bf8cf735 | ||
|
|
2117731b46 | ||
|
|
74f8fe1caf | ||
|
|
9388a3b277 | ||
|
|
13e7a05e1b | ||
|
|
885fa25c0c | ||
|
|
966b35094f | ||
|
|
65607bf3b3 | ||
|
|
5fcba7a7db | ||
|
|
f9ec3cb753 | ||
|
|
bf6ffd65a9 | ||
|
|
d680b6b43a | ||
|
|
3ead3a0ba3 | ||
|
|
4c5c977310 | ||
|
|
89a9ce87da | ||
|
|
52e469f6ac | ||
|
|
4f275de8d4 | ||
|
|
d9de41e350 | ||
|
|
a4c279ba25 | ||
|
|
e68282a9e4 | ||
|
|
8613206c3a | ||
|
|
31b8cde8bf | ||
|
|
2f2f9520db | ||
|
|
2501ae836e | ||
|
|
2c8bf91e77 | ||
|
|
0ac063498e | ||
|
|
2bf672f0cd | ||
|
|
ca209adca0 | ||
|
|
dd10c82c5e | ||
|
|
c3ad2a0931 | ||
|
|
30aacc8946 | ||
|
|
723a644937 | ||
|
|
165eeb5bf2 | ||
|
|
f1776d4a26 | ||
|
|
2f2a1b3182 | ||
|
|
0477cf7db5 | ||
|
|
2d7c7986aa | ||
|
|
bf84ea8fca | ||
|
|
f3ef8a7f96 | ||
|
|
ec214f610a | ||
|
|
013fd46722 | ||
|
|
32d583b9ef | ||
|
|
df6e201ba5 | ||
|
|
789b613b82 | ||
|
|
b698342db4 | ||
|
|
d853f2fe99 | ||
|
|
20d66b5b31 | ||
|
|
da7264c5e8 | ||
|
|
f3299f78d9 | ||
|
|
7ef0805c91 | ||
|
|
ba1afa2569 | ||
|
|
936e2d0863 | ||
|
|
166410f66a |
No files matched your search
+2
-1
@@ -1,5 +1,5 @@
|
||||
Dockerfile
|
||||
.sessions
|
||||
.*sessions
|
||||
files
|
||||
venv
|
||||
.wwebjs_auth
|
||||
@@ -11,6 +11,7 @@ examples
|
||||
# compiled output
|
||||
/dist
|
||||
/node_modules
|
||||
.yarn
|
||||
|
||||
# Logs
|
||||
logs
|
||||
|
||||
+31
-25
@@ -1,27 +1,33 @@
|
||||
module.exports = {
|
||||
parser: '@typescript-eslint/parser',
|
||||
parserOptions: {
|
||||
project: 'tsconfig.json',
|
||||
sourceType: 'module',
|
||||
},
|
||||
plugins: ['@typescript-eslint/eslint-plugin'],
|
||||
extends: [
|
||||
'plugin:@typescript-eslint/eslint-recommended',
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'prettier',
|
||||
'prettier/@typescript-eslint',
|
||||
],
|
||||
root: true,
|
||||
env: {
|
||||
node: true,
|
||||
jest: true,
|
||||
},
|
||||
rules: {
|
||||
'@typescript-eslint/interface-name-prefix': 'off',
|
||||
'@typescript-eslint/explicit-function-return-type': 'off',
|
||||
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
||||
'@typescript-eslint/no-explicit-any': 'off',
|
||||
"@typescript-eslint/ban-ts-ignore": "off",
|
||||
"@typescript-eslint/ban-ts-comment": "off"
|
||||
},
|
||||
parser: "@typescript-eslint/parser",
|
||||
parserOptions: {
|
||||
project: "tsconfig.json",
|
||||
sourceType: "module"
|
||||
},
|
||||
plugins: [
|
||||
"@typescript-eslint/eslint-plugin",
|
||||
"simple-import-sort"
|
||||
],
|
||||
extends: [
|
||||
"plugin:@typescript-eslint/eslint-recommended",
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"prettier",
|
||||
"prettier/@typescript-eslint"
|
||||
],
|
||||
root: true,
|
||||
env: {
|
||||
node: true,
|
||||
jest: true
|
||||
},
|
||||
rules: {
|
||||
"simple-import-sort/imports": "error",
|
||||
"simple-import-sort/exports": "error",
|
||||
"@typescript-eslint/interface-name-prefix": "off",
|
||||
"@typescript-eslint/explicit-function-return-type": "off",
|
||||
"@typescript-eslint/explicit-module-boundary-types": "off",
|
||||
"@typescript-eslint/no-inferrable-types": "off",
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"@typescript-eslint/ban-ts-ignore": "off",
|
||||
"@typescript-eslint/ban-ts-comment": "off",
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,18 @@
|
||||
# Idea from
|
||||
# https://stackoverflow.com/a/65539398/6753144
|
||||
#
|
||||
|
||||
OWNER=devlikeapro
|
||||
REPO=whatsapp-http-api-plus
|
||||
|
||||
# list workflows
|
||||
gh api -X GET /repos/$OWNER/$REPO/actions/workflows | jq '.workflows[] | .name,.id'
|
||||
|
||||
# copy the ID of the workflow you want to clear and set it
|
||||
WORKFLOW_ID=40218610
|
||||
|
||||
# list runs
|
||||
gh api -X GET /repos/$OWNER/$REPO/actions/workflows/$WORKFLOW_ID/runs --paginate | jq '.workflow_runs[] | .id'
|
||||
|
||||
# delete runs
|
||||
gh api -X GET /repos/$OWNER/$REPO/actions/workflows/$WORKFLOW_ID/runs --paginate | jq '.workflow_runs[] | .id' | xargs -I{} gh api -X DELETE /repos/$OWNER/$REPO/actions/runs/{} --silent
|
||||
@@ -6,13 +6,12 @@ on:
|
||||
- "*"
|
||||
|
||||
jobs:
|
||||
deploy-docker:
|
||||
runs-on: ubuntu-latest
|
||||
docker-amd64:
|
||||
runs-on: buildjet-4vcpu-ubuntu-2204
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
|
||||
@@ -21,9 +20,10 @@ jobs:
|
||||
uses: docker/metadata-action@v4
|
||||
with:
|
||||
images: ${{ secrets.DOCKER_IMAGE }}
|
||||
flavor: latest=true
|
||||
flavor: latest=false
|
||||
tags: |
|
||||
type=semver,pattern={{version}}
|
||||
type=raw,value=latest
|
||||
|
||||
- name: Login to image repository
|
||||
if: github.ref_type == 'tag'
|
||||
@@ -33,10 +33,46 @@ jobs:
|
||||
password: ${{ secrets.DOCKER_TOKEN }}
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v3
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: .
|
||||
file: Dockerfile
|
||||
platforms: 'amd64'
|
||||
push: ${{ github.ref_type == 'tag' }}
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
|
||||
docker-arm:
|
||||
runs-on: buildjet-4vcpu-ubuntu-2204-arm
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v4
|
||||
with:
|
||||
images: ${{ secrets.DOCKER_IMAGE }}
|
||||
flavor: latest=false
|
||||
tags: |
|
||||
type=raw,value=arm
|
||||
|
||||
- name: Login to image repository
|
||||
if: github.ref_type == 'tag'
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USER }}
|
||||
password: ${{ secrets.DOCKER_TOKEN }}
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: .
|
||||
file: Dockerfile
|
||||
platforms: 'linux/arm64'
|
||||
push: ${{ github.ref_type == 'tag' }}
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
@@ -31,6 +31,7 @@ jobs:
|
||||
with:
|
||||
path: docs/site/public
|
||||
name: public
|
||||
retention-days: 5
|
||||
|
||||
deploy-github-pages:
|
||||
needs: build
|
||||
|
||||
+4
-1
@@ -1,9 +1,12 @@
|
||||
.sessions
|
||||
.yarn
|
||||
example.ts
|
||||
.*sessions
|
||||
tokens
|
||||
files
|
||||
venv
|
||||
.wwebjs_auth
|
||||
test.js
|
||||
.wwebjs_cache
|
||||
|
||||
# compiled output
|
||||
/dist
|
||||
|
||||
+15
-6
@@ -1,4 +1,13 @@
|
||||
repos:
|
||||
- repo: https://github.com/pre-commit/mirrors-prettier
|
||||
rev: 'v2.7.1'
|
||||
hooks:
|
||||
- id: prettier
|
||||
types_or: [ javascript, ts ]
|
||||
files: |
|
||||
(?x)^(
|
||||
src/.*|
|
||||
)$
|
||||
- repo: https://github.com/Lucas-C/pre-commit-hooks-nodejs
|
||||
rev: v1.1.2
|
||||
hooks:
|
||||
@@ -12,9 +21,9 @@ repos:
|
||||
language: pygrep
|
||||
entry: 'plus'
|
||||
files: |
|
||||
(?x)^(
|
||||
src/api/.*|
|
||||
src/core/.*|
|
||||
src/structures/.*|
|
||||
src/config.service.ts|
|
||||
)$
|
||||
(?x)^(
|
||||
src/api/.*|
|
||||
src/core/.*|
|
||||
src/structures/.*|
|
||||
src/config.service.ts|
|
||||
)$
|
||||
@@ -0,0 +1,3 @@
|
||||
nodeLinker: node-modules
|
||||
|
||||
yarnPath: .yarn/releases/yarn-3.6.1.cjs
|
||||
+1
-1
@@ -1,2 +1,2 @@
|
||||
# Changelog
|
||||
You'll 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/)
|
||||
+34
-12
@@ -1,34 +1,56 @@
|
||||
#
|
||||
# Build
|
||||
#
|
||||
FROM node:18 as build
|
||||
FROM node:20-bullseye as build
|
||||
ENV PUPPETEER_SKIP_DOWNLOAD=True
|
||||
|
||||
# npm packages
|
||||
WORKDIR /src
|
||||
COPY package.json .
|
||||
COPY package-lock.json .
|
||||
RUN npm install
|
||||
COPY yarn.lock .
|
||||
RUN yarn set version 3.6.1
|
||||
RUN yarn install
|
||||
|
||||
# App
|
||||
WORKDIR /src
|
||||
ADD . /src
|
||||
RUN npm run build && find ./dist -name "*.d.ts" -delete
|
||||
RUN yarn install
|
||||
RUN yarn build && find ./dist -name "*.d.ts" -delete
|
||||
|
||||
#
|
||||
# Final
|
||||
#
|
||||
FROM node:18 as release
|
||||
FROM node:20-bullseye as release
|
||||
ENV PUPPETEER_SKIP_DOWNLOAD=True
|
||||
ARG USE_BROWSER=chromium
|
||||
|
||||
# Install Chrome
|
||||
# Install fonts
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y wget gnupg \
|
||||
&& wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - \
|
||||
&& sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list' \
|
||||
&& apt-get update \
|
||||
&& apt-get install -y google-chrome-stable fonts-ipafont-gothic fonts-wqy-zenhei fonts-thai-tlwg fonts-kacst fonts-freefont-ttf \
|
||||
&& apt-get install -y fonts-ipafont-gothic fonts-wqy-zenhei fonts-thai-tlwg fonts-kacst fonts-freefont-ttf \
|
||||
--no-install-recommends \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Install Chromium
|
||||
RUN if [ "$USE_BROWSER" = "chromium" ]; then \
|
||||
apt-get update \
|
||||
&& apt-get update \
|
||||
&& apt-get install -y chromium \
|
||||
--no-install-recommends \
|
||||
&& rm -rf /var/lib/apt/lists/*; \
|
||||
fi
|
||||
|
||||
# Install Chrome
|
||||
RUN if [ "$USE_BROWSER" = "chrome" ]; then \
|
||||
apt-get update \
|
||||
&& apt-get install -y wget gnupg \
|
||||
&& wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - \
|
||||
&& sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list' \
|
||||
&& apt-get update \
|
||||
&& apt-get install -y google-chrome-stable \
|
||||
--no-install-recommends \
|
||||
&& rm -rf /var/lib/apt/lists/*; \
|
||||
fi
|
||||
|
||||
# Attach sources, install packages
|
||||
WORKDIR /app
|
||||
COPY package.json ./
|
||||
@@ -37,4 +59,4 @@ COPY --from=build /src/dist ./dist
|
||||
|
||||
# Run command, etc
|
||||
EXPOSE 3000
|
||||
CMD npm run start:prod
|
||||
CMD yarn start:prod
|
||||
@@ -1,26 +1,8 @@
|
||||
build:
|
||||
docker build . -t devlikeapro/whatsapp-http-api
|
||||
|
||||
run-webjs:
|
||||
docker run \
|
||||
--rm -d \
|
||||
-p 127.0.0.1:3000:3000/tcp \
|
||||
-v `pwd`/.sessions:/app/.sessions \
|
||||
-e WHATSAPP_HOOK_URL=https://httpbin.org/post \
|
||||
-e WHATSAPP_HOOK_EVENTS=* \
|
||||
-e WHATSAPP_API_KEY=321 \
|
||||
--name whatsapp-http-api devlikeapro/whatsapp-http-api
|
||||
|
||||
run-venom:
|
||||
docker run \
|
||||
--rm -d \
|
||||
-p 127.0.0.1:3000:3000/tcp \
|
||||
-v `pwd`/.sessions:/app/.sessions \
|
||||
-e WHATSAPP_HOOK_URL=https://httpbin.org/post \
|
||||
-e WHATSAPP_HOOK_EVENTS=* \
|
||||
-e WHATSAPP_DEFAULT_ENGINE=VENOM \
|
||||
-e WHATSAPP_API_KEY=321 \
|
||||
--name whatsapp-http-api devlikeapro/whatsapp-http-api
|
||||
build-plus:
|
||||
docker build . -t devlikeapro/whatsapp-http-api-plus
|
||||
|
||||
stop:
|
||||
docker stop whatsapp-http-api
|
||||
@@ -31,8 +13,16 @@ clean: stop
|
||||
push:
|
||||
docker push devlikeapro/whatsapp-http-api
|
||||
|
||||
for-swagger:
|
||||
WHATSAPP_SWAGGER_CONFIG_ADVANCED=true npm run start
|
||||
|
||||
update-swagger:
|
||||
wget http://localhost:3000/-json -O ./docs/site/static/swagger/openapi.json
|
||||
|
||||
update-dependencies:
|
||||
. ${NVM_DIR}/nvm.sh && nvm exec npm install venom-bot whatsapp-web.js --save
|
||||
. ${NVM_DIR}/nvm.sh && nvm exec yarn up whatsapp-web.js@https://github.com/pedroslopez/whatsapp-web.js
|
||||
. ${NVM_DIR}/nvm.sh && nvm exec yarn up @adiwajshing/baileys@github:WhiskeySockets/Baileys
|
||||
. ${NVM_DIR}/nvm.sh && nvm exec yarn up venom-bot
|
||||
|
||||
start-proxy:
|
||||
docker run --rm -d --name squid-container -e TZ=UTC -p 3128:3128 ubuntu/squid:5.2-22.04_beta
|
||||
@@ -18,8 +18,8 @@
|
||||
* [Sessions](#sessions)
|
||||
* [Security](#security)
|
||||
* [Updates](#updates)
|
||||
* [Groups [soon]](#groups-soon)
|
||||
* [Contacts [soon]](#contacts-soon)
|
||||
* [Groups](#groups)
|
||||
* [Contacts](#contacts)
|
||||
- [Quick start](#quick-start)
|
||||
* [Requirements](#requirements)
|
||||
* [Send your first message](#send-your-first-message)
|
||||
@@ -61,25 +61,21 @@
|
||||
|
||||
## Updates
|
||||
- Bug fixes and updates come as soon as they are implemented.
|
||||
- One month delay in bug fixes and updates.
|
||||
|
||||
|
||||
## Groups [soon]
|
||||
- Join groups by invite
|
||||
## Groups
|
||||
- Create a group
|
||||
- Get invite for group
|
||||
- Modify group info (subject, description)
|
||||
- Modify group settings (send messages, edit info)
|
||||
- Add group participants
|
||||
- Kick group participants
|
||||
- Promote/demote group participants
|
||||
|
||||
## Contacts [soon]
|
||||
- Mention users
|
||||
## Contacts
|
||||
- Mute/unmute chats
|
||||
- Block/unblock contacts
|
||||
- Get contact info
|
||||
- Get profile pictures
|
||||
- Set user status message
|
||||
- Get presence (online\offline\typing) status
|
||||
|
||||
# Quick start
|
||||
|
||||
@@ -126,7 +122,7 @@ Read more about how to get `PASSWORD` for [Plus Version →]({{< relref "plus-ve
|
||||
Run WhatsApp HTTP API:
|
||||
|
||||
```bash
|
||||
docker run -it --rm -p 127.0.0.1:3000:3000/tcp --name whatsapp-http-api devlikeapro/whatsapp-http-api
|
||||
docker run -it --rm -p 3000:3000/tcp --name whatsapp-http-api devlikeapro/whatsapp-http-api
|
||||
|
||||
# It prints logs and the last line must be
|
||||
# WhatsApp HTTP API is running on: http://[::1]:3000
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
version: '3'
|
||||
services:
|
||||
whatsapp-http-api:
|
||||
image: devlikeapro/whatsapp-http-api
|
||||
container_name: whatsapp-http-api
|
||||
ports:
|
||||
- "3000:3000/tcp"
|
||||
environment:
|
||||
# Environment variables from https://waha.devlike.pro/docs/how-to/config/
|
||||
- WHATSAPP_HOOK_URL=https://httpbin.org/post
|
||||
- WHATSAPP_HOOK_EVENTS=message
|
||||
- WHATSAPP_DEFAULT_ENGINE=WEBJS
|
||||
restart: always
|
||||
|
||||
whatsapp-http-api-plus:
|
||||
image: devlikeapro/whatsapp-http-api-plus
|
||||
container_name: whatsapp-http-api-plus
|
||||
ports:
|
||||
- "3000:3000/tcp"
|
||||
volumes:
|
||||
- "./.sessions:/app/.sessions"
|
||||
environment:
|
||||
# Environment variables from https://waha.devlike.pro/docs/how-to/config/
|
||||
- WHATSAPP_RESTART_ALL_SESSIONS=True
|
||||
- WHATSAPP_HOOK_URL=https://httpbin.org/post
|
||||
- WHATSAPP_HOOK_EVENTS=message
|
||||
- WHATSAPP_DEFAULT_ENGINE=WEBJS
|
||||
- WHATSAPP_API_KEY=321
|
||||
- WHATSAPP_SWAGGER_USERNAME=admin
|
||||
- WHATSAPP_SWAGGER_PASSWORD=123
|
||||
- WHATSAPP_API_HOSTNAME=localhost
|
||||
restart: always
|
||||
@@ -19,42 +19,87 @@
|
||||
|
||||
[[main]]
|
||||
name = "Documentation"
|
||||
identifier = "docs"
|
||||
url = "/docs/overview/introduction/"
|
||||
# url = "/docs/1.0/overview/introduction/"
|
||||
weight = 10
|
||||
|
||||
#[[main]]
|
||||
# name = "Blog"
|
||||
# url = "/blog/"
|
||||
# weight = 20
|
||||
[[main]]
|
||||
name = "Introduction"
|
||||
weight = 11
|
||||
url = "/docs/overview/introduction"
|
||||
parent = "docs"
|
||||
|
||||
[[main]]
|
||||
name = "Get Plus"
|
||||
weight = 30
|
||||
identifier = "get-plus"
|
||||
url = ""
|
||||
name = "Quick Start"
|
||||
weight = 12
|
||||
url = "/docs/overview/introduction"
|
||||
parent = "docs"
|
||||
|
||||
|
||||
[[main]]
|
||||
name = "Send messages"
|
||||
weight = 14
|
||||
url = "/docs/how-to/send-messages"
|
||||
parent = "docs"
|
||||
|
||||
[[main]]
|
||||
name = "Receive Messages"
|
||||
weight = 15
|
||||
url = "/docs/how-to/receive-messages"
|
||||
parent = "docs"
|
||||
|
||||
[[main]]
|
||||
name = "How to guides"
|
||||
weight = 20
|
||||
url = "/docs/how-to"
|
||||
parent = "docs"
|
||||
|
||||
[[main]]
|
||||
name = "Swagger ->"
|
||||
weight = 98
|
||||
url = "https://waha.devlike.pro/swagger"
|
||||
parent = "docs"
|
||||
|
||||
[[main]]
|
||||
name = "OpenAPI JSON ->"
|
||||
weight = 99
|
||||
url = "https://waha.devlike.pro/swagger/openapi.json"
|
||||
parent = "docs"
|
||||
|
||||
[[main]]
|
||||
name = "WAHA Plus"
|
||||
weight = 30
|
||||
identifier = "waha-plus"
|
||||
|
||||
[[main]]
|
||||
name = "About WAHA Plus"
|
||||
weight = 40
|
||||
identifier = "plus-version"
|
||||
weight = 10
|
||||
url = "/docs/how-to/plus-version"
|
||||
parent = "get-plus"
|
||||
parent = "waha-plus"
|
||||
|
||||
[[main]]
|
||||
name = "Get on Boosty ->"
|
||||
weight = 50
|
||||
identifier = "boosty"
|
||||
url = "https://boosty.to/wa-http-api"
|
||||
parent = "get-plus"
|
||||
parent = "waha-plus"
|
||||
|
||||
[[main]]
|
||||
name = "Get on Patreon ->"
|
||||
weight = 60
|
||||
identifier = "patreon"
|
||||
url = "https://patreon.com/wa_http_api"
|
||||
parent = "get-plus"
|
||||
parent = "waha-plus"
|
||||
|
||||
[[main]]
|
||||
name = "Examples"
|
||||
weight = 20
|
||||
identifier = "examples"
|
||||
|
||||
[[main]]
|
||||
name = "Python ->"
|
||||
weight = 21
|
||||
url = "https://github.com/devlikeapro/whatsapp-http-api/tree/core/examples/python"
|
||||
parent = "examples"
|
||||
|
||||
|
||||
[[social]]
|
||||
name = "GitHub"
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
title = "WAHA"
|
||||
titleSeparator = "-"
|
||||
titleAddition = "WhatsApp HTTP API"
|
||||
description = "WAHA - WhatsApp HTTP API that you can configure in a click!"
|
||||
description = "WAHA - WhatsApp HTTP API that you can run in a click!"
|
||||
|
||||
## Documentation
|
||||
# docsVersion = "0.3"
|
||||
@@ -72,9 +72,8 @@ copyRight = "Copyright (c) 2021-2023 Dev Like A Pro"
|
||||
|
||||
# Alert
|
||||
alert = true
|
||||
alertDismissable = false
|
||||
# alertText = "Introducing the Doks child theme, several DX + UX updates, and more! <a class=\"alert-link stretched-link\" href=\"https://getdoks.org/blog/doks-v0.2/\" target=\"_blank\" rel=\"noopener\">Check out Doks v0.2</a>"
|
||||
alertText = "Enjoy Core version or want to get WAHA Plus verison? You can <a class=\"alert-link stretched-link\" href=\"https://waha.devlike.pro/docs/how-to/plus-version/\">support the project -></a>"
|
||||
alertDismissable = true
|
||||
alertText = "<a class=\"alert-link stretched-link\" href=\"https://waha.devlike.pro/docs/how-to/plus-version/\">Support the project and get WAHA Plus version! -></a>"
|
||||
|
||||
# Edit Page
|
||||
# repoHost [Github | Gitea | GitLab | Bitbucket | BitbucketServer ] is used for building the edit link based on git hoster
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
title : "WAHA"
|
||||
description: "WAHA - WhatsApp HTTP API that you can configure in a click!"
|
||||
lead: "WAHA - WhatsApp HTTP API that you can configure in a click!"
|
||||
description: "WAHA - WhatsApp HTTP API that you can run in a click!"
|
||||
lead: "WAHA - WhatsApp HTTP API that you can run in a click!"
|
||||
date: 2020-10-06T08:47:36+00:00
|
||||
lastmod: 2020-10-06T08:47:36+00:00
|
||||
draft: false
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
title: "Python"
|
||||
description: "WhatsApp HTTP API Python example."
|
||||
lead: "WhatsApp HTTP API Python example."
|
||||
description: "WhatsApp HTTP API Python examples."
|
||||
lead: "WhatsApp HTTP API Python examples."
|
||||
date: 2020-10-06T08:49:31+00:00
|
||||
lastmod: 2020-10-06T08:49:31+00:00
|
||||
draft: false
|
||||
@@ -12,41 +12,7 @@ parent: "help"
|
||||
weight: 630
|
||||
toc: true
|
||||
---
|
||||
We've created a few examples on how to interact with WAHA WhatsApp HTTP API with Python.
|
||||
|
||||
To show how to receive messages we've created a simple "echo" server with two functions:
|
||||
You can find the examples in [examples folder on GitHub with detailed instructions in README.md](https://github.com/devlikeapro/whatsapp-http-api/tree/core/examples/python)
|
||||
|
||||
1. When we receive a text message - just send the text back
|
||||
2. When we receive a message with a file (an image, a voice message) - download it and send the path back 
|
||||
|
||||
In order to send you messages we use **Webhooks** and configure them via environments variables. So
|
||||
what you need to
|
||||
create "echo" server is HTTP server that will receive JSON POST request and then call back WhatsApp HTTP API
|
||||
via `POST /api/sendText` endpoint with JSON body.
|
||||
|
||||
## Python echo server
|
||||
|
||||
We use Python. Feel free to create your favorite language example and contribute to the project!
|
||||
|
||||
Run "echo" server in one terminal and leave it working:
|
||||
|
||||
```bash
|
||||
# if you haven't already
|
||||
git clone https://github.com/devlikeapro/whatsapp-http-api.git
|
||||
cd whatsapp-http-api
|
||||
python -mpip install -r examples/requirements.txt
|
||||
export FLASK_APP=examples/echo.py
|
||||
flask run
|
||||
```
|
||||
|
||||
Visit [http://localhost:5000](http://localhost:5000) and check that we are good to go further.
|
||||
|
||||
Let's start WhatsApp HTTP API and configure the "on message" webhook and point it on our "http://localhost:5000/message"
|
||||
endpoint:
|
||||
|
||||
```bash
|
||||
docker run -it --network=host -e WHATSAPP_HOOK_URL=http://localhost:5000/message -e WHATSAPP_HOOK_EVENTS=* devlikeapro/whatsapp-http-api
|
||||
```
|
||||
|
||||
Now go ahead, open the second whatsapp and send to our WhatsApp HTTP API a text message! It must reply the same text.
|
||||
|
||||
 If you try to send an image the "echo server" will send a path to the downloaded file.
|
||||
@@ -1,10 +0,0 @@
|
||||
---
|
||||
title: "Help"
|
||||
description: "Help Doks."
|
||||
lead: ""
|
||||
date: 2020-10-06T08:49:15+00:00
|
||||
lastmod: 2020-10-06T08:49:15+00:00
|
||||
draft: false
|
||||
images: []
|
||||
weight: 800
|
||||
---
|
||||
@@ -1,73 +0,0 @@
|
||||
---
|
||||
title: "Changelog"
|
||||
description: "WAHA's changelog"
|
||||
lead: "Changelog"
|
||||
date: 2020-10-06T08:49:31+00:00
|
||||
lastmod: 2020-10-06T08:49:31+00:00
|
||||
draft: false
|
||||
images: []
|
||||
menu:
|
||||
docs:
|
||||
parent: "help"
|
||||
weight: 600
|
||||
toc: true
|
||||
---
|
||||
## 2021.1
|
||||
- Added [contacts API]({{< relref "/docs/how-to/contacts" >}})
|
||||
- Get all contacts
|
||||
- Get a contact
|
||||
- Get contact "about" (status)
|
||||
- Get contact profile picture
|
||||
- Check number exists (is registered in WhatsApp) - works even if the number is not in the contact list
|
||||
- Block and unblock contact
|
||||
|
||||
## 2022.12
|
||||
- Added `GET /messages/` endpoint to get chat messages [#31](https://github.com/devlikeapro/whatsapp-http-api/issues/31)
|
||||
|
||||
### Security 
|
||||
- Added `WHATSAPP_SWAGGER_USERNAME` and `WHATSAPP_SWAGGER_PASSWORD` to hide and protect swagger panel.
|
||||
|
||||
## 2022.11
|
||||
**Please test changes in test environment before update production!!**
|
||||
|
||||
### Engine  
|
||||
1. WAHA has changed its underlying engine from Venom to Whatsapp Web.JS. It might change the response and webhook's payloads.
|
||||
2. Optimize CPU and memory consumption.
|
||||
|
||||
|
||||
### Requests  
|
||||
- For all `/api/session/` requests use `name` field instead of `sessionName`.
|
||||
- For all "chatting" requests use `session` field instead of `sessionName`.
|
||||
|
||||
### Sessions 
|
||||
Now you don't have to scan QR code each time you run WAHA, WAHA saves it for you! Available only in Plus version.
|
||||
|
||||
### Authentication 
|
||||
Now you can authenticate all requests for WAHA - use `WHATSAPP_API_KEY=secret` environment variable to set "secret key".
|
||||
|
||||
If `WHATSAPP_API_KEY` is set - requests must have `X-Api-Key` header with `secret` value, where `secret` - any random secret key.
|
||||
|
||||
### Webhooks  
|
||||
#### Configuration
|
||||
|
||||
Instead of setting each webhook via environment variables - we use two environments variables:
|
||||
|
||||
- `WHATSAPP_HOOK_URL` - to set a URL
|
||||
- `WHATSAPP_HOOK_EVENTS` - to set events that are sent to the URL
|
||||
|
||||
| Previous | Current |
|
||||
|----------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------|
|
||||
| <pre>WHATSAPP_HOOK_ONMESSAGE=https://httpbin.org/post <br>WHATSAPP_HOOK_ONANYMESSAGE=https://httpbin.org/post </pre> | <pre>WHATSAPP_HOOK_URL=https://httpbin.org/post <br>WHATSAPP_HOOK_EVENTS=message,message.any</pre> |
|
||||
|
||||
#### Payload
|
||||
|
||||
The data for webhooks are wrapped inside a new `WAWebhook` object with `event` and `payload` fields to help you identify
|
||||
which handler you should call based on `event`.
|
||||
|
||||
```json
|
||||
{
|
||||
"event": "message.any",
|
||||
"payload": {
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -1,34 +0,0 @@
|
||||
---
|
||||
title: "How to Update"
|
||||
description: "Regularly update the installed WAHA to keep your bot stable, usable, and secure."
|
||||
lead: "Regularly update the installed WAHA to keep your bot stable, usable, and secure."
|
||||
date: 2020-11-12T13:26:54+01:00
|
||||
lastmod: 2020-11-12T13:26:54+01:00
|
||||
draft: false
|
||||
images: []
|
||||
menu:
|
||||
docs:
|
||||
parent: "help"
|
||||
weight: 610
|
||||
toc: true
|
||||
---
|
||||
|
||||
{{< alert icon="💡" text="Please test all new versions in the development environment before updating production!" />}}
|
||||
|
||||
## Core
|
||||
 For Core version the command is
|
||||
|
||||
```bash
|
||||
docker pull devlikeapro/whatsapp-http-api
|
||||
```
|
||||
|
||||
## Plus
|
||||
 For Plus version, we use login to get the image before:
|
||||
|
||||
```bash
|
||||
docker login -u devlikeapro -p {PASSWORD}
|
||||
docker pull devlikeapro/whatsapp-http-api-plus
|
||||
docker logout
|
||||
```
|
||||
|
||||
Read more about how to get `PASSWORD` for [Plus Version →]({{< relref "plus-version" >}})
|
||||
@@ -0,0 +1,41 @@
|
||||
---
|
||||
title : "Chats"
|
||||
description: "Chats"
|
||||
lead: ""
|
||||
date: 2020-10-06T08:48:45+00:00
|
||||
lastmod: 2020-10-06T08:48:45+00:00
|
||||
draft: false
|
||||
images: []
|
||||
weight: 127
|
||||
---
|
||||
|
||||
Chats methods.
|
||||
|
||||
Parameters in path that you can find in below endpoints:
|
||||
- `{session}` - use the session name for Whatsapp instance that you created with `POST /api/session/start` endpoint
|
||||
- `{chatId}` - chat id in format `123123123123@[c.us|g.us]`, `c.us` for direct chats and `g.us` for groups.
|
||||
|
||||
## Methods
|
||||
### Get all chats
|
||||
Get all chats
|
||||
`GET /api/{session}/chats`
|
||||
|
||||
### Get messages from chat
|
||||
Get 100 messages from the chat
|
||||
|
||||
`GET /api/{session}/chats/{chatId}/messages?limit=100`
|
||||
|
||||
|
||||
Get 100 messages from the chat, skip downloading media (images, files)
|
||||
|
||||
`GET /api/{session}/chats/{chatId}/messages?limit=100&downloadMedia=false`
|
||||
|
||||
### Delete chat
|
||||
Use the method to delete chat
|
||||
|
||||
`DELETE /api/{session}/chats/{chatId}`
|
||||
|
||||
### Clear messages
|
||||
Use the method to clear all messages from the chat
|
||||
|
||||
`DELETE /api/{session}/chats/{chatId}/messages`
|
||||
@@ -1,58 +1,157 @@
|
||||
---
|
||||
title : "Configuration"
|
||||
title: "Configuration"
|
||||
description: "Configuration"
|
||||
lead: ""
|
||||
date: 2020-10-06T08:48:45+00:00
|
||||
lastmod: 2020-10-06T08:48:45+00:00
|
||||
draft: false
|
||||
images: []
|
||||
images: [ ]
|
||||
weight: 800
|
||||
---
|
||||
|
||||
You can configure WhatsApp HTTP API behaviour via environment variables, by adding `-e WHATSAPP_VARNAME=value` at the
|
||||
begging of the command line or by using [other options](https://docs.docker.com/engine/reference/commandline/run/)
|
||||
|
||||
```bash
|
||||
docker run -it -e WHATSAPP_HOOK_EVENTS=* -e WHATSAPP_HOOK_URL=https://httpbin.org/post devlikeapro/whatsapp-http-api
|
||||
docker run -it -e "WHATSAPP_HOOK_EVENTS=*" -e WHATSAPP_HOOK_URL=https://httpbin.org/post devlikeapro/whatsapp-http-api
|
||||
```
|
||||
|
||||
It's not necessary to always run such a long command - you can save all data in
|
||||
[docker-compose.yaml](https://github.com/devlikeapro/whatsapp-http-api/blob/core/docker-compose.yaml)
|
||||
file as described on [How to deploy page ->]({{< relref "/docs/how-to/deploy" >}}).
|
||||
|
||||
## Environment variables
|
||||
|
||||
- `DEBUG=1` - show debug and verbose logs, set in any value
|
||||
- `WHATSAPP_API_PORT=3000` - listen port for HTTP server (default: `3000`)
|
||||
- `WHATSAPP_API_HOSTNAME=localhost` - Hostname for HTTP server (default: `localhost`)
|
||||
- `WHATSAPP_API_KEY=mysecret` - protect the api with a secret code. If you set it - add `X-Api-Key: mysecret` to all
|
||||
requests.
|
||||
- `WHATSAPP_SWAGGER_USERNAME=admin` + `WHATSAPP_SWAGGER_PASSWORD=admin` - protect the Swagger panel with `admin / admin`
|
||||
credentials. It doesn't affect api access!
|
||||
- `WHATSAPP_START_SESSION=default` - start session with the name right after launching the app
|
||||
The following environment variables can be used to configure the WAHA.
|
||||
|
||||
## File storage 
|
||||
### Common
|
||||
- `DEBUG=1`: Set this variable to any value to enable debug and verbose logs.
|
||||
- `WHATSAPP_API_PORT=3000`: The port number that the HTTP server will listen on. The default value is `3000`.
|
||||
- `WHATSAPP_API_HOSTNAME=localhost`: The hostname for the HTTP server. The default value is `localhost`.
|
||||
|
||||
- `WHATSAPP_FILES_MIMETYPES` - download only these mimetypes from messages (download all files be default). Mimetypes
|
||||
must be separated by a comma, without spaces: `audio,image/png,image/gif`. In order to choose type use prefix (
|
||||
like `audio,image`).
|
||||
- `WHATSAPP_FILES_LIFETIME`- to keep free space files will be removed after this time (default: `180`, in seconds)
|
||||
- `WHATSAPP_FILES_FOLDER` - folder where will be stored files from chats (images, voice messages) (
|
||||
default: `/tmp/whatsapp-files`)
|
||||
### Sessions
|
||||
- `WHATSAPP_RESTART_ALL_SESSIONS=True`: Set this variable to `True` to start all **STOPPED** sessions after container
|
||||
restarts. By default, this variable is set to `False`.
|
||||
- Please note that this will start all **STOPPED** sessions, not just the sessions that were working before the restart. You can maintain the session list by
|
||||
using `POST /api/session/stop` with the `logout: True` parameter or by calling `POST /api/session/logout` to remove
|
||||
**STOPPED** sessions. You can see all sessions, including **STOPPED** sessions, in the `GET /api/sessions/all=True`
|
||||
response.
|
||||
- `WHATSAPP_START_SESSION=session1,session2`: This variable can be used to start sessions with the specified names right
|
||||
after launching the API. Separate session names with a comma.
|
||||
|
||||
## Engines
|
||||
### 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.
|
||||
|
||||
Under the hood **WAHA** allows you to use two different engines. You can control what you want to run by settings
|
||||
`WHATSAPP_DEFAULT_ENGINE` environment variables.
|
||||
### Proxy
|
||||
#### Global proxy configuration
|
||||
If you need to use a proxy, you can set the following environment variables:
|
||||
|
||||
If you have any problems with one engine - try another!
|
||||
- `WHATSAPP_PROXY_SERVER=localhost:3128`: Use this variable to set the proxy server in the format `host:port`, without http or https.
|
||||
- `WHATSAPP_PROXY_SERVER_USERNAME=username` and `WHATSAPP_PROXY_SERVER_PASSWORD=password`: Use these variables to set up credentials for the proxy.
|
||||
- `WHATSAPP_PROXY_SERVER_LIST=host1.example.com:3138,host2.example.com:3138`: Use this variable to set a comma-separated list of addresses to use, using a round-robin algorithm to choose the server for the session.
|
||||
- `WHATSAPP_PROXY_SERVER_INDEX_PREFIX=proxy-`: Use this variable to parse the session name for the prefix and find the appropriate session.
|
||||
For example, if you have set `WHATSAPP_PROXY_SERVER_LIST=host-first:80,host-second:80,host-third:80` and `WHATSAPP_PROXY_SERVER_INDEX_PREFIX=proxy-` and you run `proxy-3` session, the `host-third:80` proxy will be chosen for that session.
|
||||
This is a way to select a proxy from while you start session.
|
||||
|
||||
{{< alert icon="👉" text="API responses and webhook payloads may differ significantly! We do our best to keep requests body stable between engines." />}}
|
||||
#### Session proxy configuration
|
||||
You can configure proxy when you start session by setting `config.proxy` fields.
|
||||
Read more about it on [**Session page** ->]({{< relref "/docs/how-to/sessions#configure-proxy" >}}).
|
||||
|
||||
### WhatsApp WebJS 
|
||||
Keep in mind that session's proxy configuration takes precedence over proxy configuration set by environment variables!
|
||||
|
||||
**WAHA** uses **WhatsApp WebJS** engine by default
|
||||
|
||||
- https://github.com/pedroslopez/whatsapp-web.js
|
||||
- `WHATSAPP_DEFAULT_ENGINE=WEBJS`
|
||||
### Security 
|
||||
- `WHATSAPP_API_KEY=mysecret`: If you set this variable, you must include the `X-Api-Key: mysecret` header in all
|
||||
requests to the API. This will protect the API with a secret code.
|
||||
- `WHATSAPP_SWAGGER_USERNAME=admin` and `WHATSAPP_SWAGGER_PASSWORD=admin`: These variables can be used to protect the
|
||||
Swagger panel with `admin / admin` credentials. This does not affect API access.
|
||||
|
||||
### Venom 
|
||||
|
||||
- https://github.com/orkestral/venom
|
||||
- `WHATSAPP_DEFAULT_ENGINE=VENOM`
|
||||
### Files 
|
||||
|
||||
The following environment variables can be used to configure the file storage options for the WAHA:
|
||||
|
||||
- `WHATSAPP_FILES_MIMETYPES`: This variable can be used to download only specific mimetypes from messages. By default,
|
||||
all files are downloaded. The mimetypes must be separated by a comma, without spaces. For
|
||||
example: `audio,image/png,image/gif`. To choose a specific type, use a prefix (like `audio,image`).
|
||||
- `WHATSAPP_FILES_LIFETIME`: This variable can be used to set the time (in seconds) after which files will be removed to
|
||||
free up space. The default value is `180`.
|
||||
- `WHATSAPP_FILES_FOLDER`: This variable can be used to set the folder where files from chats (images, voice messages)
|
||||
will be stored. The default value is `/tmp/whatsapp-files`.
|
||||
|
||||
## Examples
|
||||
|
||||
#### Debug Mode
|
||||
|
||||
To enable debug mode, set the `DEBUG` environment variable to any value:
|
||||
|
||||
```
|
||||
DEBUG=1
|
||||
```
|
||||
|
||||
#### Protecting the API with a Secret Code
|
||||
|
||||
To protect the API with a secret code, set the `WHATSAPP_API_KEY` environment variable to your secret code:
|
||||
|
||||
```
|
||||
WHATSAPP_API_KEY=mysecret
|
||||
```
|
||||
|
||||
You must include the `X-Api-Key: mysecret` header in all requests to the API.
|
||||
|
||||
#### Starting Sessions Automatically
|
||||
|
||||
To start sessions automatically when the API is launched, set the `WHATSAPP_START_SESSION` environment variable to a
|
||||
comma-separated list of session names:
|
||||
|
||||
```
|
||||
WHATSAPP_START_SESSION=session1,session2
|
||||
```
|
||||
|
||||
#### Restarting All Sessions
|
||||
|
||||
To start all **STOPPED** sessions after container restarts, set the `WHATSAPP_RESTART_ALL_SESSIONS` environment variable
|
||||
to `True`:
|
||||
|
||||
```
|
||||
WHATSAPP_RESTART_ALL_SESSIONS=True
|
||||
```
|
||||
|
||||
#### Protecting the Swagger Panel
|
||||
|
||||
To protect the Swagger panel with `admin / admin` credentials, set the `WHATSAPP_SWAGGER_USERNAME`
|
||||
and `WHATSAPP_SWAGGER_PASSWORD` environment variables:
|
||||
|
||||
```
|
||||
WHATSAPP_SWAGGER_USERNAME=admin
|
||||
WHATSAPP_SWAGGER_PASSWORD=admin
|
||||
```
|
||||
|
||||
|
||||
#### Downloading Specific Mimetypes
|
||||
|
||||
To download only specific mimetypes from messages, set the `WHATSAPP_FILES_MIMETYPES` environment variable to a
|
||||
comma-separated list of mimetypes:
|
||||
|
||||
```
|
||||
WHATSAPP_FILES_MIMETYPES=audio,image/png,image/gif
|
||||
```
|
||||
|
||||
#### Setting the File Lifetime
|
||||
|
||||
To set the time (in seconds) after which files will be removed to free up space, set the `WHATSAPP_FILES_LIFETIME`
|
||||
environment variable:
|
||||
|
||||
```
|
||||
WHATSAPP_FILES_LIFETIME=300
|
||||
```
|
||||
|
||||
#### Setting the File Storage Folder
|
||||
|
||||
To set the folder where files from chats (images, voice messages) will be stored, set the `WHATSAPP_FILES_FOLDER`
|
||||
environment variable:
|
||||
|
||||
```
|
||||
WHATSAPP_FILES_FOLDER=/home/user/whatsapp-files
|
||||
```
|
||||
@@ -0,0 +1,90 @@
|
||||
---
|
||||
title: "Deploy"
|
||||
description: "How to deploy WAHA"
|
||||
lead: ""
|
||||
date: 2020-10-06T08:48:45+00:00
|
||||
lastmod: 2020-10-06T08:48:45+00:00
|
||||
draft: false
|
||||
images: [ ]
|
||||
weight: 899
|
||||
---
|
||||
|
||||
This page provides useful information to assist you in deploying the project.
|
||||
It includes tips on how to avoid running long command lines, how to view logs, and other helpful advice.
|
||||
|
||||
## How to Use Docker Compose
|
||||
|
||||
Docker Compose is a tool that allows you to define and run multi-container Docker applications. It simplifies the
|
||||
process of managing multiple containers and their dependencies. In this topic, we will walk you through how to use
|
||||
Docker Compose with the WhatsApp HTTP API.
|
||||
|
||||
### Prerequisites
|
||||
|
||||
Before you start, you need to have Docker and Docker Compose installed on your machine. You can download Docker from the
|
||||
official website [here](https://www.docker.com/products/docker-desktop). Docker Compose is included with Docker Desktop
|
||||
for Windows and macOS, but for Linux, you need to install it separately. You can find the installation
|
||||
instructions [here](https://docs.docker.com/compose/install/).
|
||||
|
||||
### Getting Started
|
||||
|
||||
To get started, you need to clone the [docker-compose.yaml](https://github.com/devlikeapro/whatsapp-http-api/blob/core/docker-compose.yaml) file to your local machine.
|
||||
Open your terminal and run the following command:
|
||||
|
||||
```
|
||||
wget https://raw.githubusercontent.com/devlikeapro/whatsapp-http-api/core/docker-compose.yaml
|
||||
```
|
||||
|
||||
### Running the WhatsApp HTTP API Service
|
||||
|
||||
To start the `whatsapp-http-api` service, run the following command:
|
||||
|
||||
```
|
||||
docker-compose up -d whatsapp-http-api
|
||||
```
|
||||
|
||||
This will start the container in detached mode and map port 3000 to 3000 on the host machine.
|
||||
|
||||
To stop the service, run the command:
|
||||
|
||||
```
|
||||
docker-compose down
|
||||
```
|
||||
|
||||
This will stop and remove the container.
|
||||
|
||||
To restart the service, run the command:
|
||||
|
||||
```
|
||||
docker-compose restart whtasapp-http-api
|
||||
```
|
||||
|
||||
This will restart the container.
|
||||
|
||||
### Viewing the Logs
|
||||
|
||||
To view the logs for the `whatsapp-http-api` service, run the command:
|
||||
|
||||
```
|
||||
docker-compose logs -f whatsapp-http-api
|
||||
```
|
||||
|
||||
This will show the logs in real-time. To view the logs for the `whatsapp-http-api-plus` service,
|
||||
replace `whatsapp-http-api` with `whatsapp-http-api-plus`.
|
||||
|
||||
To view the logs for a specific period, run the command:
|
||||
|
||||
```
|
||||
docker-compose logs --since <time>
|
||||
```
|
||||
|
||||
Replace `<time>` with the time period you want to view the logs for. For example, `docker-compose logs --since 1h` will
|
||||
show the logs for the past hour.
|
||||
|
||||
### Using the WhatsApp HTTP API Services
|
||||
|
||||
There are two services provided in the `docker-compose.yml` file - `whatsapp-http-api` and `whatsapp-http-api-plus`.
|
||||
It is important to note that these services must not be run at the same time.
|
||||
By default, the `whatsapp-http-api` service will be used.
|
||||
|
||||
To use the `whatsapp-http-api-plus` service, you need to specify it in the command.
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
---
|
||||
title: "Engines"
|
||||
description: "Engines"
|
||||
lead: ""
|
||||
date: 2020-10-06T08:48:45+00:00
|
||||
lastmod: 2020-10-06T08:48:45+00:00
|
||||
draft: false
|
||||
images: [ ]
|
||||
weight: 800
|
||||
---
|
||||
|
||||
Under the hood **WAHA** allows you to use two different engines. You can control what you want to run by settings
|
||||
`WHATSAPP_DEFAULT_ENGINE` environment variables.
|
||||
|
||||
```bash
|
||||
docker run -it -e "WHATSAPP_DEFAULT_ENGINE=WEBJS" devlikeapro/whatsapp-http-api
|
||||
```
|
||||
|
||||
If you have any problems with one engine - try another!
|
||||
|
||||
{{< alert icon="👉" text="API responses and webhook payloads may differ significantly! We do our best to keep requests body stable between engines." />}}
|
||||
|
||||
All engines are available in Core version 
|
||||
|
||||
## WEBJS
|
||||
|
||||
A WhatsApp API client that connects through the WhatsApp Web browser app.
|
||||
It uses Puppeteer to run a real instance of Whatsapp Web to avoid getting blocked.
|
||||
|
||||
**WAHA** uses **WhatsApp WebJS** engine by default.
|
||||
|
||||
- `WHATSAPP_DEFAULT_ENGINE=WEBJS`
|
||||
- https://github.com/pedroslopez/whatsapp-web.js
|
||||
|
||||
## NOWEB
|
||||
|
||||
**NOWEB** engine does not require a browser to work with WhatsApp Web, it does so directly using a WebSocket.
|
||||
Not running Chromium saves you CPU and Memory, so you can run more instances on a single server!
|
||||
|
||||
️Working with the engine requires more attention, because it's easy to be blocked with it!
|
||||
|
||||
⚠ Read the article before using **NOWEB** engine [How to avoid blocking ->]({{< relref "/docs/overview/how-to-avoid-blocking" >}}).
|
||||
|
||||
- `WHATSAPP_DEFAULT_ENGINE=NOWEB`
|
||||
- https://github.com/WhiskeySockets/Baileys
|
||||
|
||||
## VENOM
|
||||
|
||||
It's a high-performance system developed with JavaScript to create a bot for WhatsApp
|
||||
|
||||
- `WHATSAPP_DEFAULT_ENGINE=VENOM`
|
||||
- https://github.com/orkestral/venom
|
||||
@@ -0,0 +1,179 @@
|
||||
---
|
||||
title : "Groups"
|
||||
description: "Groups"
|
||||
lead: ""
|
||||
date: 2020-10-06T08:48:45+00:00
|
||||
lastmod: 2020-10-06T08:48:45+00:00
|
||||
draft: false
|
||||
images: []
|
||||
weight: 140
|
||||
---
|
||||
|
||||
Methods for groups. Please look at swagger for details.
|
||||
|
||||
- `{session}` - use the session name for Whatsapp instance that you created with `POST /api/session/start` endpoint
|
||||
- `{groupId}` - group id in format `123123123123@g.us`. You can get the id in a few ways:
|
||||
- By [handling incoming message webhook]({{< relref "/docs/how-to/receive-messages" >}}).
|
||||
- By getting all groups (see below).
|
||||
- By creating a new group and saving the id.
|
||||
|
||||
## Create a new group
|
||||
|
||||
`POST /api/{session}/groups`
|
||||
|
||||
Request:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "Group name",
|
||||
"participants": [
|
||||
{
|
||||
"id": "123123123123@c.us"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Get all groups
|
||||
|
||||
`GET /api/{session}/groups`
|
||||
|
||||
## Get the group
|
||||
|
||||
`GET /api/{session}/groups/{groupId}`
|
||||
|
||||
## Delete the group
|
||||
|
||||
`DELETE /api/{session}/groups/{groupId}`
|
||||
|
||||
## Leave the group
|
||||
|
||||
`POST /api/{session}/groups/{groupId}/leave`
|
||||
|
||||
## Set group subject
|
||||
|
||||
Updates the group subject.
|
||||
|
||||
Returns `true` if the subject was properly updated. This can return false if the user does not have the necessary
|
||||
permissions.
|
||||
|
||||
`PUT /api/{session}/groups/{groupId}/subject`
|
||||
|
||||
Request:
|
||||
|
||||
```json
|
||||
{
|
||||
"subject": "Group name"
|
||||
}
|
||||
```
|
||||
|
||||
## Set group description
|
||||
|
||||
Updates the group description.
|
||||
|
||||
Returns `true` if the subject was properly updated. This can return false if the user does not have the necessary
|
||||
permissions.
|
||||
|
||||
`PUT /api/{session}/groups/{groupId}/description`
|
||||
|
||||
Request:
|
||||
|
||||
```json
|
||||
{
|
||||
"description": "Group description"
|
||||
}
|
||||
```
|
||||
|
||||
## Settings
|
||||
### Security for group info
|
||||
Updates the group settings to only allow admins to edit group info (title, description, photo).
|
||||
|
||||
`PUT /api/{session}/groups/{groupId}/settings/security/info-admin-only`
|
||||
|
||||
The request doesn't require any request's body and
|
||||
**can't be used to disable the security setting (allow ANY to edit group info)**.
|
||||
If you wish to have that ability - please [create an issue](https://github.com/devlikeapro/whatsapp-http-api/issues)
|
||||
|
||||
|
||||
## Participants
|
||||
|
||||
### Get participants
|
||||
|
||||
`GET /api/{session}/groups/{groupId}/participants`
|
||||
|
||||
### Add participants
|
||||
|
||||
`POST /api/{session}/groups/{groupId}/participants/add`
|
||||
|
||||
```json
|
||||
{
|
||||
"participants": [
|
||||
{
|
||||
"id": "123123123123@c.us"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Remove participants
|
||||
|
||||
`POST /api/{session}/groups/{groupId}/participants/remove`
|
||||
|
||||
```json
|
||||
{
|
||||
"participants": [
|
||||
{
|
||||
"id": "123123123123@c.us"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Admin
|
||||
|
||||
### Promote to admin
|
||||
|
||||
Promote participants to admin users.
|
||||
|
||||
`POST /api/{session}/groups/{groupId}/admin/promote`
|
||||
|
||||
```json
|
||||
{
|
||||
"participants": [
|
||||
{
|
||||
"id": "123123123123@c.us"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Demote to regular users
|
||||
|
||||
Demote participants by to regular users.
|
||||
|
||||
`POST /api/{session}/groups/{groupId}/admin/demote`
|
||||
|
||||
```json
|
||||
{
|
||||
"participants": [
|
||||
{
|
||||
"id": "123123123123@c.us"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Invite code
|
||||
|
||||
### Get invite code
|
||||
|
||||
`GET /api/{session}/groups/{groupId}/invite-code`
|
||||
|
||||
Then you can put it in the url `https://chat.whatsapp.com/{inviteCode}` and send it to contacts.
|
||||
|
||||
### Revoke invite code
|
||||
|
||||
Invalidates the current group invite code and generates a new one.
|
||||
|
||||
`POST /api/{session}/groups/{groupId}/invite-code/revoke`
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 301 KiB |
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?><svg width="145px" height="40px" viewBox="0 0 145 40" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><linearGradient x1="61.5370404%" y1="13%" x2="34.2376801%" y2="129.365079%" id="linearGradient-1"><stop stop-color="#EF7829" offset="0%"/><stop stop-color="#F0692A" offset="28%"/><stop stop-color="#F15E2C" offset="63%"/><stop stop-color="#F15A2C" offset="100%"/></linearGradient></defs><g id="Symbols" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"><g id="Blocks/Header_Unlogged" transform="translate(-15.000000, -15.000000)"><g id="icons/boosty_logo" transform="translate(15.000000, 15.000000)"><g id="Shape"><path d="M138.119363,13.14856 L132.002653,22.63144 L131.397878,13.14856 L122.575597,13.14856 C123.384615,10.366 124.037135,8.13256 124.037135,8.13256 L124.381963,7.01848 L117.6313,7.01848 L117.312997,8.13256 L115.851459,13.14856 L108.660477,13.14856 C102.750663,13.14856 99.4244032,14.8276 98.0503979,18.00352 C97.2546419,15.09952 94.6206897,13.14856 90.5782493,13.14856 C87.2902077,13.135208 84.1128672,14.3300173 81.6551724,16.504 C80.464191,14.45536 78.1061008,13.1512 74.8116711,13.1512 C71.2975199,13.1360778 67.9189855,14.5000837 65.4084881,16.94752 C64.3103448,14.656 61.8541114,13.14856 58.3262599,13.14856 C57.1586378,13.1510938 55.9961737,13.302814 54.867374,13.6 L55.397878,11.77312 C55.4066092,11.7439195 55.4181572,11.7156279 55.4323607,11.68864 L56.7904509,7 L50.066313,7 L45.7798408,21.73384 C45.668435,22.02952 45.5676393,22.32784 45.4801061,22.63144 C45.2902336,23.2752559 45.1623364,23.9355911 45.0981432,24.60352 C44.5384615,28.714 46.3474801,31.816 51.3660477,32.0272 C51.8475451,32.0843575 52.332067,32.1125687 52.8169761,32.11168 C56.2925823,32.0722669 59.6211655,30.7100879 62.1193634,28.3048 C63.1564987,30.6148 65.5676393,32.11168 69.2970822,32.11168 C72.5436452,32.0790306 75.6705362,30.8873894 78.1087533,28.7536 C79.2493369,30.80488 81.5862069,32.11168 85.061008,32.11168 L101.917772,32.11168 C107.143236,32.11168 110.095491,31.08472 111.6313,28.85392 C111.6313,30.98176 112.851459,32.11168 115.941645,32.11168 C118.437666,32.11168 121.777188,31.5652 126.366048,30.43792 L119.687003,39.9208 L126.411141,39.9208 L144.846154,13.14856 L138.119363,13.14856 Z M58.9310345,22.63144 C58.3023873,24.79096 56.2785146,26.53336 54.4217507,26.53336 C52.5649867,26.53336 51.5676393,24.79096 52.1962865,22.63144 C52.8249337,20.47192 54.8488064,18.72688 56.6923077,18.72688 C58.535809,18.72688 59.5596817,20.464 58.9310345,22.63144 Z M75.4164456,22.63144 C74.7877984,24.79096 72.7639257,26.53336 70.9071618,26.53336 C69.0503979,26.53336 68.0769231,24.79096 68.6923077,22.63144 C69.3076923,20.47192 71.3448276,18.72688 73.2015915,18.72688 C75.0583554,18.72688 76.0344828,20.464 75.4164456,22.63144 Z M84.4562334,22.63144 C85.0848806,20.46928 87.1087533,18.72688 88.9522546,18.72688 C90.795756,18.72688 91.8090186,20.46928 91.1803714,22.63144 C90.5517241,24.7936 88.5941645,26.4832 86.7586207,26.536 L86.6074271,26.536 C84.801061,26.4832 83.8355438,24.76192 84.4562334,22.63144 Z M106.127321,25.85752 C105.787798,26.64952 103.132626,26.53072 102.466844,26.54128 L95.928382,26.536 C96.8103436,25.360374 97.4758216,24.0383816 97.8938992,22.63144 C97.9363395,22.48888 97.9734748,22.34896 98.0079576,22.20904 C98.7108753,23.2096 100.201592,24.11248 103.100796,24.68536 C105.864721,25.216 106.3687,25.05496 106.127321,25.85752 Z M112.777188,23.7244 C112.204244,21.49624 109.721485,20.6224 106.241379,20.2 C104.785146,20.02576 103.965517,19.95712 104.148541,19.3288 C104.289125,18.8404 105.018568,18.73744 106.586207,18.73744 L114.230769,18.73744 L112.777188,23.7244 Z M119.496021,23.74552 C119.496021,23.70064 120.151194,21.4804 120.952255,18.72952 L125.217507,18.72952 L125.915119,25.8364 C118.888594,27.328 118.713528,26.92672 119.496021,23.74552 L119.496021,23.74552 Z" fill="#242B2C" fill-rule="nonzero"/><path d="M1.03841146,23.8920635 L7.81508464,0 L18.2158138,0 L16.1119922,7.40740741 C16.0897554,7.44994435 16.0716695,7.49460291 16.0579948,7.54074074 L10.5357227,27.1005291 L15.6903971,27.1005291 C13.537424,32.5848325 11.8524284,36.8846561 10.6354102,40 C1.12771484,39.8920635 -1.53892578,32.9481481 0.795423177,24.7322751 L1.03841146,23.8920635 Z M10.672793,40 L23.2084961,21.6 L17.8897526,21.6 L22.5169141,9.81375661 C30.4586849,10.6603175 34.1824284,17.0285714 31.993457,24.7301587 C29.6424935,33.015873 20.1285677,40 10.8659375,40 L10.672793,40 Z" fill="url(#linearGradient-1)" fill-rule="nonzero"/></g></g></g></g></svg>
|
||||
|
After Width: | Height: | Size: 4.4 KiB |
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title : "Plus version"
|
||||
description: "Plus version"
|
||||
title : "WAHA Plus"
|
||||
description: "WAHA Plus"
|
||||
lead: ""
|
||||
date: 2020-10-06T08:48:45+00:00
|
||||
lastmod: 2020-10-06T08:48:45+00:00
|
||||
@@ -16,39 +16,21 @@ weight: 900
|
||||
1. Core  the basic version that meet almost 80% people’s needs. 100% free and open source.
|
||||
2. Plus  the version with advanced messages, security, and reliability features.
|
||||
|
||||
**If you enjoy Core  version and would like to support the project** - you can subscribe on Core level.
|
||||
It's not expensive, but it supports the project a lot!
|
||||
- [Boosty ->](https://boosty.to/wa-http-api)
|
||||
- [Patreon ->](https://patreon.com/wa_http_api)
|
||||
You support the project and get **WAHA Plus** on
|
||||
<a href="https://boosty.to/wa-http-api" target="_blank">
|
||||
<b>
|
||||
Boosty ->
|
||||
</b>
|
||||
</a>
|
||||
or
|
||||
<a href="https://patreon.com/wa_http_api" target="_blank">
|
||||
<b>
|
||||
Patreon ->
|
||||
</b>
|
||||
</a>
|
||||
.
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
The Plus additional features are:
|
||||
|
||||
1. [**Send images\files\voices** →]({{< relref "/docs/how-to/send-messages" >}})
|
||||
2. [**Receive images\files\voices** →]({{< relref "/docs/how-to/receive-messages" >}})
|
||||
3. [**Webhook retries** →]({{< relref "/docs/how-to/receive-messages#retries" >}})
|
||||
4. [**Security** →]({{< relref "/docs/how-to/security" >}})
|
||||
5. [**Saving sessions** →]({{< relref "/docs/how-to/sessions" >}})
|
||||
6. [**Support multiple sessions** →]({{< relref "/docs/how-to/sessions" >}})
|
||||
7. **Instant updates** - no one month delay in getting bugfixes or new features
|
||||
8. **Priority in support** - on both bugs and features
|
||||
|
||||
## Differences
|
||||
|
||||
What is the difference between **WAHA Plus** and other SaaS solutions for WhatsApp HTTP API?
|
||||
|
||||
| | WAHA Plus | Others |
|
||||
|--------------------|:-------------------------------------------------:|----------------------------------------------------------------------------------:|
|
||||
| **Sessions** | ✔️ No limits on **accounts** or **servers**! | ➖ One account for $50/month. |
|
||||
| **Infrastructure** | ✔️ On-Premise - your server, your infrastructure! | ➖ Insecure cloud solution. |
|
||||
| **Security** | ✔️ No data leaks! | ➖ You have to give access to customers' data - phone number, name, your messages. |
|
||||
| **License Term** | ✔️ No license checks! | ➖ One month |
|
||||
| **Message price** | ✔️ All messages are Free! | ➖ Some solutions have a price for messages - $1/100 messages. |
|
||||
| **Expenses** | ✔️ $19 **donation**! | ➖ Monthly payment starts from $50 |
|
||||
|
||||
## Donations
|
||||
### Donations
|
||||
|
||||
WAHA Plus version is available through **donations** (subscriptions).
|
||||
|
||||
@@ -67,29 +49,83 @@ Treat **WAHA Plus** it as an additional episode, behind-the-scenes videos, our n
|
||||
We donate most of the donations down to the stream to the underlying libraries and tools - JS-community, Node, other libraries authors, personal contributors.
|
||||
By donating to the **WAHA** you donate to the Open Source world!
|
||||
|
||||
### Features
|
||||
|
||||
The Plus additional features are:
|
||||
|
||||
1. [**Send images\files\voices** →]({{< relref "/docs/how-to/send-messages" >}})
|
||||
2. [**Receive images\files\voices** →]({{< relref "/docs/how-to/receive-messages" >}})
|
||||
3. [**Webhook retries** →]({{< relref "/docs/how-to/receive-messages#retries" >}})
|
||||
4. [**Security** →]({{< relref "/docs/how-to/security" >}})
|
||||
5. [**Saving sessions** →]({{< relref "/docs/how-to/sessions" >}})
|
||||
6. [**Support multiple sessions** →]({{< relref "/docs/how-to/sessions" >}})
|
||||
7. **Priority in support** - on both bugs and features
|
||||
|
||||
### Why WAHA?
|
||||
|
||||
What is the difference between **WAHA Plus** and **other SaaS solutions** for WhatsApp HTTP API?
|
||||
|
||||
| | WAHA Plus | Others |
|
||||
|--------------------|:-------------------------------------------------:|----------------------------------------------------------------------------------:|
|
||||
| **Sessions** | ✔️ No limits on **accounts** or **servers**! | ➖ One account for $50/month. |
|
||||
| **Infrastructure** | ✔️ On-Premise - your server, your infrastructure! | ➖ Insecure cloud solution. |
|
||||
| **Security** | ✔️ No data leaks! | ➖ You have to give access to customers' data - phone number, name, your messages. |
|
||||
| **License Term** | ✔️ No license checks! | ➖ One month |
|
||||
| **Message price** | ✔️ All messages are Free! | ➖ Some solutions have a price for messages - $1/100 messages. |
|
||||
| **Expenses** | ✔️ $19 **donation**! | ➖ Monthly payment starts from $50 |
|
||||
|
||||
|
||||
## Get Plus 
|
||||
|
||||
For $19 one-time donation you get:
|
||||
The more you support the project - the more you get!
|
||||
|
||||
Here's available tiers for our supporters:
|
||||
|
||||
| | Plus | Advanced | Pro |
|
||||
|-------------------------------------------------------------------------------------|---------|:--------:|:-------:|
|
||||
| Access to WAHA Plus  docker image. | ✔️ | ✔️ | ✔️ |
|
||||
| An invitation to a private Github repository so you have the WAHA Plus source code. | ➖️ | ✔️ | ✔️️ |
|
||||
| Access to a Telegram group where we provide support and assistance. | ➖️ | ➖ | ✔️ |
|
||||
| **Donation** | **$19** | **$39** | **$99** |
|
||||
|
||||
For **$19** donation you get:
|
||||
1. **Secret password** for Docker Hub to download Plus image: `devlikeapro/whatsapp-http-api-plus`.
|
||||
2. **All updates for one month** for the Plus image.
|
||||
3. **NO LICENSE CHECKS** on installed instances!
|
||||
4. **NO LICENSE EXPIRATION** on installed instances! We repeated it twice to make sure that you've got it 😊
|
||||
|
||||
{{< alert icon="👉" text="We don't mind if you stay subscribed with us after one month :)" />}}
|
||||
|
||||
### Boosty
|
||||
We use Boosty to get donations (Patreon analogue) - [https://boosty.to/wa-http-api](https://boosty.to/wa-http-api).
|
||||
|
||||
Go ahead and get your monthly password to get no license expiration WhatsApp HTTP API!
|
||||
|
||||
{{< alert icon="💡" text="If you stay subscribed - the donation amount will never change for you and always be $19 to get monthly updates!" />}}
|
||||
|
||||

|
||||
|
||||
### Boosty
|
||||
<p align="center">
|
||||
<img src="boosty.svg" alt="Patreon" />
|
||||
<br/>
|
||||
<br/>
|
||||
</p>
|
||||
|
||||
Support the project and get WAHA Plus version on
|
||||
<a href="https://boosty.to/wa-http-api" target="_blank">
|
||||
<b>
|
||||
Boosty ->
|
||||
</b>
|
||||
</a>
|
||||
|
||||
Go ahead and get your own WhatsApp HTTP API!
|
||||
|
||||
### Patreon
|
||||
You can support the project and get WAHA Plus version on Patreon: [https://patreon.com/wa_http_api](https://patreon.com/wa_http_api)
|
||||
<p align="center">
|
||||
<img src="patreon.png" alt="Patreon" />
|
||||
</p>
|
||||
|
||||
{{< alert icon="💡" text="Please look at Boosty - we prefer it because of lower fees, so we can donate more to the projects!" />}}
|
||||
Support the project and get WAHA Plus version on
|
||||
<a href="https://patreon.com/wa_http_api" target="_blank">
|
||||
<b>
|
||||
Patreon ->
|
||||
</b>
|
||||
</a>
|
||||
|
||||
Go ahead and get your own WhatsApp HTTP API!
|
||||
|
||||
|
||||
### Install Plus
|
||||
@@ -100,3 +136,4 @@ docker pull devlikeapro/whatsapp-http-api-plus
|
||||
docker logout
|
||||
```
|
||||
Then in all commands use **Plus** image `devlikeapro/whatsapp-http-api-plus` instead of Core `devlikeapro/whatsapp-http-api`.
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 2.4 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 44 KiB |
@@ -0,0 +1,219 @@
|
||||
---
|
||||
title: "Polls"
|
||||
description: "How to send polls and receive votes"
|
||||
lead: ""
|
||||
date: 2020-10-06T08:48:45+00:00
|
||||
lastmod: 2020-10-06T08:48:45+00:00
|
||||
draft: false
|
||||
images: [ ]
|
||||
weight: 126
|
||||
---
|
||||
|
||||
**Available on NOWEB engine only**.
|
||||
|
||||
Waiting for WEBJS engine to support it, and we'll add it ASAP!
|
||||
|
||||

|
||||
|
||||
## Endpoints
|
||||
|
||||
### Send poll
|
||||
|
||||
Use the endpoint to send a poll!
|
||||
|
||||
```bash
|
||||
POST /api/sendPoll
|
||||
```
|
||||
|
||||
The request body is pretty simple:
|
||||
```json
|
||||
{
|
||||
"session": "default",
|
||||
"chatId": "123123123@c.us",
|
||||
"poll": {
|
||||
"name": "How are you?",
|
||||
"options": [
|
||||
"Awesome!",
|
||||
"Good!",
|
||||
"Not bad!"
|
||||
],
|
||||
"multipleAnswers": false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The response you get back:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "true_321321321@c.us_83ACBAAAAAAAAAAAAAAAAAAAA",
|
||||
"other-fields-here": "value"
|
||||
}
|
||||
```
|
||||
|
||||
You must save the `id` field from the response in your database so that you can identify the poll for which you receive
|
||||
a vote (see webhook events below).
|
||||
|
||||
## Events
|
||||
|
||||
### poll.vote
|
||||
|
||||
With this event, you receive new votes for the poll sent.
|
||||
|
||||
#### Vote from a user in direct messages.
|
||||
|
||||
```json
|
||||
{
|
||||
"event": "poll.vote",
|
||||
"session": "default",
|
||||
"payload": {
|
||||
"vote": {
|
||||
"id": "false_1111111111@c.us_83ACBE602A05C79B234B54415E95EE8A",
|
||||
"to": "me",
|
||||
"from": "1111111@c.us",
|
||||
"fromMe": false,
|
||||
"selectedOptions": [
|
||||
"Awesome!"
|
||||
],
|
||||
"timestamp": 1692861427
|
||||
},
|
||||
"poll": {
|
||||
"id": "true_1111111111@c.us_BAE5F2EF5C69001E",
|
||||
"to": "1111111111@c.us",
|
||||
"from": "me",
|
||||
"fromMe": true
|
||||
}
|
||||
},
|
||||
"engine": "NOWEB"
|
||||
}
|
||||
```
|
||||
|
||||
#### Do I receive votes only for my polls?
|
||||
|
||||
No, you receive all votes. Keep in mind that you'll get all votes with this event, even from other polls. To identify
|
||||
that it's your poll, look at the `poll.fromMe` field.
|
||||
|
||||
#### How to handle multiple-answer votes
|
||||
|
||||
For `multipleAnswers: true`, you receive the `selectedOptions` with all the selected values at a certain moment. So if a
|
||||
user has chosen 3 options from the poll, you will receive **3** `poll.vote` events:
|
||||
|
||||
1. `selectedOptions: ["First"]`
|
||||
2. `selectedOptions: ["First", "Second"]`
|
||||
3. `selectedOptions: ["First", "Second", "Third"]`
|
||||
|
||||
#### Timestamp
|
||||
|
||||
If a user clicks on the poll multiple times, you will receive multiple `poll.vote` events. This is true for
|
||||
both `multipleAnswers: false` (when a user changes their mind about answers) and `multipleAnswers: true` (when a user
|
||||
selects two or more options) events.
|
||||
|
||||
There is a little chance that you may receive votes in the wrong order (due to the nature of HTTP and Webhooks),
|
||||
like `1-3-2` instead of `1-2-3`. To determine the right order, look at the `timestamp` field. The event with a
|
||||
higher `timestamp` value is more recent.
|
||||
|
||||
👉 It's important to save the `timestamp` for each vote in your database and compare them as numbers, without converting to
|
||||
internal datetime. Right now, the `timestamp` shows the timestamp in seconds, but it may be changed to milliseconds in
|
||||
the future.
|
||||
|
||||
#### Vote from a user in a group
|
||||
|
||||
```json
|
||||
{
|
||||
"event": "poll.vote",
|
||||
"session": "default",
|
||||
"payload": {
|
||||
"vote": {
|
||||
"id": "false_3333333333333@g.us_1C18A7EAADD2A8D0324755D241C4238A",
|
||||
"to": "3333333333333@g.us",
|
||||
"from": "1111111111@c.us",
|
||||
"fromMe": false,
|
||||
"selectedOptions": [
|
||||
"Awesome!"
|
||||
],
|
||||
"timestamp": 1692861427
|
||||
},
|
||||
"poll": {
|
||||
"id": "true_3333333333333@g.us_BAE5304BA1ECF704",
|
||||
"to": "33333333333333@g.us",
|
||||
"from": "222222222@c.us",
|
||||
"fromMe": true
|
||||
}
|
||||
},
|
||||
"engine": "NOWEB"
|
||||
}
|
||||
```
|
||||
|
||||
### poll.vote.failed
|
||||
|
||||
There may be cases when WAHA fails to decrypt a vote from the user. In such cases, you will receive
|
||||
a `poll.vote.failed` event on your webhook.
|
||||
|
||||
The payload for `poll.vote.failed` is the same as for `poll.vote`, but with an empty list in `selectedOptions`.
|
||||
|
||||
```json
|
||||
{
|
||||
"event": "poll.vote.failed",
|
||||
"session": "default",
|
||||
"payload": {
|
||||
"vote": {
|
||||
"id": "false_11111111111@c.us_2E8C4CDA89EDE3BC0BC7F605364B8451",
|
||||
"to": "me",
|
||||
"from": "111111111@c.us",
|
||||
"fromMe": false,
|
||||
"selectedOptions": [],
|
||||
"timestamp": 1692956972
|
||||
},
|
||||
"poll": {
|
||||
"id": "true_1111111111@c.us_BAE595F4E0A2042C",
|
||||
"to": "111111111@c.us",
|
||||
"from": "me",
|
||||
"fromMe": true
|
||||
}
|
||||
},
|
||||
"engine": "NOWEB"
|
||||
}
|
||||
```
|
||||
|
||||
#### How should I handle poll.vote.failed events?
|
||||
|
||||
When you send a poll, save the poll configuration (question and options) in your database with the `id` field from the
|
||||
response you received from `POST /api/sendPoll`.
|
||||
|
||||
Later, when you receive a `poll.vote.failed` event, find the `id` for the poll in the database and repeat the same
|
||||
question to the user, apologizing for the inconvenience.
|
||||
For example, you can say:
|
||||
|
||||
> Sorry, we don't understand your choice 😞
|
||||
>
|
||||
> Please click one more time on the message below 👇
|
||||
|
||||
After the user clicks on the poll again, you will receive a `poll.vote` event with their choice.
|
||||
|
||||

|
||||
|
||||
#### How to test poll.vote.failed events?
|
||||
|
||||
To receive `poll.vote.failed` events, follow these steps:
|
||||
|
||||
1. Start a session and authorize it with a QR code.
|
||||
2. Send a poll to a chat.
|
||||
3. Stop the session (logout is not required).
|
||||
4. Start the session again.
|
||||
5. Vote on the poll.
|
||||
6. You will receive a `poll.vote.failed` event.
|
||||
|
||||
#### Why does the poll.vote.failed event occur and when will it be fixed?
|
||||
|
||||
The issue occurs because WAHA does not have a proper storage system, but polls require proper storage in order to
|
||||
decrypt votes later.
|
||||
|
||||
There will be two fixes for this:
|
||||
|
||||
1. In the short term, a [local file storage](https://github.com/devlikeapro/whatsapp-http-api/issues/188) will be used
|
||||
to save poll keys.
|
||||
2. In the long term, work is being done
|
||||
on [remote storages](https://github.com/devlikeapro/whatsapp-http-api/issues/41).
|
||||
|
||||
Even after these fixes are implemented, it's better to handle `poll.vote.failed` events anyway, so your application is
|
||||
prepared for such cases!
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 50 KiB |
@@ -0,0 +1,153 @@
|
||||
---
|
||||
title: "Presence"
|
||||
description: "Presence"
|
||||
lead: ""
|
||||
date: 2020-10-06T08:48:45+00:00
|
||||
lastmod: 2020-10-06T08:48:45+00:00
|
||||
draft: false
|
||||
images: [ ]
|
||||
weight: 801
|
||||
---
|
||||
|
||||
You can get presence information (online, offline with last seen, typing status) for a contact if they share their
|
||||
presence information.
|
||||
|
||||
Possible presence statuses that you can set or get for chats:
|
||||
- `online`
|
||||
- `offline`
|
||||
- `typing`
|
||||
- `recording`
|
||||
- `paused` resets the chat presence after you were `typing`
|
||||
|
||||
|
||||
## Endpoints
|
||||
|
||||
### Set presence
|
||||
You can set your global or chat-related presence with `POST /api/{session}/presence` endpoint
|
||||
|
||||
Start typing to a chat (you can use `POST /startTyping` instead)
|
||||
```json
|
||||
{
|
||||
"chatId": "111111111@c.us",
|
||||
"presence": "typing"
|
||||
}
|
||||
```
|
||||
|
||||
Clear "typing" state (you can use `POST /stopTyping` instead)
|
||||
```json
|
||||
{
|
||||
"chatId": "111111111@c.us",
|
||||
"presence": "paused"
|
||||
}
|
||||
```
|
||||
|
||||
Set global "online", all contacts will see it
|
||||
```json
|
||||
{
|
||||
"presence": "online"
|
||||
}
|
||||
```
|
||||
|
||||
💡 In the multi-device version of WhatsApp - if a desktop client is active, WhatsApp doesn't send push notifications
|
||||
to the device.
|
||||
If you would like to receive said notifications - you need to mark a session's presence as `offline`.
|
||||
|
||||
```json
|
||||
{
|
||||
"presence": "offline"
|
||||
}
|
||||
```
|
||||
|
||||
### Get all chats presence
|
||||
|
||||
{{< alert icon="👉" text="Get presence endpoints and webhook is available only in **NOWEB** engine." />}}
|
||||
[Read more about engines to choose right for you ->]({{< relref "/docs/how-to/engines" >}})
|
||||
|
||||
Here's few notes about fields:
|
||||
|
||||
- `chatId` - either contact id (`213213213@c.us`) or group chat id (`1111111111111@g.us`).
|
||||
- `lastSeen` - contains Unix timestamps indicating when a participant was last online
|
||||
- `lastKnownPresence` - contains the last known presence status, which can be
|
||||
`offline`, `online`, `typing`, `recording`, or `paused`
|
||||
|
||||
|
||||
You can get all presence information available for a session by calling `GET /api/{session}/presence/`.
|
||||
It returns both groups' and personal chats' presence information.
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"id": "2132132130@c.us",
|
||||
"presences": [
|
||||
{
|
||||
"participant": "2132132130@c.us",
|
||||
"lastKnownPresence": "offline",
|
||||
"lastSeen": 1686719326
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "11111111111111111111111@g.us",
|
||||
"presences": [
|
||||
{
|
||||
"participant": "11111111111111111111111@g.us",
|
||||
"lastKnownPresence": "online",
|
||||
"lastSeen": null
|
||||
},
|
||||
{
|
||||
"participant": "2132132130@c.us",
|
||||
"lastKnownPresence": "offline",
|
||||
"lastSeen": 1686719326
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
### Get chat presence
|
||||
|
||||
To get presence information for a single chat - call `GET /api/{session}/presence/{chatId}`.
|
||||
For a group, you'll get participants' statuses.
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "2132132130@c.us",
|
||||
"presences": [
|
||||
{
|
||||
"participant": "2132132130@c.us",
|
||||
"lastKnownPresence": "online",
|
||||
"lastSeen": null
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Subscribe to presence
|
||||
|
||||
You can subscribe to presence information by calling `POST /api/{session}/presence/{chatId}/subscribe` request
|
||||
(no body required).
|
||||
You can get later presence information for the chat with above `GET` endpoints or by listening to `presence.update`
|
||||
webhook.
|
||||
|
||||
## Events
|
||||
### presence.update
|
||||
|
||||
You can subscribe to `presence.update` webhook event to get the most recent presence information.
|
||||
|
||||
```json
|
||||
{
|
||||
"event": "presence.update",
|
||||
"session": "default",
|
||||
"payload": {
|
||||
"id": "111111111111111111@g.us",
|
||||
"presences": [
|
||||
{
|
||||
"participant": "11111111111@c.us",
|
||||
"lastKnownPresence": "typing",
|
||||
"lastSeen": null
|
||||
}
|
||||
]
|
||||
},
|
||||
"engine": "NOWEB"
|
||||
}
|
||||
```
|
||||
@@ -8,54 +8,37 @@ draft: false
|
||||
images: []
|
||||
weight: 120
|
||||
---
|
||||
We consider that you've run docker container and authenticated the session with QR code.
|
||||
|
||||
The project uses [Webhooks](https://en.wikipedia.org/wiki/Webhook) to send the messages or events from WhatsApp to your
|
||||
application.
|
||||
If you haven't yet - please follow the steps from [**Quick Start →**]({{< relref "/docs/overview/quick-start" >}}).
|
||||
|
||||
## Webhooks  
|
||||
## Webhooks
|
||||
|
||||
You can configure where you want to receive events in environment variables:
|
||||
|
||||
- `WHATSAPP_HOOK_URL=https://httpbin.org/post` - to set up a URL for the webhook
|
||||
- `WHATSAPP_HOOK_EVENTS=message,message.any,state.change,group.join,group.leave` - specify events. DO NOT specify all of
|
||||
them, it's too heavy payload, choose the right for you.
|
||||
- `WHATSAPP_HOOK_EVENTS=*` - subscribe to all events. It's not recommended for production, but it's fine for
|
||||
development.
|
||||
|
||||
On the URL that you set via `WHATSAPP_HOOK_URL` you receive JSON-data with following format:
|
||||
You must use [Webhooks]({{< relref "/docs/how-to/webhooks" >}}) to receive messages from WhatsApp to your application.
|
||||
|
||||
Start a new session with configured `message` event in webhooks - call `POST /api/sessions/start` with the payload:
|
||||
```json
|
||||
{
|
||||
"event": "message",
|
||||
"payload": {}
|
||||
"name": "default",
|
||||
"config": {
|
||||
"webhooks": [
|
||||
{
|
||||
"url": "https://httpbin.org/post",
|
||||
"events": [
|
||||
"message"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Where `event` value helps you identify the incoming event with `payload` for that events.
|
||||
|
||||
Below the list of all events that WhatsApp HTTP API sends to your.
|
||||
|
||||
{{< alert icon="👉" text="If you want to look at a webhook body - use the url `https://httpbin.org/post`" />}}
|
||||
|
||||
Run the bellow command and see look at the logs - it prints body request for all events that happen in your WhatsApp!
|
||||
|
||||
```bash
|
||||
docker run -it -e WHATSAPP_HOOK_EVENTS=* -e WHATSAPP_HOOK_URL=https://httpbin.org/post devlikeapro/whatsapp-http-api
|
||||
```
|
||||
|
||||
### Examples
|
||||
Here's few examples of how to handle webhook in different languages:
|
||||
1. [Python guide]({{< relref "/docs/examples/python" >}})
|
||||
|
||||
**Do you use another language?**
|
||||
|
||||
Please create a short guide how to handle webhook and send message after you finish your setup!
|
||||
You can create a pull request with your favorite language in the [GitHub, in examples folder](https://github.com/devlikeapro/whatsapp-http-api/tree/core/examples).
|
||||
|
||||
Just put the code and **README.md** file in the folder - we'll do the rest!
|
||||
After that WAHA'll send events (see below) about new messages to an endpoint you provided.
|
||||
|
||||
|
||||
## Events  
|
||||
## Events
|
||||
Here's examples of message-related events.
|
||||
Read [Webhooks ->]({{< relref "/docs/how-to/webhooks" >}}) to find how to set them up.
|
||||
|
||||
### message
|
||||
|
||||
@@ -64,6 +47,7 @@ Incoming message (text/audio/files)
|
||||
```json
|
||||
{
|
||||
"event": "message",
|
||||
"session": "default",
|
||||
"payload": {
|
||||
"id": "true_11111111111@c.us_AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
|
||||
"timestamp": 1667561485,
|
||||
@@ -126,42 +110,20 @@ Fired on all message creations, including your own. The payload is the same as f
|
||||
```json
|
||||
{
|
||||
"event": "message.any",
|
||||
"session": "default",
|
||||
"payload": {}
|
||||
}
|
||||
```
|
||||
|
||||
### message.ack
|
||||
Receive events when server or recipient gets the message, read or played it.
|
||||
|
||||
[See details on Webhooks page ->]({{< relref "webhooks#messageack" >}}).
|
||||
|
||||
```json
|
||||
{
|
||||
"event": "message.ack",
|
||||
"payload": {}
|
||||
}
|
||||
```
|
||||
|
||||
### state.change
|
||||
|
||||
```json
|
||||
{
|
||||
"event": "state.change",
|
||||
"payload": {}
|
||||
}
|
||||
```
|
||||
|
||||
### group.join
|
||||
|
||||
```json
|
||||
{
|
||||
"event": "group.join",
|
||||
"payload": {}
|
||||
}
|
||||
```
|
||||
|
||||
### group.leave
|
||||
|
||||
```json
|
||||
{
|
||||
"event": "group.left",
|
||||
"session": "default",
|
||||
"payload": {}
|
||||
}
|
||||
```
|
||||
@@ -176,6 +138,7 @@ For example, you can get the webhook like this with `mediaUrl` value (we've skip
|
||||
```json
|
||||
{
|
||||
"event": "message",
|
||||
"session": "default",
|
||||
"payload": {
|
||||
"id": "true_11111111111@c.us_AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
|
||||
"timestamp": 1667561485,
|
||||
@@ -188,17 +151,33 @@ Then you can use the link to download the file `http://localhost:3000/api/files/
|
||||
|
||||
To configure the url you can use environment variables `WHATSAPP_API_HOSTNAME` and `WHATSAPP_API_PORT`.
|
||||
|
||||
[Read more about file storage configuration and variables ->]({{< relref "config" >}}).
|
||||
By default, WAHA download all files that the account receive.
|
||||
If you want to limit files lifetime, specify file types for download or change directory for files -
|
||||
[read more about file storage configuration and variables ->]({{< relref "config" >}}).
|
||||
|
||||
## Webhook retries 
|
||||
## Endpoints
|
||||
|
||||
**WAHA** retries to reach your webhook URL 15 times and with 2 seconds delay between attempts.
|
||||
### Get messages
|
||||
Read messages from the history - use `GET /api/messages` endpoint.
|
||||
|
||||
## Get messages
|
||||
You can read messages from the history by using `GET /api/messages` endpoint.
|
||||
You also can get messages for chats by using
|
||||
[**Get messages from chat ->**]({{< relref "/docs/how-to/chats" >}}) endpoint.
|
||||
|
||||
{{< alert icon="👉" text="We do not recommend using this method in production, consider using webhooks instead!" />}}
|
||||
|
||||
```bash
|
||||
curl -X 'GET' \
|
||||
'http://localhost:3000/api/messages?chatId=11111111111%40c.us&limit=1000&session=default' \
|
||||
-H 'accept: application/json'
|
||||
```
|
||||
|
||||
## Examples
|
||||
Here's few examples of how to receive messages in different languages:
|
||||
1. [Python guide ->]({{< relref "/docs/examples/python" >}})
|
||||
|
||||
**Do you use another language?**
|
||||
|
||||
Please create a short guide how to handle webhook and send message after you finish your setup!
|
||||
You can create a pull request with your favorite language in the
|
||||
[GitHub, in examples folder ->](https://github.com/devlikeapro/whatsapp-http-api/tree/core/examples).
|
||||
|
||||
@@ -65,3 +65,7 @@ headers = {
|
||||
requests.get("http://localhost:3000/api/sessions", headers=headers)
|
||||
```
|
||||
|
||||
## Webhook security 
|
||||
To make sure that you get a webhook from your WAHA instance - you can use **HMAC authentication**.
|
||||
Read more about it on [Webhooks page ->]({{< relref "/docs/how-to/webhooks#hmac-authentication" >}})
|
||||
|
||||
@@ -8,13 +8,10 @@ draft: false
|
||||
images: []
|
||||
weight: 110
|
||||
---
|
||||
On your swagger documentation [http://localhost:3000/](http://localhost:3000/)
|
||||
or in [the example swagger](/swagger) you can find:
|
||||
1. List of all supported methods - we have not documented all of them on the page
|
||||
1. Requests JSON schemas
|
||||
1. Responses JSON schemas
|
||||
1. **Try it** button - no additional setup to play around with a method
|
||||
|
||||
We consider that you've run docker container and authenticated the session with QR code.
|
||||
|
||||
If you haven't yet - please follow the steps from [**Quick Start →**]({{< relref "/docs/overview/quick-start" >}}).
|
||||
|
||||
## Fields
|
||||
There are common fields that you can find in almost all requests:
|
||||
@@ -28,7 +25,13 @@ Read more about [multiple sessions →]({{< relref "/docs/how-to/sessions" >}})
|
||||
For phone number `12132132131` the `chatId` is `12132132131@c.us`.
|
||||
- **Groups** use random number with `@g.us` at the end.
|
||||
|
||||
## Send text  
|
||||
When sending media (images, voice, files) you can either use:
|
||||
- `file.data` field with base64 encoded file (https://base64.guru/converter/encode/file)
|
||||
- `file.url` field with public available URL for that file
|
||||
|
||||
## Endpoints
|
||||
|
||||
### Send text
|
||||
To send text message - use `POST /api/sendText` with example payload.
|
||||
```json
|
||||
{
|
||||
@@ -37,8 +40,30 @@ To send text message - use `POST /api/sendText` with example payload.
|
||||
"text": "Hi there!"
|
||||
}
|
||||
```
|
||||
#### Mention contact
|
||||
If you send a message in a group and want to mention a participant in the message -
|
||||
use `mentions` field for that in `POST /api/sendText` request.
|
||||
|
||||
## Reply on message  
|
||||
Please note that you MUST mention a number in the text as well in the format `@2132132130` and
|
||||
also mention it in `mentions` in format `2132132130@c.us`
|
||||
|
||||
```json
|
||||
{
|
||||
"session": "default",
|
||||
"chatId": "12132132130@c.us",
|
||||
"text": "Hi there! @2132132130",
|
||||
"mentions": [
|
||||
"2132132130@c.us"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Send poll
|
||||
We have a dedicated page [how to send polls and receive votes]({{< relref "/docs/how-to/polls" >}})!
|
||||
|
||||

|
||||
|
||||
### Reply on message
|
||||
To reply on a message - use `POST /api/reply` with example payload.
|
||||
```json
|
||||
{
|
||||
@@ -49,10 +74,10 @@ To reply on a message - use `POST /api/reply` with example payload.
|
||||
}
|
||||
```
|
||||
|
||||
### Reply files 
|
||||
#### Reply files 
|
||||
WAHA does not support reply with files (images, voice, etc). If you're interested in it - please create an issue in GitHub.
|
||||
|
||||
## Add a reaction  
|
||||
### Add a reaction
|
||||
Use `PUT /api/reaction` method to set reaction to a message.
|
||||
|
||||
{{< alert icon="👉" text="Reaction API uses PUT, not POST request! Please make sure you send right request." />}}
|
||||
@@ -74,14 +99,14 @@ To remove reaction from a message - send empty string in the reaction request.
|
||||
}
|
||||
```
|
||||
|
||||
## Send image 
|
||||
### Send image 
|
||||
Use `POST /api/sendImage` to send images.
|
||||
|
||||
You can send images in two ways:
|
||||
1. Provide a URL for the image.
|
||||
2. Encode the whole file content into base64 and send it in the request body.
|
||||
|
||||
### URL
|
||||
#### URL
|
||||
```json
|
||||
{
|
||||
"session": "default",
|
||||
@@ -95,7 +120,7 @@ You can send images in two ways:
|
||||
}
|
||||
```
|
||||
|
||||
### Base64
|
||||
#### Base64
|
||||
```json
|
||||
{
|
||||
"session": "default",
|
||||
@@ -109,14 +134,14 @@ You can send images in two ways:
|
||||
}
|
||||
```
|
||||
|
||||
## Send voice 
|
||||
### Send voice 
|
||||
Use `POST /api/sendVoice` to send voice messages.
|
||||
|
||||
You can send voice messages in two ways:
|
||||
1. Provide a URL for the voice.
|
||||
2. Encode the whole file content into base64 and send it in the request body.
|
||||
|
||||
### URL
|
||||
#### URL
|
||||
```json
|
||||
{
|
||||
"session": "default",
|
||||
@@ -130,7 +155,7 @@ You can send voice messages in two ways:
|
||||
}
|
||||
```
|
||||
|
||||
### Base64
|
||||
#### Base64
|
||||
```json
|
||||
{
|
||||
"chatId": "11111111111@c.us",
|
||||
@@ -142,14 +167,15 @@ You can send voice messages in two ways:
|
||||
"session": "default"
|
||||
}
|
||||
```
|
||||
## Send file 
|
||||
|
||||
### Send file 
|
||||
Use `POST /api/sendFile` to send a file as a document.
|
||||
|
||||
You can send files in two ways:
|
||||
1. Provide a URL for the file.
|
||||
2. Encode the whole file content into base64 and send it in the request body.
|
||||
|
||||
### URL
|
||||
#### URL
|
||||
```json
|
||||
{
|
||||
"chatId": "11111111111@c.us",
|
||||
@@ -162,7 +188,7 @@ You can send files in two ways:
|
||||
}
|
||||
```
|
||||
|
||||
### Base64
|
||||
#### Base64
|
||||
```json
|
||||
{
|
||||
"session": "default",
|
||||
@@ -176,3 +202,73 @@ You can send files in two ways:
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### Send status (aka stories)
|
||||
With [NOWEB engine]({{< relref "/docs/how-to/engines" >}}) you can send statuses (aka stories)!
|
||||
|
||||
It is always necessary to inform the list of `contacts` that will have access to the posted status!
|
||||
|
||||
|
||||
#### Send text status
|
||||
`POST /api/{session}/status/text`
|
||||
```json
|
||||
{
|
||||
"contacts": [
|
||||
"55xxxxxxxxxxx@c.us"
|
||||
],
|
||||
"text": "Have a look! https://waha.devlike.pro/",
|
||||
"backgroundColor": "#38b42f",
|
||||
"font": 1
|
||||
}
|
||||
```
|
||||
|
||||
#### Send image status 
|
||||
`POST /api/{session}/status/image`
|
||||
|
||||
```json
|
||||
{
|
||||
"contacts": [
|
||||
"55xxxxxxxxxxx@c.us"
|
||||
],
|
||||
"file": {
|
||||
"mimetype": "image/jpeg",
|
||||
"filename": "filename.jpeg",
|
||||
"data": "/9j/4AAQSkZJRgABAQAAAQABAAD/4gIoSUNDX1BST0ZJTEUAAQEAAAIYAAAAAAQwAABtbnRyUkdCIFhZWiAAAAAAAAAAAAAAAABhY3NwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAA9tYAAQAAAADTLQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAlkZXNjAAAA8AAAAHRyWFlaAAABZAAAABRnWFlaAAABeAAAABRiWFlaAAABjAAAABRyVFJDAAABoAAAAChnVFJDAAABoAAAAChiVFJDAAABoAAAACh3dHB0AAAByAAAABRjcHJ0AAAB3AAAADxtbHVjAAAAAAAAAAEAAAAMZW5VUwAAAFgAAAAcAHMAUgBHAEIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFhZWiAAAAAAAABvogAAOPUAAAOQWFlaIAAAAAAAAGKZAAC3hQAAGNpYWVogAAAAAAAAJKAAAA+EAAC2z3BhcmEAAAAAAAQAAAACZmYAAPKnAAANWQAAE9AAAApbAAAAAAAAAABYWVogAAAAAAAA9tYAAQAAAADTLW1sdWMAAAAAAAAAAQAAAAxlblVTAAAAIAAAABwARwBvAG8AZwBsAGUAIABJAG4AYwAuACAAMgAwADEANv/bAEMABgQFBgUEBgYFBgcHBggKEAoKCQkKFA4PDBAXFBgYFxQWFhodJR8aGyMcFhYgLCAjJicpKikZHy0wLSgwJSgpKP/bAEMBBwcHCggKEwoKEygaFhooKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKP/AABEIADAAyAMBIgACEQEDEQH/xAAbAAABBQEBAAAAAAAAAAAAAAAAAwQFBgcCAf/EADwQAAEDBAECAwQGBwkBAAAAAAECAwQABQYRIRIxB0FRExQiYRUlcXOBkQgnMjNCdbEWJENSdJKhssHh/8QAFAEBAAAAAAAAAAAAAAAAAAAAAP/EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAMAwEAAhEDEQA/AMg8UfEO755kUqVMlPJtocUIkILIbab3x8PYqI5JPO/lqqVuk90boFN0bpPdTrOLXR7DJGUobZ+iGJYhLWXR1h0gEDp76+Ic0ENujdJ7o3QKbo3Se6N0Etjh+v7f98mtT3WUY4fr+3/fJrU90Cm6N1INWOc7jj18Qlv6PZfEdaisdXWda+H05HNcSLNcY9njXV6KtFukrLbL5I6VqG9gc7/hPl5UDLdG6kLtZJ1qgWyZMS2li4tF6OUrCiUjXceXcVF7oFN0bpPdG6BTdG6T3RugU3Ruk90boFN0bpPdG6BTde7pLdG6C04Rl1wxe7sPx33DDKwJEYqJQ4jz48iB2NFVbdFBi26N0nuvCdgj1GqDX7PguKY/h1ryLxMuN0bVd0lyBa7WhPtlND/EWpXAB2DrjgjueBZr+jGW/wBGq7qw2Vc3rYu/NKUm5NpQ60vpRtJKeFDWjv5/Ko7x8t0y+454e5HZ4z8q0LsbUQrYbK0tOo7pVrseSPtSfSvHLLc7N+ipc0XaBIhOSb62+yiQgoUtspQArR50SD39KCFRh2IYjYbVO8SJ13Xc7qwJUe02kNpW0wf2VurXwCeeB6efOvL9hOKi1WLLLBdLk9hky4JgT0yEoEuAvudkDpV8PPb07740zxhyq8xoOO5HjmO2G8Y7PtrPTLkWpMpbLg3ttau6QPIHz6h5VlmXZhmN88PnY8/HbfbcZXLbUp2JbPdULe0ekA70o6Sd6B4FBB+LOHnBM3m2ZDrj8NKUPxX1626yobB44JB6gdelPPEPDYWH45ihekyV5DdYnv0uMrp9nHaV+7AGt9R89n+E1q2LY+14xYdgdwlLQZWOy/o28KWoAqhoHtEqO/klKftWr0rFvFfKzmef3e8pJ92dd9nFT5JYR8KBry2Bv7SaCHxw/X9v++TWqbrJ8cP1/b/vk1qm6DRref1GXb+cN/0RURdLMY3h5Y7x9Iznfe5LrXujjm2Gukq+JCfInXP2mpS3H9RV2/nDf/VFGQH9SuKf6+R/VygcZ5HfmYx4dxojS3pD0AobbQNqUo9GgBUBltkteNssW1ya7LyXQXLaYKSxFB7IJ1tS+3AP/m9GXl9uxPHsCenW1x/3i3Kacmtr05EaISFFseatlJ9dJNZxlWNKxK7Q5zbxuNllOJlRZwPX7wnqCiFHzXrv69/XQSM+z4riYjxcwk3WTeXWkvOQ7aEARUq5AWpXdWvIf/TH5bYIMK12y949MdmWW5BYaL6Ql1pxPdtYHG+D+R+03vxUv97t99FwtVmsVwsk9lt6NOdt4fUv4RsKWPP035EVTrjdciv8Ozw8gt8K12F2ehLbkaH7uOtXCiN9/hUTvWt0HKJXhnHbQ1IlZNKf6R1usNIShKtc6BAJG6a5xjjOPy4DlvmGba7jHTKiPKT0qKD5KHqNj86t2a3e/YrlDthw6ww4ENgISw6IHt3ZO0glXUQd8kj145pDxqXPVGw5V4HTcjbyZI6QnTm0dXA4HPkKDjKccwbD7yIt8u93lKW0lxMSGykuoBH7S16CdHyA54qCzjHINrttsvePTXJtiuSFFlbqdONrT3Qrt6H8iKmvHKBMXnftm4clTbkRhKVoZUQogHYBA5pLNWl2Lwoxyx3BJaub7709TCuFtNnqA6h5b6h/z6UC+RYdiuNuwXb5kj8aPJioeTGba9rJWo9yABpKO2iRyd1GWrG7FKYvF+kXSU1iMBxLbb/sv7xJWQNISkjg7Ou3mPmQ58bgn6fsqtDq+h443+KqVtDSr34KXCBbkKem225pmPMoG1ltSeFADv3P+00DeLYsYyqFP/sXJurN1hMmQYNySnb7Y7lCk+fbj5j13VDCtjYrRPBWM7CyCZf5ra2bXbYTyn33ElKdkD4dnueCdfKs4UvqUVa6eo716b8qDvdFJ7ooMW3Rurt4seHF5wDI5caZEeVay4oxJoQS263v4fi7BQHBB538tGqJ1UFzw/xKy7DobkTHL5IhxHFFRZ6UOICj3ISsEJP2U1vWeZPe7fKg3e+TZsSU+JLzby+oKcAAB7caAGgNDjtVW6qOqgtuI+IOVYe2tvHL5LhMrV1KZSQtsn16FAjfz1XmXeIGU5g2hvJL5LnMoV1pZUQlsK9ehICd8nnXnVT6qOqgnrFlV7sEK5Q7Nc5EONcW/ZS22lAB1OiNH8FKHHrUNukuqjqoJbHD9fQPvk1qnVWUY2fr6B98mtS3QP0XW4otq7Yia4m2OOB5cUAdKnBrSvXfA/Kh263F63sW56a4u3R1FbMYgdLajvZHnzs/nTHdG6B9LulwnR4sedNckR4iPZxm1gaZRx8I19g7+ldM3i6M2hdobnu/RCle09zUApCVb3tOxtPPPHz9aj90boJyw5bkmOslixXuRFjEkiOoJcbST30lQOvwpvesgvV/WF3+6yZ+t9KHCAhG+/SkaAqL3RugszOfZmxbU29nJJaYqU9CT0pLqU+gc11fjvdREy7XK4oit3Oe/MRFT7Nj2xBLaOON9z2HJphujdBq3iZ4gXmNl7pxHJvq5UZkaYKHmgvR6tbBAPbeqzKbMl3CW7Luct6bMe/ePPK6lK9B8h8hTRAShOkAJHoK63QP7jdbjdXW3btNcmOtNhptTgAKUDska8hs1zbLncLRORNs05+DMQOkOtHun/KodiPkaZbo3QTt/wAvyXI2UsX68vSoySFewSlLbaiOxUEgb/GobqpPdG6BTqoqyYFh9xy28x48aO6IQWDIklJCG0b5581EcACig//Z"
|
||||
},
|
||||
"caption": "string"
|
||||
}
|
||||
```
|
||||
|
||||
#### Send voice status 
|
||||
`POST /api/{session}/status/voice`
|
||||
|
||||
```json
|
||||
{
|
||||
"contacts": [
|
||||
"55xxxxxxxxxxx@c.us"
|
||||
],
|
||||
"file": {
|
||||
"mimetype": "audio/mp4",
|
||||
"filename": "voice-message.mp3",
|
||||
"data": "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 truncated
|
||||
},
|
||||
"backgroundColor": "#38b42f"
|
||||
}
|
||||
```
|
||||
|
||||
#### Send video status 
|
||||
`POST /api/{session}/status/video`
|
||||
|
||||
```json
|
||||
{
|
||||
"contacts": [
|
||||
"55xxxxxxxxxxx@c.us"
|
||||
],
|
||||
"file": {
|
||||
"mimetype": "audio/mp4",
|
||||
"filename": "video-message.mp3",
|
||||
"data": "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 truncated
|
||||
},
|
||||
"backgroundColor": "#38b42f"
|
||||
}
|
||||
```
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 50 KiB |
@@ -1,44 +1,15 @@
|
||||
---
|
||||
title : "Sessions"
|
||||
title: "Sessions"
|
||||
description: "Sessions"
|
||||
lead: ""
|
||||
date: 2020-10-06T08:48:45+00:00
|
||||
lastmod: 2020-10-06T08:48:45+00:00
|
||||
draft: false
|
||||
images: []
|
||||
weight: 200
|
||||
images: [ ]
|
||||
weight: 125
|
||||
---
|
||||
|
||||
## Saving session 
|
||||
|
||||
Plus  version allows you to save "session" state and avoid scanning QR code everytime when
|
||||
you start a container.
|
||||
|
||||
### File storage
|
||||
|
||||
If you want to save your session and do not scan QR code everytime when you launch WAHA - connect a local file storage
|
||||
to the container. WAHA stores authentication information in the directory and reuses it after restart.
|
||||
|
||||
[Attach volume](https://docs.docker.com/storage/volumes/) part to the command:
|
||||
```bash
|
||||
-v `pwd`/.sessions:/app/.sessions
|
||||
```
|
||||
|
||||
The full command would be:
|
||||
```bash
|
||||
docker run --rm -d -v `pwd`/.sessions:/app/.sessions -p 127.0.0.1:3000:3000/tcp --name whatsapp-http-api devlikeapro/whatsapp-http-api
|
||||
```
|
||||
|
||||
### Remote storage 
|
||||
|
||||
If you're interested in using some "remote" storage (like Redis or other Databases) to save sessions - please create an
|
||||
issue on GitHub.
|
||||
|
||||
For instances, it may be useful if you run WAHA in a cluster of servers and do not have shared file storage
|
||||
|
||||
## Multiple sessions 
|
||||
|
||||
If you want to save server's CPU and Memory - run multiple sessions inside one docker container!
|
||||
## Sessions
|
||||
|
||||
### Start
|
||||
|
||||
@@ -49,6 +20,68 @@ In order to start a new session - call `POST /api/sessions/start`
|
||||
"name": "default"
|
||||
}
|
||||
```
|
||||
#### Configure webhooks
|
||||
You can configure webhooks for a session:
|
||||
```json
|
||||
{
|
||||
"name": "default",
|
||||
"config": {
|
||||
"webhooks": [
|
||||
{
|
||||
"url": "https://httpbin.org/post",
|
||||
"events": [
|
||||
"message"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Read more about available options on [**Webhooks page ->**]({{< relref "/docs/how-to/webhooks#hmac-authentication" >}})
|
||||
|
||||
The configuration is saved and will be applied if the docker container restarts,
|
||||
and you set `WHATSAPP_RESTART_ALL_SESSIONS` environment variables.
|
||||
Read more about it in [Autostart section](#autostart).
|
||||
|
||||
#### Configure proxy
|
||||
You can configure proxy for a session by setting `config.proxy` fields:
|
||||
- `server` - proxy server address, without `http://` or `https://` prefixes
|
||||
- `username` and `password` - set this if the proxy requires authentication
|
||||
|
||||
|
||||
**No authentication**
|
||||
```json
|
||||
{
|
||||
"name": "default",
|
||||
"config": {
|
||||
"proxy": {
|
||||
"server": "localhost:3128"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Proxy with authentication**
|
||||
```json
|
||||
{
|
||||
"name": "default",
|
||||
"config": {
|
||||
"proxy": {
|
||||
"server": "localhost:3128",
|
||||
"username": "username",
|
||||
"password": "P@ssw0rd"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The configuration is saved and will be applied if the docker container restarts,
|
||||
and you set `WHATSAPP_RESTART_ALL_SESSIONS` environment variables.
|
||||
Read more about it in [Autostart section](#autostart).
|
||||
|
||||
You can configure proxy when for all sessions by set up environment variables.
|
||||
Read more about it on [**Configuration page** ->]({{< relref "/docs/how-to/config#proxy" >}}).
|
||||
|
||||
### List
|
||||
|
||||
@@ -65,15 +98,138 @@ The response:
|
||||
]
|
||||
```
|
||||
|
||||
You can add `?all=true` parameter to the request `GET /api/session?all=True` it'll show you ALL session,
|
||||
including **STOPPED**,
|
||||
so you can know which one will be restarted if you set `WHATSAPP_RESTART_ALL_SESSIONS=True` environment variable.
|
||||
|
||||
### Stop
|
||||
|
||||
In order to stop a new session - call `POST /api/sessions/stop`
|
||||
|
||||
{{< alert icon="👉" text="The stop request does not log out the account. Manually remove the session storage." />}}
|
||||
{{< alert icon="👉" text="The stop request does not log out the account by default. Set 'logout' field to 'true'." />}}
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "default"
|
||||
"name": "default",
|
||||
"logout": true
|
||||
}
|
||||
```
|
||||
|
||||
### Logout
|
||||
|
||||
In order to log out the session - call `POST /api/sessions/logout`
|
||||
|
||||
{{< alert icon="👉" text="You must stop session first." />}}
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "default",
|
||||
"logout": true
|
||||
}
|
||||
```
|
||||
|
||||
### Get me
|
||||
|
||||
Get information about the associated account for that session (if any).
|
||||
```bash
|
||||
GET /api/sessions/{session}/me
|
||||
```
|
||||
|
||||
**Authenticated and working** session's response:
|
||||
```json
|
||||
{
|
||||
"id": "11111111111@c.us",
|
||||
"pushName": "string"
|
||||
}
|
||||
```
|
||||
|
||||
**Stopped** or **not authenticated** session returns null:
|
||||
```json
|
||||
null
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
## Authentication
|
||||
### Get QR
|
||||
The simplest way to authenticate a new session - get QR code and scan it on your device.
|
||||
```bash
|
||||
GET /api/{session}/auth/qr
|
||||
```
|
||||
You'll get QR image that you can scan and get authenticated
|
||||
|
||||
### Get pairing code
|
||||
{{< alert icon="👉" text="Available in **NOWEB** engine only." />}}
|
||||
|
||||
You can [link a session with phone number](https://faq.whatsapp.com/1324084875126592) - make a request to the endpoint.
|
||||
```bash
|
||||
POST /api/{session}/auth/request-code
|
||||
```
|
||||
|
||||
Body example:
|
||||
```json
|
||||
{
|
||||
"phoneNumber": "12132132130"
|
||||
}
|
||||
```
|
||||
|
||||
You'll get code in the response that you can use on your WhatsApp app to connect the session:
|
||||
```json
|
||||
{
|
||||
"code": "ABCD-ABCD"
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
## Advanced sessions 
|
||||
|
||||
With [WAHA Plus version]({{< relref "plus-version" >}}) you can save session state to avoid scanning QR code everytime,
|
||||
configure autostart options so when the docker container restarts - it restores all previously run sessions!
|
||||
|
||||
### Session persistent
|
||||
|
||||
#### File storage
|
||||
If you want to save your session and do not scan QR code everytime when you launch WAHA - connect a local file storage
|
||||
to the container. WAHA stores authentication information in the directory and reuses it after restart.
|
||||
|
||||
[Attach volume](https://docs.docker.com/storage/volumes/) part to the command:
|
||||
|
||||
```bash
|
||||
-v `pwd`/.sessions:/app/.sessions
|
||||
```
|
||||
|
||||
The full command would be:
|
||||
|
||||
```bash
|
||||
docker run --rm -d -v `pwd`/.sessions:/app/.sessions -p 3000:3000/tcp --name whatsapp-http-api devlikeapro/whatsapp-http-api-plus
|
||||
```
|
||||
|
||||
#### Remote storage 
|
||||
|
||||
If you're interested in using some "remote" storage (like Redis or other Databases) to save sessions - please create an
|
||||
issue on GitHub.
|
||||
|
||||
For instances, it may be useful if you run WAHA in a cluster of servers and do not have shared file storage
|
||||
|
||||
### Autostart
|
||||
If you don't want to call `POST /api/sessions/start` for every session each time when the container restart -
|
||||
you can use set of these environment variables to start sessions for you:
|
||||
|
||||
- `WHATSAPP_RESTART_ALL_SESSIONS=True`: Set this variable to `True` to start all **STOPPED** sessions after container
|
||||
restarts. By default, this variable is set to `False`.
|
||||
- Please note that this will start all **STOPPED** sessions, not just the sessions that were working before the restart. You can maintain the session list by
|
||||
using `POST /api/session/stop` with the `logout: True` parameter or by calling `POST /api/session/logout` to remove
|
||||
**STOPPED** sessions. You can see all sessions, including **STOPPED** sessions, in the `GET /api/sessions/all=True`
|
||||
response.
|
||||
- `WHATSAPP_START_SESSION=session1,session2`: This variable can be used to start sessions with the specified names right
|
||||
after launching the API. Separate session names with a comma.
|
||||
|
||||
|
||||
### Multiple sessions
|
||||
|
||||
If you want to save server's CPU and Memory - run multiple sessions inside one docker container!
|
||||
[Plus version]({{< relref "plus-version" >}}) supports multiple sessions in one container.
|
||||
|
||||
@@ -0,0 +1,446 @@
|
||||
---
|
||||
title : "Webhooks"
|
||||
description: "Webhooks"
|
||||
lead: ""
|
||||
date: 2020-10-06T08:48:45+00:00
|
||||
lastmod: 2020-10-06T08:48:45+00:00
|
||||
draft: false
|
||||
images: []
|
||||
weight: 121
|
||||
---
|
||||
|
||||
The project uses [Webhooks](https://en.wikipedia.org/wiki/Webhook) to send the messages and events from WhatsApp to your
|
||||
application.
|
||||
|
||||
Webhooks are a way for two different applications to communicate with each other in real-time.
|
||||
When a certain event happens in one application, it sends a message to another application through a webhook URL.
|
||||
The receiving application can then take action based on the information received.
|
||||
|
||||
## Setup webhooks
|
||||
### Session webhooks
|
||||
You can define webhooks configuration per session when you start it with `POST /api/sessions/start` request data.
|
||||
|
||||
Here's a simple example:
|
||||
```json
|
||||
{
|
||||
"name": "default",
|
||||
"config": {
|
||||
"webhooks": [
|
||||
{
|
||||
"url": "https://httpbin.org/post",
|
||||
"events": [
|
||||
"message"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
Here's available configuration options for webhooks
|
||||
```json
|
||||
{
|
||||
"name": "default",
|
||||
"config": {
|
||||
"webhooks": [
|
||||
{
|
||||
"url": "https://httpbin.org/post",
|
||||
"events": [
|
||||
"message"
|
||||
],
|
||||
"hmac": {
|
||||
"key": "your-secret-key"
|
||||
},
|
||||
"retries": {
|
||||
"delaySeconds": 2,
|
||||
"attempts": 15
|
||||
},
|
||||
"customHeaders": [
|
||||
{
|
||||
"name": "X-My-Custom-Header",
|
||||
"value": "Value"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Global webhooks
|
||||
There's a way how you can configure webhooks for ALL sessions - by settings these environment variables:
|
||||
|
||||
- `WHATSAPP_HOOK_URL=https://httpbin.org/post` - to set up a URL for the webhook
|
||||
- `WHATSAPP_HOOK_EVENTS=message,message.any,state.change` - specify events. Do not specify all of
|
||||
them, it's too heavy payload, choose the right for you.
|
||||
- `WHATSAPP_HOOK_EVENTS=*` - subscribe to all events. It's not recommended for production, but it's fine for
|
||||
development.
|
||||
|
||||
That webhook configuration **does not appear** in `session.config` field in `GET /api/sessions/` request.
|
||||
|
||||
## Webhook payload
|
||||
|
||||
On the URL that you set you'll receive **HTTP POST** request with a JSON string with following format:
|
||||
|
||||
```json
|
||||
{
|
||||
"event": "message",
|
||||
"session": "default",
|
||||
"engine": "WEBJS",
|
||||
"payload": {
|
||||
...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Where `event` value helps you identify the incoming event with `payload` for that events.
|
||||
|
||||
Below the list of all events that WhatsApp HTTP API sends to your.
|
||||
|
||||
{{< alert icon="👉" text="If you want to look at a webhook body - use the url https://httpbin.org/post" />}}
|
||||
|
||||
Run the bellow command and see look at the logs - it prints body request for all events that happen in your WhatsApp!
|
||||
|
||||
```bash
|
||||
docker run -it -e "WHATSAPP_HOOK_EVENTS=*" -e WHATSAPP_HOOK_URL=https://httpbin.org/post -p 3000:3000 devlikeapro/whatsapp-http-api
|
||||
```
|
||||
|
||||
|
||||
|
||||
## Events
|
||||
|
||||
### message
|
||||
|
||||
Incoming message (text/audio/files)
|
||||
|
||||
```json
|
||||
{
|
||||
"event": "message",
|
||||
"session": "default",
|
||||
"engine": "WEBJS",
|
||||
"payload": {
|
||||
"id": "true_11111111111@c.us_AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
|
||||
"timestamp": 1667561485,
|
||||
"from": "11111111111@c.us",
|
||||
"fromMe": true,
|
||||
"to": "11111111111@c.us",
|
||||
"body": "Hi there!",
|
||||
"hasMedia": false,
|
||||
"ack": 1,
|
||||
"vCards": [],
|
||||
"_data": {
|
||||
"id": {
|
||||
"fromMe": true,
|
||||
"remote": "11111111111@c.us",
|
||||
"id": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
|
||||
"_serialized": "true_11111111111@c.us_AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
},
|
||||
"body": "Hi there!",
|
||||
"type": "chat",
|
||||
"t": 1667561485,
|
||||
"notifyName": "MyName",
|
||||
"from": "11111111111@c.us",
|
||||
"to": "11111111111@c.us",
|
||||
"self": "in",
|
||||
"ack": 1,
|
||||
"isNewMsg": true,
|
||||
"star": false,
|
||||
"kicNotified": false,
|
||||
"recvFresh": true,
|
||||
"isFromTemplate": false,
|
||||
"pollInvalidated": false,
|
||||
"latestEditMsgKey": null,
|
||||
"latestEditSenderTimestampMs": null,
|
||||
"broadcast": false,
|
||||
"mentionedJidList": [],
|
||||
"isVcardOverMmsDocument": false,
|
||||
"isForwarded": false,
|
||||
"hasReaction": false,
|
||||
"ephemeralOutOfSync": false,
|
||||
"productHeaderImageRejected": false,
|
||||
"lastPlaybackProgress": 0,
|
||||
"isDynamicReplyButtonsMsg": false,
|
||||
"isMdHistoryMsg": false,
|
||||
"stickerSentTs": 0,
|
||||
"isAvatar": false,
|
||||
"requiresDirectConnection": false,
|
||||
"pttForwardedFeaturesEnabled": true,
|
||||
"isEphemeral": false,
|
||||
"isStatusV3": false,
|
||||
"links": []
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### message.any
|
||||
|
||||
Fired on all message creations, including your own. The payload is the same as for [message](#message) event.
|
||||
|
||||
```json
|
||||
{
|
||||
"event": "message.any",
|
||||
"session": "default",
|
||||
"engine": "WEBJS",
|
||||
"payload": {
|
||||
...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### message.ack
|
||||
Receive events when server or recipient gets the message, read or played it.
|
||||
|
||||
`ackName` field contains message status (`ack` has the same meaning, but show the value in int, but we keep it for backward compatability, they much to each other)
|
||||
|
||||
Possible message ack statuses:
|
||||
- `ackName: ERROR, ack: -1`
|
||||
- `ackName: PENDING, ack: 0`
|
||||
- `ackName: SERVER, ack: 1`
|
||||
- `ackName: DEVICE, ack: 2`
|
||||
- `ackName: READ, ack: 3`
|
||||
- `ackName: PLAYED, ack: 4`
|
||||
|
||||
|
||||
The payload may have more fields, it depends on the engine you use, but here's a minimum amount that all engines send:
|
||||
```json
|
||||
{
|
||||
"event": "message.ack",
|
||||
"session": "default",
|
||||
"engine": "WEBJS",
|
||||
"payload": {
|
||||
"id":"true_11111111111@c.us_4CC5EDD64BC22EBA6D639F2AF571346C",
|
||||
"from":"11111111111@c.us",
|
||||
"participant": null,
|
||||
"fromMe":true,
|
||||
"ack":3,
|
||||
"ackName":"READ"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### state.change
|
||||
|
||||
It's an internal engine's state, not **session** `status`.
|
||||
|
||||
```json
|
||||
{
|
||||
"event": "state.change",
|
||||
"session": "default",
|
||||
"engine": "WEBJS",
|
||||
"payload": {
|
||||
...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### group.join
|
||||
|
||||
```json
|
||||
{
|
||||
"event": "group.join",
|
||||
"session": "default",
|
||||
"engine": "WEBJS",
|
||||
"payload": {
|
||||
...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### group.leave
|
||||
|
||||
```json
|
||||
{
|
||||
"event": "group.left",
|
||||
"session": "default",
|
||||
"engine": "WEBJS",
|
||||
"payload": {
|
||||
...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### presence.update
|
||||
|
||||
- `payload.id` indicates the chat - either direct chat with a contact or a group chat.
|
||||
- `payload.id.[].participant` - certain participant presence status. For a direct chat there's only one participant.
|
||||
|
||||
```json
|
||||
{
|
||||
"event": "presence.update",
|
||||
"session": "default",
|
||||
"engine": "NOWEB",
|
||||
"payload": {
|
||||
"id": "111111111111111111@g.us",
|
||||
"presences": [
|
||||
{
|
||||
"participant": "11111111111@c.us",
|
||||
"lastKnownPresence": "typing",
|
||||
"lastSeen": null
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### poll.vote
|
||||
We have a dedicated page [how to send polls and receive votes]({{< relref "/docs/how-to/polls" >}})!
|
||||
|
||||
```json
|
||||
{
|
||||
"event": "poll.vote",
|
||||
"session": "default",
|
||||
"payload": {
|
||||
"vote": {
|
||||
"id": "false_1111111111@c.us_83ACBE602A05C79B234B54415E95EE8A",
|
||||
"to": "me",
|
||||
"from": "1111111@c.us",
|
||||
"fromMe": false,
|
||||
"selectedOptions": ["Awesome!"],
|
||||
"timestamp": 1692861427
|
||||
},
|
||||
"poll": {
|
||||
"id": "true_1111111111@c.us_BAE5F2EF5C69001E",
|
||||
"to": "1111111111@c.us",
|
||||
"from": "me",
|
||||
"fromMe": true
|
||||
}
|
||||
},
|
||||
"engine": "NOWEB"
|
||||
}
|
||||
```
|
||||
|
||||
### poll.vote.failed
|
||||
We have a dedicated page [how to send polls and receive votes]({{< relref "/docs/how-to/polls" >}})!
|
||||
|
||||
```json
|
||||
{
|
||||
"event": "poll.vote.failed",
|
||||
"session": "default",
|
||||
"payload": {
|
||||
"vote": {
|
||||
"id": "false_11111111111@c.us_2E8C4CDA89EDE3BC0BC7F605364B8451",
|
||||
"to": "me",
|
||||
"from": "111111111@c.us",
|
||||
"fromMe": false,
|
||||
"selectedOptions": [],
|
||||
"timestamp": 1692956972
|
||||
},
|
||||
"poll": {
|
||||
"id": "true_1111111111@c.us_BAE595F4E0A2042C",
|
||||
"to": "111111111@c.us",
|
||||
"from": "me",
|
||||
"fromMe": true
|
||||
}
|
||||
},
|
||||
"engine": "NOWEB"
|
||||
}
|
||||
```
|
||||
|
||||
## Webhooks Advanced 
|
||||
### HMAC authentication
|
||||
|
||||
You can authenticate webhook sender by using [HMAC Authentication](https://www.okta.com/identity-101/hmac/).
|
||||
|
||||
1. Define you secret key in `config.hmac.key` field when you start session with `POST /api/sessions/start`:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "default",
|
||||
"config": {
|
||||
"webhooks": [
|
||||
{
|
||||
"url": "https://httpbin.org/post",
|
||||
"events": [
|
||||
"message"
|
||||
],
|
||||
"hmac": {
|
||||
"key": "your-secret-key"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
2. After that you'll receive all webhooks payload with two additional headers:
|
||||
- `X-Webhook-Hmac` - message authentication code for the raw **body** in HTTP POST request that send to your endpoint.
|
||||
- `X-Webhook-Hmac-Algorithm` - `sha512` - algorithm that have been used to create `X-Webhook-Hmac` value.
|
||||
|
||||
3. Implement the authentication algorithm by hashing body and using secret key and then verifying it with `X-Webhook-Hmac`
|
||||
value. Please [check your implementation here ->](https://www.devglan.com/online-tools/hmac-sha256-online)
|
||||
|
||||
Here's example for
|
||||
```
|
||||
# Full body
|
||||
{"event":"message","session":"default","engine":"WEBJS"}
|
||||
# Secret key
|
||||
my-secret-key
|
||||
# X-Webhook-Hmac-Algorithm
|
||||
sha512
|
||||
# X-Webhook-Hmac
|
||||
208f8a55dde9e05519e898b10b89bf0d0b3b0fdf11fdbf09b6b90476301b98d8097c462b2b17a6ce93b6b47a136cf2e78a33a63f6752c2c1631777076153fa89
|
||||
```
|
||||
|
||||
|
||||
### Retries
|
||||
**WAHA** retries to reach your webhook URL **15 times** with **2 seconds delay** between attempts by default in
|
||||
[Plus Version →]({{< relref "plus-version" >}})
|
||||
|
||||
You can configure those parameters by settings `config.retries` structure when `POST /api/sessions/start`:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "default",
|
||||
"config": {
|
||||
"webhooks": [
|
||||
{
|
||||
"url": "https://httpbin.org/post",
|
||||
"events": [
|
||||
"message"
|
||||
],
|
||||
"retries": {
|
||||
"delaySeconds": 2,
|
||||
"attempts": 15
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### Custom Headers
|
||||
You can send any customer headers by defining `config.webhooks.customHeaders` fields this way:
|
||||
```json
|
||||
{
|
||||
"name": "default",
|
||||
"config": {
|
||||
"webhooks": [
|
||||
{
|
||||
"url": "https://httpbin.org/post",
|
||||
"events": [
|
||||
"message"
|
||||
],
|
||||
"customHeaders": [
|
||||
{
|
||||
"name": "X-My-Custom-Header",
|
||||
"value": "Value"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Examples
|
||||
Here's few examples of how to handle webhook in different languages:
|
||||
1. [Python guide]({{< relref "/docs/examples/python" >}})
|
||||
|
||||
**Do you use another language?**
|
||||
|
||||
Please create a short guide how to handle webhook and send message after you finish your setup!
|
||||
You can create a pull request with your favorite language in the
|
||||
[GitHub, in examples folder ->](https://github.com/devlikeapro/whatsapp-http-api/tree/core/examples).
|
||||
@@ -0,0 +1,170 @@
|
||||
---
|
||||
title: "Changelog"
|
||||
description: "WAHA's changelog"
|
||||
lead: "Changelog"
|
||||
date: 2020-10-06T08:49:31+00:00
|
||||
lastmod: 2020-10-06T08:49:31+00:00
|
||||
draft: false
|
||||
images: [ ]
|
||||
menu:
|
||||
docs:
|
||||
parent: "help"
|
||||
weight: 199
|
||||
toc: true
|
||||
---
|
||||
## 2023.9
|
||||
September 2023
|
||||
- Add `GET /api/sessions/{session/me` endpoint so you [can get the phone number associated with the session](https://waha.devlike.pro/docs/how-to/sessions/#get-me)
|
||||
- Add [polls support in NOWEB engine](https://waha.devlike.pro/docs/how-to/polls)
|
||||
- Add dedicated [Get QR](https://waha.devlike.pro/docs/how-to/sessions/#get-qr) endpoint!
|
||||
- Support [pairing method (NOWEB)](https://waha.devlike.pro/docs/how-to/sessions/#get-pairing-code) - you can connect with a code instead of QR.
|
||||
- Add string field `ackName: DEVICE|READ|...` in [message.ack payload](https://waha.devlike.pro/docs/how-to/webhooks/#messageack)
|
||||
- Support `message.ack` event webhook in **NOWEB** engine
|
||||
|
||||
## 2023.8
|
||||
August 2023
|
||||
- Added [stories (aka status) endpoints](https://waha.devlike.pro/docs/how-to/send-messages/#send-status-aka-stories) to **NOWEB** engine!
|
||||
- `POST /api/{session}/status/text|image|voice|video` - choose one that suites your!
|
||||
- Added [chats endpoints](https://waha.devlike.pro/docs/how-to/chats)
|
||||
- Get all chats - `GET /api/{session/chats`
|
||||
- Delete chat - `DELETE /api/{session/chats/{chatId}`
|
||||
- Get messages from chat - `GET /api/{session/chats/{chatId}/messages?limit=1000?downloadMedia=true`
|
||||
- Clear messages in the chat `DELETE /api/{session/chats/{chatId}/messages`
|
||||
- Added `downloadMedia` flag to speed up `GET /api/messages` and `GET /api/{session/chats/{chatId}/messages` performance.
|
||||
- Added [set presence endpoint](https://waha.devlike.pro/docs/how-to/presence)!
|
||||
- With `online` presence you can show your status as online [#121](https://github.com/devlikeapro/whatsapp-http-api/issues/121)
|
||||
- With `offline` presence you can get notifications for your device [#28](https://github.com/devlikeapro/whatsapp-http-api/issues/28)
|
||||
- Added security settings for edit group info `PUT /api/{session}/groups/{groupId}/settings/security/info-admin-only` - updates the group settings to only allow admins to edit group info (title, description, photo).
|
||||
|
||||
## 2023.7
|
||||
July 2023
|
||||
- Now session can have their own [Webhooks](https://waha.devlike.pro/docs/how-to/webhooks) -
|
||||
you can define webhook configuration when you start a session with `POST /api/session/start`!
|
||||
- Add HMAC authentication for webhooks
|
||||
- Configure retries
|
||||
- Add you custom headers
|
||||
- Added [proxy configuration](https://waha.devlike.pro/docs/how-to/config/#proxy) with supporting proxy authentication.
|
||||
Thanks **puntolino** for the contribution!
|
||||
You can control proxy's settings per session with `POST /api/session/start` or globally with environment variables.
|
||||
- Added [presence information](https://waha.devlike.pro/docs/how-to/presence) - now you can get online status for
|
||||
a contact by calling endpoints or receiving a webhook event!
|
||||
- Now you can mention contact in groups by settings `mentions` field in `POST /api/sendText`
|
||||
[read more about it in Send Messages ->]({{< relref "/docs/how-to/send-messages" >}})
|
||||
|
||||
## 2023.6
|
||||
June 2023
|
||||
|
||||
Improvements on session management, restarting sessions and more:
|
||||
|
||||
- Added `WHATSAPP_RESTART_ALL_SESSIONS=True`: Set this variable to `True` to start all **STOPPED** sessions after
|
||||
container restarts. By default, this variable is set to `False`.
|
||||
- Please note that this will start all **STOPPED** sessions, not just the sessions that were working before the
|
||||
restart. You can maintain the session list by
|
||||
using `POST /api/session/stop` with the `logout: True` parameter or by calling `POST /api/session/logout` to remove
|
||||
**STOPPED** sessions. You can see all sessions, including **STOPPED** sessions, in the `GET /api/sessions/all=True`
|
||||
response.
|
||||
- `WHATSAPP_START_SESSION` now support more than one session! Separate session names by command, and it'll start them
|
||||
ALWAYS after container restart `WHATSAPP_START_SESSION=session1,session2`
|
||||
- `WHATSAPP_SWAGGER_CONFIG_ADVANCED=true` enables advanced configuration options for Swagger documentation - you can customize host, port and base URL for the requests.
|
||||
Disabled by default.
|
||||
- Added `?all=true` parameter to `GET /api/session?all=True` endpoint - it'll show you ALL session, included **STOPPED
|
||||
**, so you can know which one will be restarted if you set `WHATSAPP_RESTART_ALL_SESSIONS=True` environment variable.
|
||||
- Added `POST /api/sessions/logout` that allow you to logout from session - remove saved credentials.
|
||||
- Added `logout` boolean parameter to `POST /api/sessions/stop` request that allow you to stop the session AND logout at
|
||||
the same time.
|
||||
- Added [How to deploy page ->]({{< relref "/docs/how-to/deploy" >}}) with
|
||||
[docker-compose.yaml](https://github.com/devlikeapro/whatsapp-http-api/blob/core/docker-compose.yaml) example
|
||||
- Added `engine` field in webhook payload
|
||||
|
||||
```json
|
||||
{
|
||||
"event": "message",
|
||||
"session": "default",
|
||||
"engine": "WEBJS",
|
||||
"payload": {}
|
||||
}
|
||||
```
|
||||
|
||||
## 2023.5
|
||||
May 2023
|
||||
|
||||
- Added new [NOWEB engine]({{< relref "/docs/how-to/engines" >}}). **NOWEB** engine does not require a browser to work
|
||||
with
|
||||
WhatsApp Web, it does so directly using a WebSocket.
|
||||
- Less CPU and RAM usage!
|
||||
- Send Locations API works!
|
||||
- Send Link Preview API works!
|
||||
- ⚠ Read the article before using it [How to avoid blocking ->]({{< relref "/docs/overview/how-to-avoid-blocking" >}}).
|
||||
|
||||
## 2023.4
|
||||
March 2023
|
||||
|
||||
- Add [Groups API]({{< relref "/docs/how-to/groups" >}})
|
||||
- Use Chromium by default instead of Chrome
|
||||
|
||||
## 2023.1
|
||||
January 2023
|
||||
|
||||
- Added [Contacts API]({{< relref "/docs/how-to/contacts" >}})
|
||||
- Get all contacts
|
||||
- Get a contact
|
||||
- Get contact "about" (status)
|
||||
- Get contact profile picture
|
||||
- Check number exists (is registered in WhatsApp) - works even if the number is not in the contact list
|
||||
- Block and unblock contact
|
||||
|
||||
## 2022.12
|
||||
December 2023
|
||||
|
||||
- Added `GET /messages/` endpoint to get chat messages [#31](https://github.com/devlikeapro/whatsapp-http-api/issues/31)
|
||||
- Added `WHATSAPP_SWAGGER_USERNAME` and `WHATSAPP_SWAGGER_PASSWORD` to hide and protect swagger panel.
|
||||
|
||||
## 2022.11
|
||||
|
||||
**Please test changes in test environment before update production!!**
|
||||
|
||||
**Engine** 
|
||||
|
||||
1. WAHA has changed its underlying engine from Venom to Whatsapp Web.JS. It might change the response and webhook's
|
||||
payloads.
|
||||
2. Optimize CPU and memory consumption.
|
||||
|
||||
**Requests** 
|
||||
|
||||
- For all `/api/session/` requests use `name` field instead of `sessionName`.
|
||||
- For all "chatting" requests use `session` field instead of `sessionName`.
|
||||
|
||||
**Sessions** 
|
||||
|
||||
Now you don't have to scan QR code each time you run WAHA, WAHA saves it for you! Available only in Plus version.
|
||||
|
||||
**Authentication** 
|
||||
|
||||
Now you can authenticate all requests for WAHA - use `WHATSAPP_API_KEY=secret` environment variable to set "secret key".
|
||||
|
||||
If `WHATSAPP_API_KEY` is set - requests must have `X-Api-Key` header with `secret` value, where `secret` - any random
|
||||
secret key.
|
||||
|
||||
**Webhooks** 
|
||||
|
||||
Instead of setting each webhook via environment variables - we use two environments variables:
|
||||
|
||||
- `WHATSAPP_HOOK_URL` - to set a URL
|
||||
- `WHATSAPP_HOOK_EVENTS` - to set events that are sent to the URL
|
||||
|
||||
| Previous | Current |
|
||||
|----------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------|
|
||||
| <pre>WHATSAPP_HOOK_ONMESSAGE=https://httpbin.org/post <br>WHATSAPP_HOOK_ONANYMESSAGE=https://httpbin.org/post </pre> | <pre>WHATSAPP_HOOK_URL=https://httpbin.org/post <br>WHATSAPP_HOOK_EVENTS=message,message.any</pre> |
|
||||
|
||||
** Webhook payload**
|
||||
|
||||
The data for webhooks are wrapped inside a new `WAWebhook` object with `event` and `payload` fields to help you identify
|
||||
which handler you should call based on `event`.
|
||||
|
||||
```json
|
||||
{
|
||||
"event": "message.any",
|
||||
"payload": {
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -5,15 +5,36 @@ lead: "Answers to frequently asked questions."
|
||||
date: 2020-10-06T08:49:31+00:00
|
||||
lastmod: 2020-10-06T08:49:31+00:00
|
||||
draft: false
|
||||
images: []
|
||||
images: [ ]
|
||||
menu:
|
||||
docs:
|
||||
parent: "help"
|
||||
weight: 630
|
||||
weight: 130
|
||||
toc: true
|
||||
---
|
||||
|
||||
## How to update?
|
||||
|
||||
{{< alert icon="💡" text="Please test all new versions in the development environment before updating production!" />}}
|
||||
|
||||
 For **Core** version the command is
|
||||
|
||||
```bash
|
||||
docker pull devlikeapro/whatsapp-http-api
|
||||
```
|
||||
|
||||
 For **Plus** version, we use login to get the image before:
|
||||
|
||||
```bash
|
||||
docker login -u devlikeapro -p {PASSWORD}
|
||||
docker pull devlikeapro/whatsapp-http-api-plus
|
||||
docker logout
|
||||
```
|
||||
|
||||
Read more about how to get `PASSWORD` for [Plus Version →]({{< relref "plus-version" >}})
|
||||
|
||||
## How much CPU and Memory does WhatsApp HTTP API consume?
|
||||
|
||||
The benchmark has been made on **Intel(R) Core(TM) i7-10510U CPU @ 1.80GHz**.
|
||||
It may differ from case to case, it depends on usage pattern - how many messages you get, how many send, etc.
|
||||
|
||||
@@ -22,6 +43,7 @@ It may differ from case to case, it depends on usage pattern - how many messages
|
||||
| 1 | 30% | 400MB |
|
||||
| 10 | 270% | 1.5GB |
|
||||
|
||||
|
||||
The nature of WhatsApp HTTP API and its underlying engines - is to run **real** WhatsApp Web version in Chrome and
|
||||
communicate with it to prevent blocking from WhatsApp. It's the reason why it's so demanding on resources.
|
||||
|
||||
If you're looking for less resource demanded engine - [have a look at **NOWEB** engine ->]({{< relref "/docs/how-to/engines" >}})
|
||||
@@ -0,0 +1,58 @@
|
||||
---
|
||||
title: "How to Avoid Blocking"
|
||||
description: "How to Avoid Blocking From Whatsapp"
|
||||
date: 2020-11-12T13:26:54+01:00
|
||||
lastmod: 2020-11-12T13:26:54+01:00
|
||||
draft: false
|
||||
images: [ ]
|
||||
menu:
|
||||
docs:
|
||||
parent: "help"
|
||||
weight: 120
|
||||
toc: true
|
||||
---
|
||||
|
||||
How to Avoid Blocking in WhatsApp when developing bots.
|
||||
|
||||
It's important to keep in mind that WhatsApp has strict policies in place to prevent spamming and abuse of their
|
||||
platform.
|
||||
|
||||
If you're developing a bot for WhatsApp, it's crucial to follow these guidelines to avoid getting blocked.
|
||||
|
||||
## Guidelines to Follow
|
||||
|
||||
### 1. Only Reply to Messages
|
||||
|
||||
When developing a bot for WhatsApp, you **should never initiate a conversation**.
|
||||
Instead, your bot **should only reply** to messages that it receives.
|
||||
This will prevent your bot from being flagged as spam by WhatsApp's users and algorithms.
|
||||
|
||||
### 2. Avoid Spamming and Sending Unnecessary Content
|
||||
|
||||
Sending too many messages or sending content that the user did not request can also lead to your bot being blocked. Make
|
||||
sure to only send relevant and useful information to the user.
|
||||
Additionally, do not send too many messages at once, as this can also trigger spam filters.
|
||||
|
||||
### 3. Other Considerations
|
||||
|
||||
There are other guidelines to follow when developing a bot for WhatsApp, such as avoiding the use of banned words and
|
||||
not sharing sensitive or inappropriate content.
|
||||
Make sure to read WhatsApp's policies thoroughly to ensure that your bot complies with all of their rules.
|
||||
|
||||
## How to Process Messages
|
||||
|
||||
When processing messages in your bot, it's important to follow certain steps to avoid being flagged as spam.
|
||||
Here's a recommended process to follow:
|
||||
|
||||
1. **Send seen** before processing the message. This can be done by sending a `POST /api/sendSeen/` request to
|
||||
the WAHA API.
|
||||
|
||||
2. **Start typing** before sending a message and wait for a random interval depending on the size of the message. This can
|
||||
be done by sending a `POST /api/startTyping/` request.
|
||||
|
||||
3. **Stop typing** before sending the message. This can be done by sending a `POST /api/stopTyping/` request.
|
||||
|
||||
4. **Send the text message** using the `POST /api/sendText` request.
|
||||
|
||||
By following these steps, you can ensure that your bot processes messages in a way that's compliant with WhatsApp's
|
||||
guidelines and reduces the risk of being blocked.
|
||||
@@ -22,58 +22,56 @@ toc: true
|
||||
|
||||
{{< alert icon="👉" text="Run WhatsApp HTTP API in less than 5 minutes!" />}}
|
||||
|
||||
Go straight to the [Quick Start →]({{< relref "quick-start" >}})
|
||||
We will guide you through the necessary steps to successfully send your first text message using WhatsApp HTTP API in
|
||||
[**Quick Start →**]({{< relref "quick-start" >}}).
|
||||
|
||||
## Features
|
||||
WAHA is distributed in two versions:
|
||||
- Core  - the basic version that meet almost 80% people's needs. 100% free and [open source](https://github.com/devlikeapro/whatsapp-http-api).
|
||||
- Plus  - the version with advanced messages, security, and reliability features. One-time donation, no license expiration, quick updates, read more about [Plus Version →]({{< relref "plus-version" >}})
|
||||
- Core  - the basic version that meet almost 80% people's needs. 100% free and [open source ->](https://github.com/devlikeapro/whatsapp-http-api).
|
||||
- Plus  - the version with advanced messages, security, and reliability features. Donations, no license expiration, quick updates, read more about [Plus Version →]({{< relref "plus-version" >}})
|
||||
|
||||
Here's the available features:
|
||||
|
||||
### Messages
|
||||
- Send messages  
|
||||
- Receive messages  
|
||||
- Message replies  
|
||||
- Send location  
|
||||
- Send buttons  
|
||||
- Receive location  
|
||||
- React to messages  
|
||||
- Send messages
|
||||
- Receive messages
|
||||
- Message replies
|
||||
- Send location
|
||||
- Send buttons
|
||||
- Receive location
|
||||
- React to messages
|
||||
- Send media (images/documents/files) 
|
||||
- Send voice messages 
|
||||
- Receive media (images/audio/video/documents) 
|
||||
- Receive messages - webhook retries 
|
||||
|
||||
### Sessions
|
||||
- Multi Device  
|
||||
- Get the screenshot  
|
||||
- Session saving (don't have to scan QR on every restart) 
|
||||
- Session saving on remote storage (database, redis) 
|
||||
- Single WhatsApp account running inside one container 
|
||||
- Multiple WhatsApp account running inside one container 
|
||||
|
||||
### Security
|
||||
- API authentication 
|
||||
- Swagger panel authentication 
|
||||
|
||||
### Updates
|
||||
- Bug fixes and updates come as soon as they are implemented. 
|
||||
- One month delay in bug fixes and updates. 
|
||||
|
||||
|
||||
### Groups 
|
||||
- Join groups by invite
|
||||
### Groups
|
||||
- Create a group
|
||||
- Get invite for group
|
||||
- Modify group info (subject, description)
|
||||
- Modify group settings (send messages, edit info)
|
||||
- Add group participants
|
||||
- Kick group participants
|
||||
- Promote/demote group participants
|
||||
|
||||
### Contacts 
|
||||
- Mention users
|
||||
### Contacts
|
||||
- Mute/unmute chats
|
||||
- Block/unblock contacts
|
||||
- Get contact info
|
||||
- Get profile pictures
|
||||
- Set user status message
|
||||
- Get presence (online\offline\typing) status
|
||||
|
||||
### Sessions
|
||||
- Multi Device
|
||||
- Get the screenshot
|
||||
- Single WhatsApp account running inside one container
|
||||
- Multiple WhatsApp account running inside one container 
|
||||
- Session saving (don't have to scan QR on every restart) 
|
||||
- Session saving on remote storage (database, redis) 
|
||||
|
||||
### Security
|
||||
- API authentication 
|
||||
- Swagger panel authentication 
|
||||
|
||||
### Updates
|
||||
- Bug fixes and updates come as soon as they are implemented.
|
||||
|
||||
@@ -13,7 +13,10 @@ weight: 110
|
||||
toc: true
|
||||
---
|
||||
|
||||
On this page you're going to install WAHA, run it, authenticate the client using QR code and send the first text message to WhatsApp!
|
||||
On this page you're going to install WAHA, run it, authenticate the client using QR code and send the first text message
|
||||
to WhatsApp!
|
||||
|
||||

|
||||
|
||||
## Requirements
|
||||
|
||||
@@ -31,7 +34,21 @@ Also Docker makes installation and update processes so simple, just one command!
|
||||
|
||||
## Send your first message
|
||||
|
||||
Let's go over steps that allow you to send your first text message via WhatsApp HTTP API!
|
||||
We will guide you through the necessary steps to successfully send your first text message using WhatsApp HTTP API.
|
||||
|
||||
**If you find it easier to follow a video** tutorial, we've got you covered!
|
||||
A 3-minute video tutorial is available to help you navigate WhatsApp HTTP API smoothly.
|
||||
|
||||
<div class="d-flex justify-content-center my-4">
|
||||
<iframe
|
||||
width="560" height="315"
|
||||
src="https://www.youtube.com/embed/RFerMyAUPRg"
|
||||
title="YouTube video player"
|
||||
frameborder="0"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
||||
allowfullscreen
|
||||
></iframe>
|
||||
</div>
|
||||
|
||||
### 1. Download image
|
||||
|
||||
@@ -53,12 +70,34 @@ docker logout
|
||||
|
||||
Read more about how to get `PASSWORD` for [Plus Version →]({{< relref "plus-version" >}})
|
||||
|
||||
#### 1.1 Download image on ARM
|
||||
|
||||
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
|
||||
# Rename it, so you can use devlikeapro/whatsapp-http-api image in other place
|
||||
docker tag devlikeapro/whatsapp-http-api:arm devlikeapro/whatsapp-http-api
|
||||
```
|
||||
|
||||
 For Plus version the command is:
|
||||
```bash
|
||||
docker login -u devlikeapro -p {PASSWORD}
|
||||
docker pull devlikeapro/whatsapp-http-api-plus:arm
|
||||
docker logout
|
||||
|
||||
# Rename it, so you can use devlikeapro/whatsapp-http-api image in other place
|
||||
docker tag devlikeapro/whatsapp-http-api-plus:arm devlikeapro/whatsapp-http-api
|
||||
```
|
||||
|
||||
### 2. Run WhatsApp HTTP API
|
||||
|
||||
Run WhatsApp HTTP API:
|
||||
|
||||
```bash
|
||||
docker run -it --rm -p 127.0.0.1:3000:3000/tcp --name whatsapp-http-api devlikeapro/whatsapp-http-api
|
||||
docker run -it --rm -p 3000:3000/tcp --name whatsapp-http-api devlikeapro/whatsapp-http-api
|
||||
|
||||
# It prints logs and the last line must be
|
||||
# WhatsApp HTTP API is running on: http://[::1]:3000
|
||||
@@ -83,8 +122,8 @@ How to log in - the instruction on WhatsApp site
|
||||
|
||||
When your ready - find `POST /api/session/start`, click on **Try it out**, then **Execute** a bit below.
|
||||
|
||||
|
||||
The example payload:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "default"
|
||||
@@ -93,7 +132,8 @@ The example payload:
|
||||
|
||||

|
||||
|
||||
By using the request with `name` values you can start multiple session (WhatsApp accounts) inside the single docker container in Plus
|
||||
By using the request with `name` values you can start multiple session (WhatsApp accounts) inside the single docker
|
||||
container in Plus
|
||||
 version and only one account in Core  version.
|
||||
|
||||
Read more about [multiple sessions →]({{< relref "/docs/how-to/sessions" >}})
|
||||
@@ -123,6 +163,7 @@ way: use a phone international phone number without `+` symbol and add `@c.us` a
|
||||
For phone number `12132132131` the `chatId` is `12132132131@c.us`.
|
||||
|
||||
The example payload:
|
||||
|
||||
```json
|
||||
{
|
||||
"chatId": "12132132130@c.us",
|
||||
@@ -144,5 +185,6 @@ curl -d "{\"chatId\": \"${PHONE}@c.us\", \"text\": \"Hello from WhatsApp HTTP AP
|
||||
## What is next?
|
||||
- Right now you've sent simple text message, but you can send more! [Send messages →]({{< relref "/docs/how-to/send-messages" >}})
|
||||
- Read how to receive and response to messages [Receive messages →]({{< relref "/docs/how-to/receive-messages" >}})
|
||||
- [Save session information, so you don't have to scan QR after container restart ->]({{< relref "/docs/how-to/sessions" >}})
|
||||
- Make sure your [API is properly protected ->]({{< relref "/docs/how-to/security" >}})
|
||||
|
||||
@@ -4,11 +4,16 @@
|
||||
<div class="col-lg-12 text-center">
|
||||
<h1 class="mt-0">{{ .Title }}</h1>
|
||||
</div>
|
||||
<div class="col-lg-9 col-xl-8 text-center">
|
||||
<div class="col-lg-9 col-xl-9 text-center">
|
||||
<div class="mb-4">
|
||||
<img src="/images/logo.png" title="WhatsApp HTTP API" alt="logo" width="100"/>
|
||||
</div>
|
||||
<p class="lead">{{ .Params.lead | safeHTML }}</p>
|
||||
<code>
|
||||
docker run -it -p 3000:3000/tcp devlikeapro/whatsapp-http-api
|
||||
</code>
|
||||
<br/>
|
||||
<br/>
|
||||
<a class="btn btn-primary btn-lg px-4 mb-2"
|
||||
href="/docs/{{ if .Site.Params.options.docsVersioning }}{{ .Site.Params.docsVersion }}/{{ end }}overview/introduction/"
|
||||
role="button">Get Started</a>
|
||||
@@ -22,6 +27,14 @@
|
||||
<section class="section section-sm">
|
||||
<div class="container">
|
||||
<div class="row justify-content-center text-center">
|
||||
<div class="col-lg-5">
|
||||
<h2 class="h4">It's free!</h2>
|
||||
<p>
|
||||
<b>WAHA Core</b> version is always free!
|
||||
<br>
|
||||
Even <b>WAHA Plus</b> version have no licence expiration!
|
||||
</p>
|
||||
</div>
|
||||
<div class="col-lg-5">
|
||||
<h2 class="h4">Language agnostic</h2>
|
||||
<p>
|
||||
@@ -36,17 +49,6 @@
|
||||
<h2 class="h4">Well documented</h2>
|
||||
<p>Documentation has a lof examples and how-to guides.</p>
|
||||
</div>
|
||||
<div class="col-lg-5">
|
||||
<h2 class="h4">No blocking</h2>
|
||||
<p>
|
||||
Under the hood it runs a real instance of Whatsapp Web to avoid getting blocked.
|
||||
<br/>
|
||||
<small class="text-muted">
|
||||
WhatsApp does not allow bots or unofficial clients on their platform, so this shouldn't be considered
|
||||
totally safe.
|
||||
</small>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-center text-center">
|
||||
<div class="col-lg-5">
|
||||
@@ -67,11 +69,14 @@
|
||||
</p>
|
||||
</div>
|
||||
<div class="col-lg-5">
|
||||
<h2 class="h4">It's free!</h2>
|
||||
<h2 class="h4">No blocking</h2>
|
||||
<p>
|
||||
<b>WAHA Core</b> version is always free!
|
||||
<br>
|
||||
Even <b>WAHA Plus</b> version does not require monthly donations and have no licence expiration!
|
||||
Under the hood it runs a real instance of Whatsapp Web to avoid getting blocked.
|
||||
<br/>
|
||||
<small class="text-muted">
|
||||
WhatsApp does not allow bots or unofficial clients on their platform, so this shouldn't be considered
|
||||
totally safe.
|
||||
</small>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>devlikeapro/whatsapp-http-api</title>
|
||||
<title>WAHA - WhatsApp HTTP API</title>
|
||||
<link href="./swagger-ui.css" rel="stylesheet" type="text/css">
|
||||
<link href="./favicon-32x32.png" rel="icon" sizes="32x32" type="image/png"/>
|
||||
<link href="./favicon-16x16.png" rel="icon" sizes="16x16" type="image/png"/>
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"openapi":"3.0.0","paths":{"/api/sessions/start":{"post":{"operationId":"SessionsController_start","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SessionStartRequest"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SessionDTO"}}}}},"tags":["sessions"],"security":[{"api_key":[]}]}},"/api/sessions/stop":{"post":{"operationId":"SessionsController_stop","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SessionStopRequest"}}}},"responses":{"201":{"description":""}},"tags":["sessions"],"security":[{"api_key":[]}]}},"/api/sessions":{"get":{"operationId":"SessionsController_list","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/SessionDTO"}}}}}},"tags":["sessions"],"security":[{"api_key":[]}]}},"/api/checkNumberStatus":{"get":{"operationId":"ChattingController_checkNumberStatus","summary":"Check number status","parameters":[{"name":"phone","required":true,"in":"query","schema":{"type":"string"}},{"name":"session","required":true,"in":"query","schema":{"default":"default","type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WANumberExistResult"}}}}},"tags":["chatting"],"security":[{"api_key":[]}]}},"/api/sendContactVcard":{"post":{"operationId":"ChattingController_sendContactVcard","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageContactVcardRequest"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["chatting"],"security":[{"api_key":[]}]}},"/api/sendText":{"get":{"operationId":"ChattingController_sendTextGet","summary":"Send a text message","parameters":[{"name":"phone","required":true,"in":"query","schema":{"type":"string"}},{"name":"text","required":true,"in":"query","schema":{"type":"string"}},{"name":"session","required":true,"in":"query","schema":{"default":"default","type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["chatting"],"security":[{"api_key":[]}]},"post":{"operationId":"ChattingController_sendText","summary":"Send a text message","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageTextRequest"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WAMessage"}}}}},"tags":["chatting"],"security":[{"api_key":[]}]}},"/api/sendTextButtons":{"post":{"operationId":"ChattingController_sendTextButtons","summary":"Send a text message with buttons","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageTextButtonsRequest"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["chatting"],"security":[{"api_key":[]}]}},"/api/sendLocation":{"post":{"operationId":"ChattingController_sendLocation","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageLocationRequest"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["chatting"],"security":[{"api_key":[]}]}},"/api/sendLinkPreview":{"post":{"operationId":"ChattingController_sendLinkPreview","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageLinkPreviewRequest"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["chatting"],"security":[{"api_key":[]}]}},"/api/sendImage":{"post":{"operationId":"ChattingController_sendImage","summary":"Send an image. Either from an URL or base64 data - look at the request schemas for details.","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageImageRequest"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["chatting"],"security":[{"api_key":[]}]}},"/api/sendFile":{"post":{"operationId":"ChattingController_sendFile","summary":"Send a file. Either from an URL or base64 data - look at the request schemas for details.","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageFileRequest"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["chatting"],"security":[{"api_key":[]}]}},"/api/sendVoice":{"post":{"operationId":"ChattingController_sendVoice","summary":"Send an voice message. Either from an URL or basLine truncated
|
||||
{"openapi":"3.0.0","paths":{"/api/{session}/auth/qr":{"get":{"operationId":"AuthController_getQR","summary":"Get QR code for pairing WhatsApp Web.","parameters":[{"name":"session","required":true,"in":"path","description":"WhatsApp session name","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["auth"],"security":[{"api_key":[]}]}},"/api/{session}/auth/request-code":{"post":{"operationId":"AuthController_requestCode","summary":"Request authentication code. NOWEB and NOWEB_MOBILE engines only","parameters":[{"name":"session","required":true,"in":"path","description":"WhatsApp session name","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RequestCodeRequest"}}}},"responses":{"201":{"description":""}},"tags":["auth"],"security":[{"api_key":[]}]}},"/api/{session}/auth/authorize-code":{"post":{"operationId":"AuthController_authorizeCode","summary":"Send OTP authentication code. NOWEB_MOBILE engine only","parameters":[{"name":"session","required":true,"in":"path","description":"WhatsApp session name","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OTPRequest"}}}},"responses":{"201":{"description":""}},"tags":["auth"],"security":[{"api_key":[]}]}},"/api/sessions/start":{"post":{"operationId":"SessionsController_start","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SessionStartRequest"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SessionDTO"}}}}},"tags":["sessions"],"security":[{"api_key":[]}]}},"/api/sessions/stop":{"post":{"operationId":"SessionsController_stop","summary":"Stop session","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SessionStopRequest"}}}},"responses":{"201":{"description":""}},"tags":["sessions"],"security":[{"api_key":[]}]}},"/api/sessions/logout":{"post":{"operationId":"SessionsController_clean","summary":"Logout from session.","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SessionLogoutRequest"}}}},"responses":{"201":{"description":""}},"tags":["sessions"],"security":[{"api_key":[]}]}},"/api/sessions":{"get":{"operationId":"SessionsController_list","parameters":[{"name":"all","required":false,"in":"query","example":false,"description":"Return all sessions, including those that are in the STOPPED state.","schema":{"type":"boolean"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/SessionDTO"}}}}}},"tags":["sessions"],"security":[{"api_key":[]}]}},"/api/checkNumberStatus":{"get":{"operationId":"ChattingController_checkNumberStatus","summary":"Check number status","parameters":[{"name":"phone","required":true,"in":"query","schema":{"type":"string"}},{"name":"session","required":true,"in":"query","schema":{"default":"default","type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WANumberExistResult"}}}}},"tags":["chatting"],"security":[{"api_key":[]}]}},"/api/sendContactVcard":{"post":{"operationId":"ChattingController_sendContactVcard","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageContactVcardRequest"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["chatting"],"security":[{"api_key":[]}]}},"/api/sendText":{"get":{"operationId":"ChattingController_sendTextGet","summary":"Send a text message","parameters":[{"name":"phone","required":true,"in":"query","schema":{"type":"string"}},{"name":"text","required":true,"in":"query","schema":{"type":"string"}},{"name":"session","required":true,"in":"query","schema":{"default":"default","type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["chatting"],"security":[{"api_key":[]}]},"post":{"operationId":"ChattingController_sendText","summary":"Send a text message","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageTextRequest"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WAMessage"}}}}},"tags":["chatting"],"security":[{"api_key":[]}]}},"/api/sendTextButtons":{"post":{"operationId":"ChattingController_sendTextButtons","summary":"Send a text message with buttons","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageTextButtonsRequest"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"taLine truncated
|
||||
@@ -0,0 +1,118 @@
|
||||
# WAHA (WhatsApp HTTP API) Python examples
|
||||
|
||||
## Installation
|
||||
|
||||
We assume that you have installed software:
|
||||
|
||||
1. Python 3
|
||||
2. Docker
|
||||
|
||||
### 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).
|
||||
|
||||
Here are the steps from [Quick Start](https://waha.devlike.pro/docs/overview/quick-start/):
|
||||
|
||||
Download and start WhatsApp HTTP API docker container
|
||||
|
||||
```bash
|
||||
# Download the image
|
||||
docker pull devlikeapro/whatsapp-http-api
|
||||
# Run the docker container
|
||||
docker run -it --rm --network=host -e WHATSAPP_HOOK_URL=http://localhost:5000/bot -e "WHATSAPP_HOOK_EVENTS=*" --name whatsapp-http-api devlikeapro/whatsapp-http-api
|
||||
|
||||
# It prints logs and the last line must be
|
||||
# WhatsApp HTTP API is running on: http://[::1]:3000
|
||||
```
|
||||
|
||||
#### Download image - ARM
|
||||
|
||||
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
|
||||
# Rename it, so you can use devlikeapro/whatsapp-http-api image in other place
|
||||
docker tag devlikeapro/whatsapp-http-api:arm devlikeapro/whatsapp-http-api
|
||||
# Run the docker container
|
||||
docker run -it --rm --network=host -e WHATSAPP_HOOK_URL=http://localhost:5000/bot -e "WHATSAPP_HOOK_EVENTS=*" --name whatsapp-http-api devlikeapro/whatsapp-http-api
|
||||
|
||||
# It prints logs and the last line must be
|
||||
# WhatsApp HTTP API is running on: http://[::1]:3000
|
||||
```
|
||||
|
||||
### Start session and scan QR
|
||||
|
||||
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).
|
||||
|
||||
```json
|
||||
{
|
||||
"chatId": "12132132130@c.us",
|
||||
"text": "Hi there!",
|
||||
"session": "default"
|
||||
}
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
```bash
|
||||
# Clone the git repository with example
|
||||
git clone https://github.com/devlikeapro/whatsapp-http-api.git
|
||||
# Open python example folder
|
||||
cd whatsapp-http-api/examples/python
|
||||
# Install requirements
|
||||
python -mpip install -r requirements.txt
|
||||
# Run the bot
|
||||
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!
|
||||
|
||||
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 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:
|
||||
|
||||
```bash
|
||||
# Download the image
|
||||
docker pull devlikeapro/whatsapp-http-api-plus
|
||||
# Run the docker container
|
||||
docker run -it --rm --network=host -e WHATSAPP_HOOK_URL=http://localhost:5000/bot -e "WHATSAPP_HOOK_EVENTS=*" --name whatsapp-http-api devlikeapro/whatsapp-http-api-plus
|
||||
```
|
||||
|
||||
Run the WhatsApp Download Files bot:
|
||||
|
||||
```bash
|
||||
# Clone the git repository with example
|
||||
git clone https://github.com/devlikeapro/whatsapp-http-api.git
|
||||
# Open python example folder
|
||||
cd whatsapp-http-api/examples/python
|
||||
# Install requirements
|
||||
python -mpip install -r requirements.txt
|
||||
# Run the bot
|
||||
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!
|
||||
@@ -1,44 +0,0 @@
|
||||
from pprint import pprint
|
||||
|
||||
import requests
|
||||
from flask import Flask
|
||||
from flask import request
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
|
||||
@app.route("/")
|
||||
def whatsapp_echo():
|
||||
return "WhatsApp HTTP API Echo server is ready!"
|
||||
|
||||
|
||||
@app.route("/message", methods=["GET", "POST"])
|
||||
def whatsapp_webhook():
|
||||
data = request.get_json()
|
||||
if not data:
|
||||
return "WhatsApp HTTP API Echo server is ready!"
|
||||
pprint(data)
|
||||
if data['event'] != "message":
|
||||
return f"Unknown event {data['event']}"
|
||||
|
||||
payload = data["payload"]
|
||||
# The text
|
||||
text = payload["body"]
|
||||
# Number in format 791111111@c.us
|
||||
from_ = payload["from"]
|
||||
|
||||
if payload.get("mediaUrl", None):
|
||||
# Download file and set text to path
|
||||
client_url = payload["mediaUrl"]
|
||||
filename = client_url.split("/")[-1]
|
||||
path = "/tmp/" + filename
|
||||
r = requests.get(client_url)
|
||||
open(path, "wb").write(r.content)
|
||||
text = f"We have downloaded file here: {path}"
|
||||
|
||||
# Send a text back via WhatsApp HTTP API
|
||||
response = requests.post(
|
||||
"http://localhost:3000/api/sendText", json={"chatId": from_, "text": text}
|
||||
)
|
||||
response.raise_for_status()
|
||||
return "OK"
|
||||
@@ -1,2 +1,3 @@
|
||||
flask
|
||||
requests
|
||||
flask~=1.0
|
||||
requests~=2.0
|
||||
markupsafe==2.0.1
|
||||
@@ -0,0 +1,84 @@
|
||||
from os.path import abspath
|
||||
from pprint import pprint
|
||||
|
||||
import requests
|
||||
from flask import Flask
|
||||
from flask import request
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
|
||||
def send_message(chat_id, text):
|
||||
"""
|
||||
Send message to chat_id.
|
||||
:param chat_id: Phone number + "@c.us" suffix - 1231231231@c.us
|
||||
:param text: Message for the recipient
|
||||
"""
|
||||
# Send a text back via WhatsApp HTTP API
|
||||
response = requests.post(
|
||||
"http://localhost:3000/api/sendText",
|
||||
json={
|
||||
"chatId": chat_id,
|
||||
"text": text,
|
||||
"session": "default",
|
||||
},
|
||||
)
|
||||
response.raise_for_status()
|
||||
|
||||
def send_seen(chat_id, message_id, participant):
|
||||
response = requests.post(
|
||||
"http://localhost:3000/api/sendSeen",
|
||||
json={
|
||||
"session": "default",
|
||||
"chatId": chat_id,
|
||||
"messageId": message_id,
|
||||
"participant": participant,
|
||||
},
|
||||
)
|
||||
response.raise_for_status()
|
||||
|
||||
@app.route("/")
|
||||
def whatsapp_echo():
|
||||
return "WhatsApp Download Files Bot is ready!"
|
||||
|
||||
|
||||
@app.route("/bot", methods=["GET", "POST"])
|
||||
def whatsapp_webhook():
|
||||
if request.method == "GET":
|
||||
return "WhatsApp Download Files Bot is ready!"
|
||||
|
||||
data = request.get_json()
|
||||
pprint(data)
|
||||
if data["event"] != "message":
|
||||
# We can't process other event yet
|
||||
return f"Unknown event {data['event']}"
|
||||
|
||||
payload = data["payload"]
|
||||
# Ignore messages without files
|
||||
if not payload.get("mediaUrl", None):
|
||||
return "No files in the message"
|
||||
|
||||
# Number in format 791111111@c.us
|
||||
chat_id = payload["from"]
|
||||
# Message ID - false_11111111111@c.us_AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
message_id = payload['id']
|
||||
# For groups - who sent the message
|
||||
participant = payload.get('participant')
|
||||
# IMPORTANT - Always send seen before sending new message
|
||||
send_seen(chat_id=chat_id, message_id=message_id, participant=participant)
|
||||
|
||||
# Download the file and download it to the current folder
|
||||
client_url = payload["mediaUrl"]
|
||||
filename = client_url.split("/")[-1]
|
||||
path = abspath("./" + filename)
|
||||
r = requests.get(client_url)
|
||||
with open(path, "wb") as f:
|
||||
f.write(r.content)
|
||||
|
||||
# Send a text back via WhatsApp HTTP API
|
||||
text = f"We have downloaded file here: {path}"
|
||||
print(text)
|
||||
send_message(chat_id=chat_id, text=text)
|
||||
|
||||
# Send OK back
|
||||
return "OK"
|
||||
@@ -0,0 +1,88 @@
|
||||
from pprint import pprint
|
||||
|
||||
import requests
|
||||
from flask import Flask
|
||||
from flask import request
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
|
||||
def send_message(chat_id, text):
|
||||
"""
|
||||
Send message to chat_id.
|
||||
:param chat_id: Phone number + "@c.us" suffix - 1231231231@c.us
|
||||
:param text: Message for the recipient
|
||||
"""
|
||||
# Send a text back via WhatsApp HTTP API
|
||||
response = requests.post(
|
||||
"http://localhost:3000/api/sendText",
|
||||
json={
|
||||
"chatId": chat_id,
|
||||
"text": text,
|
||||
"session": "default",
|
||||
},
|
||||
)
|
||||
response.raise_for_status()
|
||||
|
||||
def reply(chat_id, message_id, text):
|
||||
response = requests.post(
|
||||
"http://localhost:3000/api/reply",
|
||||
json={
|
||||
"chatId": chat_id,
|
||||
"text": text,
|
||||
"reply_to": message_id,
|
||||
"session": "default",
|
||||
},
|
||||
)
|
||||
response.raise_for_status()
|
||||
|
||||
|
||||
def send_seen(chat_id, message_id, participant):
|
||||
response = requests.post(
|
||||
"http://localhost:3000/api/sendSeen",
|
||||
json={
|
||||
"session": "default",
|
||||
"chatId": chat_id,
|
||||
"messageId": message_id,
|
||||
"participant": participant,
|
||||
},
|
||||
)
|
||||
response.raise_for_status()
|
||||
|
||||
|
||||
@app.route("/")
|
||||
def whatsapp_echo():
|
||||
return "WhatsApp Echo Bot is ready!"
|
||||
|
||||
|
||||
@app.route("/bot", methods=["GET", "POST"])
|
||||
def whatsapp_webhook():
|
||||
if request.method == "GET":
|
||||
return "WhatsApp Echo Bot is ready!"
|
||||
|
||||
data = request.get_json()
|
||||
pprint(data)
|
||||
if data["event"] != "message":
|
||||
# We can't process other event yet
|
||||
return f"Unknown event {data['event']}"
|
||||
|
||||
# Payload that we've got
|
||||
payload = data["payload"]
|
||||
# The text
|
||||
text = payload["body"]
|
||||
# Number in format 1231231231@c.us or @g.us for group
|
||||
chat_id = payload["from"]
|
||||
# Message ID - false_11111111111@c.us_AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
message_id = payload['id']
|
||||
# For groups - who sent the message
|
||||
participant = payload.get('participant')
|
||||
# IMPORTANT - Always send seen before sending new message
|
||||
send_seen(chat_id=chat_id, message_id=message_id, participant=participant)
|
||||
|
||||
# Send a text back via WhatsApp HTTP API
|
||||
send_message(chat_id=chat_id, text=text)
|
||||
# OR reply on the message
|
||||
reply(chat_id=chat_id, message_id=message_id, text=text)
|
||||
|
||||
# Send OK back
|
||||
return "OK"
|
||||
Generated
-32623
File diff suppressed because it is too large.
Load diff
+17
-5
@@ -22,6 +22,8 @@
|
||||
"test:e2e": "jest --config ./test/jest-e2e.json"
|
||||
},
|
||||
"dependencies": {
|
||||
"@adiwajshing/baileys": "github:WhiskeySockets/Baileys",
|
||||
"@adiwajshing/keyed-db": "^0.2.4",
|
||||
"@nestjs/common": "^9.0.9",
|
||||
"@nestjs/config": "^0.5.0",
|
||||
"@nestjs/core": "^9.0.9",
|
||||
@@ -29,12 +31,20 @@
|
||||
"@nestjs/platform-express": "^9.0.9",
|
||||
"@nestjs/serve-static": "^2.1.3",
|
||||
"@nestjs/swagger": "^6.0.5",
|
||||
"@types/lodash": "^4.14.194",
|
||||
"@types/ws": "^8.5.4",
|
||||
"class-validator": "^0.12.2",
|
||||
"del": "^6.0.0",
|
||||
"express-basic-auth": "^1.2.1",
|
||||
"file-type": "16.5.4",
|
||||
"https-proxy-agent": "^7.0.0",
|
||||
"libphonenumber-js": "^1.10.36",
|
||||
"link-preview-js": "^3.0.4",
|
||||
"lodash": "^4.17.21",
|
||||
"mime-types": "^2.1.27",
|
||||
"passport": "^0.6.0",
|
||||
"passport-headerapikey": "^1.2.2",
|
||||
"qrcode": "^1.5.1",
|
||||
"qrcode-terminal": "^0.12.0",
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"request": "^2.88.2",
|
||||
@@ -42,8 +52,8 @@
|
||||
"rimraf": "^3.0.2",
|
||||
"rxjs": "^7.1.0",
|
||||
"swagger-ui-express": "^4.1.4",
|
||||
"venom-bot": "^4.3.7",
|
||||
"whatsapp-web.js": "^1.19.3"
|
||||
"venom-bot": "5.0.1",
|
||||
"whatsapp-web.js": "https://github.com/pedroslopez/whatsapp-web.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nestjs/cli": "^9.0.0",
|
||||
@@ -51,13 +61,14 @@
|
||||
"@nestjs/testing": "^9.0.9",
|
||||
"@types/express": "^4.17.3",
|
||||
"@types/jest": "26.0.10",
|
||||
"@types/node": "^13.9.1",
|
||||
"@types/node": "^18.14.2",
|
||||
"@types/supertest": "^2.0.8",
|
||||
"@typescript-eslint/eslint-plugin": "3.9.1",
|
||||
"@typescript-eslint/parser": "3.9.1",
|
||||
"eslint": "7.7.0",
|
||||
"eslint-config-prettier": "^6.10.0",
|
||||
"eslint-plugin-import": "^2.20.1",
|
||||
"eslint-plugin-simple-import-sort": "^10.0.0",
|
||||
"jest": "26.4.2",
|
||||
"prettier": "^1.19.1",
|
||||
"supertest": "^4.0.2",
|
||||
@@ -65,7 +76,7 @@
|
||||
"ts-loader": "^6.2.1",
|
||||
"ts-node": "^10.9.1",
|
||||
"tsconfig-paths": "^4.1.0",
|
||||
"typescript": "4.7.4"
|
||||
"typescript": "^4.8.4"
|
||||
},
|
||||
"jest": {
|
||||
"moduleFileExtensions": [
|
||||
@@ -80,5 +91,6 @@
|
||||
},
|
||||
"coverageDirectory": "../coverage",
|
||||
"testEnvironment": "node"
|
||||
}
|
||||
},
|
||||
"packageManager": "yarn@3.6.0"
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
import { Body, Controller, Get, Post, Res } from '@nestjs/common';
|
||||
import { ApiOperation, ApiSecurity, ApiTags } from '@nestjs/swagger';
|
||||
|
||||
import { SessionManager } from '../core/abc/manager.abc';
|
||||
import { WhatsappSession } from '../core/abc/session.abc';
|
||||
import { OTPRequest, RequestCodeRequest } from '../structures/auth.dto';
|
||||
import { SessionApiParam, SessionParam } from './helpers';
|
||||
import { WAHASessionStatus } from '../structures/enums.dto';
|
||||
import { UnprocessableEntityException } from '@nestjs/common/exceptions/unprocessable-entity.exception';
|
||||
import { Readable } from 'stream';
|
||||
import { Response } from 'express';
|
||||
|
||||
@ApiSecurity('api_key')
|
||||
@Controller('api/:session/auth')
|
||||
@ApiTags('auth')
|
||||
class AuthController {
|
||||
constructor(private manager: SessionManager) {}
|
||||
|
||||
@Get('qr')
|
||||
@SessionApiParam
|
||||
@ApiOperation({
|
||||
summary: 'Get QR code for pairing WhatsApp Web.',
|
||||
})
|
||||
async getQR(@Res() res: Response, @SessionParam session: WhatsappSession) {
|
||||
if (session.status != WAHASessionStatus.SCAN_QR_CODE) {
|
||||
const err = `Can get QR code only in SCAN_QR_CODE status. The current status is '${session.status}'`;
|
||||
throw new UnprocessableEntityException(err);
|
||||
}
|
||||
|
||||
const buffer = await session.getQR();
|
||||
const stream = new Readable();
|
||||
stream.push(buffer);
|
||||
stream.push(null);
|
||||
|
||||
res.set({
|
||||
'Content-Type': 'image/png',
|
||||
'Content-Length': buffer.length,
|
||||
});
|
||||
stream.pipe(res);
|
||||
}
|
||||
|
||||
@Post('request-code')
|
||||
@SessionApiParam
|
||||
@ApiOperation({
|
||||
summary: 'Request authentication code. NOWEB and NOWEB_MOBILE engines only',
|
||||
})
|
||||
requestCode(
|
||||
@SessionParam session: WhatsappSession,
|
||||
@Body() request: RequestCodeRequest,
|
||||
) {
|
||||
return session.requestCode(request.phoneNumber, request.method);
|
||||
}
|
||||
|
||||
@Post('authorize-code')
|
||||
@SessionApiParam
|
||||
@ApiOperation({
|
||||
summary: 'Send OTP authentication code. NOWEB_MOBILE engine only',
|
||||
})
|
||||
authorizeCode(
|
||||
@SessionParam session: WhatsappSession,
|
||||
@Body() request: OTPRequest,
|
||||
) {
|
||||
return session.authorizeCode(request.code);
|
||||
}
|
||||
}
|
||||
|
||||
export { AuthController };
|
||||
@@ -0,0 +1,56 @@
|
||||
import { Controller, Delete, Get, Param, Post, Query } from '@nestjs/common';
|
||||
import { ApiOperation, ApiSecurity, ApiTags } from '@nestjs/swagger';
|
||||
|
||||
import { SessionManager } from '../core/abc/manager.abc';
|
||||
import { WhatsappSession } from '../core/abc/session.abc';
|
||||
import { SessionApiParam, SessionParam } from './helpers';
|
||||
import { GetChatMessagesQuery } from '../structures/chats.dto';
|
||||
import { parseBool } from '../helpers';
|
||||
|
||||
@ApiSecurity('api_key')
|
||||
@Controller('api/:session/chats')
|
||||
@ApiTags('chats')
|
||||
class ChatsController {
|
||||
constructor(private manager: SessionManager) {}
|
||||
|
||||
@Get('')
|
||||
@SessionApiParam
|
||||
@ApiOperation({ summary: 'Get all chats' })
|
||||
getAllChats(@SessionParam session: WhatsappSession) {
|
||||
return session.getChats();
|
||||
}
|
||||
|
||||
@Delete(':chatId')
|
||||
@SessionApiParam
|
||||
@ApiOperation({ summary: 'Deletes the chat' })
|
||||
deleteChat(
|
||||
@SessionParam session: WhatsappSession,
|
||||
@Param('chatId') chatId: string,
|
||||
) {
|
||||
return session.deleteChat(chatId);
|
||||
}
|
||||
|
||||
@Get(':chatId/messages')
|
||||
@SessionApiParam
|
||||
@ApiOperation({ summary: 'Gets messages in the chat' })
|
||||
getChatMessages(
|
||||
@Query() query: GetChatMessagesQuery,
|
||||
@SessionParam session: WhatsappSession,
|
||||
@Param('chatId') chatId: string,
|
||||
) {
|
||||
const downloadMedia = parseBool(query.downloadMedia);
|
||||
return session.getChatMessages(chatId, query.limit, downloadMedia);
|
||||
}
|
||||
|
||||
@Delete(':chatId/messages')
|
||||
@SessionApiParam
|
||||
@ApiOperation({ summary: 'Clears all messages from the chat' })
|
||||
clearMessages(
|
||||
@SessionParam session: WhatsappSession,
|
||||
@Param('chatId') chatId: string,
|
||||
) {
|
||||
return session.clearMessages(chatId);
|
||||
}
|
||||
}
|
||||
|
||||
export { ChatsController };
|
||||
+139
-112
@@ -1,134 +1,161 @@
|
||||
import {Body, Controller, Get, Post, Put, Query} from '@nestjs/common';
|
||||
import {ApiOperation, ApiSecurity, ApiTags} from "@nestjs/swagger";
|
||||
import { Body, Controller, Get, Post, Put, Query } from '@nestjs/common';
|
||||
import { ApiOperation, ApiSecurity, ApiTags } from '@nestjs/swagger';
|
||||
|
||||
import { SessionManager } from '../core/abc/manager.abc';
|
||||
import {
|
||||
ChatRequest,
|
||||
CheckNumberStatusQuery,
|
||||
MessageContactVcardRequest,
|
||||
MessageFileRequest,
|
||||
MessageImageRequest,
|
||||
MessageLinkPreviewRequest,
|
||||
MessageLocationRequest,
|
||||
MessageReactionRequest,
|
||||
MessageReplyRequest,
|
||||
MessageTextButtonsRequest,
|
||||
MessageTextQuery,
|
||||
MessageTextRequest,
|
||||
MessageVoiceRequest
|
||||
} from "../structures/chatting.dto";
|
||||
import {WAMessage, WANumberExistResult} from "../structures/responses.dto";
|
||||
import {SessionManager} from "../core/abc/manager.abc";
|
||||
ChatRequest,
|
||||
CheckNumberStatusQuery,
|
||||
GetMessageQuery,
|
||||
MessageContactVcardRequest,
|
||||
MessageFileRequest,
|
||||
MessageImageRequest,
|
||||
MessageLinkPreviewRequest,
|
||||
MessageLocationRequest,
|
||||
MessagePollRequest,
|
||||
MessageReactionRequest,
|
||||
MessageReplyRequest,
|
||||
MessageTextButtonsRequest,
|
||||
MessageTextQuery,
|
||||
MessageTextRequest,
|
||||
MessageVoiceRequest,
|
||||
SendSeenRequest,
|
||||
WANumberExistResult,
|
||||
} from '../structures/chatting.dto';
|
||||
import { WAMessage } from '../structures/responses.dto';
|
||||
|
||||
@ApiSecurity('api_key')
|
||||
@Controller('api')
|
||||
@ApiTags('chatting')
|
||||
export class ChattingController {
|
||||
constructor(private manager: SessionManager) {
|
||||
}
|
||||
constructor(private manager: SessionManager) {}
|
||||
|
||||
@Get('/checkNumberStatus')
|
||||
@ApiOperation({summary: 'Check number status'})
|
||||
async checkNumberStatus(@Query() request: CheckNumberStatusQuery): Promise<WANumberExistResult> {
|
||||
const whatsapp = this.manager.getSession(request.session)
|
||||
return whatsapp.checkNumberStatus(request)
|
||||
}
|
||||
@Get('/checkNumberStatus')
|
||||
@ApiOperation({ summary: 'Check number status' })
|
||||
async checkNumberStatus(
|
||||
@Query() request: CheckNumberStatusQuery,
|
||||
): Promise<WANumberExistResult> {
|
||||
const whatsapp = this.manager.getSession(request.session);
|
||||
return whatsapp.checkNumberStatus(request);
|
||||
}
|
||||
|
||||
@Post('/sendContactVcard')
|
||||
sendContactVcard(@Body() request: MessageContactVcardRequest) {
|
||||
const whatsapp = this.manager.getSession(request.session)
|
||||
return whatsapp.sendContactVCard(request)
|
||||
}
|
||||
@Post('/sendContactVcard')
|
||||
sendContactVcard(@Body() request: MessageContactVcardRequest) {
|
||||
const whatsapp = this.manager.getSession(request.session);
|
||||
return whatsapp.sendContactVCard(request);
|
||||
}
|
||||
|
||||
@Get('/sendText')
|
||||
@ApiOperation({summary: 'Send a text message'})
|
||||
sendTextGet(
|
||||
@Query() query: MessageTextQuery,
|
||||
) {
|
||||
const whatsapp = this.manager.getSession(query.session)
|
||||
const msg = new MessageTextRequest()
|
||||
msg.chatId = query.phone
|
||||
msg.text = query.text
|
||||
return whatsapp.sendText(msg)
|
||||
}
|
||||
@Get('/sendText')
|
||||
@ApiOperation({ summary: 'Send a text message' })
|
||||
sendTextGet(@Query() query: MessageTextQuery) {
|
||||
const whatsapp = this.manager.getSession(query.session);
|
||||
const msg = new MessageTextRequest();
|
||||
msg.chatId = query.phone;
|
||||
msg.text = query.text;
|
||||
return whatsapp.sendText(msg);
|
||||
}
|
||||
|
||||
@Post('/sendText')
|
||||
@ApiOperation({summary: 'Send a text message'})
|
||||
sendText(@Body() request: MessageTextRequest): Promise<WAMessage> {
|
||||
const whatsapp = this.manager.getSession(request.session)
|
||||
return whatsapp.sendText(request)
|
||||
}
|
||||
@Post('/sendText')
|
||||
@ApiOperation({ summary: 'Send a text message' })
|
||||
sendText(@Body() request: MessageTextRequest): Promise<WAMessage> {
|
||||
const whatsapp = this.manager.getSession(request.session);
|
||||
return whatsapp.sendText(request);
|
||||
}
|
||||
|
||||
@Post('/sendTextButtons')
|
||||
@ApiOperation({summary: 'Send a text message with buttons'})
|
||||
sendTextButtons(@Body() request: MessageTextButtonsRequest) {
|
||||
const whatsapp = this.manager.getSession(request.session)
|
||||
return whatsapp.sendTextButtons(request)
|
||||
}
|
||||
@Post('/sendTextButtons')
|
||||
@ApiOperation({ summary: 'Send a text message with buttons' })
|
||||
sendTextButtons(@Body() request: MessageTextButtonsRequest) {
|
||||
const whatsapp = this.manager.getSession(request.session);
|
||||
return whatsapp.sendTextButtons(request);
|
||||
}
|
||||
|
||||
@Post('/sendLocation')
|
||||
sendLocation(@Body() request: MessageLocationRequest) {
|
||||
const whatsapp = this.manager.getSession(request.session)
|
||||
return whatsapp.sendLocation(request)
|
||||
}
|
||||
@Post('/sendPoll')
|
||||
@ApiOperation({ summary: 'Send a poll with options' })
|
||||
sendPoll(@Body() request: MessagePollRequest) {
|
||||
const whatsapp = this.manager.getSession(request.session);
|
||||
return whatsapp.sendPoll(request);
|
||||
}
|
||||
|
||||
@Post('/sendLinkPreview')
|
||||
sendLinkPreview(@Body() request: MessageLinkPreviewRequest) {
|
||||
const whatsapp = this.manager.getSession(request.session)
|
||||
return whatsapp.sendLinkPreview(request)
|
||||
}
|
||||
@Post('/sendLocation')
|
||||
sendLocation(@Body() request: MessageLocationRequest) {
|
||||
const whatsapp = this.manager.getSession(request.session);
|
||||
return whatsapp.sendLocation(request);
|
||||
}
|
||||
|
||||
@Post('/sendImage')
|
||||
@ApiOperation({summary: "Send an image. Either from an URL or base64 data - look at the request schemas for details."})
|
||||
sendImage(@Body() request: MessageImageRequest) {
|
||||
const whatsapp = this.manager.getSession(request.session)
|
||||
return whatsapp.sendImage(request)
|
||||
}
|
||||
@Post('/sendLinkPreview')
|
||||
sendLinkPreview(@Body() request: MessageLinkPreviewRequest) {
|
||||
const whatsapp = this.manager.getSession(request.session);
|
||||
return whatsapp.sendLinkPreview(request);
|
||||
}
|
||||
|
||||
@Post('/sendFile')
|
||||
@ApiOperation({summary: "Send a file. Either from an URL or base64 data - look at the request schemas for details."})
|
||||
sendFile(@Body() request: MessageFileRequest) {
|
||||
const whatsapp = this.manager.getSession(request.session)
|
||||
return whatsapp.sendFile(request)
|
||||
}
|
||||
@Post('/sendImage')
|
||||
@ApiOperation({
|
||||
summary:
|
||||
'Send an image. Either from an URL or base64 data - look at the request schemas for details.',
|
||||
})
|
||||
sendImage(@Body() request: MessageImageRequest) {
|
||||
const whatsapp = this.manager.getSession(request.session);
|
||||
return whatsapp.sendImage(request);
|
||||
}
|
||||
|
||||
@Post('/sendVoice')
|
||||
@ApiOperation({summary: "Send an voice message. Either from an URL or base64 data - look at the request schemas for details."})
|
||||
sendVoice(@Body() request: MessageVoiceRequest) {
|
||||
const whatsapp = this.manager.getSession(request.session)
|
||||
return whatsapp.sendVoice(request)
|
||||
}
|
||||
@Post('/sendFile')
|
||||
@ApiOperation({
|
||||
summary:
|
||||
'Send a file. Either from an URL or base64 data - look at the request schemas for details.',
|
||||
})
|
||||
sendFile(@Body() request: MessageFileRequest) {
|
||||
const whatsapp = this.manager.getSession(request.session);
|
||||
return whatsapp.sendFile(request);
|
||||
}
|
||||
|
||||
@Post('/reply')
|
||||
@ApiOperation({summary: 'Reply to a text message'})
|
||||
reply(@Body() request: MessageReplyRequest) {
|
||||
const whatsapp = this.manager.getSession(request.session)
|
||||
return whatsapp.reply(request)
|
||||
}
|
||||
@Post('/sendVoice')
|
||||
@ApiOperation({
|
||||
summary:
|
||||
'Send an voice message. Either from an URL or base64 data - look at the request schemas for details.',
|
||||
})
|
||||
sendVoice(@Body() request: MessageVoiceRequest) {
|
||||
const whatsapp = this.manager.getSession(request.session);
|
||||
return whatsapp.sendVoice(request);
|
||||
}
|
||||
|
||||
@Post('/sendSeen')
|
||||
sendSeen(@Body() chat: ChatRequest) {
|
||||
const whatsapp = this.manager.getSession(chat.session)
|
||||
return whatsapp.sendSeen(chat)
|
||||
}
|
||||
@Post('/reply')
|
||||
@ApiOperation({ summary: 'Reply to a text message' })
|
||||
reply(@Body() request: MessageReplyRequest) {
|
||||
const whatsapp = this.manager.getSession(request.session);
|
||||
return whatsapp.reply(request);
|
||||
}
|
||||
|
||||
@Post('/startTyping')
|
||||
async startTyping(@Body() chat: ChatRequest) {
|
||||
// It's infinitive action
|
||||
const whatsapp = this.manager.getSession(chat.session)
|
||||
await whatsapp.startTyping(chat)
|
||||
return {result: true}
|
||||
}
|
||||
@Post('/sendSeen')
|
||||
sendSeen(@Body() chat: SendSeenRequest) {
|
||||
const whatsapp = this.manager.getSession(chat.session);
|
||||
return whatsapp.sendSeen(chat);
|
||||
}
|
||||
|
||||
@Post('/stopTyping')
|
||||
async stopTyping(@Body() chat: ChatRequest) {
|
||||
const whatsapp = this.manager.getSession(chat.session)
|
||||
await whatsapp.stopTyping(chat)
|
||||
return {result: true}
|
||||
}
|
||||
@Post('/startTyping')
|
||||
async startTyping(@Body() chat: ChatRequest) {
|
||||
// It's infinitive action
|
||||
const whatsapp = this.manager.getSession(chat.session);
|
||||
await whatsapp.startTyping(chat);
|
||||
return { result: true };
|
||||
}
|
||||
|
||||
@Put('/reaction')
|
||||
@ApiOperation({summary: 'React to a message with an emoji'})
|
||||
setReaction(@Body() request: MessageReactionRequest) {
|
||||
const whatsapp = this.manager.getSession(request.session)
|
||||
return whatsapp.setReaction(request)
|
||||
}
|
||||
@Post('/stopTyping')
|
||||
async stopTyping(@Body() chat: ChatRequest) {
|
||||
const whatsapp = this.manager.getSession(chat.session);
|
||||
await whatsapp.stopTyping(chat);
|
||||
return { result: true };
|
||||
}
|
||||
|
||||
@Put('/reaction')
|
||||
@ApiOperation({ summary: 'React to a message with an emoji' })
|
||||
setReaction(@Body() request: MessageReactionRequest) {
|
||||
const whatsapp = this.manager.getSession(request.session);
|
||||
return whatsapp.setReaction(request);
|
||||
}
|
||||
|
||||
@Get('/messages')
|
||||
@ApiOperation({ summary: 'Get messages in a chat' })
|
||||
getMessages(@Query() query: GetMessageQuery) {
|
||||
const whatsapp = this.manager.getSession(query.session);
|
||||
return whatsapp.getMessages(query);
|
||||
}
|
||||
}
|
||||
@@ -1,63 +1,76 @@
|
||||
import {Body, Controller, Get, Post, Query} from '@nestjs/common';
|
||||
import {ApiOperation, ApiSecurity, ApiTags} from "@nestjs/swagger";
|
||||
import {SessionManager} from "../core/abc/manager.abc";
|
||||
import {ContactQuery, ContactRequest} from "../structures/contacts.dto";
|
||||
import {SessionQuery} from "../structures/base.dto";
|
||||
import {CheckNumberStatusQuery} from "../structures/chatting.dto";
|
||||
import {WANumberExistResult} from "../structures/responses.dto";
|
||||
import { Body, Controller, Get, Post, Query } from '@nestjs/common';
|
||||
import { ApiOperation, ApiSecurity, ApiTags } from '@nestjs/swagger';
|
||||
|
||||
import { SessionManager } from '../core/abc/manager.abc';
|
||||
import { SessionQuery } from '../structures/base.dto';
|
||||
import {
|
||||
CheckNumberStatusQuery,
|
||||
WANumberExistResult,
|
||||
} from '../structures/chatting.dto';
|
||||
import { ContactQuery, ContactRequest } from '../structures/contacts.dto';
|
||||
|
||||
@ApiSecurity('api_key')
|
||||
@Controller('api/contacts')
|
||||
@ApiTags('contacts')
|
||||
export class ContactsController {
|
||||
constructor(private manager: SessionManager) {
|
||||
}
|
||||
constructor(private manager: SessionManager) {}
|
||||
|
||||
@Get('/')
|
||||
@ApiOperation({summary: 'Get contact basic info. The method always return result, even if the phone number is not registered in WhatsApp. For that - use /check-exists endpoint below.'})
|
||||
get(@Query() query: ContactQuery) {
|
||||
const whatsapp = this.manager.getSession(query.session)
|
||||
return whatsapp.getContact(query)
|
||||
}
|
||||
@Get('/')
|
||||
@ApiOperation({
|
||||
summary:
|
||||
'Get contact basic info. The method always return result, even if the phone number is not registered in WhatsApp. For that - use /check-exists endpoint below.',
|
||||
})
|
||||
get(@Query() query: ContactQuery) {
|
||||
const whatsapp = this.manager.getSession(query.session);
|
||||
return whatsapp.getContact(query);
|
||||
}
|
||||
|
||||
@Get('/all')
|
||||
@ApiOperation({summary: 'Get all contacts'})
|
||||
getAll(@Query() query: SessionQuery) {
|
||||
const whatsapp = this.manager.getSession(query.session)
|
||||
return whatsapp.getContacts()
|
||||
}
|
||||
@Get('/all')
|
||||
@ApiOperation({ summary: 'Get all contacts' })
|
||||
getAll(@Query() query: SessionQuery) {
|
||||
const whatsapp = this.manager.getSession(query.session);
|
||||
return whatsapp.getContacts();
|
||||
}
|
||||
|
||||
@Get('/check-exists')
|
||||
@ApiOperation({summary: 'Check phone number is registered in WhatsApp.'})
|
||||
async checkExists(@Query() request: CheckNumberStatusQuery): Promise<WANumberExistResult> {
|
||||
const whatsapp = this.manager.getSession(request.session)
|
||||
return whatsapp.checkNumberStatus(request)
|
||||
}
|
||||
@Get('/check-exists')
|
||||
@ApiOperation({ summary: 'Check phone number is registered in WhatsApp.' })
|
||||
async checkExists(
|
||||
@Query() request: CheckNumberStatusQuery,
|
||||
): Promise<WANumberExistResult> {
|
||||
const whatsapp = this.manager.getSession(request.session);
|
||||
return whatsapp.checkNumberStatus(request);
|
||||
}
|
||||
|
||||
@Get('/about')
|
||||
@ApiOperation({summary: 'Gets the Contact\'s current "about" info. Returns null if you don\'t have permission to read their status.'})
|
||||
getAbout(@Query() query: ContactQuery) {
|
||||
const whatsapp = this.manager.getSession(query.session)
|
||||
return whatsapp.getContactAbout(query)
|
||||
}
|
||||
@Get('/about')
|
||||
@ApiOperation({
|
||||
summary:
|
||||
'Gets the Contact\'s current "about" info. Returns null if you don\'t have permission to read their status.',
|
||||
})
|
||||
getAbout(@Query() query: ContactQuery) {
|
||||
const whatsapp = this.manager.getSession(query.session);
|
||||
return whatsapp.getContactAbout(query);
|
||||
}
|
||||
|
||||
@Get('/profile-picture')
|
||||
@ApiOperation({summary: 'Returns the contact\'s profile picture URL, if privacy settings allow it.'})
|
||||
getProfilePicture(@Query() query: ContactQuery) {
|
||||
const whatsapp = this.manager.getSession(query.session)
|
||||
return whatsapp.getContactProfilePicture(query)
|
||||
}
|
||||
@Get('/profile-picture')
|
||||
@ApiOperation({
|
||||
summary:
|
||||
"Returns the contact's profile picture URL, if privacy settings allow it.",
|
||||
})
|
||||
getProfilePicture(@Query() query: ContactQuery) {
|
||||
const whatsapp = this.manager.getSession(query.session);
|
||||
return whatsapp.getContactProfilePicture(query);
|
||||
}
|
||||
|
||||
@Post('/block')
|
||||
@ApiOperation({summary:"Block contact"})
|
||||
block(@Body() request: ContactRequest) {
|
||||
const whatsapp = this.manager.getSession(request.session)
|
||||
return whatsapp.blockContact(request)
|
||||
}
|
||||
@Post('/unblock')
|
||||
@ApiOperation({summary:"Unblock contact"})
|
||||
unblock(@Body() request: ContactRequest) {
|
||||
const whatsapp = this.manager.getSession(request.session)
|
||||
return whatsapp.unblockContact(request)
|
||||
}
|
||||
@Post('/block')
|
||||
@ApiOperation({ summary: 'Block contact' })
|
||||
block(@Body() request: ContactRequest) {
|
||||
const whatsapp = this.manager.getSession(request.session);
|
||||
return whatsapp.blockContact(request);
|
||||
}
|
||||
@Post('/unblock')
|
||||
@ApiOperation({ summary: 'Unblock contact' })
|
||||
unblock(@Body() request: ContactRequest) {
|
||||
const whatsapp = this.manager.getSession(request.session);
|
||||
return whatsapp.unblockContact(request);
|
||||
}
|
||||
}
|
||||
+36
-35
@@ -1,6 +1,13 @@
|
||||
import {ArgumentsHost, Catch, ExceptionFilter, HttpException, HttpStatus,} from '@nestjs/common';
|
||||
import {Request, Response} from 'express';
|
||||
import {VERSION} from "../version";
|
||||
import {
|
||||
ArgumentsHost,
|
||||
Catch,
|
||||
ExceptionFilter,
|
||||
HttpException,
|
||||
HttpStatus,
|
||||
} from '@nestjs/common';
|
||||
import { Request, Response } from 'express';
|
||||
|
||||
import { VERSION } from '../version';
|
||||
|
||||
/**
|
||||
* Serializer error to JSON.
|
||||
@@ -8,42 +15,36 @@ import {VERSION} from "../version";
|
||||
* Credits: https://stackoverflow.com/a/72707578/6753144
|
||||
*/
|
||||
export function serializeError(err: unknown) {
|
||||
const properties = Object.getOwnPropertyNames(err)
|
||||
// getOwnPropertyNames does not get 'name', like "ReferenceError"
|
||||
properties.push('name')
|
||||
return JSON.parse(JSON.stringify(err, properties))
|
||||
const properties = Object.getOwnPropertyNames(err);
|
||||
// getOwnPropertyNames does not get 'name', like "ReferenceError"
|
||||
properties.push('name');
|
||||
return JSON.parse(JSON.stringify(err, properties));
|
||||
}
|
||||
|
||||
@Catch()
|
||||
export class AllExceptionsFilter implements ExceptionFilter {
|
||||
catch(exception: HttpException | Error, host: ArgumentsHost): void {
|
||||
|
||||
const ctx = host.switchToHttp();
|
||||
const response = ctx.getResponse<Response>();
|
||||
const request = ctx.getRequest<Request>();
|
||||
|
||||
if (exception instanceof HttpException) {
|
||||
response
|
||||
.status(exception.getStatus())
|
||||
.json(exception.getResponse());
|
||||
return
|
||||
}
|
||||
|
||||
const httpStatus = HttpStatus.INTERNAL_SERVER_ERROR
|
||||
response
|
||||
.status(httpStatus)
|
||||
.json({
|
||||
statusCode: httpStatus,
|
||||
timestamp: new Date().toISOString(),
|
||||
exception: serializeError(exception),
|
||||
request: {
|
||||
path: request.url,
|
||||
method: request.method,
|
||||
body: request.body,
|
||||
query: request.query,
|
||||
},
|
||||
version: VERSION,
|
||||
});
|
||||
catch(exception: HttpException | Error, host: ArgumentsHost): void {
|
||||
const ctx = host.switchToHttp();
|
||||
const response = ctx.getResponse<Response>();
|
||||
const request = ctx.getRequest<Request>();
|
||||
|
||||
if (exception instanceof HttpException) {
|
||||
response.status(exception.getStatus()).json(exception.getResponse());
|
||||
return;
|
||||
}
|
||||
|
||||
const httpStatus = HttpStatus.INTERNAL_SERVER_ERROR;
|
||||
response.status(httpStatus).json({
|
||||
statusCode: httpStatus,
|
||||
timestamp: new Date().toISOString(),
|
||||
exception: serializeError(exception),
|
||||
request: {
|
||||
path: request.url,
|
||||
method: request.method,
|
||||
body: request.body,
|
||||
query: request.query,
|
||||
},
|
||||
version: VERSION,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
import {
|
||||
Body,
|
||||
Controller,
|
||||
Delete,
|
||||
Get,
|
||||
Param,
|
||||
Post,
|
||||
Put,
|
||||
} from '@nestjs/common';
|
||||
import { ApiOperation, ApiSecurity, ApiTags } from '@nestjs/swagger';
|
||||
|
||||
import { SessionManager } from '../core/abc/manager.abc';
|
||||
import { WhatsappSession } from '../core/abc/session.abc';
|
||||
import {
|
||||
CreateGroupRequest,
|
||||
DescriptionRequest,
|
||||
ParticipantsRequest,
|
||||
SubjectRequest,
|
||||
} from '../structures/groups.dto';
|
||||
import { SessionApiParam, SessionParam } from './helpers';
|
||||
|
||||
@ApiSecurity('api_key')
|
||||
@Controller('api/:session/groups')
|
||||
@ApiTags('groups')
|
||||
export class GroupsController {
|
||||
constructor(private manager: SessionManager) {}
|
||||
|
||||
@Post('')
|
||||
@SessionApiParam
|
||||
@ApiOperation({ summary: 'Create a new group.' })
|
||||
createGroup(
|
||||
@SessionParam session: WhatsappSession,
|
||||
@Body() request: CreateGroupRequest,
|
||||
) {
|
||||
return session.createGroup(request);
|
||||
}
|
||||
|
||||
@Get('')
|
||||
@SessionApiParam
|
||||
@ApiOperation({ summary: 'Get all groups.' })
|
||||
getGroups(@SessionParam session: WhatsappSession) {
|
||||
return session.getGroups();
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
@SessionApiParam
|
||||
@ApiOperation({ summary: 'Get the group.' })
|
||||
getGroup(@SessionParam session: WhatsappSession, @Param('id') id: string) {
|
||||
return session.getGroup(id);
|
||||
}
|
||||
|
||||
@Put(':id/settings/security/info-admin-only')
|
||||
@SessionApiParam
|
||||
@ApiOperation({
|
||||
summary:
|
||||
'Updates the group settings to only allow admins to edit group info (title, description, photo).',
|
||||
})
|
||||
setGroupAdminOnly(
|
||||
@SessionParam session: WhatsappSession,
|
||||
@Param('id') id: string,
|
||||
) {
|
||||
return session.setInfoAdminsOnly(id, true);
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
@SessionApiParam
|
||||
@ApiOperation({ summary: 'Delete the group.' })
|
||||
deleteGroup(@SessionParam session: WhatsappSession, @Param('id') id: string) {
|
||||
return session.deleteGroup(id);
|
||||
}
|
||||
|
||||
@Post(':id/leave')
|
||||
@SessionApiParam
|
||||
@ApiOperation({ summary: 'Leave the group.' })
|
||||
leaveGroup(@SessionParam session: WhatsappSession, @Param('id') id: string) {
|
||||
return session.leaveGroup(id);
|
||||
}
|
||||
|
||||
@Put(':id/description')
|
||||
@ApiOperation({
|
||||
summary:
|
||||
'Updates the group description.\n' +
|
||||
'Returns true if the subject was properly updated. This can return false if the user does not have the necessary permissions.\n',
|
||||
})
|
||||
@SessionApiParam
|
||||
setDescription(
|
||||
@SessionParam session: WhatsappSession,
|
||||
@Param('id') id: string,
|
||||
@Body() request: DescriptionRequest,
|
||||
) {
|
||||
return session.setDescription(id, request.description);
|
||||
}
|
||||
|
||||
@Put(':id/subject')
|
||||
@SessionApiParam
|
||||
@ApiOperation({
|
||||
summary:
|
||||
'Updates the group subject.\n' +
|
||||
'Returns true if the subject was properly updated. This can return false if the user does not have the necessary permissions.\n',
|
||||
})
|
||||
setSubject(
|
||||
@SessionParam session: WhatsappSession,
|
||||
@Param('id') id: string,
|
||||
@Body() request: SubjectRequest,
|
||||
) {
|
||||
return session.setSubject(id, request.subject);
|
||||
}
|
||||
|
||||
@Get(':id/invite-code')
|
||||
@SessionApiParam
|
||||
@ApiOperation({ summary: 'Gets the invite code for a specific group.' })
|
||||
getInviteCode(
|
||||
@SessionParam session: WhatsappSession,
|
||||
@Param('id') id: string,
|
||||
): Promise<string> {
|
||||
return session.getInviteCode(id);
|
||||
}
|
||||
|
||||
@Post(':id/invite-code/revoke')
|
||||
@SessionApiParam
|
||||
@ApiOperation({
|
||||
summary:
|
||||
'Invalidates the current group invite code and generates a new one.',
|
||||
})
|
||||
revokeInviteCode(
|
||||
@SessionParam session: WhatsappSession,
|
||||
@Param('id') id: string,
|
||||
): Promise<string> {
|
||||
return session.revokeInviteCode(id);
|
||||
}
|
||||
|
||||
@Get(':id/participants/')
|
||||
@SessionApiParam
|
||||
@ApiOperation({ summary: 'Get a list of participants by in the group.' })
|
||||
getParticipants(
|
||||
@SessionParam session: WhatsappSession,
|
||||
@Param('id') id: string,
|
||||
) {
|
||||
return session.getParticipants(id);
|
||||
}
|
||||
|
||||
@Post(':id/participants/add')
|
||||
@SessionApiParam
|
||||
@ApiOperation({ summary: 'Adds a list of participants by ID to the group.' })
|
||||
addParticipants(
|
||||
@SessionParam session: WhatsappSession,
|
||||
@Param('id') id: string,
|
||||
@Body() request: ParticipantsRequest,
|
||||
) {
|
||||
return session.addParticipants(id, request);
|
||||
}
|
||||
|
||||
@Post(':id/participants/remove')
|
||||
@SessionApiParam
|
||||
@ApiOperation({
|
||||
summary: 'Removes a list of participants by ID to the group.',
|
||||
})
|
||||
removeParticipants(
|
||||
@SessionParam session: WhatsappSession,
|
||||
@Param('id') id: string,
|
||||
@Body() request: ParticipantsRequest,
|
||||
) {
|
||||
return session.removeParticipants(id, request);
|
||||
}
|
||||
|
||||
@Post(':id/admin/promote')
|
||||
@SessionApiParam
|
||||
@ApiOperation({ summary: 'Promote participants to admin users.' })
|
||||
promoteToAdmin(
|
||||
@SessionParam session: WhatsappSession,
|
||||
@Param('id') id: string,
|
||||
@Body() request: ParticipantsRequest,
|
||||
) {
|
||||
return session.promoteParticipantsToAdmin(id, request);
|
||||
}
|
||||
|
||||
@Post(':id/admin/demote')
|
||||
@SessionApiParam
|
||||
@ApiOperation({ summary: 'Demotes participants by to regular users.' })
|
||||
demoteToAdmin(
|
||||
@SessionParam session: WhatsappSession,
|
||||
@Param('id') id: string,
|
||||
@Body() request: ParticipantsRequest,
|
||||
) {
|
||||
return session.demoteParticipantsToUser(id, request);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import { Injectable, Param, PipeTransform } from '@nestjs/common';
|
||||
import { ApiParam } from '@nestjs/swagger';
|
||||
|
||||
import { SessionManager } from '../core/abc/manager.abc';
|
||||
import { WhatsappSession } from '../core/abc/session.abc';
|
||||
|
||||
/**
|
||||
* Get session name and return Whatsapp session back (if exists)
|
||||
* use it as
|
||||
@Param('session', SessionPipe) session: WhatsappSession,
|
||||
*/
|
||||
@Injectable()
|
||||
export class SessionPipe implements PipeTransform<WhatsappSession> {
|
||||
constructor(private manager: SessionManager) {}
|
||||
|
||||
async transform(value: any) {
|
||||
return this.manager.getSession(value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decorator for a method that uses SessionPipe above
|
||||
*/
|
||||
export const SessionApiParam = ApiParam({
|
||||
name: 'session',
|
||||
required: true,
|
||||
type: 'string',
|
||||
description: 'WhatsApp session name',
|
||||
});
|
||||
|
||||
/**
|
||||
* Session param
|
||||
@SessionParam session: WhatsappSession,
|
||||
*/
|
||||
export const SessionParam = Param('session', SessionPipe);
|
||||
@@ -0,0 +1,89 @@
|
||||
import {
|
||||
BadRequestException,
|
||||
Body,
|
||||
Controller,
|
||||
Get,
|
||||
Param,
|
||||
Post,
|
||||
} from '@nestjs/common';
|
||||
import { ApiOperation, ApiSecurity, ApiTags } from '@nestjs/swagger';
|
||||
|
||||
import { SessionManager } from '../core/abc/manager.abc';
|
||||
import { WhatsappSession } from '../core/abc/session.abc';
|
||||
import {
|
||||
WAHAChatPresences,
|
||||
WAHASessionPresence,
|
||||
} from '../structures/presence.dto';
|
||||
import { SessionApiParam, SessionParam } from './helpers';
|
||||
import { WAHAPresenceStatus } from '../structures/enums.dto';
|
||||
|
||||
@ApiSecurity('api_key')
|
||||
@Controller('api/:session/presence')
|
||||
@ApiTags('presence')
|
||||
export class PresenceController {
|
||||
constructor(private manager: SessionManager) {}
|
||||
|
||||
@Post('')
|
||||
@SessionApiParam
|
||||
@ApiOperation({ summary: 'Set session presence' })
|
||||
setPresence(
|
||||
@SessionParam session: WhatsappSession,
|
||||
@Body() request: WAHASessionPresence,
|
||||
) {
|
||||
// Validate request
|
||||
const presencesWithoutChatId = [
|
||||
WAHAPresenceStatus.ONLINE,
|
||||
WAHAPresenceStatus.OFFLINE,
|
||||
];
|
||||
const requiresNoChatId = presencesWithoutChatId.includes(request.presence);
|
||||
const requiresChatId = !requiresNoChatId;
|
||||
|
||||
if (requiresNoChatId && request.chatId) {
|
||||
const msg = {
|
||||
detail: `'${request.presence}' presence works on the global scope and doesn't require 'chatId' field.`,
|
||||
};
|
||||
throw new BadRequestException(msg);
|
||||
} else if (requiresChatId && !request.chatId) {
|
||||
const msg = {
|
||||
detail: `'${request.presence}' presence requires 'chatId' field.`,
|
||||
};
|
||||
throw new BadRequestException(msg);
|
||||
}
|
||||
|
||||
return session.setPresence(request.presence, request.chatId);
|
||||
}
|
||||
|
||||
@Get('')
|
||||
@SessionApiParam
|
||||
@ApiOperation({ summary: 'Get all subscribed presence information.' })
|
||||
getPresenceAll(
|
||||
@SessionParam session: WhatsappSession,
|
||||
): Promise<WAHAChatPresences[]> {
|
||||
return session.getPresences();
|
||||
}
|
||||
|
||||
@Get(':chatId')
|
||||
@SessionApiParam
|
||||
@ApiOperation({
|
||||
summary:
|
||||
"Get the presence for the chat id. If it hasn't been subscribed - it also subscribes to it.",
|
||||
})
|
||||
getPresence(
|
||||
@SessionParam session: WhatsappSession,
|
||||
@Param('chatId') chatId: string,
|
||||
): Promise<WAHAChatPresences> {
|
||||
return session.getPresence(chatId);
|
||||
}
|
||||
|
||||
@Post(':chatId/subscribe')
|
||||
@SessionApiParam
|
||||
@ApiOperation({
|
||||
summary: 'Subscribe to presence events for the chat.',
|
||||
})
|
||||
subscribe(
|
||||
@SessionParam session: WhatsappSession,
|
||||
@Param('chatId') chatId: string,
|
||||
): Promise<void> {
|
||||
return session.subscribePresence(chatId);
|
||||
}
|
||||
}
|
||||
@@ -1,34 +1,29 @@
|
||||
import {Controller, Get, Query, Res} from '@nestjs/common';
|
||||
import {ApiSecurity, ApiTags} from "@nestjs/swagger";
|
||||
import {Readable} from "stream";
|
||||
import {Response} from 'express';
|
||||
import {SessionManager} from "../core/abc/manager.abc";
|
||||
import {SessionQuery} from "../structures/base.dto";
|
||||
import { Controller, Get, Query, Res } from '@nestjs/common';
|
||||
import { ApiSecurity, ApiTags } from '@nestjs/swagger';
|
||||
import { Response } from 'express';
|
||||
import { Readable } from 'stream';
|
||||
|
||||
import { SessionManager } from '../core/abc/manager.abc';
|
||||
import { SessionQuery } from '../structures/base.dto';
|
||||
|
||||
@ApiSecurity('api_key')
|
||||
@Controller('api')
|
||||
@ApiTags('screenshot')
|
||||
export class ScreenshotController {
|
||||
constructor(private manager: SessionManager) {
|
||||
}
|
||||
constructor(private manager: SessionManager) {}
|
||||
|
||||
@Get('/screenshot')
|
||||
async screenshot(
|
||||
@Res() res: Response,
|
||||
@Query() sessionQuery: SessionQuery,
|
||||
) {
|
||||
const whatsappService = this.manager.getSession(sessionQuery.session)
|
||||
const buffer = await whatsappService.getScreenshot();
|
||||
const stream = new Readable();
|
||||
stream.push(buffer);
|
||||
stream.push(null);
|
||||
@Get('/screenshot')
|
||||
async screenshot(@Res() res: Response, @Query() sessionQuery: SessionQuery) {
|
||||
const whatsappService = this.manager.getSession(sessionQuery.session);
|
||||
const buffer = await whatsappService.getScreenshot();
|
||||
const stream = new Readable();
|
||||
stream.push(buffer);
|
||||
stream.push(null);
|
||||
|
||||
res.set({
|
||||
'Content-Type': 'image/png',
|
||||
'Content-Length': buffer.length,
|
||||
});
|
||||
stream.pipe(res)
|
||||
}
|
||||
res.set({
|
||||
'Content-Type': 'image/png',
|
||||
'Content-Length': buffer.length,
|
||||
});
|
||||
stream.pipe(res);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,30 +1,73 @@
|
||||
import {Body, Controller, Get, Post} from '@nestjs/common';
|
||||
import {ApiSecurity, ApiTags} from "@nestjs/swagger";
|
||||
import {SessionDTO, SessionStartRequest, SessionStopRequest} from "../structures/sessions.dto";
|
||||
import {SessionManager} from "../core/abc/manager.abc";
|
||||
import { Body, Controller, Get, Post, Query } from '@nestjs/common';
|
||||
import { UnprocessableEntityException } from '@nestjs/common/exceptions/unprocessable-entity.exception';
|
||||
import { ApiOperation, ApiSecurity, ApiTags } from '@nestjs/swagger';
|
||||
|
||||
import { SessionManager } from '../core/abc/manager.abc';
|
||||
import { parseBool } from '../helpers';
|
||||
import {
|
||||
ListSessionsQuery,
|
||||
MeInfo,
|
||||
SessionDTO,
|
||||
SessionInfo,
|
||||
SessionLogoutRequest,
|
||||
SessionStartRequest,
|
||||
SessionStopRequest,
|
||||
} from '../structures/sessions.dto';
|
||||
import { SessionApiParam, SessionParam } from './helpers';
|
||||
import { WhatsappSession } from '../core/abc/session.abc';
|
||||
import { WAHAChatPresences } from '../structures/presence.dto';
|
||||
|
||||
@ApiSecurity('api_key')
|
||||
@Controller('api/sessions')
|
||||
@ApiTags('sessions')
|
||||
export class SessionsController {
|
||||
constructor(private manager: SessionManager) {
|
||||
}
|
||||
class SessionsController {
|
||||
constructor(private manager: SessionManager) {}
|
||||
|
||||
@Post('/start/')
|
||||
async start(@Body() request: SessionStartRequest): Promise<SessionDTO> {
|
||||
const result = await this.manager.start(request);
|
||||
await this.manager.sessionStorage.configRepository.save(
|
||||
request.name,
|
||||
request.config,
|
||||
);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Post('/start/')
|
||||
start(@Body() request: SessionStartRequest): SessionDTO {
|
||||
return this.manager.start(request)
|
||||
@Post('/stop/')
|
||||
@ApiOperation({ summary: 'Stop session' })
|
||||
async stop(@Body() request: SessionStopRequest): Promise<void> {
|
||||
await this.manager.stop(request);
|
||||
if (request.logout) {
|
||||
await this.manager.logout(request);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@Post('/stop/')
|
||||
stop(@Body() request: SessionStopRequest): Promise<void> {
|
||||
return this.manager.stop(request)
|
||||
}
|
||||
@Post('/logout/')
|
||||
@ApiOperation({ summary: 'Logout from session.' })
|
||||
clean(@Body() request: SessionLogoutRequest): Promise<void> {
|
||||
return this.manager.logout(request);
|
||||
}
|
||||
|
||||
@Get('/')
|
||||
list(): SessionDTO[] {
|
||||
return this.manager.getSessions()
|
||||
}
|
||||
@Get('/')
|
||||
async list(@Query() query: ListSessionsQuery): Promise<SessionInfo[]> {
|
||||
const all = parseBool(query.all);
|
||||
return this.manager.getSessions(all);
|
||||
}
|
||||
}
|
||||
|
||||
@ApiSecurity('api_key')
|
||||
@Controller('api/sessions/:session')
|
||||
@ApiTags('sessions')
|
||||
class SessionController {
|
||||
constructor(private manager: SessionManager) {}
|
||||
|
||||
@Get('me')
|
||||
@SessionApiParam
|
||||
@ApiOperation({ summary: 'Get information about the authenticated account' })
|
||||
getMe(@SessionParam session: WhatsappSession): Promise<MeInfo> {
|
||||
return session.getSessionMeInfo();
|
||||
}
|
||||
}
|
||||
|
||||
export { SessionController, SessionsController };
|
||||
@@ -0,0 +1,69 @@
|
||||
import {
|
||||
Body,
|
||||
Controller,
|
||||
Delete,
|
||||
Get,
|
||||
Param,
|
||||
Post,
|
||||
Query,
|
||||
} from '@nestjs/common';
|
||||
import { ApiOperation, ApiSecurity, ApiTags } from '@nestjs/swagger';
|
||||
|
||||
import { SessionManager } from '../core/abc/manager.abc';
|
||||
import { WhatsappSession } from '../core/abc/session.abc';
|
||||
import { SessionApiParam, SessionParam } from './helpers';
|
||||
import {
|
||||
ImageStatus,
|
||||
TextStatus,
|
||||
VideoStatus,
|
||||
VoiceStatus,
|
||||
} from '../structures/status.dto';
|
||||
|
||||
@ApiSecurity('api_key')
|
||||
@Controller('api/:session/status')
|
||||
@ApiTags('status')
|
||||
class StatusController {
|
||||
constructor(private manager: SessionManager) {}
|
||||
|
||||
@Post('text')
|
||||
@SessionApiParam
|
||||
@ApiOperation({ summary: 'Send text status.' })
|
||||
sendTextStatus(
|
||||
@SessionParam session: WhatsappSession,
|
||||
@Body() status: TextStatus,
|
||||
) {
|
||||
return session.sendTextStatus(status);
|
||||
}
|
||||
|
||||
@Post('image')
|
||||
@SessionApiParam
|
||||
@ApiOperation({ summary: 'Send image status.' })
|
||||
sendImageStatus(
|
||||
@SessionParam session: WhatsappSession,
|
||||
@Body() status: ImageStatus,
|
||||
) {
|
||||
return session.sendImageStatus(status);
|
||||
}
|
||||
|
||||
@Post('voice')
|
||||
@SessionApiParam
|
||||
@ApiOperation({ summary: 'Send voice status.' })
|
||||
sendVoiceStatus(
|
||||
@SessionParam session: WhatsappSession,
|
||||
@Body() status: VoiceStatus,
|
||||
) {
|
||||
return session.sendVoiceStatus(status);
|
||||
}
|
||||
|
||||
@Post('video')
|
||||
@SessionApiParam
|
||||
@ApiOperation({ summary: 'Send video status.' })
|
||||
sendVideoStatus(
|
||||
@SessionParam session: WhatsappSession,
|
||||
@Body() status: VideoStatus,
|
||||
) {
|
||||
return session.sendVideoStatus(status);
|
||||
}
|
||||
}
|
||||
|
||||
export { StatusController };
|
||||
@@ -1,15 +1,14 @@
|
||||
import {Controller, Get} from '@nestjs/common';
|
||||
import {ApiSecurity, ApiTags} from "@nestjs/swagger";
|
||||
import {VERSION} from "../version";
|
||||
import { Controller, Get } from '@nestjs/common';
|
||||
import { ApiSecurity, ApiTags } from '@nestjs/swagger';
|
||||
|
||||
import { VERSION } from '../version';
|
||||
|
||||
@ApiSecurity('api_key')
|
||||
@Controller('api/version')
|
||||
@ApiTags('other')
|
||||
export class VersionController {
|
||||
@Get('')
|
||||
async get() {
|
||||
return VERSION
|
||||
}
|
||||
@Get('')
|
||||
async get() {
|
||||
return VERSION;
|
||||
}
|
||||
}
|
||||
|
||||
+126
-77
@@ -1,90 +1,139 @@
|
||||
import {Injectable} from "@nestjs/common";
|
||||
import {ConfigService} from "@nestjs/config";
|
||||
import {WhatsappEngine} from "./structures/enums.dto";
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
|
||||
import { parseBool } from './helpers';
|
||||
import { WAHAEngine } from './structures/enums.dto';
|
||||
import { WebhookConfig } from './structures/webhooks.config.dto';
|
||||
|
||||
@Injectable()
|
||||
export class WhatsappConfigService {
|
||||
public files_uri = '/api/files'
|
||||
public schema = "http"
|
||||
public files_uri = '/api/files';
|
||||
public schema = 'http';
|
||||
|
||||
constructor(private configService: ConfigService) {
|
||||
constructor(private configService: ConfigService) {}
|
||||
|
||||
get filesURL(): string {
|
||||
return `${this.schema}://${this.hostname}:${this.port}${this.files_uri}/`;
|
||||
}
|
||||
|
||||
get hostname(): string {
|
||||
return this.configService.get('WHATSAPP_API_HOSTNAME', 'localhost');
|
||||
}
|
||||
|
||||
get port(): string {
|
||||
return this.configService.get('WHATSAPP_API_PORT', '3000');
|
||||
}
|
||||
|
||||
get filesFolder(): string {
|
||||
return this.configService.get(
|
||||
'WHATSAPP_FILES_FOLDER',
|
||||
'/tmp/whatsapp-files',
|
||||
);
|
||||
}
|
||||
|
||||
get filesLifetime(): number {
|
||||
return this.configService.get<number>('WHATSAPP_FILES_LIFETIME', 180);
|
||||
}
|
||||
|
||||
get mimetypes(): string[] | null {
|
||||
const types = this.configService.get('WHATSAPP_FILES_MIMETYPES', '');
|
||||
return types ? types.split(',') : null;
|
||||
}
|
||||
|
||||
get startSessions(): string[] {
|
||||
const value: string = this.configService.get('WHATSAPP_START_SESSION', '');
|
||||
if (!value) {
|
||||
return [];
|
||||
}
|
||||
return value.split(',');
|
||||
}
|
||||
|
||||
get filesURL(): string {
|
||||
return `${this.schema}://${this.hostname}:${this.port}${this.files_uri}/`
|
||||
get shouldRestartAllSessions(): boolean {
|
||||
const value: string = this.configService.get(
|
||||
'WHATSAPP_RESTART_ALL_SESSIONS',
|
||||
'false',
|
||||
);
|
||||
return parseBool(value);
|
||||
}
|
||||
|
||||
get proxyServer(): string[] | string | undefined {
|
||||
const single = this.configService.get('WHATSAPP_PROXY_SERVER', undefined);
|
||||
const multipleValues = this.configService.get(
|
||||
'WHATSAPP_PROXY_SERVER_LIST',
|
||||
undefined,
|
||||
);
|
||||
const multiple = multipleValues ? multipleValues.split(',') : undefined;
|
||||
return single ? single : multiple;
|
||||
}
|
||||
|
||||
get proxyServerIndexPrefix(): string | undefined {
|
||||
return this.configService.get(
|
||||
'WHATSAPP_PROXY_SERVER_INDEX_PREFIX',
|
||||
undefined,
|
||||
);
|
||||
}
|
||||
|
||||
get proxyServerUsername(): string | undefined {
|
||||
return this.configService.get('WHATSAPP_PROXY_SERVER_USERNAME', undefined);
|
||||
}
|
||||
|
||||
get proxyServerPassword(): string | undefined {
|
||||
return this.configService.get('WHATSAPP_PROXY_SERVER_PASSWORD', undefined);
|
||||
}
|
||||
|
||||
getWebhookConfig(): WebhookConfig | undefined {
|
||||
const url = this.getWebhookUrl();
|
||||
const events = this.getWebhookEvents();
|
||||
if (!url || events.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
return { url: url, events: events };
|
||||
}
|
||||
|
||||
get hostname(): string {
|
||||
return this.configService.get(
|
||||
'WHATSAPP_API_HOSTNAME',
|
||||
'localhost',
|
||||
)
|
||||
private getWebhookUrl(): string | undefined {
|
||||
return this.get('WHATSAPP_HOOK_URL');
|
||||
}
|
||||
|
||||
private getWebhookEvents(): string[] {
|
||||
const value = this.get('WHATSAPP_HOOK_EVENTS', '');
|
||||
return value ? value.split(',') : [];
|
||||
}
|
||||
|
||||
getDefaultEngineName(): WAHAEngine {
|
||||
const value = this.get('WHATSAPP_DEFAULT_ENGINE', WAHAEngine.WEBJS);
|
||||
if (value in WAHAEngine) {
|
||||
return WAHAEngine[value];
|
||||
}
|
||||
console.log(
|
||||
`Unknown WhatsApp default engine, using WEBJS. WHATSAPP_DEFAULT_ENGINE=${value}`,
|
||||
);
|
||||
}
|
||||
|
||||
get port(): string {
|
||||
return this.configService.get(
|
||||
'WHATSAPP_API_PORT',
|
||||
'3000',
|
||||
)
|
||||
get(name: string, defaultValue = undefined): any {
|
||||
return this.configService.get(name, defaultValue);
|
||||
}
|
||||
|
||||
getApiKey(): string | undefined {
|
||||
return this.configService.get('WHATSAPP_API_KEY', '');
|
||||
}
|
||||
|
||||
getSwaggerUsernamePassword(): [string, string] | undefined {
|
||||
const user = this.configService.get('WHATSAPP_SWAGGER_USERNAME', undefined);
|
||||
const password = this.configService.get(
|
||||
'WHATSAPP_SWAGGER_PASSWORD',
|
||||
undefined,
|
||||
);
|
||||
if (!user && !password) {
|
||||
console.log(
|
||||
'Please set up both WHATSAPP_SWAGGER_USERNAME and WHATSAPP_SWAGGER_PASSWORD ' +
|
||||
'to enable swagger authentication.',
|
||||
);
|
||||
return undefined;
|
||||
}
|
||||
return [user, password];
|
||||
}
|
||||
|
||||
get filesFolder(): string {
|
||||
return this.configService.get(
|
||||
'WHATSAPP_FILES_FOLDER',
|
||||
'/tmp/whatsapp-files',
|
||||
)
|
||||
}
|
||||
|
||||
get filesLifetime(): number {
|
||||
return this.configService.get<number>(
|
||||
'WHATSAPP_FILES_LIFETIME',
|
||||
180,
|
||||
)
|
||||
}
|
||||
|
||||
get mimetypes(): string[] | null {
|
||||
const types = this.configService.get('WHATSAPP_FILES_MIMETYPES', "")
|
||||
return types ? types.split(',') : null
|
||||
}
|
||||
|
||||
get startSession(): string | undefined {
|
||||
return this.configService.get('WHATSAPP_START_SESSION', undefined)
|
||||
}
|
||||
|
||||
getWebhookUrl(): string | undefined {
|
||||
return this.get('WHATSAPP_HOOK_URL')
|
||||
}
|
||||
|
||||
getWebhookEvents(): string[] {
|
||||
const value = this.get('WHATSAPP_HOOK_EVENTS', "")
|
||||
return value ? value.split(',') : []
|
||||
}
|
||||
|
||||
getDefaultEngineName(): WhatsappEngine {
|
||||
const value = this.get("WHATSAPP_DEFAULT_ENGINE", WhatsappEngine.WEBJS)
|
||||
if (value in WhatsappEngine) {
|
||||
return WhatsappEngine[value]
|
||||
}
|
||||
console.log(`Unknown WhatsApp default engine, using WEBJS. WHATSAPP_DEFAULT_ENGINE=${value}`)
|
||||
|
||||
}
|
||||
|
||||
get(name: string, defaultValue = undefined): any {
|
||||
return this.configService.get(name, defaultValue)
|
||||
}
|
||||
|
||||
getApiKey(): string | undefined {
|
||||
return this.configService.get("WHATSAPP_API_KEY", "")
|
||||
}
|
||||
|
||||
getSwaggerUsernamePassword(): [string, string] | undefined {
|
||||
const user = this.configService.get("WHATSAPP_SWAGGER_USERNAME", undefined)
|
||||
const password = this.configService.get("WHATSAPP_SWAGGER_PASSWORD", undefined)
|
||||
if (!user && !password) {
|
||||
console.log("Please set up both WHATSAPP_SWAGGER_USERNAME and WHATSAPP_SWAGGER_PASSWORD " +
|
||||
"to enable swagger authentication.")
|
||||
return undefined
|
||||
}
|
||||
return [user, password]
|
||||
}
|
||||
getSwaggerAdvancedConfigEnabled(): boolean {
|
||||
return this.configService.get('WHATSAPP_SWAGGER_CONFIG_ADVANCED', false);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
export class QR {
|
||||
private base64: string;
|
||||
|
||||
save(base64) {
|
||||
this.base64 = base64.replace(/^data:image\/png;base64,/, '');
|
||||
}
|
||||
|
||||
get(): Buffer {
|
||||
return Buffer.from(this.base64, 'base64');
|
||||
}
|
||||
}
|
||||
+28
-16
@@ -1,28 +1,40 @@
|
||||
import {OnApplicationShutdown} from "@nestjs/common";
|
||||
import {SessionDTO, SessionStartRequest, SessionStopRequest} from "../../structures/sessions.dto";
|
||||
import {WhatsappSession} from "./session.abc";
|
||||
import {WhatsappEngine} from "../../structures/enums.dto";
|
||||
import {MediaStorage} from "./storage.abc";
|
||||
import {WebhookConductor} from "./webhooks.abc";
|
||||
import { OnApplicationShutdown } from '@nestjs/common';
|
||||
|
||||
import { WAHAEngine } from '../../structures/enums.dto';
|
||||
import {
|
||||
SessionDTO,
|
||||
SessionInfo,
|
||||
SessionLogoutRequest,
|
||||
SessionStartRequest,
|
||||
SessionStopRequest,
|
||||
} from '../../structures/sessions.dto';
|
||||
import { WhatsappSession } from './session.abc';
|
||||
import { LocalSessionStorage, MediaStorage } from './storage.abc';
|
||||
import { WebhookConductor } from './webhooks.abc';
|
||||
|
||||
export abstract class SessionManager implements OnApplicationShutdown {
|
||||
public sessionStorage: LocalSessionStorage;
|
||||
|
||||
abstract start(request: SessionStartRequest): SessionDTO
|
||||
protected abstract getEngine(engine: WAHAEngine): typeof WhatsappSession;
|
||||
|
||||
abstract stop(request: SessionStopRequest): Promise<void>
|
||||
protected abstract get EngineClass(): typeof WhatsappSession;
|
||||
|
||||
abstract getSession(name: string): WhatsappSession
|
||||
protected abstract get WebhookConductorClass(): typeof WebhookConductor;
|
||||
|
||||
abstract getSessions(): SessionDTO[]
|
||||
protected abstract get MediaStorageClass(): typeof MediaStorage;
|
||||
|
||||
abstract onApplicationShutdown(signal ?: string)
|
||||
abstract onApplicationShutdown(signal?: string);
|
||||
|
||||
protected abstract getEngine(engine: WhatsappEngine): typeof WhatsappSession
|
||||
//
|
||||
// API Methods
|
||||
//
|
||||
abstract start(request: SessionStartRequest): Promise<SessionDTO>;
|
||||
|
||||
protected abstract get EngineClass(): typeof WhatsappSession
|
||||
abstract stop(request: SessionStopRequest): Promise<void>;
|
||||
|
||||
protected abstract get WebhookConductorClass(): typeof WebhookConductor
|
||||
abstract logout(request: SessionLogoutRequest): Promise<void>;
|
||||
|
||||
protected abstract get MediaStorageClass(): typeof MediaStorage
|
||||
abstract getSession(name: string, error?: boolean): WhatsappSession;
|
||||
|
||||
abstract getSessions(all: boolean): Promise<SessionInfo[]>;
|
||||
}
|
||||
|
||||
+381
-158
@@ -1,164 +1,387 @@
|
||||
import {WAEvents, WhatsappStatus} from "../../structures/enums.dto";
|
||||
import {ConsoleLogger} from "@nestjs/common";
|
||||
import { ConsoleLogger } from '@nestjs/common';
|
||||
import { EventEmitter } from 'events';
|
||||
import * as fs from 'fs';
|
||||
import { MessageId } from 'whatsapp-web.js';
|
||||
|
||||
import {
|
||||
ChatRequest,
|
||||
CheckNumberStatusQuery,
|
||||
MessageContactVcardRequest,
|
||||
MessageFileRequest,
|
||||
MessageImageRequest,
|
||||
MessageLinkPreviewRequest,
|
||||
MessageLocationRequest,
|
||||
MessageReactionRequest,
|
||||
MessageReplyRequest,
|
||||
MessageTextButtonsRequest,
|
||||
MessageTextRequest,
|
||||
MessageVoiceRequest
|
||||
} from "../../structures/chatting.dto";
|
||||
import {MediaStorage} from "./storage.abc";
|
||||
import {MessageId} from "whatsapp-web.js";
|
||||
import {ContactQuery, ContactRequest} from "../../structures/contacts.dto";
|
||||
import {NotImplementedByEngineError} from "../exceptions";
|
||||
ChatRequest,
|
||||
CheckNumberStatusQuery,
|
||||
GetMessageQuery,
|
||||
MessageContactVcardRequest,
|
||||
MessageFileRequest,
|
||||
MessageImageRequest,
|
||||
MessageLinkPreviewRequest,
|
||||
MessageLocationRequest,
|
||||
MessagePollRequest,
|
||||
MessageReactionRequest,
|
||||
MessageReplyRequest,
|
||||
MessageTextButtonsRequest,
|
||||
MessageTextRequest,
|
||||
MessageVoiceRequest,
|
||||
} from '../../structures/chatting.dto';
|
||||
import { ContactQuery, ContactRequest } from '../../structures/contacts.dto';
|
||||
import {
|
||||
WAHAEngine,
|
||||
WAHAEvents,
|
||||
WAHAPresenceStatus,
|
||||
WAHASessionStatus,
|
||||
} from '../../structures/enums.dto';
|
||||
import {
|
||||
CreateGroupRequest,
|
||||
ParticipantsRequest,
|
||||
} from '../../structures/groups.dto';
|
||||
import { WAHAChatPresences } from '../../structures/presence.dto';
|
||||
import {
|
||||
MeInfo,
|
||||
ProxyConfig,
|
||||
SessionConfig,
|
||||
} from '../../structures/sessions.dto';
|
||||
import { NotImplementedByEngineError } from '../exceptions';
|
||||
import { LocalSessionStorage, MediaStorage } from './storage.abc';
|
||||
import { OTPRequest, RequestCodeRequest } from '../../structures/auth.dto';
|
||||
import {
|
||||
ImageStatus,
|
||||
TextStatus,
|
||||
VideoStatus,
|
||||
VoiceStatus,
|
||||
} from '../../structures/status.dto';
|
||||
|
||||
export abstract class WhatsappSession {
|
||||
public status: WhatsappStatus;
|
||||
|
||||
public constructor(public name: string, protected storage: MediaStorage, protected log: ConsoleLogger) {
|
||||
this.name = name
|
||||
this.status = WhatsappStatus.STARTING
|
||||
this.log = log
|
||||
}
|
||||
|
||||
getBrowserExecutablePath() {
|
||||
return "/usr/bin/google-chrome-stable"
|
||||
}
|
||||
|
||||
getBrowserArgsForPuppeteer() {
|
||||
// Run optimized version of Chrome
|
||||
// References:
|
||||
// https://github.com/pedroslopez/whatsapp-web.js/issues/1420
|
||||
// https://www.bannerbear.com/blog/ways-to-speed-up-puppeteer-screenshots/
|
||||
return [
|
||||
'--no-sandbox',
|
||||
'--disable-client-side-phishing-detection',
|
||||
'--disable-setuid-sandbox',
|
||||
'--disable-component-update',
|
||||
'--disable-default-apps',
|
||||
'--disable-popup-blocking',
|
||||
'--disable-offer-store-unmasked-wallet-cards',
|
||||
'--disable-speech-api',
|
||||
'--hide-scrollbars',
|
||||
'--mute-audio',
|
||||
'--disable-extensions',
|
||||
'--disable-dev-shm-usage',
|
||||
'--disable-accelerated-2d-canvas',
|
||||
'--no-first-run',
|
||||
'--no-default-browser-check',
|
||||
'--no-pings',
|
||||
'--password-store=basic',
|
||||
'--use-mock-keychain',
|
||||
'--no-zygote',
|
||||
'--single-process',
|
||||
'--disable-gpu'
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
/** Start the session */
|
||||
abstract start()
|
||||
|
||||
/** Stop the session */
|
||||
abstract stop(): void
|
||||
|
||||
/** Subscribe the handler to specific hook */
|
||||
abstract subscribe(hook: WAEvents | string, handler: (message) => void)
|
||||
|
||||
/**
|
||||
* START - Methods for API
|
||||
*/
|
||||
abstract getScreenshot(): Promise<Buffer | string>
|
||||
|
||||
abstract checkNumberStatus(request: CheckNumberStatusQuery)
|
||||
|
||||
abstract sendText(request: MessageTextRequest)
|
||||
|
||||
abstract sendContactVCard(request: MessageContactVcardRequest)
|
||||
|
||||
abstract sendTextButtons(request: MessageTextButtonsRequest)
|
||||
|
||||
abstract sendLocation(request: MessageLocationRequest)
|
||||
|
||||
abstract sendLinkPreview(request: MessageLinkPreviewRequest)
|
||||
|
||||
abstract sendImage(request: MessageImageRequest)
|
||||
|
||||
abstract sendFile(request: MessageFileRequest)
|
||||
|
||||
abstract sendVoice(request: MessageVoiceRequest)
|
||||
|
||||
abstract reply(request: MessageReplyRequest)
|
||||
|
||||
abstract sendSeen(chat: ChatRequest)
|
||||
|
||||
abstract startTyping(chat: ChatRequest)
|
||||
|
||||
abstract stopTyping(chat: ChatRequest)
|
||||
|
||||
abstract setReaction(request: MessageReactionRequest)
|
||||
|
||||
/**
|
||||
* Contacts methods
|
||||
*/
|
||||
public getContact(query: ContactQuery) {
|
||||
throw new NotImplementedByEngineError()
|
||||
}
|
||||
|
||||
public getContacts() {
|
||||
throw new NotImplementedByEngineError()
|
||||
}
|
||||
|
||||
public getContactAbout(query: ContactQuery) {
|
||||
throw new NotImplementedByEngineError()
|
||||
}
|
||||
|
||||
public getContactProfilePicture(query: ContactQuery) {
|
||||
throw new NotImplementedByEngineError()
|
||||
}
|
||||
|
||||
public blockContact(request: ContactRequest) {
|
||||
throw new NotImplementedByEngineError()
|
||||
}
|
||||
|
||||
public unblockContact(request: ContactRequest) {
|
||||
throw new NotImplementedByEngineError()
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* END - Methods for API
|
||||
*/
|
||||
|
||||
/**
|
||||
* Add WhatsApp suffix (@c.us) to the phone number if it doesn't have it yet
|
||||
* @param phone
|
||||
*/
|
||||
protected ensureSuffix(phone) {
|
||||
const suffix = "@c.us"
|
||||
if (phone.includes("@")) {
|
||||
return phone
|
||||
}
|
||||
return phone + suffix
|
||||
}
|
||||
|
||||
protected deserializeId(messageId: string): MessageId {
|
||||
const parts = messageId.split("_")
|
||||
return {
|
||||
"fromMe": parts[0] === "true",
|
||||
"remote": parts[1],
|
||||
"id": parts[2],
|
||||
"_serialized": messageId
|
||||
}
|
||||
}
|
||||
const CHROME_PATH = '/usr/bin/google-chrome-stable';
|
||||
const CHROMIUM_PATH = '/usr/bin/chromium';
|
||||
|
||||
export function getBrowserExecutablePath() {
|
||||
if (fs.existsSync(CHROME_PATH)) {
|
||||
return CHROME_PATH;
|
||||
}
|
||||
return CHROMIUM_PATH;
|
||||
}
|
||||
|
||||
export function ensureSuffix(phone) {
|
||||
const suffix = '@c.us';
|
||||
if (phone.includes('@')) {
|
||||
return phone;
|
||||
}
|
||||
return phone + suffix;
|
||||
}
|
||||
|
||||
export enum WAHAInternalEvent {
|
||||
engine_start = 'engine.start',
|
||||
}
|
||||
|
||||
export interface SessionParams {
|
||||
name: string;
|
||||
storage: MediaStorage;
|
||||
log: ConsoleLogger;
|
||||
sessionStorage: LocalSessionStorage;
|
||||
proxyConfig?: ProxyConfig;
|
||||
sessionConfig: SessionConfig;
|
||||
}
|
||||
|
||||
export abstract class WhatsappSession {
|
||||
public status: WAHASessionStatus;
|
||||
public events: EventEmitter;
|
||||
public engine: WAHAEngine;
|
||||
|
||||
public name: string;
|
||||
protected storage: MediaStorage;
|
||||
protected log: ConsoleLogger;
|
||||
protected sessionStorage: LocalSessionStorage;
|
||||
protected proxyConfig?: ProxyConfig;
|
||||
public sessionConfig: SessionConfig;
|
||||
|
||||
public constructor({
|
||||
name,
|
||||
log,
|
||||
sessionStorage,
|
||||
proxyConfig,
|
||||
storage,
|
||||
sessionConfig,
|
||||
}: SessionParams) {
|
||||
this.name = name;
|
||||
this.proxyConfig = proxyConfig;
|
||||
this.status = WAHASessionStatus.STARTING;
|
||||
this.log = log;
|
||||
this.sessionStorage = sessionStorage;
|
||||
this.events = new EventEmitter();
|
||||
this.storage = storage;
|
||||
this.sessionConfig = sessionConfig;
|
||||
}
|
||||
|
||||
getBrowserExecutablePath() {
|
||||
return getBrowserExecutablePath();
|
||||
}
|
||||
|
||||
getBrowserArgsForPuppeteer() {
|
||||
// Run optimized version of Chrome
|
||||
// References:
|
||||
// https://github.com/pedroslopez/whatsapp-web.js/issues/1420
|
||||
// https://www.bannerbear.com/blog/ways-to-speed-up-puppeteer-screenshots/
|
||||
return [
|
||||
'--no-sandbox',
|
||||
'--disable-client-side-phishing-detection',
|
||||
'--disable-setuid-sandbox',
|
||||
'--disable-component-update',
|
||||
'--disable-default-apps',
|
||||
'--disable-popup-blocking',
|
||||
'--disable-offer-store-unmasked-wallet-cards',
|
||||
'--disable-speech-api',
|
||||
'--hide-scrollbars',
|
||||
'--mute-audio',
|
||||
'--disable-extensions',
|
||||
'--disable-dev-shm-usage',
|
||||
'--disable-accelerated-2d-canvas',
|
||||
'--no-first-run',
|
||||
'--no-default-browser-check',
|
||||
'--no-pings',
|
||||
'--password-store=basic',
|
||||
'--use-mock-keychain',
|
||||
'--no-zygote',
|
||||
'--single-process',
|
||||
'--disable-gpu',
|
||||
];
|
||||
}
|
||||
|
||||
/** Start the session */
|
||||
abstract start();
|
||||
|
||||
/** Stop the session */
|
||||
abstract stop(): void;
|
||||
|
||||
/** Subscribe the handler to specific hook */
|
||||
abstract subscribe(hook: WAHAEvents | string, handler: (message) => void);
|
||||
|
||||
/**
|
||||
* START - Methods for API
|
||||
*/
|
||||
|
||||
/**
|
||||
* Auth methods
|
||||
*/
|
||||
|
||||
public getQR(): Promise<Buffer> {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public requestCode(phoneNumber: string, method: string) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public authorizeCode(code: string) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
abstract getScreenshot(): Promise<Buffer | string>;
|
||||
|
||||
public getSessionMeInfo(): Promise<MeInfo | null> {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
/**
|
||||
* Other methods
|
||||
*/
|
||||
abstract checkNumberStatus(request: CheckNumberStatusQuery);
|
||||
|
||||
abstract sendText(request: MessageTextRequest);
|
||||
|
||||
abstract sendContactVCard(request: MessageContactVcardRequest);
|
||||
|
||||
abstract sendTextButtons(request: MessageTextButtonsRequest);
|
||||
|
||||
sendPoll(request: MessagePollRequest) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
abstract sendLocation(request: MessageLocationRequest);
|
||||
|
||||
abstract sendLinkPreview(request: MessageLinkPreviewRequest);
|
||||
|
||||
abstract sendImage(request: MessageImageRequest);
|
||||
|
||||
abstract sendFile(request: MessageFileRequest);
|
||||
|
||||
abstract sendVoice(request: MessageVoiceRequest);
|
||||
|
||||
abstract reply(request: MessageReplyRequest);
|
||||
|
||||
abstract sendSeen(chat: ChatRequest);
|
||||
|
||||
abstract startTyping(chat: ChatRequest);
|
||||
|
||||
abstract stopTyping(chat: ChatRequest);
|
||||
|
||||
getMessages(query: GetMessageQuery) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
abstract setReaction(request: MessageReactionRequest);
|
||||
|
||||
/**
|
||||
* Chats methods
|
||||
*/
|
||||
public getChats() {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public deleteChat(chatId) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public getChatMessages(
|
||||
chatId: string,
|
||||
limit: number,
|
||||
downloadMedia: boolean,
|
||||
) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public clearMessages(chatId) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
/**
|
||||
* Contacts methods
|
||||
*/
|
||||
public getContact(query: ContactQuery) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public getContacts() {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public getContactAbout(query: ContactQuery) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public getContactProfilePicture(query: ContactQuery) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public blockContact(request: ContactRequest) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public unblockContact(request: ContactRequest) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
/**
|
||||
* Group methods
|
||||
*/
|
||||
public createGroup(request: CreateGroupRequest) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public getGroups() {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public getGroup(id) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public setInfoAdminsOnly(id, value) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public deleteGroup(id) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public leaveGroup(id) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public setDescription(id, description) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public setSubject(id, description) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public getInviteCode(id): Promise<string> {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public revokeInviteCode(id): Promise<string> {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public getParticipants(id) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public addParticipants(id, request: ParticipantsRequest) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public removeParticipants(id, request: ParticipantsRequest) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public promoteParticipantsToAdmin(id, request: ParticipantsRequest) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public demoteParticipantsToUser(id, request: ParticipantsRequest) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public setPresence(presence: WAHAPresenceStatus, chatId?: string) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public getPresences(): Promise<WAHAChatPresences[]> {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public getPresence(id: string): Promise<WAHAChatPresences> {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public subscribePresence(id: string): Promise<void> {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
/**
|
||||
* Status methods
|
||||
*/
|
||||
public sendTextStatus(status: TextStatus) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public sendImageStatus(status: ImageStatus) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public sendVoiceStatus(status: VoiceStatus) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public sendVideoStatus(status: VideoStatus) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
/**
|
||||
* END - Methods for API
|
||||
*/
|
||||
|
||||
/**
|
||||
* Add WhatsApp suffix (@c.us) to the phone number if it doesn't have it yet
|
||||
* @param phone
|
||||
*/
|
||||
protected ensureSuffix(phone) {
|
||||
return ensureSuffix(phone);
|
||||
}
|
||||
|
||||
protected deserializeId(messageId: string): MessageId {
|
||||
const parts = messageId.split('_');
|
||||
return {
|
||||
fromMe: parts[0] === 'true',
|
||||
remote: parts[1],
|
||||
id: parts[2],
|
||||
_serialized: messageId,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,40 @@
|
||||
export abstract class MediaStorage {
|
||||
abstract save(messageId: string, mimetype: string, buffer: Buffer): Promise<string>
|
||||
import { SessionConfig } from '../../structures/sessions.dto';
|
||||
|
||||
abstract class MediaStorage {
|
||||
abstract save(
|
||||
messageId: string,
|
||||
mimetype: string,
|
||||
buffer: Buffer,
|
||||
): Promise<string>;
|
||||
}
|
||||
|
||||
abstract class SessionConfigRepository {
|
||||
abstract save(sessionName: string, config: SessionConfig): Promise<void>;
|
||||
|
||||
abstract get(sessionName: string): Promise<SessionConfig>;
|
||||
}
|
||||
|
||||
abstract class SessionStorage {
|
||||
protected constructor(public engine: string) {
|
||||
this.engine = engine;
|
||||
}
|
||||
|
||||
abstract init(sessionName?: string): Promise<void>;
|
||||
|
||||
abstract clean(sessionName: string): Promise<void>;
|
||||
|
||||
abstract getAll(): Promise<string[]>;
|
||||
|
||||
//
|
||||
// Repositories
|
||||
//
|
||||
abstract get configRepository(): SessionConfigRepository;
|
||||
}
|
||||
|
||||
abstract class LocalSessionStorage extends SessionStorage {
|
||||
public sessionsFolder: string;
|
||||
|
||||
abstract getFolderPath(sessionName: string): string;
|
||||
}
|
||||
|
||||
export { LocalSessionStorage, MediaStorage, SessionConfigRepository };
|
||||
@@ -1,5 +1,20 @@
|
||||
import {WhatsappSession} from "./session.abc";
|
||||
import { ConsoleLogger } from '@nestjs/common';
|
||||
|
||||
import { WebhookConfig } from '../../structures/webhooks.config.dto';
|
||||
import { WhatsappSession } from './session.abc';
|
||||
|
||||
export abstract class WebhookSender {
|
||||
protected url: string;
|
||||
constructor(
|
||||
protected log: ConsoleLogger,
|
||||
protected webhookConfig: WebhookConfig,
|
||||
) {
|
||||
this.url = webhookConfig.url;
|
||||
}
|
||||
|
||||
abstract send(json);
|
||||
}
|
||||
|
||||
export abstract class WebhookConductor {
|
||||
abstract configure(session: WhatsappSession)
|
||||
abstract configure(session: WhatsappSession, webhooks: WebhookConfig[]);
|
||||
}
|
||||
+62
-48
@@ -1,55 +1,69 @@
|
||||
import {ConsoleLogger, Module} from '@nestjs/common';
|
||||
import {ScreenshotController} from "../api/screenshot.controller";
|
||||
import {ConfigModule} from "@nestjs/config";
|
||||
import {WhatsappConfigService} from "../config.service";
|
||||
import {ServeStaticModule} from "@nestjs/serve-static";
|
||||
import {SessionsController} from "../api/sessions.controller";
|
||||
import {ChattingController} from "../api/chatting.controller";
|
||||
import {ContactsController} from "../api/contacts.controller";
|
||||
import {VersionController} from "../api/version.controller";
|
||||
import {PassportModule} from "@nestjs/passport";
|
||||
import {SessionManager} from "./abc/manager.abc";
|
||||
import {SessionManagerCore} from "./manager.core";
|
||||
import { ConsoleLogger, Module } from '@nestjs/common';
|
||||
import { ConfigModule } from '@nestjs/config';
|
||||
import { PassportModule } from '@nestjs/passport';
|
||||
import { ServeStaticModule } from '@nestjs/serve-static';
|
||||
|
||||
import { ChattingController } from '../api/chatting.controller';
|
||||
import { ContactsController } from '../api/contacts.controller';
|
||||
import { GroupsController } from '../api/groups.controller';
|
||||
import { PresenceController } from '../api/presence.controller';
|
||||
import { ScreenshotController } from '../api/screenshot.controller';
|
||||
import {
|
||||
SessionController,
|
||||
SessionsController,
|
||||
} from '../api/sessions.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({
|
||||
isGlobal: true,
|
||||
}),
|
||||
ServeStaticModule.forRootAsync({
|
||||
imports: [],
|
||||
extraProviders: [WhatsappConfigService],
|
||||
inject: [WhatsappConfigService],
|
||||
useFactory: (config: WhatsappConfigService) => {
|
||||
return [{
|
||||
rootPath: config.filesFolder,
|
||||
serveRoot: config.files_uri,
|
||||
}]
|
||||
ConfigModule.forRoot({
|
||||
isGlobal: true,
|
||||
}),
|
||||
ServeStaticModule.forRootAsync({
|
||||
imports: [],
|
||||
extraProviders: [WhatsappConfigService],
|
||||
inject: [WhatsappConfigService],
|
||||
useFactory: (config: WhatsappConfigService) => {
|
||||
return [
|
||||
{
|
||||
rootPath: config.filesFolder,
|
||||
serveRoot: config.files_uri,
|
||||
},
|
||||
}),
|
||||
PassportModule,
|
||||
]
|
||||
export const CONTROLLERS = [
|
||||
SessionsController,
|
||||
ChattingController,
|
||||
ContactsController,
|
||||
ScreenshotController,
|
||||
VersionController,
|
||||
]
|
||||
const PROVIDERS = [
|
||||
{
|
||||
provide: SessionManager,
|
||||
useClass: SessionManagerCore,
|
||||
];
|
||||
},
|
||||
WhatsappConfigService,
|
||||
ConsoleLogger,
|
||||
]
|
||||
|
||||
}),
|
||||
PassportModule,
|
||||
];
|
||||
export const CONTROLLERS = [
|
||||
AuthController,
|
||||
SessionsController,
|
||||
SessionController,
|
||||
ChattingController,
|
||||
ChatsController,
|
||||
StatusController,
|
||||
ContactsController,
|
||||
GroupsController,
|
||||
PresenceController,
|
||||
ScreenshotController,
|
||||
VersionController,
|
||||
];
|
||||
const PROVIDERS = [
|
||||
{
|
||||
provide: SessionManager,
|
||||
useClass: SessionManagerCore,
|
||||
},
|
||||
WhatsappConfigService,
|
||||
ConsoleLogger,
|
||||
];
|
||||
|
||||
@Module({
|
||||
imports: IMPORTS,
|
||||
controllers: CONTROLLERS,
|
||||
providers: PROVIDERS,
|
||||
imports: IMPORTS,
|
||||
controllers: CONTROLLERS,
|
||||
providers: PROVIDERS,
|
||||
})
|
||||
export class AppModuleCore {
|
||||
|
||||
}
|
||||
export class AppModuleCore {}
|
||||
+13
-12
@@ -1,20 +1,21 @@
|
||||
import {UnprocessableEntityException} from "@nestjs/common";
|
||||
import { UnprocessableEntityException } from '@nestjs/common';
|
||||
|
||||
export const DOCS_URL = "https://waha.devlike.pro/"
|
||||
export const DOCS_URL = 'https://waha.devlike.pro/';
|
||||
|
||||
export class NotImplementedByEngineError extends UnprocessableEntityException {
|
||||
constructor(msg = "") {
|
||||
let error = "The method is not implemented by the engine."
|
||||
if (msg) {
|
||||
error += ` ${msg}`
|
||||
}
|
||||
super(error);
|
||||
constructor(msg = '') {
|
||||
let error = 'The method is not implemented by the engine.';
|
||||
if (msg) {
|
||||
error += ` ${msg}`;
|
||||
}
|
||||
super(error);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class AvailableInPlusVersion extends UnprocessableEntityException {
|
||||
constructor() {
|
||||
super(`The feature is available only in Plus version. Check this out: ${DOCS_URL}`);
|
||||
}
|
||||
constructor() {
|
||||
super(
|
||||
`The feature is available only in Plus version. Check this out: ${DOCS_URL}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
import { Agent } from 'https';
|
||||
import { HttpsProxyAgent } from 'https-proxy-agent';
|
||||
import { URL } from 'url';
|
||||
|
||||
import { WhatsappConfigService } from '../config.service';
|
||||
import { ProxyConfig } from '../structures/sessions.dto';
|
||||
import { WhatsappSession } from './abc/session.abc';
|
||||
|
||||
export function getProxyConfig(
|
||||
config: WhatsappConfigService,
|
||||
sessions: Record<string, WhatsappSession>,
|
||||
sessionName?: string,
|
||||
): ProxyConfig | undefined {
|
||||
// Single proxy server configuration
|
||||
if (typeof config.proxyServer === 'string') {
|
||||
return {
|
||||
server: config.proxyServer,
|
||||
username: config.proxyServerUsername,
|
||||
password: config.proxyServerPassword,
|
||||
};
|
||||
}
|
||||
|
||||
// Multiple proxy server configuration
|
||||
if (Array.isArray(config.proxyServer)) {
|
||||
const prefix = config.proxyServerIndexPrefix;
|
||||
let indexToUse: number = undefined;
|
||||
if (prefix && sessionName.includes(prefix)) {
|
||||
// match numbers at the end of the string
|
||||
const matches = sessionName.match(/\d+$/);
|
||||
indexToUse = matches ? parseInt(matches[0], 10) : undefined;
|
||||
}
|
||||
let idx = 0;
|
||||
idx = Object.keys(sessions).length % config.proxyServer.length;
|
||||
const index = indexToUse ? indexToUse % config.proxyServer.length : idx;
|
||||
return {
|
||||
server: config.proxyServer[index],
|
||||
username: config.proxyServerUsername,
|
||||
password: config.proxyServerPassword,
|
||||
};
|
||||
}
|
||||
|
||||
// No proxy server configuration
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function getAuthenticatedUrl(
|
||||
url: string,
|
||||
username: string,
|
||||
password: string,
|
||||
): string {
|
||||
const hasSchema = url.startsWith('http://') || url.startsWith('https://');
|
||||
if (!hasSchema) {
|
||||
url = `http://${url}`;
|
||||
}
|
||||
const parsedUrl = new URL(url);
|
||||
parsedUrl.protocol = parsedUrl.protocol || 'http';
|
||||
parsedUrl.username = username;
|
||||
parsedUrl.password = password;
|
||||
return parsedUrl.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return https Agent proxy for the config
|
||||
* @param proxyConfig
|
||||
*/
|
||||
export function createAgentProxy(proxyConfig: ProxyConfig): Agent | undefined {
|
||||
const url = getAuthenticatedUrl(
|
||||
proxyConfig.server,
|
||||
proxyConfig.username,
|
||||
proxyConfig.password,
|
||||
);
|
||||
return new HttpsProxyAgent(url);
|
||||
}
|
||||
+199
-96
@@ -1,116 +1,219 @@
|
||||
import {ConsoleLogger, Injectable, NotFoundException, UnprocessableEntityException} from "@nestjs/common";
|
||||
import {SessionManager} from "./abc/manager.abc";
|
||||
import {WhatsappSession} from "./abc/session.abc";
|
||||
import {WhatsappEngine} from "../structures/enums.dto";
|
||||
import {SessionDTO, SessionStartRequest, SessionStopRequest} from "../structures/sessions.dto";
|
||||
import {WhatsappConfigService} from "../config.service";
|
||||
import {WhatsappSessionVenomCore} from "./session.venom.core";
|
||||
import {WhatsappSessionWebJSCore} from "./session.webjs.core";
|
||||
import {DOCS_URL} from "./exceptions";
|
||||
import {WebhookConductorCore} from "./webhooks.core";
|
||||
import {MediaStorageCore} from "./storage.core";
|
||||
import {
|
||||
ConsoleLogger,
|
||||
Injectable,
|
||||
NotFoundException,
|
||||
UnprocessableEntityException,
|
||||
} from '@nestjs/common';
|
||||
|
||||
import { WhatsappConfigService } from '../config.service';
|
||||
import { WAHAEngine, WAHASessionStatus } from '../structures/enums.dto';
|
||||
import {
|
||||
ProxyConfig,
|
||||
SessionDTO,
|
||||
SessionInfo,
|
||||
SessionLogoutRequest,
|
||||
SessionStartRequest,
|
||||
SessionStopRequest,
|
||||
} 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 { DOCS_URL } from './exceptions';
|
||||
import { getProxyConfig } from './helpers.proxy';
|
||||
import { WhatsappSessionNoWebCore } from './session.noweb.core';
|
||||
import { WhatsappSessionVenomCore } from './session.venom.core';
|
||||
import { WhatsappSessionWebJSCore } from './session.webjs.core';
|
||||
import { MediaStorageCore, SessionStorageCore } from './storage.core';
|
||||
import { WebhookConductorCore } from './webhooks.core';
|
||||
|
||||
export class OnlyDefaultSessionIsAllowed extends UnprocessableEntityException {
|
||||
constructor() {
|
||||
super(`WAHA Core support only 'default' session. If you want to run more then one WhatsApp account - please get WAHA PLUS version. Check this out: ${DOCS_URL}`);
|
||||
}
|
||||
constructor() {
|
||||
super(
|
||||
`WAHA Core support only 'default' session. If you want to run more then one WhatsApp account - please get WAHA PLUS version. Check this out: ${DOCS_URL}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class SessionManagerCore extends SessionManager {
|
||||
private session: WhatsappSession;
|
||||
DEFAULT = "default"
|
||||
private session: WhatsappSession;
|
||||
DEFAULT = 'default';
|
||||
|
||||
// @ts-ignore
|
||||
protected MediaStorageClass = MediaStorageCore;
|
||||
// @ts-ignore
|
||||
protected WebhookConductorClass = WebhookConductorCore;
|
||||
protected readonly EngineClass: typeof WhatsappSession;
|
||||
|
||||
constructor(
|
||||
private config: WhatsappConfigService,
|
||||
private log: ConsoleLogger,
|
||||
) {
|
||||
super();
|
||||
|
||||
this.log.setContext('SessionManager');
|
||||
this.session = undefined;
|
||||
const engineName = this.config.getDefaultEngineName();
|
||||
this.EngineClass = this.getEngine(engineName);
|
||||
this.sessionStorage = new SessionStorageCore(engineName.toLowerCase());
|
||||
|
||||
this.startPredefinedSessions();
|
||||
}
|
||||
|
||||
protected startPredefinedSessions() {
|
||||
const startSessions = this.config.startSessions;
|
||||
startSessions.forEach((sessionName) => {
|
||||
this.start({ name: sessionName });
|
||||
});
|
||||
}
|
||||
|
||||
protected getEngine(engine: WAHAEngine): typeof WhatsappSession {
|
||||
if (engine === WAHAEngine.WEBJS) {
|
||||
return WhatsappSessionWebJSCore;
|
||||
} else if (engine === WAHAEngine.VENOM) {
|
||||
return WhatsappSessionVenomCore;
|
||||
} else if (engine === WAHAEngine.NOWEB) {
|
||||
return WhatsappSessionNoWebCore;
|
||||
} else {
|
||||
throw new NotFoundException(`Unknown whatsapp engine '${engine}'.`);
|
||||
}
|
||||
}
|
||||
|
||||
private onlyDefault(name: string) {
|
||||
if (name !== this.DEFAULT) {
|
||||
throw new OnlyDefaultSessionIsAllowed();
|
||||
}
|
||||
}
|
||||
|
||||
async onApplicationShutdown(signal?: string) {
|
||||
if (!this.session) {
|
||||
return;
|
||||
}
|
||||
await this.stop({ name: this.DEFAULT, logout: false });
|
||||
}
|
||||
|
||||
//
|
||||
// API Methods
|
||||
//
|
||||
async start(request: SessionStartRequest): Promise<SessionDTO> {
|
||||
this.onlyDefault(request.name);
|
||||
|
||||
const name = request.name;
|
||||
this.log.log(`'${name}' - starting session...`);
|
||||
const log = new ConsoleLogger(`WhatsappSession - ${name}`);
|
||||
const storage = new this.MediaStorageClass();
|
||||
const webhookLog = new ConsoleLogger(`Webhook - ${name}`);
|
||||
const webhook = new this.WebhookConductorClass(webhookLog);
|
||||
const proxyConfig = this.getProxyConfig(request);
|
||||
const sessionConfig: SessionParams = {
|
||||
name,
|
||||
storage,
|
||||
log,
|
||||
sessionStorage: this.sessionStorage,
|
||||
proxyConfig: proxyConfig,
|
||||
sessionConfig: request.config,
|
||||
};
|
||||
await this.sessionStorage.init(name);
|
||||
// @ts-ignore
|
||||
protected MediaStorageClass = MediaStorageCore
|
||||
// @ts-ignore
|
||||
protected WebhookConductorClass = WebhookConductorCore
|
||||
protected readonly EngineClass: typeof WhatsappSession;
|
||||
const session = new this.EngineClass(sessionConfig);
|
||||
this.session = session;
|
||||
|
||||
// configure webhooks
|
||||
const webhooks = this.getWebhooks(request);
|
||||
session.events.on(WAHAInternalEvent.engine_start, () =>
|
||||
webhook.configure(session, webhooks),
|
||||
);
|
||||
|
||||
constructor(
|
||||
private config: WhatsappConfigService,
|
||||
private log: ConsoleLogger,
|
||||
) {
|
||||
super()
|
||||
// start session
|
||||
await session.start();
|
||||
return {
|
||||
name: session.name,
|
||||
status: session.status,
|
||||
config: session.sessionConfig,
|
||||
};
|
||||
}
|
||||
|
||||
this.log.setContext('SessionManager')
|
||||
this.session = undefined
|
||||
this.EngineClass = this.getEngine(this.config.getDefaultEngineName())
|
||||
|
||||
// Start session from the start
|
||||
if (config.startSession) {
|
||||
this.start({name: config.startSession})
|
||||
}
|
||||
/**
|
||||
* Combine per session and global webhooks
|
||||
*/
|
||||
private getWebhooks(request: SessionStartRequest) {
|
||||
let webhooks: WebhookConfig[] = [];
|
||||
if (request.config?.webhooks) {
|
||||
webhooks = webhooks.concat(request.config.webhooks);
|
||||
}
|
||||
const globalWebhookConfig = this.config.getWebhookConfig();
|
||||
webhooks.push(globalWebhookConfig);
|
||||
return webhooks;
|
||||
}
|
||||
|
||||
protected getEngine(engine: WhatsappEngine): typeof WhatsappSession {
|
||||
if (engine === WhatsappEngine.WEBJS) {
|
||||
return WhatsappSessionWebJSCore
|
||||
} else if (engine === WhatsappEngine.VENOM) {
|
||||
return WhatsappSessionVenomCore
|
||||
} else {
|
||||
throw new NotFoundException(`Unknown whatsapp engine '${engine}'.`)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get either session's or global proxy if defined
|
||||
*/
|
||||
protected getProxyConfig(
|
||||
request: SessionStartRequest,
|
||||
): ProxyConfig | undefined {
|
||||
if (request.config?.proxy) {
|
||||
return request.config.proxy;
|
||||
}
|
||||
const sessions = { [request.name]: this.session };
|
||||
return getProxyConfig(this.config, sessions, request.name);
|
||||
}
|
||||
|
||||
private onlyDefault(name: string) {
|
||||
if (name !== this.DEFAULT) {
|
||||
throw new OnlyDefaultSessionIsAllowed()
|
||||
}
|
||||
async stop(request: SessionStopRequest): Promise<void> {
|
||||
this.onlyDefault(request.name);
|
||||
|
||||
const name = request.name;
|
||||
this.log.log(`Stopping ${name} session...`);
|
||||
const session = this.getSession(name);
|
||||
await session.stop();
|
||||
this.log.log(`"${name}" has been stopped.`);
|
||||
this.session = undefined;
|
||||
}
|
||||
|
||||
async logout(request: SessionLogoutRequest) {
|
||||
await this.sessionStorage.clean(request.name);
|
||||
}
|
||||
|
||||
getSession(name: string, error = true): WhatsappSession {
|
||||
this.onlyDefault(name);
|
||||
const session = this.session;
|
||||
if (!session) {
|
||||
if (error) {
|
||||
throw new NotFoundException(
|
||||
`We didn't find a session with name '${name}'. Please start it first by using POST /sessions/start request`,
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
return session;
|
||||
}
|
||||
|
||||
getSession(name: string): WhatsappSession {
|
||||
this.onlyDefault(name)
|
||||
if (!this.session) {
|
||||
throw new NotFoundException(
|
||||
`We didn't find a session with name '${name}'. Please start it first by using POST /sessions/start request`,
|
||||
);
|
||||
}
|
||||
return this.session
|
||||
}
|
||||
|
||||
getSessions(): SessionDTO[] {
|
||||
async getSessions(all: boolean): Promise<SessionInfo[]> {
|
||||
if (!this.session) {
|
||||
if (!all) {
|
||||
return [];
|
||||
}
|
||||
return [
|
||||
{
|
||||
name: this.DEFAULT,
|
||||
status: WAHASessionStatus.STOPPED,
|
||||
config: undefined,
|
||||
me: null,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
async onApplicationShutdown(signal?: string) {
|
||||
if (!this.session){
|
||||
return
|
||||
}
|
||||
await this.stop({name: this.DEFAULT})
|
||||
}
|
||||
|
||||
start(request: SessionStartRequest): SessionDTO {
|
||||
this.onlyDefault(request.name)
|
||||
|
||||
const name = request.name
|
||||
this.log.log(`'${name}' - starting session...`)
|
||||
const log = new ConsoleLogger(`WhatsappSession - ${name}`)
|
||||
const storage = new this.MediaStorageClass()
|
||||
const webhookLog = new ConsoleLogger(`Webhook - ${name}`)
|
||||
const webhook = new this.WebhookConductorClass(
|
||||
webhookLog,
|
||||
this.config.getWebhookUrl(),
|
||||
this.config.getWebhookEvents()
|
||||
)
|
||||
|
||||
// @ts-ignore
|
||||
const session = new this.EngineClass(name, storage, log)
|
||||
this.session = session
|
||||
|
||||
session.start().then(() => webhook.configure(session))
|
||||
return {name: session.name, status: session.status}
|
||||
}
|
||||
|
||||
async stop(request: SessionStopRequest): Promise<void> {
|
||||
const name = request.name
|
||||
this.log.log(`Stopping ${name} session...`)
|
||||
const session = this.getSession(name)
|
||||
await session.stop()
|
||||
this.log.log(`"${name}" has been stopped.`)
|
||||
this.session = undefined
|
||||
}
|
||||
|
||||
const me = await this.session.getSessionMeInfo().catch((err) => null);
|
||||
return [
|
||||
{
|
||||
name: this.session.name,
|
||||
status: this.session.status,
|
||||
config: this.session.sessionConfig,
|
||||
me: me,
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,968 @@
|
||||
import makeWASocket, {
|
||||
Browsers,
|
||||
DisconnectReason,
|
||||
getAggregateVotesInPollMessage,
|
||||
getKeyAuthor,
|
||||
isJidGroup,
|
||||
jidNormalizedUser,
|
||||
makeInMemoryStore,
|
||||
PresenceData,
|
||||
proto,
|
||||
useMultiFileAuthState,
|
||||
WAMessageContent,
|
||||
WAMessageKey,
|
||||
} from '@adiwajshing/baileys';
|
||||
import { UnprocessableEntityException } from '@nestjs/common';
|
||||
import { request } from 'express';
|
||||
import * as fs from 'fs/promises';
|
||||
import { Agent } from 'https';
|
||||
import * as lodash from 'lodash';
|
||||
import { update } from 'lodash';
|
||||
import { PairingCodeResponse } from 'src/structures/auth.dto';
|
||||
import { Message } from 'whatsapp-web.js';
|
||||
|
||||
import { flipObject, splitAt } from '../helpers';
|
||||
import {
|
||||
Button,
|
||||
ChatRequest,
|
||||
CheckNumberStatusQuery,
|
||||
MessageContactVcardRequest,
|
||||
MessageDestination,
|
||||
MessageFileRequest,
|
||||
MessageImageRequest,
|
||||
MessageLinkPreviewRequest,
|
||||
MessageLocationRequest,
|
||||
MessagePollRequest,
|
||||
MessageReactionRequest,
|
||||
MessageReplyRequest,
|
||||
MessageTextButtonsRequest,
|
||||
MessageTextRequest,
|
||||
MessageVoiceRequest,
|
||||
SendSeenRequest,
|
||||
WANumberExistResult,
|
||||
} from '../structures/chatting.dto';
|
||||
import { ContactQuery, ContactRequest } from '../structures/contacts.dto';
|
||||
import {
|
||||
ACK_UNKNOWN,
|
||||
SECOND,
|
||||
WAHAEngine,
|
||||
WAHAEvents,
|
||||
WAHAPresenceStatus,
|
||||
WAHASessionStatus,
|
||||
WAMessageAck,
|
||||
} from '../structures/enums.dto';
|
||||
import {
|
||||
CreateGroupRequest,
|
||||
ParticipantsRequest,
|
||||
} from '../structures/groups.dto';
|
||||
import {
|
||||
WAHAChatPresences,
|
||||
WAHAPresenceData,
|
||||
} from '../structures/presence.dto';
|
||||
import { WAMessage } from '../structures/responses.dto';
|
||||
import { BROADCAST_ID, TextStatus } from '../structures/status.dto';
|
||||
import {
|
||||
PollVote,
|
||||
PollVotePayload,
|
||||
WAMessageAckBody,
|
||||
} from '../structures/webhooks.dto';
|
||||
import {
|
||||
ensureSuffix,
|
||||
WAHAInternalEvent,
|
||||
WhatsappSession,
|
||||
} from './abc/session.abc';
|
||||
import {
|
||||
AvailableInPlusVersion,
|
||||
NotImplementedByEngineError,
|
||||
} 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');
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const logger = require('pino')();
|
||||
|
||||
export const BaileysEvents = {
|
||||
CONNECTION_UPDATE: 'connection.update',
|
||||
CREDS_UPDATE: 'creds.update',
|
||||
MESSAGES_UPDATE: 'messages.update',
|
||||
MESSAGES_UPSERT: 'messages.upsert',
|
||||
MESSAGE_RECEIPT_UPDATE: 'message-receipt.update',
|
||||
GROUPS_UPSERT: 'groups.upsert',
|
||||
PRESENCE_UPDATE: 'presence.update',
|
||||
};
|
||||
|
||||
const PresenceStatuses = {
|
||||
unavailable: WAHAPresenceStatus.OFFLINE,
|
||||
available: WAHAPresenceStatus.ONLINE,
|
||||
composing: WAHAPresenceStatus.TYPING,
|
||||
recording: WAHAPresenceStatus.RECORDING,
|
||||
paused: WAHAPresenceStatus.PAUSED,
|
||||
};
|
||||
const ToEnginePresenceStatus = flipObject(PresenceStatuses);
|
||||
|
||||
export class WhatsappSessionNoWebCore extends WhatsappSession {
|
||||
engine = WAHAEngine.NOWEB;
|
||||
|
||||
sock: any;
|
||||
store: any;
|
||||
private qr: QR;
|
||||
|
||||
public constructor(config) {
|
||||
super(config);
|
||||
this.qr = new QR();
|
||||
}
|
||||
|
||||
start() {
|
||||
return this.buildClient();
|
||||
}
|
||||
|
||||
getSocketConfig(agent, state) {
|
||||
return {
|
||||
agent: agent,
|
||||
fetchAgent: agent,
|
||||
auth: state,
|
||||
printQRInTerminal: true,
|
||||
browser: Browsers.macOS('Chrome'),
|
||||
logger: logger,
|
||||
mobile: false,
|
||||
defaultQueryTimeoutMs: undefined,
|
||||
getMessage: (key) => this.getMessage(key),
|
||||
};
|
||||
}
|
||||
|
||||
async makeSocket() {
|
||||
const authFolder = this.sessionStorage.getFolderPath(this.name);
|
||||
await fs.mkdir(authFolder, { recursive: true });
|
||||
const { state, saveCreds } = await useMultiFileAuthState(authFolder);
|
||||
const agent = this.makeAgent();
|
||||
const socketConfig = this.getSocketConfig(agent, state);
|
||||
const sock: any = makeWASocket(socketConfig);
|
||||
sock.ev.on(BaileysEvents.CREDS_UPDATE, saveCreds);
|
||||
this.debugLogEnginesEvents(sock);
|
||||
return sock;
|
||||
}
|
||||
|
||||
protected makeAgent(): Agent {
|
||||
if (!this.proxyConfig) {
|
||||
return undefined;
|
||||
}
|
||||
return createAgentProxy(this.proxyConfig);
|
||||
}
|
||||
|
||||
connectStore() {
|
||||
this.log.debug(`Connecting store...`);
|
||||
if (!this.store) {
|
||||
this.log.debug(`Making a new auth store...`);
|
||||
this.store = makeInMemoryStore({});
|
||||
}
|
||||
this.log.debug(`Binding store to socket...`);
|
||||
this.store.bind(this.sock.ev);
|
||||
}
|
||||
|
||||
resubscribeToKnownPresences() {
|
||||
for (const jid in this.store.presences) {
|
||||
this.sock.presenceSubscribe(jid);
|
||||
}
|
||||
}
|
||||
|
||||
async buildClient() {
|
||||
this.sock = await this.makeSocket();
|
||||
this.connectStore();
|
||||
this.listenConnectionEvents();
|
||||
this.events.emit(WAHAInternalEvent.engine_start);
|
||||
}
|
||||
|
||||
protected async getMessage(
|
||||
key: WAMessageKey,
|
||||
): Promise<WAMessageContent | undefined> {
|
||||
if (!this.store) {
|
||||
return proto.Message.fromObject({});
|
||||
}
|
||||
const msg = await this.store.loadMessage(key.remoteJid!, key.id!);
|
||||
return msg?.message || undefined;
|
||||
}
|
||||
|
||||
protected listenConnectionEvents() {
|
||||
this.log.debug(`Start listening ${BaileysEvents.CONNECTION_UPDATE}...`);
|
||||
this.sock.ev.on(BaileysEvents.CONNECTION_UPDATE, async (update) => {
|
||||
const { connection, lastDisconnect, qr } = update;
|
||||
if (connection === 'connecting') {
|
||||
this.qr.save('');
|
||||
this.status = WAHASessionStatus.WORKING;
|
||||
this.resubscribeToKnownPresences();
|
||||
return;
|
||||
} else if (connection === 'close') {
|
||||
const shouldReconnect =
|
||||
lastDisconnect.error?.output?.statusCode !==
|
||||
DisconnectReason.loggedOut;
|
||||
this.log.error(
|
||||
'connection closed due to ',
|
||||
lastDisconnect.error,
|
||||
', reconnecting ',
|
||||
shouldReconnect,
|
||||
);
|
||||
this.qr.save('');
|
||||
this.status = WAHASessionStatus.FAILED;
|
||||
// reconnect if not logged out
|
||||
if (shouldReconnect) {
|
||||
setTimeout(() => this.buildClient(), 2 * SECOND);
|
||||
}
|
||||
}
|
||||
|
||||
// Save QR
|
||||
if (qr) {
|
||||
this.status = WAHASessionStatus.SCAN_QR_CODE;
|
||||
QRCode.toDataURL(qr).then((url) => {
|
||||
this.qr.save(url);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected debugLogEnginesEvents(sock) {
|
||||
if (!process.env.DEBUG) {
|
||||
return;
|
||||
}
|
||||
|
||||
sock.ev.process((events) => {
|
||||
this.log.debug('Engine events');
|
||||
this.log.debug('=============');
|
||||
console.log(JSON.stringify(events));
|
||||
this.log.debug('=============');
|
||||
});
|
||||
}
|
||||
|
||||
stop() {
|
||||
this.sock.ev.removeAllListeners();
|
||||
this.sock.ws.removeAllListeners();
|
||||
this.sock.ws.close();
|
||||
return;
|
||||
}
|
||||
|
||||
async getSessionMeInfo(): Promise<MeInfo | null> {
|
||||
const me = this.sock.authState?.creds?.me;
|
||||
if (!me) {
|
||||
return null;
|
||||
}
|
||||
const meId = jidNormalizedUser(me.id);
|
||||
const meInfo: MeInfo = {
|
||||
id: toCusFormat(meId),
|
||||
pushName: me.name,
|
||||
};
|
||||
return meInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* START - Methods for API
|
||||
*/
|
||||
|
||||
/**
|
||||
* Auth methods
|
||||
*/
|
||||
public async getQR(): Promise<Buffer> {
|
||||
return Promise.resolve(this.qr.get());
|
||||
}
|
||||
|
||||
public async requestCode(
|
||||
phoneNumber: string,
|
||||
method: string,
|
||||
): Promise<PairingCodeResponse> {
|
||||
if (method) {
|
||||
const err = `NOWEB engine doesn't support any 'method', remove it and try again`;
|
||||
throw new UnprocessableEntityException(err);
|
||||
}
|
||||
|
||||
if (this.status == WAHASessionStatus.STARTING) {
|
||||
this.log.debug('Waiting for connection update...');
|
||||
await this.sock.waitForConnectionUpdate((update) => !!update.qr);
|
||||
}
|
||||
|
||||
if (this.status != WAHASessionStatus.SCAN_QR_CODE) {
|
||||
const err = `Can request code only in SCAN_QR_CODE status. The current status is ${this.status}`;
|
||||
throw new UnprocessableEntityException(err);
|
||||
}
|
||||
|
||||
this.log.debug('Requesting pairing code...');
|
||||
const code: string = await this.sock.requestPairingCode(phoneNumber);
|
||||
// show it as ABCD-ABCD
|
||||
const parts = splitAt(code, 4);
|
||||
const codeRepr = parts.join('-');
|
||||
this.log.debug(`Your code: ${codeRepr}`);
|
||||
return { code: codeRepr };
|
||||
}
|
||||
|
||||
async getScreenshot(): Promise<Buffer | string> {
|
||||
if (this.status === WAHASessionStatus.STARTING) {
|
||||
throw new UnprocessableEntityException(
|
||||
`The session is starting, please try again after few seconds`,
|
||||
);
|
||||
} else if (this.status === WAHASessionStatus.SCAN_QR_CODE) {
|
||||
return Promise.resolve(this.qr.get());
|
||||
} else if (this.status === WAHASessionStatus.WORKING) {
|
||||
throw new UnprocessableEntityException(
|
||||
`Can not get screenshot for non chrome based engine.`,
|
||||
);
|
||||
} else {
|
||||
throw new UnprocessableEntityException(`Unknown status - ${this.status}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Other methods
|
||||
*/
|
||||
async checkNumberStatus(
|
||||
request: CheckNumberStatusQuery,
|
||||
): Promise<WANumberExistResult> {
|
||||
const phone = request.phone.split('@')[0];
|
||||
const [result] = await this.sock.onWhatsApp(phone);
|
||||
if (!result) {
|
||||
return { numberExists: false };
|
||||
}
|
||||
return { numberExists: result.exists };
|
||||
}
|
||||
|
||||
sendText(request: MessageTextRequest) {
|
||||
const chatId = this.ensureSuffix(request.chatId);
|
||||
const message = {
|
||||
text: request.text,
|
||||
mentions: request.mentions?.map(toJID),
|
||||
};
|
||||
return this.sock.sendMessage(chatId, message);
|
||||
}
|
||||
|
||||
sendTextButtons(request: MessageTextButtonsRequest) {
|
||||
const buttons = request.buttons.map((button: Button) => {
|
||||
return {
|
||||
buttonId: button.id,
|
||||
buttonText: { displayText: button.text },
|
||||
type: 1,
|
||||
};
|
||||
});
|
||||
|
||||
const buttonMessage = {
|
||||
text: request.title,
|
||||
buttons: buttons,
|
||||
headerType: 1,
|
||||
};
|
||||
|
||||
return this.sock.sendMessage(request.chatId, buttonMessage);
|
||||
}
|
||||
|
||||
async sendPoll(request: MessagePollRequest) {
|
||||
const requestPoll = request.poll;
|
||||
const poll = {
|
||||
name: requestPoll.name,
|
||||
values: requestPoll.options,
|
||||
selectableCount: requestPoll.multipleAnswers
|
||||
? requestPoll.options.length
|
||||
: 1,
|
||||
};
|
||||
const message = { poll: poll };
|
||||
const remoteJid = toJID(request.chatId);
|
||||
const result = await this.sock.sendMessage(remoteJid, message);
|
||||
return this.toWAMessage(result);
|
||||
}
|
||||
|
||||
sendContactVCard(request: MessageContactVcardRequest) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
async reply(request: MessageReplyRequest) {
|
||||
const { id } = parseMessageId(request.reply_to);
|
||||
const quotedMessage = await this.store.loadMessage(
|
||||
toJID(request.chatId),
|
||||
id,
|
||||
);
|
||||
const message = {
|
||||
text: request.text,
|
||||
mentions: request.mentions?.map(toJID),
|
||||
};
|
||||
return await this.sock.sendMessage(request.chatId, message, {
|
||||
quoted: quotedMessage,
|
||||
});
|
||||
}
|
||||
|
||||
sendImage(request: MessageImageRequest) {
|
||||
throw new AvailableInPlusVersion();
|
||||
}
|
||||
|
||||
sendFile(request: MessageFileRequest) {
|
||||
throw new AvailableInPlusVersion();
|
||||
}
|
||||
|
||||
sendVoice(request: MessageVoiceRequest) {
|
||||
throw new AvailableInPlusVersion();
|
||||
}
|
||||
|
||||
sendLocation(request: MessageLocationRequest) {
|
||||
return this.sock.sendMessage(request.chatId, {
|
||||
location: {
|
||||
degreesLatitude: request.latitude,
|
||||
degreesLongitude: request.longitude,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
sendLinkPreview(request: MessageLinkPreviewRequest) {
|
||||
const text = `${request.title}\n${request.url}`;
|
||||
const chatId = this.ensureSuffix(request.chatId);
|
||||
return this.sock.sendMessage(chatId, { text: text });
|
||||
}
|
||||
|
||||
async sendSeen(request: SendSeenRequest) {
|
||||
const key = parseMessageId(request.messageId);
|
||||
const data = {
|
||||
remoteJid: key.remoteJid,
|
||||
id: key.id,
|
||||
participant: request.participant,
|
||||
};
|
||||
return this.sock.readMessages([data]);
|
||||
}
|
||||
|
||||
async startTyping(request: ChatRequest) {
|
||||
return this.sock.sendPresenceUpdate('composing', request.chatId);
|
||||
}
|
||||
|
||||
async stopTyping(request: ChatRequest) {
|
||||
return this.sock.sendPresenceUpdate('paused', request.chatId);
|
||||
}
|
||||
|
||||
async setReaction(request: MessageReactionRequest) {
|
||||
const key = parseMessageId(request.messageId);
|
||||
const reactionMessage = {
|
||||
react: {
|
||||
text: request.reaction,
|
||||
key: key,
|
||||
},
|
||||
};
|
||||
return this.sock.sendMessage(key.remoteJid, reactionMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Contacts methods
|
||||
*/
|
||||
getContact(query: ContactQuery) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
getContacts() {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public async getContactAbout(query: ContactQuery) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public async getContactProfilePicture(query: ContactQuery) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public async blockContact(request: ContactRequest) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public async unblockContact(request: ContactRequest) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
/**
|
||||
* Group methods
|
||||
*/
|
||||
public createGroup(request: CreateGroupRequest) {
|
||||
const participants = request.participants.map(getId);
|
||||
return this.sock.groupCreate(request.name, participants);
|
||||
}
|
||||
|
||||
public async getGroups() {
|
||||
return await this.sock.groupFetchAllParticipating();
|
||||
}
|
||||
|
||||
public async getGroup(id) {
|
||||
const groups = await this.sock.groupFetchAllParticipating();
|
||||
return groups[id];
|
||||
}
|
||||
|
||||
public async deleteGroup(id) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public async leaveGroup(id) {
|
||||
return this.sock.groupLeave(id);
|
||||
}
|
||||
|
||||
public async setDescription(id, description) {
|
||||
return this.sock.groupUpdateDescription(id, description);
|
||||
}
|
||||
|
||||
public async setSubject(id, subject) {
|
||||
return this.sock.groupUpdateSubject(id, subject);
|
||||
}
|
||||
|
||||
public async getInviteCode(id): Promise<string> {
|
||||
return this.sock.groupInviteCode(id);
|
||||
}
|
||||
|
||||
public async revokeInviteCode(id): Promise<string> {
|
||||
await this.sock.groupRevokeInvite(id);
|
||||
return this.sock.groupInviteCode(id);
|
||||
}
|
||||
|
||||
public async getParticipants(id) {
|
||||
const groups = await this.sock.groupFetchAllParticipating();
|
||||
return groups[id].participants;
|
||||
}
|
||||
|
||||
public async addParticipants(id, request: ParticipantsRequest) {
|
||||
const participants = request.participants.map(getId);
|
||||
return this.sock.groupParticipantsUpdate(id, participants, 'add');
|
||||
}
|
||||
|
||||
public async removeParticipants(id, request: ParticipantsRequest) {
|
||||
const participants = request.participants.map(getId);
|
||||
return this.sock.groupParticipantsUpdate(id, participants, 'remove');
|
||||
}
|
||||
|
||||
public async promoteParticipantsToAdmin(id, request: ParticipantsRequest) {
|
||||
const participants = request.participants.map(getId);
|
||||
return this.sock.groupParticipantsUpdate(id, participants, 'promote');
|
||||
}
|
||||
|
||||
public async demoteParticipantsToUser(id, request: ParticipantsRequest) {
|
||||
const participants = request.participants.map(getId);
|
||||
return this.sock.groupParticipantsUpdate(id, participants, 'demote');
|
||||
}
|
||||
|
||||
public async setPresence(presence: WAHAPresenceStatus, chatId?: string) {
|
||||
const enginePresence = ToEnginePresenceStatus[presence];
|
||||
if (!enginePresence) {
|
||||
throw new NotImplementedByEngineError(
|
||||
`NOWEB engine doesn't support '${presence}' presence.`,
|
||||
);
|
||||
}
|
||||
await this.sock.sendPresenceUpdate(enginePresence, chatId);
|
||||
}
|
||||
|
||||
public async getPresences(): Promise<WAHAChatPresences[]> {
|
||||
const result: WAHAChatPresences[] = [];
|
||||
for (const remoteJid in this.store.presences) {
|
||||
const storedPresences = this.store.presences[remoteJid];
|
||||
result.push(this.toWahaPresences(remoteJid, storedPresences));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public async getPresence(chatId: string): Promise<WAHAChatPresences> {
|
||||
await this.subscribePresence(chatId);
|
||||
const remoteJid = toJID(chatId);
|
||||
const storedPresences = this.store.presences[remoteJid];
|
||||
return this.toWahaPresences(remoteJid, storedPresences || []);
|
||||
}
|
||||
|
||||
public async subscribePresence(chatId: string): Promise<void> {
|
||||
const remoteJid = toJID(chatId);
|
||||
if (this.store.presences[remoteJid]) {
|
||||
return;
|
||||
}
|
||||
// Have no info - subscribe to listen for events
|
||||
await this.sock.presenceSubscribe(remoteJid);
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Status methods
|
||||
*/
|
||||
public sendTextStatus(status: TextStatus) {
|
||||
const message = { text: status.text };
|
||||
const options = {
|
||||
backgroundColor: status.backgroundColor,
|
||||
font: status.font,
|
||||
statusJidList: status.contacts.map(toJID),
|
||||
};
|
||||
return this.sock.sendMessage(BROADCAST_ID, message, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* END - Methods for API
|
||||
*/
|
||||
|
||||
subscribe(event, handler) {
|
||||
if (event === WAHAEvents.MESSAGE) {
|
||||
this.sock.ev.on(BaileysEvents.MESSAGES_UPSERT, ({ messages }) => {
|
||||
this.handleIncomingMessages(messages, handler, false);
|
||||
});
|
||||
} else if (event === WAHAEvents.MESSAGE_ANY) {
|
||||
this.sock.ev.on(BaileysEvents.MESSAGES_UPSERT, ({ messages }) =>
|
||||
this.handleIncomingMessages(messages, handler, true),
|
||||
);
|
||||
} else if (event === WAHAEvents.MESSAGE_ACK) {
|
||||
// Direct message ack
|
||||
this.sock.ev.on(BaileysEvents.MESSAGES_UPDATE, (events) => {
|
||||
events
|
||||
.filter(isMine)
|
||||
.filter(isAckUpdateMessageEvent)
|
||||
.map(this.convertMessageUpdateToMessageAck)
|
||||
.forEach(handler);
|
||||
});
|
||||
// Group message ack
|
||||
this.sock.ev.on(BaileysEvents.MESSAGE_RECEIPT_UPDATE, (events) => {
|
||||
events
|
||||
.filter(isMine)
|
||||
.map(this.convertMessageReceiptUpdateToMessageAck)
|
||||
.forEach(handler);
|
||||
});
|
||||
} else if (event === WAHAEvents.STATE_CHANGE) {
|
||||
this.sock.ev.on(BaileysEvents.CONNECTION_UPDATE, handler);
|
||||
} else if (event === WAHAEvents.GROUP_JOIN) {
|
||||
this.sock.ev.on(BaileysEvents.GROUPS_UPSERT, handler);
|
||||
} else if (event === WAHAEvents.PRESENCE_UPDATE) {
|
||||
this.sock.ev.on(BaileysEvents.PRESENCE_UPDATE, (data) =>
|
||||
handler(this.toWahaPresences(data.id, data.presences)),
|
||||
);
|
||||
} else if (event === WAHAEvents.POLL_VOTE) {
|
||||
this.sock.ev.on(BaileysEvents.MESSAGES_UPDATE, (events) => {
|
||||
events.forEach((event) =>
|
||||
this.handleMessagesUpdatePollVote(event, handler),
|
||||
);
|
||||
});
|
||||
} else if (event === WAHAEvents.POLL_VOTE_FAILED) {
|
||||
this.sock.ev.on(BaileysEvents.MESSAGES_UPSERT, ({ messages }) => {
|
||||
messages.forEach((message) =>
|
||||
this.handleMessageUpsertPollVoteFailed(message, handler),
|
||||
);
|
||||
});
|
||||
} else {
|
||||
throw new NotImplementedByEngineError(
|
||||
`Engine does not support webhook event: ${event}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private handleIncomingMessages(messages, handler, includeFromMe) {
|
||||
for (const message of messages) {
|
||||
// if there is no text or media message
|
||||
if (!message) return;
|
||||
if (!message.message) return;
|
||||
// Ignore poll votes, we have dedicated handler for that
|
||||
if (message.message.pollUpdateMessage) return;
|
||||
// Do not include my messages
|
||||
if (!includeFromMe && message.key.fromMe) {
|
||||
continue;
|
||||
}
|
||||
this.processIncomingMessage(message).then(handler);
|
||||
}
|
||||
}
|
||||
|
||||
private processIncomingMessage(message) {
|
||||
return this.downloadMedia(message)
|
||||
.then(this.toWAMessage)
|
||||
.catch((error) => {
|
||||
this.log.error('Failed to process incoming message');
|
||||
this.log.error(error);
|
||||
console.trace(error);
|
||||
});
|
||||
}
|
||||
|
||||
protected toWAMessage(message): Promise<WAMessage> {
|
||||
const fromToParticipant = getFromToParticipant(message);
|
||||
const id = buildMessageId(message.key);
|
||||
let body = message.message.conversation;
|
||||
if (!body) {
|
||||
// Some of the messages have no conversation, but instead have text in extendedTextMessage
|
||||
// https://github.com/devlikeapro/whatsapp-http-api/issues/90
|
||||
body = message.message.extendedTextMessage?.text;
|
||||
}
|
||||
return Promise.resolve({
|
||||
id: id,
|
||||
timestamp: message.messageTimestamp,
|
||||
from: toCusFormat(fromToParticipant.from),
|
||||
fromMe: message.key.fromMe,
|
||||
body: body,
|
||||
to: toCusFormat(fromToParticipant.to),
|
||||
participant: toCusFormat(fromToParticipant.participant),
|
||||
// @ts-ignore
|
||||
hasMedia: Boolean(message.mediaUrl),
|
||||
// @ts-ignore
|
||||
mediaUrl: message.mediaUrl,
|
||||
// @ts-ignore
|
||||
ack: message.ack,
|
||||
// @ts-ignore
|
||||
ackName: WAMessageAck[message.ack] || ACK_UNKNOWN,
|
||||
location: message.location,
|
||||
vCards: message.vCards,
|
||||
_data: message,
|
||||
});
|
||||
}
|
||||
|
||||
protected convertMessageUpdateToMessageAck(event): WAMessageAckBody {
|
||||
const message = event;
|
||||
const fromToParticipant = getFromToParticipant(message);
|
||||
const id = buildMessageId(message.key);
|
||||
const ack = message.update.status - 1;
|
||||
const body: WAMessageAckBody = {
|
||||
id: id,
|
||||
from: toCusFormat(fromToParticipant.from),
|
||||
to: toCusFormat(fromToParticipant.to),
|
||||
participant: toCusFormat(fromToParticipant.participant),
|
||||
fromMe: message.key.fromMe,
|
||||
ack: ack,
|
||||
ackName: WAMessageAck[ack] || ACK_UNKNOWN,
|
||||
};
|
||||
return body;
|
||||
}
|
||||
|
||||
protected convertMessageReceiptUpdateToMessageAck(event): WAMessageAckBody {
|
||||
const fromToParticipant = getFromToParticipant(event);
|
||||
const id = buildMessageId(event.key);
|
||||
|
||||
const receipt = event.receipt;
|
||||
let ack;
|
||||
if (receipt.receiptTimestamp) {
|
||||
ack = WAMessageAck.SERVER;
|
||||
} else if (receipt.playedTimestamp) {
|
||||
ack = WAMessageAck.PLAYED;
|
||||
} else if (receipt.readTimestamp) {
|
||||
ack = WAMessageAck.READ;
|
||||
}
|
||||
const body: WAMessageAckBody = {
|
||||
id: id,
|
||||
from: toCusFormat(fromToParticipant.from),
|
||||
to: toCusFormat(fromToParticipant.to),
|
||||
participant: toCusFormat(fromToParticipant.participant),
|
||||
fromMe: event.key.fromMe,
|
||||
ack: ack,
|
||||
ackName: WAMessageAck[ack] || ACK_UNKNOWN,
|
||||
};
|
||||
return body;
|
||||
}
|
||||
|
||||
protected async handleMessagesUpdatePollVote(event, handler) {
|
||||
const { key, update } = event;
|
||||
const pollUpdates = update?.pollUpdates;
|
||||
if (!pollUpdates) {
|
||||
return;
|
||||
}
|
||||
|
||||
const pollCreationMessageKey = key;
|
||||
const pollCreationMessage = await this.getMessage(key);
|
||||
// Handle updates one by one, so we can get Vote Message for the specific vote
|
||||
for (const pollUpdate of pollUpdates) {
|
||||
const votes = getAggregateVotesInPollMessage({
|
||||
message: pollCreationMessage,
|
||||
pollUpdates: [pollUpdate],
|
||||
});
|
||||
|
||||
// Get selected options for the author
|
||||
const selectedOptions = [];
|
||||
for (const voteAggregation of votes) {
|
||||
for (const voter of voteAggregation.voters) {
|
||||
if (voter === getKeyAuthor(pollUpdate.pollUpdateMessageKey)) {
|
||||
selectedOptions.push(voteAggregation.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Build payload and call the handler
|
||||
const voteDestination = getDestination(pollUpdate.pollUpdateMessageKey);
|
||||
const pollVote: PollVote = {
|
||||
...voteDestination,
|
||||
selectedOptions: selectedOptions,
|
||||
timestamp: pollUpdate.senderTimestampMs,
|
||||
};
|
||||
const payload: PollVotePayload = {
|
||||
vote: pollVote,
|
||||
poll: getDestination(pollCreationMessageKey),
|
||||
};
|
||||
handler(payload);
|
||||
}
|
||||
}
|
||||
protected async handleMessageUpsertPollVoteFailed(message, handler) {
|
||||
const pollUpdateMessage = message.message?.pollUpdateMessage;
|
||||
if (!pollUpdateMessage) {
|
||||
return;
|
||||
}
|
||||
const pollCreationMessageKey = pollUpdateMessage.pollCreationMessageKey;
|
||||
const pollCreationMessage = await this.getMessage(pollCreationMessageKey);
|
||||
if (pollCreationMessage) {
|
||||
// We found message, so later the engine will issue a message.update message
|
||||
return;
|
||||
}
|
||||
|
||||
// We didn't find the creation message, so send failed one
|
||||
const pollUpdateMessageKey = message.key;
|
||||
const voteDestination = getDestination(pollUpdateMessageKey);
|
||||
const pollVote: PollVote = {
|
||||
...voteDestination,
|
||||
selectedOptions: [],
|
||||
// change to below line when the PR merged, so we have the same timestamps
|
||||
// https://github.com/WhiskeySockets/Baileys/pull/348
|
||||
// Or without toNumber() - it depends on the PR above
|
||||
// timestamp: pollUpdateMessage.senderTimestampMs.toNumber()
|
||||
timestamp: message.messageTimestamp,
|
||||
};
|
||||
const payload: PollVotePayload = {
|
||||
vote: pollVote,
|
||||
poll: getDestination(pollCreationMessageKey),
|
||||
};
|
||||
handler(payload);
|
||||
}
|
||||
|
||||
private toWahaPresences(
|
||||
remoteJid,
|
||||
storedPresences: PresenceData[],
|
||||
): WAHAChatPresences {
|
||||
const presences: WAHAPresenceData[] = [];
|
||||
for (const participant in storedPresences) {
|
||||
const data: PresenceData = storedPresences[participant];
|
||||
const lastKnownPresence = lodash.get(
|
||||
PresenceStatuses,
|
||||
data.lastKnownPresence,
|
||||
data.lastKnownPresence,
|
||||
);
|
||||
const presence: WAHAPresenceData = {
|
||||
participant: toCusFormat(participant),
|
||||
// @ts-ignore
|
||||
lastKnownPresence: lastKnownPresence,
|
||||
lastSeen: data.lastSeen || null,
|
||||
};
|
||||
presences.push(presence);
|
||||
}
|
||||
const chatId = toCusFormat(remoteJid);
|
||||
return { id: chatId, presences: presences };
|
||||
}
|
||||
|
||||
protected async downloadMedia(message: Message) {
|
||||
if (!message.hasMedia) {
|
||||
return message;
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
message.mediaUrl = await this.storage.save(message.key.id, '', undefined);
|
||||
return message;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert from 11111111111@s.whatsapp.net to 11111111111@c.us
|
||||
*/
|
||||
function toCusFormat(remoteJid) {
|
||||
if (isJidGroup(remoteJid)) {
|
||||
return remoteJid;
|
||||
}
|
||||
if (!remoteJid) {
|
||||
return;
|
||||
}
|
||||
if (remoteJid == 'me') {
|
||||
return remoteJid;
|
||||
}
|
||||
const number = remoteJid.split('@')[0];
|
||||
return ensureSuffix(number);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert from 11111111111@c.us to 11111111111@s.whatsapp.net
|
||||
* @param chatId
|
||||
*/
|
||||
export function toJID(chatId) {
|
||||
if (isJidGroup(chatId)) {
|
||||
return chatId;
|
||||
}
|
||||
const number = chatId.split('@')[0];
|
||||
return number + '@s.whatsapp.net';
|
||||
}
|
||||
|
||||
/**
|
||||
* Build WAHA message id from engine one
|
||||
* {id: "AAA", remoteJid: "11111111111@s.whatsapp.net", "fromMe": false}
|
||||
* false_11111111111@c.us_AA
|
||||
*/
|
||||
function buildMessageId({ id, remoteJid, fromMe }) {
|
||||
const chatId = toCusFormat(remoteJid);
|
||||
return `${fromMe || false}_${chatId}_${id}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse message id from WAHA to engine
|
||||
* false_11111111111@c.us_AAA
|
||||
* {id: "AAA", remoteJid: "11111111111@s.whatsapp.net", "fromMe": false}
|
||||
*/
|
||||
function parseMessageId(messageId) {
|
||||
const parts = messageId.split('_');
|
||||
if (parts.length != 3) {
|
||||
throw new Error(
|
||||
'Message id be in format false_11111111111@c.us_AAAAAAAAAAAAAAAAAAAA',
|
||||
);
|
||||
}
|
||||
const fromMe = parts[0] == 'true';
|
||||
const chatId = parts[1];
|
||||
const remoteJid = toJID(chatId);
|
||||
const id = parts[2];
|
||||
return { fromMe: fromMe, id: id, remoteJid: remoteJid };
|
||||
}
|
||||
|
||||
function getId(object) {
|
||||
return object.id;
|
||||
}
|
||||
|
||||
function isMine(message) {
|
||||
return message?.key?.fromMe;
|
||||
}
|
||||
|
||||
function isNotMine(message) {
|
||||
return !message?.key?.fromMe;
|
||||
}
|
||||
|
||||
function isAckUpdateMessageEvent(event) {
|
||||
return event?.update.status != null;
|
||||
}
|
||||
|
||||
function getFromToParticipant(message) {
|
||||
const isGroupMessage = Boolean(message.key.participant);
|
||||
let participant: string;
|
||||
let to: string;
|
||||
if (isGroupMessage) {
|
||||
participant = message.key.participant;
|
||||
to = message.key.remoteJid;
|
||||
}
|
||||
const from = message.key.remoteJid;
|
||||
return {
|
||||
from: from,
|
||||
to: to,
|
||||
participant: participant,
|
||||
};
|
||||
}
|
||||
|
||||
function getTo(key, meId = undefined) {
|
||||
// For group - always to group JID
|
||||
const isGroupMessage = Boolean(key.participant);
|
||||
if (isGroupMessage) {
|
||||
return key.remoteJid;
|
||||
}
|
||||
if (key.fromMe) {
|
||||
return key.remoteJid;
|
||||
}
|
||||
return meId || 'me';
|
||||
}
|
||||
|
||||
function getFrom(key, meId) {
|
||||
// For group - always from participant
|
||||
const isGroupMessage = Boolean(key.participant);
|
||||
if (isGroupMessage) {
|
||||
return key.participant;
|
||||
}
|
||||
if (key.fromMe) {
|
||||
return meId || 'me';
|
||||
}
|
||||
return key.remoteJid;
|
||||
}
|
||||
|
||||
function getDestination(key, meId = undefined): MessageDestination {
|
||||
return {
|
||||
id: buildMessageId(key),
|
||||
to: toCusFormat(getTo(key, meId)),
|
||||
from: toCusFormat(getFrom(key, meId)),
|
||||
fromMe: key.fromMe,
|
||||
};
|
||||
}
|
||||
+276
-217
@@ -1,236 +1,295 @@
|
||||
import {WhatsappSession} from "./abc/session.abc";
|
||||
import { UnprocessableEntityException } from '@nestjs/common/exceptions/unprocessable-entity.exception';
|
||||
import { create, CreateConfig, Message, Whatsapp } from 'venom-bot';
|
||||
|
||||
import {
|
||||
Button,
|
||||
ChatRequest,
|
||||
CheckNumberStatusQuery,
|
||||
MessageContactVcardRequest,
|
||||
MessageFileRequest,
|
||||
MessageImageRequest,
|
||||
MessageLinkPreviewRequest,
|
||||
MessageLocationRequest,
|
||||
MessageReactionRequest,
|
||||
MessageReplyRequest,
|
||||
MessageTextButtonsRequest,
|
||||
MessageTextRequest
|
||||
} from "../structures/chatting.dto";
|
||||
import {WAMessage, WANumberExistResult} from "../structures/responses.dto";
|
||||
import {create, Message, Whatsapp} from "venom-bot";
|
||||
import {WAEvents, WhatsappStatus} from "../structures/enums.dto";
|
||||
import {NotImplementedByEngineError} from "./exceptions";
|
||||
import {MediaStorage} from "./abc/storage.abc";
|
||||
import {UnprocessableEntityException} from "@nestjs/common/exceptions/unprocessable-entity.exception";
|
||||
import {ConsoleLogger} from "@nestjs/common";
|
||||
import {ContactQuery} from "../structures/contacts.dto";
|
||||
|
||||
class QR {
|
||||
private base64: string;
|
||||
|
||||
save(base64) {
|
||||
this.base64 = base64.replace(/^data:image\/png;base64,/, '');
|
||||
}
|
||||
|
||||
get(): Buffer {
|
||||
return Buffer.from(this.base64, "base64")
|
||||
}
|
||||
|
||||
}
|
||||
Button,
|
||||
ChatRequest,
|
||||
CheckNumberStatusQuery,
|
||||
GetMessageQuery,
|
||||
MessageContactVcardRequest,
|
||||
MessageFileRequest,
|
||||
MessageImageRequest,
|
||||
MessageLinkPreviewRequest,
|
||||
MessageLocationRequest,
|
||||
MessageReactionRequest,
|
||||
MessageReplyRequest,
|
||||
MessageTextButtonsRequest,
|
||||
MessageTextRequest,
|
||||
WANumberExistResult,
|
||||
} from '../structures/chatting.dto';
|
||||
import {
|
||||
WAHAEngine,
|
||||
WAHAEvents,
|
||||
WAHASessionStatus,
|
||||
} from '../structures/enums.dto';
|
||||
import { WAMessage } from '../structures/responses.dto';
|
||||
import { WAHAInternalEvent, WhatsappSession } from './abc/session.abc';
|
||||
import { NotImplementedByEngineError } from './exceptions';
|
||||
import { QR } from './QR';
|
||||
|
||||
export class WhatsappSessionVenomCore extends WhatsappSession {
|
||||
whatsapp: Whatsapp;
|
||||
private qr: QR
|
||||
engine = WAHAEngine.VENOM;
|
||||
|
||||
public constructor(public name: string, protected storage: MediaStorage, protected log: ConsoleLogger) {
|
||||
super(name, storage, log);
|
||||
this.qr = new QR()
|
||||
whatsapp: Whatsapp;
|
||||
private qr: QR;
|
||||
|
||||
public constructor(config) {
|
||||
super(config);
|
||||
this.qr = new QR();
|
||||
}
|
||||
|
||||
protected buildClient() {
|
||||
const venomOptions: CreateConfig = {
|
||||
headless: true,
|
||||
devtools: false,
|
||||
debug: false,
|
||||
logQR: true,
|
||||
browserArgs: this.getBrowserArgsForPuppeteer(),
|
||||
autoClose: 60000,
|
||||
puppeteerOptions: {},
|
||||
};
|
||||
this.addProxyConfig(venomOptions);
|
||||
return create(this.name, this.getCatchQR(), undefined, venomOptions);
|
||||
}
|
||||
|
||||
protected addProxyConfig(venomOptions: CreateConfig) {
|
||||
if (this.proxyConfig?.server !== undefined) {
|
||||
venomOptions.addProxy = [this.proxyConfig?.server];
|
||||
}
|
||||
if (
|
||||
this.proxyConfig?.username !== undefined &&
|
||||
this.proxyConfig?.password !== undefined
|
||||
) {
|
||||
venomOptions.userProxy = this.proxyConfig?.username;
|
||||
venomOptions.userPass = this.proxyConfig?.password;
|
||||
}
|
||||
}
|
||||
|
||||
protected getCatchQR() {
|
||||
return (base64Qrimg, asciiQR, attempts, urlCode) => {
|
||||
this.qr.save(base64Qrimg);
|
||||
this.status = WAHASessionStatus.SCAN_QR_CODE;
|
||||
this.log.debug('Number of attempts to read the qrcode: ', attempts);
|
||||
this.log.log('Terminal qrcode:');
|
||||
// Log QR image in console without this.log to make it pretty
|
||||
console.log(asciiQR);
|
||||
};
|
||||
}
|
||||
|
||||
async start() {
|
||||
try {
|
||||
this.whatsapp = await this.buildClient();
|
||||
} catch (error) {
|
||||
this.status = WAHASessionStatus.FAILED;
|
||||
this.log.error(error);
|
||||
this.qr.save('');
|
||||
return;
|
||||
}
|
||||
|
||||
protected buildClient() {
|
||||
return create(this.name,
|
||||
this.getCatchQR(),
|
||||
undefined,
|
||||
{
|
||||
headless: true,
|
||||
devtools: false,
|
||||
useChrome: true,
|
||||
debug: false,
|
||||
logQR: true,
|
||||
browserArgs: this.getBrowserArgsForPuppeteer(),
|
||||
autoClose: 60000,
|
||||
puppeteerOptions: {},
|
||||
multidevice: true,
|
||||
}
|
||||
)
|
||||
this.status = WAHASessionStatus.WORKING;
|
||||
this.events.emit(WAHAInternalEvent.engine_start);
|
||||
return this;
|
||||
}
|
||||
|
||||
stop() {
|
||||
return this.whatsapp.close();
|
||||
}
|
||||
|
||||
subscribe(event: WAHAEvents | string, handler: (message) => void) {
|
||||
if (event === WAHAEvents.MESSAGE) {
|
||||
return this.whatsapp.onMessage((message: Message) =>
|
||||
this.processIncomingMessage(message).then(handler),
|
||||
);
|
||||
} else if (event === WAHAEvents.MESSAGE_ANY) {
|
||||
return this.whatsapp.onAnyMessage((message: Message) =>
|
||||
this.processIncomingMessage(message).then(handler),
|
||||
);
|
||||
} else if (event === WAHAEvents.STATE_CHANGE) {
|
||||
return this.whatsapp.onStateChange(handler);
|
||||
} else if (event === WAHAEvents.MESSAGE_ACK) {
|
||||
return this.whatsapp.onAck(handler);
|
||||
} else if (event === WAHAEvents.GROUP_JOIN) {
|
||||
return this.whatsapp.onAddedToGroup(handler);
|
||||
} else {
|
||||
throw new NotImplementedByEngineError(
|
||||
`Engine does not support webhook event: ${event}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
protected getCatchQR() {
|
||||
return (base64Qrimg, asciiQR, attempts, urlCode) => {
|
||||
this.qr.save(base64Qrimg)
|
||||
this.status = WhatsappStatus.SCAN_QR_CODE
|
||||
this.log.debug('Number of attempts to read the qrcode: ', attempts);
|
||||
this.log.log('Terminal qrcode:');
|
||||
// Log QR image in console without this.log to make it pretty
|
||||
console.log(asciiQR);
|
||||
};
|
||||
/**
|
||||
* START - Methods for API
|
||||
*/
|
||||
getScreenshot(): Promise<Buffer | string> {
|
||||
if (this.status === WAHASessionStatus.STARTING) {
|
||||
throw new UnprocessableEntityException(
|
||||
`The session is starting, please try again after few seconds`,
|
||||
);
|
||||
} else if (this.status === WAHASessionStatus.SCAN_QR_CODE) {
|
||||
return Promise.resolve(this.qr.get());
|
||||
} else if (this.status === WAHASessionStatus.WORKING) {
|
||||
return this.whatsapp.page.screenshot();
|
||||
} else {
|
||||
throw new UnprocessableEntityException(`Unknown status - ${this.status}`);
|
||||
}
|
||||
}
|
||||
|
||||
async start() {
|
||||
try {
|
||||
this.whatsapp = await this.buildClient()
|
||||
} catch (error) {
|
||||
this.status = WhatsappStatus.FAILED
|
||||
this.log.error(error)
|
||||
this.qr.save("")
|
||||
return
|
||||
}
|
||||
|
||||
this.status = WhatsappStatus.WORKING
|
||||
return this
|
||||
async checkNumberStatus(
|
||||
request: CheckNumberStatusQuery,
|
||||
): Promise<WANumberExistResult> {
|
||||
try {
|
||||
return await this.whatsapp.checkNumberStatus(
|
||||
this.ensureSuffix(request.phone),
|
||||
);
|
||||
} catch (error) {
|
||||
// Catch number doesn't exist error and return it as is
|
||||
if (error.status === 404 && !error.numberExists) {
|
||||
return error;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
sendContactVCard(request: MessageContactVcardRequest) {
|
||||
return this.whatsapp.sendContactVcard(
|
||||
request.chatId,
|
||||
request.contactsId,
|
||||
request.name,
|
||||
);
|
||||
}
|
||||
|
||||
stop() {
|
||||
return this.whatsapp.close()
|
||||
sendText(request: MessageTextRequest) {
|
||||
return this.whatsapp.sendText(
|
||||
this.ensureSuffix(request.chatId),
|
||||
request.text,
|
||||
);
|
||||
}
|
||||
|
||||
reply(request: MessageReplyRequest) {
|
||||
return this.whatsapp.reply(request.chatId, request.text, request.reply_to);
|
||||
}
|
||||
|
||||
sendFile(request: MessageFileRequest) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
sendImage(request: MessageImageRequest) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
async sendVoice(request) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
sendLinkPreview(request: MessageLinkPreviewRequest) {
|
||||
return this.whatsapp.sendLinkPreview(
|
||||
request.chatId,
|
||||
request.url,
|
||||
request.title,
|
||||
);
|
||||
}
|
||||
|
||||
sendLocation(request: MessageLocationRequest) {
|
||||
return this.whatsapp.sendLocation(
|
||||
request.chatId,
|
||||
String(request.latitude),
|
||||
String(request.longitude),
|
||||
request.title,
|
||||
);
|
||||
}
|
||||
|
||||
sendSeen(chat: ChatRequest) {
|
||||
return this.whatsapp.markMarkSeenMessage(chat.chatId);
|
||||
}
|
||||
|
||||
sendTextButtons(request: MessageTextButtonsRequest) {
|
||||
const buttons = request.buttons.map((button: Button) => {
|
||||
return {
|
||||
buttonId: button.id,
|
||||
buttonText: {
|
||||
displayText: button.text,
|
||||
},
|
||||
};
|
||||
});
|
||||
return this.whatsapp.sendButtons(
|
||||
this.ensureSuffix(request.chatId),
|
||||
request.title,
|
||||
request.footer,
|
||||
buttons,
|
||||
);
|
||||
}
|
||||
|
||||
startTyping(chat: ChatRequest) {
|
||||
return this.whatsapp.startTyping(chat.chatId, false);
|
||||
}
|
||||
|
||||
stopTyping(chat: ChatRequest) {
|
||||
return;
|
||||
// The method is not available yet in new venom-bot
|
||||
// return this.whatsapp.stopTyping(chat.chatId);
|
||||
}
|
||||
|
||||
async getChatMessages(chatId: string, limit: number) {
|
||||
const messages = await this.whatsapp.getAllMessagesInChat(
|
||||
chatId,
|
||||
true,
|
||||
false,
|
||||
);
|
||||
// Go over messages, download media, and convert to right format.
|
||||
const result = [];
|
||||
for (const [count, message] of messages.entries()) {
|
||||
if (count > limit) {
|
||||
// Have enough in the list, stop processing
|
||||
break;
|
||||
}
|
||||
result.push(await this.processIncomingMessage(message));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
subscribe(event: WAEvents | string, handler: (message) => void) {
|
||||
if (event === WAEvents.MESSAGE) {
|
||||
return this.whatsapp.onMessage((message: Message) => this.processIncomingMessage(message).then(handler))
|
||||
} else if (event === WAEvents.MESSAGE_ANY) {
|
||||
return this.whatsapp.onAnyMessage((message: Message) => this.processIncomingMessage(message).then(handler))
|
||||
} else if (event === WAEvents.STATE_CHANGE) {
|
||||
return this.whatsapp.onStateChange(handler)
|
||||
} else if (event === WAEvents.MESSAGE_ACK) {
|
||||
return this.whatsapp.onAck(handler)
|
||||
} else if (event === WAEvents.GROUP_JOIN) {
|
||||
return this.whatsapp.onAddedToGroup(handler)
|
||||
} else {
|
||||
throw new NotImplementedByEngineError(`Engine does not support webhook event: ${event}`)
|
||||
}
|
||||
async getMessages(query: GetMessageQuery) {
|
||||
return this.getChatMessages(query.chatId, query.limit);
|
||||
}
|
||||
|
||||
setReaction(request: MessageReactionRequest) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
/**
|
||||
* END - Methods for API
|
||||
*/
|
||||
|
||||
protected async downloadAndDecryptMedia(message: Message) {
|
||||
if (!message.isMMS || !message.isMedia) {
|
||||
return message;
|
||||
}
|
||||
// @ts-ignore
|
||||
message.mediaUrl = await this.storage.save(message.id, '', undefined);
|
||||
return message;
|
||||
}
|
||||
|
||||
private processIncomingMessage(message: Message) {
|
||||
return this.downloadAndDecryptMedia(message).then(this.toWAMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* START - Methods for API
|
||||
*/
|
||||
getScreenshot(): Promise<Buffer | string> {
|
||||
if (this.status === WhatsappStatus.STARTING) {
|
||||
throw new UnprocessableEntityException(`The session is starting, please try again after few seconds`);
|
||||
} else if (this.status === WhatsappStatus.SCAN_QR_CODE) {
|
||||
return Promise.resolve(this.qr.get())
|
||||
} else if (this.status === WhatsappStatus.WORKING) {
|
||||
return this.whatsapp.page.screenshot()
|
||||
} else {
|
||||
throw new UnprocessableEntityException(`Unknown status - ${this.status}`);
|
||||
}
|
||||
}
|
||||
|
||||
async checkNumberStatus(request: CheckNumberStatusQuery): Promise<WANumberExistResult> {
|
||||
try {
|
||||
return await this.whatsapp.checkNumberStatus(this.ensureSuffix(request.phone))
|
||||
} catch (error) {
|
||||
// Catch number doesn't exist error and return it as is
|
||||
if (error.status === 404 && !error.numberExists){
|
||||
return error
|
||||
}
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
sendContactVCard(request: MessageContactVcardRequest) {
|
||||
return this.whatsapp.sendContactVcard(request.chatId, request.contactsId, request.name)
|
||||
}
|
||||
|
||||
sendText(request: MessageTextRequest) {
|
||||
return this.whatsapp.sendText(this.ensureSuffix(request.chatId), request.text)
|
||||
}
|
||||
|
||||
reply(request: MessageReplyRequest) {
|
||||
return this.whatsapp.reply(request.chatId, request.text, request.reply_to)
|
||||
}
|
||||
|
||||
sendFile(request: MessageFileRequest) {
|
||||
throw new NotImplementedByEngineError()
|
||||
}
|
||||
|
||||
sendImage(request: MessageImageRequest) {
|
||||
throw new NotImplementedByEngineError()
|
||||
}
|
||||
|
||||
async sendVoice(request) {
|
||||
throw new NotImplementedByEngineError()
|
||||
}
|
||||
|
||||
sendLinkPreview(request: MessageLinkPreviewRequest) {
|
||||
return this.whatsapp.sendLinkPreview(request.chatId, request.url, request.title)
|
||||
}
|
||||
|
||||
sendLocation(request: MessageLocationRequest) {
|
||||
return this.whatsapp.sendLocation(request.chatId, request.latitude, request.longitude, request.title)
|
||||
}
|
||||
|
||||
sendSeen(chat: ChatRequest) {
|
||||
return this.whatsapp.sendSeen(chat.chatId)
|
||||
}
|
||||
|
||||
sendTextButtons(request: MessageTextButtonsRequest) {
|
||||
const buttons = request.buttons.map((button: Button) => {
|
||||
return {
|
||||
buttonId: button.id,
|
||||
buttonText: {
|
||||
displayText: button.body
|
||||
}
|
||||
}
|
||||
})
|
||||
return this.whatsapp.sendButtons(this.ensureSuffix(request.chatId), request.title, buttons, request.text)
|
||||
}
|
||||
|
||||
startTyping(chat: ChatRequest) {
|
||||
return this.whatsapp.startTyping(chat.chatId)
|
||||
}
|
||||
|
||||
stopTyping(chat: ChatRequest) {
|
||||
return this.whatsapp.stopTyping(chat.chatId)
|
||||
}
|
||||
|
||||
setReaction(request: MessageReactionRequest) {
|
||||
throw new NotImplementedByEngineError()
|
||||
}
|
||||
|
||||
/**
|
||||
* END - Methods for API
|
||||
*/
|
||||
|
||||
protected async downloadAndDecryptMedia(message: Message) {
|
||||
if (!message.isMMS || !message.isMedia) {
|
||||
return message
|
||||
}
|
||||
// @ts-ignore
|
||||
message.mediaUrl = await this.storage.save(message.id, "", undefined)
|
||||
return message
|
||||
}
|
||||
|
||||
private processIncomingMessage(message: Message) {
|
||||
return this.downloadAndDecryptMedia(message).then(this.toWAMessage)
|
||||
}
|
||||
|
||||
protected toWAMessage(message: Message): Promise<WAMessage> {
|
||||
// @ts-ignore
|
||||
return Promise.resolve({
|
||||
id: message.id,
|
||||
timestamp: message.timestamp,
|
||||
from: message.from,
|
||||
fromMe: message.fromMe,
|
||||
to: message.to,
|
||||
body: message.body,
|
||||
// @ts-ignore
|
||||
hasMedia: Boolean(message.mediaUrl),
|
||||
// @ts-ignore
|
||||
mediaUrl: message.mediaUrl,
|
||||
// @ts-ignore
|
||||
ack: message.ack,
|
||||
location: undefined,
|
||||
vCards: undefined,
|
||||
_data: message,
|
||||
})
|
||||
}
|
||||
protected toWAMessage(message: Message): Promise<WAMessage> {
|
||||
// @ts-ignore
|
||||
return Promise.resolve({
|
||||
id: message.id,
|
||||
timestamp: message.timestamp,
|
||||
from: message.from,
|
||||
fromMe: message.fromMe,
|
||||
to: message.to,
|
||||
body: message.body,
|
||||
// @ts-ignore
|
||||
hasMedia: Boolean(message.mediaUrl),
|
||||
// @ts-ignore
|
||||
mediaUrl: message.mediaUrl,
|
||||
// @ts-ignore
|
||||
ack: message.ack,
|
||||
location: undefined,
|
||||
vCards: undefined,
|
||||
_data: message,
|
||||
});
|
||||
}
|
||||
}
|
||||
+506
-200
@@ -1,246 +1,552 @@
|
||||
import {UnprocessableEntityException} from "@nestjs/common/exceptions/unprocessable-entity.exception";
|
||||
import {Buttons, Chat, Client, Contact, Events, Message} from "whatsapp-web.js";
|
||||
import {Message as MessageInstance} from "whatsapp-web.js/src/structures"
|
||||
import {WAEvents, WhatsappStatus} from "../structures/enums.dto";
|
||||
import {WhatsappSession} from "./abc/session.abc";
|
||||
import {WAMessage, WANumberExistResult} from "../structures/responses.dto";
|
||||
import { UnprocessableEntityException } from '@nestjs/common/exceptions/unprocessable-entity.exception';
|
||||
import {
|
||||
ChatRequest,
|
||||
CheckNumberStatusQuery,
|
||||
MessageContactVcardRequest,
|
||||
MessageFileRequest,
|
||||
MessageImageRequest,
|
||||
MessageLinkPreviewRequest,
|
||||
MessageLocationRequest,
|
||||
MessageReactionRequest,
|
||||
MessageReplyRequest,
|
||||
MessageTextButtonsRequest,
|
||||
MessageTextRequest,
|
||||
MessageVoiceRequest
|
||||
} from "../structures/chatting.dto";
|
||||
import {AvailableInPlusVersion, NotImplementedByEngineError} from "./exceptions";
|
||||
import {ContactQuery, ContactRequest} from "../structures/contacts.dto";
|
||||
Buttons,
|
||||
Chat,
|
||||
Client,
|
||||
ClientOptions,
|
||||
Contact,
|
||||
Events,
|
||||
GroupChat,
|
||||
Location,
|
||||
Message,
|
||||
} from 'whatsapp-web.js';
|
||||
import { Message as MessageInstance } from 'whatsapp-web.js/src/structures';
|
||||
|
||||
import {
|
||||
Button,
|
||||
ChatRequest,
|
||||
CheckNumberStatusQuery,
|
||||
GetMessageQuery,
|
||||
MessageContactVcardRequest,
|
||||
MessageFileRequest,
|
||||
MessageImageRequest,
|
||||
MessageLinkPreviewRequest,
|
||||
MessageLocationRequest,
|
||||
MessageReactionRequest,
|
||||
MessageReplyRequest,
|
||||
MessageTextButtonsRequest,
|
||||
MessageTextRequest,
|
||||
MessageVoiceRequest,
|
||||
SendSeenRequest,
|
||||
WANumberExistResult,
|
||||
} from '../structures/chatting.dto';
|
||||
import { ContactQuery, ContactRequest } from '../structures/contacts.dto';
|
||||
import {
|
||||
ACK_UNKNOWN,
|
||||
WAHAEngine,
|
||||
WAHAEvents,
|
||||
WAHAPresenceStatus,
|
||||
WAHASessionStatus,
|
||||
WAMessageAck,
|
||||
} from '../structures/enums.dto';
|
||||
import {
|
||||
CreateGroupRequest,
|
||||
ParticipantsRequest,
|
||||
} from '../structures/groups.dto';
|
||||
import { WAMessage } from '../structures/responses.dto';
|
||||
import { WAHAInternalEvent, WhatsappSession } from './abc/session.abc';
|
||||
import {
|
||||
AvailableInPlusVersion,
|
||||
NotImplementedByEngineError,
|
||||
} from './exceptions';
|
||||
import { QR } from './QR';
|
||||
import { MeInfo } from '../structures/sessions.dto';
|
||||
import { jidNormalizedUser } from '@adiwajshing/baileys';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const QRCode = require('qrcode');
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const qrcode = require('qrcode-terminal');
|
||||
|
||||
|
||||
export class WhatsappSessionWebJSCore extends WhatsappSession {
|
||||
whatsapp: Client;
|
||||
engine = WAHAEngine.WEBJS;
|
||||
|
||||
protected buildClient() {
|
||||
return new Client({
|
||||
puppeteer: {
|
||||
headless: true,
|
||||
executablePath: this.getBrowserExecutablePath(),
|
||||
args: this.getBrowserArgsForPuppeteer(),
|
||||
}
|
||||
});
|
||||
}
|
||||
whatsapp: Client;
|
||||
protected qr: QR;
|
||||
|
||||
async start() {
|
||||
this.whatsapp = this.buildClient()
|
||||
public constructor(config) {
|
||||
super(config);
|
||||
this.qr = new QR();
|
||||
}
|
||||
|
||||
this.whatsapp.initialize().catch(error => {
|
||||
this.status = WhatsappStatus.FAILED
|
||||
this.log.error(error)
|
||||
return
|
||||
});
|
||||
protected buildClient() {
|
||||
const clientOptions: ClientOptions = {
|
||||
puppeteer: {
|
||||
headless: true,
|
||||
executablePath: this.getBrowserExecutablePath(),
|
||||
args: this.getBrowserArgsForPuppeteer(),
|
||||
},
|
||||
};
|
||||
this.addProxyConfig(clientOptions);
|
||||
return new Client(clientOptions);
|
||||
}
|
||||
|
||||
// Connect events
|
||||
this.whatsapp.on(Events.QR_RECEIVED, (qr) => {
|
||||
qrcode.generate(qr, {small: true});
|
||||
this.status = WhatsappStatus.SCAN_QR_CODE
|
||||
});
|
||||
protected addProxyConfig(clientOptions: ClientOptions) {
|
||||
if (this.proxyConfig?.server !== undefined) {
|
||||
// push the proxy server to the args
|
||||
clientOptions.puppeteer.args.push(
|
||||
`--proxy-server=${this.proxyConfig?.server}`,
|
||||
);
|
||||
|
||||
this.whatsapp.on(Events.AUTHENTICATED, () => {
|
||||
this.status = WhatsappStatus.WORKING
|
||||
this.log.log(`Session '${this.name}' has been authenticated!`)
|
||||
});
|
||||
return this
|
||||
}
|
||||
|
||||
stop() {
|
||||
return this.whatsapp.pupPage.close()
|
||||
}
|
||||
|
||||
/**
|
||||
* START - Methods for API
|
||||
*/
|
||||
async getScreenshot(): Promise<Buffer | string> {
|
||||
if (this.status === WhatsappStatus.FAILED) {
|
||||
throw new UnprocessableEntityException(`The session under FAILED status. Please try to restart it.`);
|
||||
}
|
||||
return await this.whatsapp.pupPage.screenshot()
|
||||
}
|
||||
|
||||
async checkNumberStatus(request: CheckNumberStatusQuery): Promise<WANumberExistResult> {
|
||||
const exist = await this.whatsapp.isRegisteredUser(this.ensureSuffix(request.phone))
|
||||
return {numberExists: exist}
|
||||
}
|
||||
|
||||
sendText(request: MessageTextRequest) {
|
||||
return this.whatsapp.sendMessage(this.ensureSuffix(request.chatId), request.text)
|
||||
}
|
||||
|
||||
sendTextButtons(request: MessageTextButtonsRequest) {
|
||||
const message = new Buttons("", request.buttons, request.title, request.text)
|
||||
return this.whatsapp.sendMessage(this.ensureSuffix(request.chatId), message)
|
||||
}
|
||||
|
||||
sendContactVCard(request: MessageContactVcardRequest) {
|
||||
throw new NotImplementedByEngineError()
|
||||
}
|
||||
|
||||
reply(request: MessageReplyRequest) {
|
||||
const options = {
|
||||
quotedMessageId: request.reply_to,
|
||||
// Authenticate
|
||||
if (this.proxyConfig?.username && this.proxyConfig?.password) {
|
||||
clientOptions.proxyAuthentication = {
|
||||
username: this.proxyConfig?.username,
|
||||
password: this.proxyConfig?.password,
|
||||
};
|
||||
return this.whatsapp.sendMessage(request.chatId, request.text, options)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sendImage(request: MessageImageRequest) {
|
||||
throw new AvailableInPlusVersion()
|
||||
}
|
||||
async start() {
|
||||
this.whatsapp = this.buildClient();
|
||||
this.whatsapp.initialize().catch((error) => {
|
||||
this.status = WAHASessionStatus.FAILED;
|
||||
this.log.error(error);
|
||||
return;
|
||||
});
|
||||
this.listenConnectionEvents();
|
||||
this.events.emit(WAHAInternalEvent.engine_start);
|
||||
return this;
|
||||
}
|
||||
|
||||
sendFile(request: MessageFileRequest) {
|
||||
throw new AvailableInPlusVersion()
|
||||
}
|
||||
stop() {
|
||||
return this.whatsapp.destroy();
|
||||
}
|
||||
|
||||
sendVoice(request: MessageVoiceRequest) {
|
||||
throw new AvailableInPlusVersion()
|
||||
async getSessionMeInfo(): Promise<MeInfo | null> {
|
||||
const clientInfo = this.whatsapp?.info;
|
||||
if (!clientInfo) {
|
||||
return null;
|
||||
}
|
||||
const wid = clientInfo.wid;
|
||||
const meInfo: MeInfo = {
|
||||
id: wid._serialized,
|
||||
pushName: clientInfo.pushname,
|
||||
};
|
||||
return meInfo;
|
||||
}
|
||||
|
||||
sendLinkPreview(request: MessageLinkPreviewRequest) {
|
||||
throw new NotImplementedByEngineError()
|
||||
}
|
||||
protected listenConnectionEvents() {
|
||||
this.whatsapp.on(Events.QR_RECEIVED, (qr) => {
|
||||
this.log.debug('QR received');
|
||||
// Convert to image and save
|
||||
QRCode.toDataURL(qr).then((url) => {
|
||||
this.qr.save(url);
|
||||
});
|
||||
// Print in terminal
|
||||
qrcode.generate(qr, { small: true });
|
||||
this.status = WAHASessionStatus.SCAN_QR_CODE;
|
||||
});
|
||||
|
||||
sendLocation(request: MessageLocationRequest) {
|
||||
throw new NotImplementedByEngineError()
|
||||
}
|
||||
this.whatsapp.on(Events.AUTHENTICATED, () => {
|
||||
this.status = WAHASessionStatus.WORKING;
|
||||
this.qr.save('');
|
||||
this.log.log(`Session '${this.name}' has been authenticated!`);
|
||||
});
|
||||
}
|
||||
|
||||
async sendSeen(request: ChatRequest) {
|
||||
const chat: Chat = await this.whatsapp.getChatById(request.chatId)
|
||||
await chat.sendSeen()
|
||||
}
|
||||
/**
|
||||
* START - Methods for API
|
||||
*/
|
||||
|
||||
async startTyping(request: ChatRequest) {
|
||||
const chat: Chat = await this.whatsapp.getChatById(request.chatId)
|
||||
await chat.sendStateTyping()
|
||||
}
|
||||
/**
|
||||
* Auth methods
|
||||
*/
|
||||
public async getQR(): Promise<Buffer> {
|
||||
return Promise.resolve(this.qr.get());
|
||||
}
|
||||
|
||||
async stopTyping(request: ChatRequest) {
|
||||
const chat: Chat = await this.whatsapp.getChatById(request.chatId)
|
||||
await chat.clearState()
|
||||
async getScreenshot(): Promise<Buffer | string> {
|
||||
if (this.status === WAHASessionStatus.FAILED) {
|
||||
throw new UnprocessableEntityException(
|
||||
`The session under FAILED status. Please try to restart it.`,
|
||||
);
|
||||
}
|
||||
return await this.whatsapp.pupPage.screenshot();
|
||||
}
|
||||
|
||||
async setReaction(request: MessageReactionRequest) {
|
||||
const messageId = this.deserializeId(request.messageId)
|
||||
console.log(messageId)
|
||||
async checkNumberStatus(
|
||||
request: CheckNumberStatusQuery,
|
||||
): Promise<WANumberExistResult> {
|
||||
const exist = await this.whatsapp.isRegisteredUser(
|
||||
this.ensureSuffix(request.phone),
|
||||
);
|
||||
return { numberExists: exist };
|
||||
}
|
||||
|
||||
// Recreate instance to react on it
|
||||
const message = new MessageInstance(this.whatsapp)
|
||||
message.id = messageId
|
||||
message._data = {id: messageId}
|
||||
sendText(request: MessageTextRequest) {
|
||||
const options = {
|
||||
// It's fine to sent just ids instead of Contact object
|
||||
mentions: request.mentions as unknown as Contact[],
|
||||
};
|
||||
return this.whatsapp.sendMessage(
|
||||
this.ensureSuffix(request.chatId),
|
||||
request.text,
|
||||
options,
|
||||
);
|
||||
}
|
||||
|
||||
return message.react(request.reaction)
|
||||
}
|
||||
sendTextButtons(request: MessageTextButtonsRequest) {
|
||||
const buttons = request.buttons.map((button: Button) => {
|
||||
return {
|
||||
id: button.id,
|
||||
body: button.text,
|
||||
};
|
||||
});
|
||||
const message = new Buttons('', buttons, request.title, request.footer);
|
||||
return this.whatsapp.sendMessage(
|
||||
this.ensureSuffix(request.chatId),
|
||||
message,
|
||||
);
|
||||
}
|
||||
|
||||
sendContactVCard(request: MessageContactVcardRequest) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
/**
|
||||
* Contacts methods
|
||||
*/
|
||||
getContact(query: ContactQuery) {
|
||||
return this.whatsapp.getContactById(this.ensureSuffix(query.contactId)).then(this.toWAContact)
|
||||
}
|
||||
reply(request: MessageReplyRequest) {
|
||||
const options = {
|
||||
quotedMessageId: request.reply_to,
|
||||
// It's fine to sent just ids instead of Contact object
|
||||
mentions: request.mentions as unknown as Contact[],
|
||||
};
|
||||
return this.whatsapp.sendMessage(request.chatId, request.text, options);
|
||||
}
|
||||
|
||||
getContacts() {
|
||||
return this.whatsapp.getContacts().then(contacts => contacts.map(this.toWAContact))
|
||||
}
|
||||
sendImage(request: MessageImageRequest) {
|
||||
throw new AvailableInPlusVersion();
|
||||
}
|
||||
|
||||
public async getContactAbout(query: ContactQuery) {
|
||||
const contact = await this.whatsapp.getContactById(this.ensureSuffix(query.contactId))
|
||||
return {"about": await contact.getAbout()}
|
||||
}
|
||||
sendFile(request: MessageFileRequest) {
|
||||
throw new AvailableInPlusVersion();
|
||||
}
|
||||
|
||||
public async getContactProfilePicture(query: ContactQuery) {
|
||||
const contact = await this.whatsapp.getContactById(this.ensureSuffix(query.contactId))
|
||||
return {"profilePictureURL": await contact.getProfilePicUrl()}
|
||||
}
|
||||
sendVoice(request: MessageVoiceRequest) {
|
||||
throw new AvailableInPlusVersion();
|
||||
}
|
||||
|
||||
public async blockContact(request: ContactRequest) {
|
||||
const contact = await this.whatsapp.getContactById(this.ensureSuffix(request.contactId))
|
||||
await contact.block()
|
||||
}
|
||||
async sendLocation(request: MessageLocationRequest) {
|
||||
const location = new Location(
|
||||
request.latitude,
|
||||
request.longitude,
|
||||
request.title,
|
||||
);
|
||||
return this.whatsapp.sendMessage(request.chatId, location);
|
||||
}
|
||||
|
||||
public async unblockContact(request: ContactRequest) {
|
||||
const contact = await this.whatsapp.getContactById(this.ensureSuffix(request.contactId))
|
||||
await contact.unblock()
|
||||
}
|
||||
sendLinkPreview(request: MessageLinkPreviewRequest) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
async sendSeen(request: SendSeenRequest) {
|
||||
const chat: Chat = await this.whatsapp.getChatById(request.chatId);
|
||||
await chat.sendSeen();
|
||||
}
|
||||
|
||||
async startTyping(request: ChatRequest) {
|
||||
const chat: Chat = await this.whatsapp.getChatById(request.chatId);
|
||||
await chat.sendStateTyping();
|
||||
}
|
||||
|
||||
async stopTyping(request: ChatRequest) {
|
||||
const chat: Chat = await this.whatsapp.getChatById(request.chatId);
|
||||
await chat.clearState();
|
||||
}
|
||||
|
||||
async getMessages(query: GetMessageQuery) {
|
||||
return this.getChatMessages(query.chatId, query.limit, query.downloadMedia);
|
||||
}
|
||||
|
||||
async setReaction(request: MessageReactionRequest) {
|
||||
const messageId = this.deserializeId(request.messageId);
|
||||
// Recreate instance to react on it
|
||||
const message = new MessageInstance(this.whatsapp);
|
||||
message.id = messageId;
|
||||
message._data = { id: messageId };
|
||||
|
||||
/**
|
||||
* END - Methods for API
|
||||
*/
|
||||
|
||||
subscribe(event, handler) {
|
||||
if (event === WAEvents.MESSAGE) {
|
||||
this.whatsapp.on(Events.MESSAGE_RECEIVED, (message) => this.processIncomingMessage(message).then(handler))
|
||||
} else if (event === WAEvents.MESSAGE_ANY) {
|
||||
this.whatsapp.on(Events.MESSAGE_CREATE, (message) => this.processIncomingMessage(message).then(handler))
|
||||
} else if (event === WAEvents.STATE_CHANGE) {
|
||||
this.whatsapp.on(Events.STATE_CHANGED, handler)
|
||||
} else if (event === WAEvents.MESSAGE_ACK) {
|
||||
// We do not download media here
|
||||
this.whatsapp.on(Events.MESSAGE_ACK, (message) => this.toWAMessage(message).then(handler))
|
||||
} else if (event === WAEvents.GROUP_JOIN) {
|
||||
this.whatsapp.on(Events.GROUP_JOIN, handler)
|
||||
} else if (event === WAEvents.GROUP_LEAVE) {
|
||||
this.whatsapp.on(Events.GROUP_LEAVE, handler)
|
||||
} else {
|
||||
throw new NotImplementedByEngineError(`Engine does not support webhook event: ${event}`)
|
||||
}
|
||||
return message.react(request.reaction);
|
||||
}
|
||||
|
||||
/**
|
||||
* Chats methods
|
||||
*/
|
||||
getChats() {
|
||||
return this.whatsapp.getChats();
|
||||
}
|
||||
|
||||
async getChatMessages(chatId: string, limit: number, downloadMedia: boolean) {
|
||||
const chat: Chat = await this.whatsapp.getChatById(
|
||||
this.ensureSuffix(chatId),
|
||||
);
|
||||
const messages = await chat.fetchMessages({
|
||||
limit: limit,
|
||||
});
|
||||
// Go over messages, download media, and convert to right format.
|
||||
const result = [];
|
||||
for (const message of messages) {
|
||||
const msg = await this.processIncomingMessage(message, downloadMedia);
|
||||
result.push(msg);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
async deleteChat(chatId) {
|
||||
const chat = await this.whatsapp.getChatById(chatId);
|
||||
return chat.delete();
|
||||
}
|
||||
|
||||
async clearMessages(chatId) {
|
||||
const chat = await this.whatsapp.getChatById(chatId);
|
||||
return chat.clearMessages();
|
||||
}
|
||||
|
||||
/**
|
||||
* Contacts methods
|
||||
*/
|
||||
getContact(query: ContactQuery) {
|
||||
return this.whatsapp
|
||||
.getContactById(this.ensureSuffix(query.contactId))
|
||||
.then(this.toWAContact);
|
||||
}
|
||||
|
||||
private processIncomingMessage(message: Message) {
|
||||
return this.downloadMedia(message).then(this.toWAMessage)
|
||||
getContacts() {
|
||||
return this.whatsapp
|
||||
.getContacts()
|
||||
.then((contacts) => contacts.map(this.toWAContact));
|
||||
}
|
||||
|
||||
public async getContactAbout(query: ContactQuery) {
|
||||
const contact = await this.whatsapp.getContactById(
|
||||
this.ensureSuffix(query.contactId),
|
||||
);
|
||||
return { about: await contact.getAbout() };
|
||||
}
|
||||
|
||||
public async getContactProfilePicture(query: ContactQuery) {
|
||||
const contact = await this.whatsapp.getContactById(
|
||||
this.ensureSuffix(query.contactId),
|
||||
);
|
||||
return { profilePictureURL: await contact.getProfilePicUrl() };
|
||||
}
|
||||
|
||||
public async blockContact(request: ContactRequest) {
|
||||
const contact = await this.whatsapp.getContactById(
|
||||
this.ensureSuffix(request.contactId),
|
||||
);
|
||||
await contact.block();
|
||||
}
|
||||
|
||||
public async unblockContact(request: ContactRequest) {
|
||||
const contact = await this.whatsapp.getContactById(
|
||||
this.ensureSuffix(request.contactId),
|
||||
);
|
||||
await contact.unblock();
|
||||
}
|
||||
|
||||
/**
|
||||
* Group methods
|
||||
*/
|
||||
public createGroup(request: CreateGroupRequest) {
|
||||
const participantIds = request.participants.map(
|
||||
(participant) => participant.id,
|
||||
);
|
||||
return this.whatsapp.createGroup(request.name, participantIds);
|
||||
}
|
||||
|
||||
public async setInfoAdminsOnly(id, value) {
|
||||
const groupChat = (await this.whatsapp.getChatById(id)) as GroupChat;
|
||||
return groupChat.setInfoAdminsOnly(value);
|
||||
}
|
||||
|
||||
public getGroups() {
|
||||
return this.whatsapp
|
||||
.getChats()
|
||||
.then((chats) => chats.filter((chat) => chat.isGroup));
|
||||
}
|
||||
|
||||
public getGroup(id) {
|
||||
return this.whatsapp.getChatById(id);
|
||||
}
|
||||
|
||||
public async deleteGroup(id) {
|
||||
const groupChat = (await this.whatsapp.getChatById(id)) as GroupChat;
|
||||
return groupChat.delete();
|
||||
}
|
||||
|
||||
public async leaveGroup(id) {
|
||||
const groupChat = (await this.whatsapp.getChatById(id)) as GroupChat;
|
||||
return groupChat.leave();
|
||||
}
|
||||
|
||||
public async setDescription(id, description) {
|
||||
const groupChat = (await this.whatsapp.getChatById(id)) as GroupChat;
|
||||
return groupChat.setDescription(description);
|
||||
}
|
||||
|
||||
public async setSubject(id, subject) {
|
||||
const groupChat = (await this.whatsapp.getChatById(id)) as GroupChat;
|
||||
return groupChat.setSubject(subject);
|
||||
}
|
||||
|
||||
public async getInviteCode(id): Promise<string> {
|
||||
const groupChat = (await this.whatsapp.getChatById(id)) as GroupChat;
|
||||
return groupChat.getInviteCode();
|
||||
}
|
||||
|
||||
public async revokeInviteCode(id): Promise<string> {
|
||||
const groupChat = (await this.whatsapp.getChatById(id)) as GroupChat;
|
||||
await groupChat.revokeInvite();
|
||||
return groupChat.getInviteCode();
|
||||
}
|
||||
|
||||
public async getParticipants(id) {
|
||||
const groupChat = (await this.whatsapp.getChatById(id)) as GroupChat;
|
||||
return groupChat.participants;
|
||||
}
|
||||
|
||||
public async addParticipants(id, request: ParticipantsRequest) {
|
||||
const groupChat = (await this.whatsapp.getChatById(id)) as GroupChat;
|
||||
const participantIds = request.participants.map(
|
||||
(participant) => participant.id,
|
||||
);
|
||||
return groupChat.addParticipants(participantIds);
|
||||
}
|
||||
|
||||
public async removeParticipants(id, request: ParticipantsRequest) {
|
||||
const groupChat = (await this.whatsapp.getChatById(id)) as GroupChat;
|
||||
const participantIds = request.participants.map(
|
||||
(participant) => participant.id,
|
||||
);
|
||||
return groupChat.removeParticipants(participantIds);
|
||||
}
|
||||
|
||||
public async promoteParticipantsToAdmin(id, request: ParticipantsRequest) {
|
||||
const groupChat = (await this.whatsapp.getChatById(id)) as GroupChat;
|
||||
const participantIds = request.participants.map(
|
||||
(participant) => participant.id,
|
||||
);
|
||||
return groupChat.promoteParticipants(participantIds);
|
||||
}
|
||||
|
||||
public async demoteParticipantsToUser(id, request: ParticipantsRequest) {
|
||||
const groupChat = (await this.whatsapp.getChatById(id)) as GroupChat;
|
||||
const participantIds = request.participants.map(
|
||||
(participant) => participant.id,
|
||||
);
|
||||
return groupChat.demoteParticipants(participantIds);
|
||||
}
|
||||
|
||||
public async setPresence(presence: WAHAPresenceStatus, chatId?: string) {
|
||||
let chat: Chat;
|
||||
switch (presence) {
|
||||
case WAHAPresenceStatus.ONLINE:
|
||||
await this.whatsapp.sendPresenceAvailable();
|
||||
break;
|
||||
case WAHAPresenceStatus.OFFLINE:
|
||||
await this.whatsapp.sendPresenceUnavailable();
|
||||
break;
|
||||
case WAHAPresenceStatus.TYPING:
|
||||
chat = await this.whatsapp.getChatById(chatId);
|
||||
await chat.sendStateTyping();
|
||||
break;
|
||||
case WAHAPresenceStatus.RECORDING:
|
||||
chat = await this.whatsapp.getChatById(chatId);
|
||||
await chat.sendStateRecording();
|
||||
break;
|
||||
case WAHAPresenceStatus.PAUSED:
|
||||
chat = await this.whatsapp.getChatById(chatId);
|
||||
await chat.clearState();
|
||||
break;
|
||||
default:
|
||||
throw new NotImplementedByEngineError(
|
||||
`WEBJS engine doesn't support '${presence}' presence.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
protected toWAMessage(message: Message): Promise<WAMessage> {
|
||||
// @ts-ignore
|
||||
return Promise.resolve({
|
||||
id: message.id._serialized,
|
||||
timestamp: message.timestamp,
|
||||
from: message.from,
|
||||
fromMe: message.fromMe,
|
||||
to: message.to,
|
||||
body: message.body,
|
||||
// @ts-ignore
|
||||
hasMedia: Boolean(message.mediaUrl),
|
||||
// @ts-ignore
|
||||
mediaUrl: message.mediaUrl,
|
||||
// @ts-ignore
|
||||
ack: message.ack,
|
||||
location: message.location,
|
||||
vCards: message.vCards,
|
||||
_data: message.rawData,
|
||||
})
|
||||
/**
|
||||
* END - Methods for API
|
||||
*/
|
||||
|
||||
subscribe(event, handler) {
|
||||
if (event === WAHAEvents.MESSAGE) {
|
||||
this.whatsapp.on(Events.MESSAGE_RECEIVED, (message) =>
|
||||
this.processIncomingMessage(message).then(handler),
|
||||
);
|
||||
} else if (event === WAHAEvents.MESSAGE_ANY) {
|
||||
this.whatsapp.on(Events.MESSAGE_CREATE, (message) =>
|
||||
this.processIncomingMessage(message).then(handler),
|
||||
);
|
||||
} else if (event === WAHAEvents.STATE_CHANGE) {
|
||||
this.whatsapp.on(Events.STATE_CHANGED, handler);
|
||||
} else if (event === WAHAEvents.MESSAGE_ACK) {
|
||||
// We do not download media here
|
||||
this.whatsapp.on(Events.MESSAGE_ACK, (message) =>
|
||||
this.toWAMessage(message).then(handler),
|
||||
);
|
||||
} else if (event === WAHAEvents.GROUP_JOIN) {
|
||||
this.whatsapp.on(Events.GROUP_JOIN, handler);
|
||||
} else if (event === WAHAEvents.GROUP_LEAVE) {
|
||||
this.whatsapp.on(Events.GROUP_LEAVE, handler);
|
||||
} else {
|
||||
throw new NotImplementedByEngineError(
|
||||
`Engine does not support webhook event: ${event}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
protected toWAContact(contact: Contact) {
|
||||
// @ts-ignore
|
||||
contact.id = contact.id._serialized
|
||||
return contact
|
||||
private async processIncomingMessage(message: Message, downloadMedia = true) {
|
||||
if (downloadMedia) {
|
||||
try {
|
||||
message = await this.downloadMedia(message);
|
||||
} catch (e) {
|
||||
this.log.error('Failed when tried to download media for a message');
|
||||
this.log.error(e, e.stack);
|
||||
}
|
||||
}
|
||||
return await this.toWAMessage(message);
|
||||
}
|
||||
|
||||
protected toWAMessage(message: Message): Promise<WAMessage> {
|
||||
// @ts-ignore
|
||||
return Promise.resolve({
|
||||
id: message.id._serialized,
|
||||
timestamp: message.timestamp,
|
||||
from: message.from,
|
||||
fromMe: message.fromMe,
|
||||
to: message.to,
|
||||
body: message.body,
|
||||
// @ts-ignore
|
||||
hasMedia: Boolean(message.mediaUrl),
|
||||
// @ts-ignore
|
||||
mediaUrl: message.mediaUrl,
|
||||
ack: message.ack,
|
||||
ackName: WAMessageAck[message.ack] || ACK_UNKNOWN,
|
||||
location: message.location,
|
||||
vCards: message.vCards,
|
||||
_data: message.rawData,
|
||||
});
|
||||
}
|
||||
|
||||
protected async downloadMedia(message: Message) {
|
||||
if (!message.hasMedia) {
|
||||
return message
|
||||
}
|
||||
protected toWAContact(contact: Contact) {
|
||||
// @ts-ignore
|
||||
contact.id = contact.id._serialized;
|
||||
return contact;
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
message.mediaUrl = await this.storage.save(message.id._serialized, "", undefined)
|
||||
return message
|
||||
protected async downloadMedia(message: Message) {
|
||||
if (!message.hasMedia) {
|
||||
return message;
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
message.mediaUrl = await this.storage.save(
|
||||
message.id._serialized,
|
||||
'',
|
||||
undefined,
|
||||
);
|
||||
return message;
|
||||
}
|
||||
}
|
||||
|
||||
+107
-4
@@ -1,8 +1,111 @@
|
||||
import {DOCS_URL} from "./exceptions";
|
||||
import {MediaStorage} from "./abc/storage.abc";
|
||||
import * as crypto from 'crypto';
|
||||
import * as fs from 'fs/promises';
|
||||
import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
|
||||
import { SessionConfig } from '../structures/sessions.dto';
|
||||
import {
|
||||
LocalSessionStorage,
|
||||
MediaStorage,
|
||||
SessionConfigRepository,
|
||||
} from './abc/storage.abc';
|
||||
import { DOCS_URL } from './exceptions';
|
||||
|
||||
export class MediaStorageCore implements MediaStorage {
|
||||
async save(messageId: string, mimetype: string, buffer: Buffer): Promise<string>{
|
||||
return Promise.resolve(`Media attachment's available only in WAHA Plus version. ${DOCS_URL}`)
|
||||
async save(
|
||||
messageId: string,
|
||||
mimetype: string,
|
||||
buffer: Buffer,
|
||||
): Promise<string> {
|
||||
return Promise.resolve(
|
||||
`Media attachment's available only in WAHA Plus version. ${DOCS_URL}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class LocalSessionConfigRepository extends SessionConfigRepository {
|
||||
FILENAME = '.waha.session.config.json';
|
||||
|
||||
constructor(private storage: LocalSessionStorage) {
|
||||
super();
|
||||
}
|
||||
|
||||
async get(sessionName: string): Promise<SessionConfig> {
|
||||
const filepath = this.getFilePath(sessionName);
|
||||
// Check file exists
|
||||
try {
|
||||
await fs.access(filepath, fs.constants.F_OK);
|
||||
} catch (error) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Try to load config
|
||||
let content;
|
||||
try {
|
||||
content = await fs.readFile(filepath, 'utf-8');
|
||||
} catch (error) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return JSON.parse(content);
|
||||
}
|
||||
|
||||
async save(sessionName: string, config: SessionConfig) {
|
||||
// Create folder if not exist
|
||||
const folder = this.storage.getFolderPath(sessionName);
|
||||
await fs.mkdir(folder, { recursive: true });
|
||||
// Save config
|
||||
const filepath = this.getFilePath(sessionName);
|
||||
const content = JSON.stringify(config || null);
|
||||
await fs.writeFile(filepath, content);
|
||||
}
|
||||
|
||||
private getFilePath(sessionName): string {
|
||||
const folder = this.storage.getFolderPath(sessionName);
|
||||
return path.join(folder, this.FILENAME);
|
||||
}
|
||||
}
|
||||
|
||||
export class SessionStorageCore extends LocalSessionStorage {
|
||||
public sessionsFolder: string;
|
||||
|
||||
constructor(engine: string) {
|
||||
super(engine);
|
||||
this.sessionsFolder = path.join(os.tmpdir(), 'waha-sessions');
|
||||
}
|
||||
|
||||
get engineFolder() {
|
||||
return path.join(this.sessionsFolder, this.engine);
|
||||
}
|
||||
|
||||
async init(sessionName?: string) {
|
||||
const folder = sessionName
|
||||
? this.getFolderPath(sessionName)
|
||||
: this.engineFolder;
|
||||
await fs.mkdir(folder, { recursive: true });
|
||||
}
|
||||
|
||||
getFolderPath(name: string): string {
|
||||
const suffix = crypto.createHash('md5').update(name).digest('hex');
|
||||
return path.join(this.engineFolder, `${name}-${suffix}`);
|
||||
}
|
||||
|
||||
async clean(sessionName: string) {
|
||||
const folder = this.getFolderPath(sessionName);
|
||||
await fs.rm(folder, { recursive: true });
|
||||
}
|
||||
|
||||
async getAll(): Promise<string[]> {
|
||||
await this.init();
|
||||
const content = await fs.readdir(this.engineFolder, {
|
||||
withFileTypes: true,
|
||||
});
|
||||
return content
|
||||
.filter((dirent) => dirent.isDirectory())
|
||||
.map((dirent) => dirent.name);
|
||||
}
|
||||
|
||||
get configRepository(): LocalSessionConfigRepository {
|
||||
return new LocalSessionConfigRepository(this);
|
||||
}
|
||||
}
|
||||
@@ -1,40 +1,76 @@
|
||||
import {DocumentBuilder, SwaggerModule} from "@nestjs/swagger";
|
||||
import {INestApplication} from "@nestjs/common";
|
||||
import {VERSION} from "../version";
|
||||
import { INestApplication } from '@nestjs/common';
|
||||
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
|
||||
|
||||
import { WhatsappConfigService } from '../config.service';
|
||||
import { VERSION } from '../version';
|
||||
|
||||
export class SwaggerModuleCore {
|
||||
configure(app: INestApplication) {
|
||||
this.setUpAuth(app)
|
||||
configure(app: INestApplication) {
|
||||
this.setUpAuth(app);
|
||||
const builder = new DocumentBuilder();
|
||||
|
||||
const options = new DocumentBuilder()
|
||||
.setTitle('WAHA - WhatsApp HTTP API')
|
||||
.setDescription('WhatsApp HTTP API that you can configure in a click!')
|
||||
.setExternalDoc("Documentation", "https://waha.devlike.pro/")
|
||||
.setVersion(VERSION.version)
|
||||
.addTag('sessions', 'Control WhatsApp sessions')
|
||||
.addTag('screenshot', 'Get screenshot of WhatsApp and show QR code')
|
||||
.addTag('chatting', 'Chat methods')
|
||||
.addTag(
|
||||
'contacts',
|
||||
`Contacts methods.<br>
|
||||
builder
|
||||
.setTitle('WAHA - WhatsApp HTTP API')
|
||||
.setDescription('WhatsApp HTTP API that you can run in a click!')
|
||||
.setExternalDoc('Documentation', 'https://waha.devlike.pro/')
|
||||
.setVersion(VERSION.version)
|
||||
.addTag('sessions', 'Control WhatsApp sessions')
|
||||
.addTag('auth', 'Authentication')
|
||||
.addTag('screenshot', 'Get screenshot of WhatsApp and show QR code')
|
||||
.addTag('chatting', 'Chatting methods')
|
||||
.addTag(
|
||||
'status',
|
||||
'Status (aka stories) methods. <b>NOWEB</b> engine only!',
|
||||
)
|
||||
.addTag('chats', `Chats methods`)
|
||||
.addTag(
|
||||
'contacts',
|
||||
`Contacts methods.<br>
|
||||
Use phone number (without +) or phone number and \`@c.us\` at the end as \`contactId\`.<br>
|
||||
'E.g: \`12312312310\` OR \`12312312310@c.us\`<br>`
|
||||
)
|
||||
.addTag('other', 'Other methods')
|
||||
.addApiKey({
|
||||
type: 'apiKey',
|
||||
description: 'Your secret api key',
|
||||
name: 'X-Api-Key'
|
||||
}
|
||||
)
|
||||
.build();
|
||||
|
||||
const document = SwaggerModule.createDocument(app, options);
|
||||
SwaggerModule.setup('', app, document);
|
||||
'E.g: \`12312312310\` OR \`12312312310@c.us\`<br>`,
|
||||
)
|
||||
.addTag('groups', `Groups methods.<br>`)
|
||||
.addTag('presence', `Presence information`)
|
||||
.addTag('other', 'Other methods')
|
||||
.addApiKey({
|
||||
type: 'apiKey',
|
||||
description: 'Your secret api key',
|
||||
name: 'X-Api-Key',
|
||||
});
|
||||
|
||||
const config = app.get(WhatsappConfigService);
|
||||
if (config.getSwaggerAdvancedConfigEnabled()) {
|
||||
builder.addServer('{protocol}://{host}:{port}/{baseUrl}', '', {
|
||||
protocol: {
|
||||
default: 'http',
|
||||
enum: ['http', 'https'],
|
||||
description: 'The protocol used to access the server.',
|
||||
},
|
||||
host: {
|
||||
default: config.hostname,
|
||||
description: 'The hostname or IP address of the server.',
|
||||
},
|
||||
port: {
|
||||
default: config.port,
|
||||
description:
|
||||
'The port number on which the server is listening for requests',
|
||||
},
|
||||
baseUrl: {
|
||||
default: '',
|
||||
description:
|
||||
'The base URL path for all API endpoints. This can be used to group related endpoints together under a common path.',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
protected setUpAuth(app: INestApplication) {
|
||||
return undefined
|
||||
}
|
||||
const options = builder.build();
|
||||
const document = SwaggerModule.createDocument(app, options);
|
||||
SwaggerModule.setup('', app, document, {
|
||||
customSiteTitle: 'WAHA - WhatsApp HTTP API',
|
||||
});
|
||||
}
|
||||
|
||||
protected setUpAuth(app: INestApplication) {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
+97
-66
@@ -1,76 +1,107 @@
|
||||
import {WhatsappSession} from "./abc/session.abc";
|
||||
import {ConsoleLogger} from "@nestjs/common";
|
||||
import {WAEvents} from "../structures/enums.dto";
|
||||
import {WAWebhook} from "../structures/responses.dto";
|
||||
import {NotImplementedByEngineError} from "./exceptions";
|
||||
import {WebhookConductor} from "./abc/webhooks.abc";
|
||||
import request = require('request');
|
||||
import { ConsoleLogger } from '@nestjs/common';
|
||||
|
||||
import { WAHAEvents } from '../structures/enums.dto';
|
||||
import { WebhookConfig } from '../structures/webhooks.config.dto';
|
||||
import { WhatsappSession } from './abc/session.abc';
|
||||
import { WebhookConductor, WebhookSender } from './abc/webhooks.abc';
|
||||
import { NotImplementedByEngineError } from './exceptions';
|
||||
import request = require('request');
|
||||
import { WAWebhook } from '../structures/webhooks.dto';
|
||||
|
||||
export class WebhookSenderCore extends WebhookSender {
|
||||
send(json) {
|
||||
this.log.log(`Sending POST to ${this.url}...`);
|
||||
this.log.debug(`POST DATA: ${JSON.stringify(json)}`);
|
||||
|
||||
request.post(this.url, { json: json }, (error, res, body) => {
|
||||
if (error) {
|
||||
this.log.error(error);
|
||||
return;
|
||||
}
|
||||
this.log.log(`POST request was sent with status code: ${res.statusCode}`);
|
||||
this.log.verbose(`Response: ${JSON.stringify(body)}`);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class WebhookConductorCore implements WebhookConductor {
|
||||
constructor(protected log: ConsoleLogger, private readonly url, private readonly events: WAEvents[] | string[]) {
|
||||
this.url = url
|
||||
this.events = this.getSuitableEvents(events)
|
||||
this.log = log
|
||||
constructor(protected log: ConsoleLogger) {
|
||||
this.log = log;
|
||||
}
|
||||
|
||||
protected buildSender(webhookConfig: WebhookConfig): WebhookSender {
|
||||
return new WebhookSenderCore(this.log, webhookConfig);
|
||||
}
|
||||
|
||||
private getSuitableEvents(events: WAHAEvents[] | string[]): WAHAEvents[] {
|
||||
const allEvents = Object.values(WAHAEvents);
|
||||
|
||||
// Enable all events if * in the events
|
||||
// @ts-ignore
|
||||
if (events.includes('*')) {
|
||||
return allEvents;
|
||||
}
|
||||
|
||||
private getSuitableEvents(events: WAEvents[] | string[]) {
|
||||
const allEvents = Object.values(WAEvents)
|
||||
// Get only known events, log and ignore others
|
||||
const rightEvents = [];
|
||||
for (const event of events) {
|
||||
// @ts-ignore
|
||||
if (!allEvents.includes(event)) {
|
||||
this.log.error(`Unknown event for webhook: '${event}'`);
|
||||
continue;
|
||||
}
|
||||
rightEvents.push(event);
|
||||
}
|
||||
return rightEvents;
|
||||
}
|
||||
|
||||
// Enable all events if * in the events
|
||||
// @ts-ignore
|
||||
if (events.includes("*")) {
|
||||
return allEvents
|
||||
public configure(session: WhatsappSession, webhooks: WebhookConfig[]) {
|
||||
for (const webhookConfig of webhooks) {
|
||||
this.configureSingleWebhook(session, webhookConfig);
|
||||
}
|
||||
}
|
||||
|
||||
private configureSingleWebhook(
|
||||
session: WhatsappSession,
|
||||
webhook: WebhookConfig,
|
||||
) {
|
||||
if (!webhook || !webhook.url || webhook.events.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const url = webhook.url;
|
||||
this.log.log(`Configuring webhooks for ${url}...`);
|
||||
const events = this.getSuitableEvents(webhook.events);
|
||||
const sender = this.buildSender(webhook);
|
||||
for (const event of events) {
|
||||
try {
|
||||
session.subscribe(event, (data: any) =>
|
||||
this.callWebhook(event, session, data, sender),
|
||||
);
|
||||
} catch (error) {
|
||||
if (error instanceof NotImplementedByEngineError) {
|
||||
this.log.error(error);
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
|
||||
// Get only known events, log and ignore others
|
||||
const rightEvents = []
|
||||
for (const event of events) {
|
||||
// @ts-ignore
|
||||
if (!allEvents.includes(event)) {
|
||||
this.log.error(`Unknown event for webhook: '${event}'`)
|
||||
continue
|
||||
}
|
||||
rightEvents.push(event)
|
||||
}
|
||||
return rightEvents
|
||||
}
|
||||
this.log.log(`Event '${event}' is enabled for url: ${url}`);
|
||||
}
|
||||
this.log.log(`Webhooks were configured for ${url}.`);
|
||||
}
|
||||
|
||||
public configure(session: WhatsappSession) {
|
||||
this.log.log('Configuring webhooks...')
|
||||
for (const event of this.events) {
|
||||
try {
|
||||
session.subscribe(event, (data: any) => this.callWebhook(event, data, this.url))
|
||||
} catch (error) {
|
||||
if (error instanceof NotImplementedByEngineError) {
|
||||
this.log.error(error)
|
||||
} else {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
this.log.log(`Event '${event}' is enabled for url: ${this.url}`)
|
||||
}
|
||||
this.log.log('Webhooks were configured.')
|
||||
}
|
||||
|
||||
public callWebhook(event, data: any, url) {
|
||||
const json: WAWebhook = {event: event, payload: data}
|
||||
this.log.log(`Sending POST to ${url}...`)
|
||||
this.log.debug(`POST DATA: ${JSON.stringify(json)}`)
|
||||
this.post(json, url)
|
||||
}
|
||||
|
||||
protected post(json, url) {
|
||||
request.post(
|
||||
url,
|
||||
{json: json},
|
||||
(error, res, body) => {
|
||||
if (error) {
|
||||
this.log.error(error)
|
||||
return
|
||||
}
|
||||
this.log.log(`POST request was sent with status code: ${res.statusCode}`)
|
||||
this.log.verbose(`Response: ${JSON.stringify(body)}`)
|
||||
})
|
||||
}
|
||||
public callWebhook(
|
||||
event,
|
||||
session: WhatsappSession,
|
||||
data: any,
|
||||
sender: WebhookSender,
|
||||
) {
|
||||
const json: WAWebhook = {
|
||||
event: event,
|
||||
session: session.name,
|
||||
payload: data,
|
||||
engine: session.engine,
|
||||
};
|
||||
sender.send(json);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
function parseBool(value: any): boolean {
|
||||
if (!value) {
|
||||
return false;
|
||||
}
|
||||
if (value.toLowerCase() === 'true' || value.toLowerCase() === '1') {
|
||||
return true;
|
||||
}
|
||||
if (value.toLowerCase() === 'false' || value.toLowerCase() === '0') {
|
||||
return false;
|
||||
}
|
||||
|
||||
console.error(
|
||||
'parseBool got an unexpected value:',
|
||||
value,
|
||||
'(accepted values : "true", "false")',
|
||||
);
|
||||
throw new Error(
|
||||
'Error: parseBool got unexpected value - use "true" or "false" values',
|
||||
);
|
||||
}
|
||||
|
||||
function flipObject(object) {
|
||||
return Object.fromEntries(
|
||||
Object.entries(object).map(([key, value]) => [value, key]),
|
||||
);
|
||||
}
|
||||
|
||||
function splitAt(str: string, index) {
|
||||
const fst = [...str];
|
||||
const snd = fst.splice(index);
|
||||
return [fst.join(''), snd.join('')];
|
||||
}
|
||||
|
||||
export { flipObject, parseBool, splitAt };
|
||||
+43
-33
@@ -1,44 +1,54 @@
|
||||
import {NestFactory} from '@nestjs/core';
|
||||
import {WhatsappConfigService} from "./config.service";
|
||||
import {AllExceptionsFilter} from "./api/exception.filter";
|
||||
import {getWAHAVersion, VERSION, WAHAVersion} from "./version";
|
||||
import {AppModuleCore} from "./core/app.module.core";
|
||||
import {SwaggerModuleCore} from "./core/swagger.module.core";
|
||||
import { NestFactory } from '@nestjs/core';
|
||||
import { json, urlencoded } from 'express';
|
||||
|
||||
async function loadModules(): Promise<[typeof AppModuleCore, typeof SwaggerModuleCore]> {
|
||||
const version = getWAHAVersion()
|
||||
console.log(`WAHA (WhatsApp HTTP API) - Running ${version} version...`)
|
||||
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, WAHAVersion } from './version';
|
||||
|
||||
if (version === WAHAVersion.CORE) {
|
||||
const {AppModuleCore} = await import("./core/app.module.core")
|
||||
const {SwaggerModuleCore} = await import("./core/swagger.module.core")
|
||||
return [AppModuleCore, SwaggerModuleCore]
|
||||
}
|
||||
// Ignore if it's core version - there's no plus module
|
||||
// @ts-ignore
|
||||
const {AppModulePlus} = await import("./plus/app.module.plus")
|
||||
// @ts-ignore
|
||||
const {SwaggerModulePlus} = await import("./plus/swagger.module.plus")
|
||||
return [AppModulePlus, SwaggerModulePlus]
|
||||
async function loadModules(): Promise<
|
||||
[typeof AppModuleCore, typeof SwaggerModuleCore]
|
||||
> {
|
||||
const version = getWAHAVersion();
|
||||
console.log(`WAHA (WhatsApp HTTP API) - Running ${version} version...`);
|
||||
|
||||
if (version === WAHAVersion.CORE) {
|
||||
const { AppModuleCore } = await import('./core/app.module.core');
|
||||
const { SwaggerModuleCore } = await import('./core/swagger.module.core');
|
||||
return [AppModuleCore, SwaggerModuleCore];
|
||||
}
|
||||
// Ignore if it's core version - there's no plus module
|
||||
// @ts-ignore
|
||||
const { AppModulePlus } = await import('./plus/app.module.plus');
|
||||
// @ts-ignore
|
||||
const { SwaggerModulePlus } = await import('./plus/swagger.module.plus');
|
||||
return [AppModulePlus, SwaggerModulePlus];
|
||||
}
|
||||
|
||||
async function bootstrap() {
|
||||
const [AppModule, SwaggerModule] = await loadModules()
|
||||
const app = await NestFactory.create(AppModule, {
|
||||
logger: process.env.DEBUG != undefined ? ['log', 'debug', 'error', 'verbose', 'warn'] :
|
||||
['log', 'error', 'warn'],
|
||||
});
|
||||
const [AppModule, SwaggerModule] = await loadModules();
|
||||
const app = await NestFactory.create(AppModule, {
|
||||
logger:
|
||||
process.env.DEBUG != undefined
|
||||
? ['log', 'debug', 'error', 'verbose', 'warn']
|
||||
: ['log', 'error', 'warn'],
|
||||
});
|
||||
|
||||
app.enableShutdownHooks();
|
||||
app.useGlobalFilters(new AllExceptionsFilter());
|
||||
app.enableShutdownHooks();
|
||||
app.useGlobalFilters(new AllExceptionsFilter());
|
||||
|
||||
// Configure swagger
|
||||
const swagger = new SwaggerModule()
|
||||
swagger.configure(app)
|
||||
// Allow to send big body - for images and attachments
|
||||
app.use(json({ limit: '50mb' }));
|
||||
app.use(urlencoded({ limit: '50mb', extended: false }));
|
||||
|
||||
const config = app.get(WhatsappConfigService);
|
||||
await app.listen(config.port);
|
||||
console.log(`WhatsApp HTTP API is running on: ${await app.getUrl()}`);
|
||||
// Configure swagger
|
||||
const swagger = new SwaggerModule();
|
||||
swagger.configure(app);
|
||||
|
||||
const config = app.get(WhatsappConfigService);
|
||||
await app.listen(config.port);
|
||||
console.log(`WhatsApp HTTP API is running on: ${await app.getUrl()}`);
|
||||
}
|
||||
|
||||
bootstrap();
|
||||
@@ -0,0 +1,24 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
|
||||
export class RequestCodeRequest {
|
||||
@ApiProperty({
|
||||
description: 'Mobile phone number in international format',
|
||||
example: '12132132130',
|
||||
})
|
||||
phoneNumber: string;
|
||||
@ApiProperty({
|
||||
description:
|
||||
'How would you like to receive the one time code for registration? |sms|voice. Leave empty for Web pairing.',
|
||||
example: null,
|
||||
required: false,
|
||||
})
|
||||
method: string;
|
||||
}
|
||||
|
||||
export class OTPRequest {
|
||||
code: string;
|
||||
}
|
||||
|
||||
export class PairingCodeResponse {
|
||||
code: string;
|
||||
}
|
||||
@@ -1,13 +1,13 @@
|
||||
import {IsNotEmpty} from "class-validator";
|
||||
import { IsNotEmpty } from 'class-validator';
|
||||
|
||||
export const WHATSAPP_DEFAULT_SESSION_NAME = "default"
|
||||
export const WHATSAPP_DEFAULT_SESSION_NAME = 'default';
|
||||
|
||||
/**
|
||||
* Queries
|
||||
*/
|
||||
export class SessionQuery {
|
||||
@IsNotEmpty()
|
||||
session: string = WHATSAPP_DEFAULT_SESSION_NAME;
|
||||
@IsNotEmpty()
|
||||
session: string = WHATSAPP_DEFAULT_SESSION_NAME;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -15,5 +15,5 @@ export class SessionQuery {
|
||||
*/
|
||||
|
||||
export class SessionBaseRequest {
|
||||
session = WHATSAPP_DEFAULT_SESSION_NAME
|
||||
session = WHATSAPP_DEFAULT_SESSION_NAME;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
|
||||
import { SessionBaseRequest, SessionQuery } from './base.dto';
|
||||
|
||||
/**
|
||||
* Queries
|
||||
*/
|
||||
|
||||
export class GetChatMessagesQuery extends SessionQuery {
|
||||
limit: number = 100;
|
||||
@ApiProperty({
|
||||
example: true,
|
||||
required: false,
|
||||
description: 'Download media for messages',
|
||||
})
|
||||
downloadMedia: boolean = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests
|
||||
*/
|
||||
+139
-132
@@ -1,188 +1,195 @@
|
||||
import {IsNotEmpty, IsString} from "class-validator";
|
||||
import {ApiExtraModels, ApiProperty, getSchemaPath} from "@nestjs/swagger";
|
||||
import {SessionBaseRequest, SessionQuery} from "./base.dto";
|
||||
import { ApiExtraModels, ApiProperty, getSchemaPath } from '@nestjs/swagger';
|
||||
import { IsNumber, IsString } from 'class-validator';
|
||||
|
||||
import { SessionBaseRequest, SessionQuery } from './base.dto';
|
||||
import {
|
||||
BinaryFile,
|
||||
RemoteFile,
|
||||
VoiceBinaryFile,
|
||||
VoiceRemoteFile,
|
||||
} from './files.dto';
|
||||
import { ChatIdProperty } from './properties.dto';
|
||||
|
||||
/**
|
||||
* Queries
|
||||
*/
|
||||
export class CheckNumberStatusQuery extends SessionQuery {
|
||||
@IsString()
|
||||
phone: string
|
||||
@IsString()
|
||||
phone: string;
|
||||
}
|
||||
|
||||
export class MessageTextQuery extends SessionQuery {
|
||||
@IsString()
|
||||
phone: string
|
||||
@IsString()
|
||||
text: string
|
||||
@IsString()
|
||||
phone: string;
|
||||
@IsString()
|
||||
text: string;
|
||||
}
|
||||
|
||||
export class ChatQuery extends SessionQuery {
|
||||
@ChatIdProperty()
|
||||
chatId: string;
|
||||
}
|
||||
|
||||
export class GetMessageQuery extends ChatQuery {
|
||||
@IsNumber()
|
||||
limit: number;
|
||||
@ApiProperty({
|
||||
example: true,
|
||||
required: false,
|
||||
description: 'Download media for messages',
|
||||
})
|
||||
downloadMedia: true;
|
||||
}
|
||||
|
||||
export class GetPresenceQuery extends ChatQuery {}
|
||||
|
||||
/**
|
||||
* Requests
|
||||
*/
|
||||
export class ChatRequest extends SessionBaseRequest {
|
||||
@ApiProperty({
|
||||
example: '11111111111@c.us',
|
||||
})
|
||||
chatId: string
|
||||
@ChatIdProperty()
|
||||
chatId: string;
|
||||
}
|
||||
|
||||
export class MessageRequest extends SessionBaseRequest{
|
||||
@ApiProperty({
|
||||
example: "false_11111111111@c.us_AAAAAAAAAAAAAAAAAAAA",
|
||||
})
|
||||
messageId: string
|
||||
export class SendSeenRequest extends ChatRequest {
|
||||
@ApiProperty({
|
||||
example: 'false_11111111111@c.us_AAAAAAAAAAAAAAAAAAAA',
|
||||
required: false,
|
||||
description:
|
||||
"NOWEB engine only - it's important to mark ALL messages as seen",
|
||||
})
|
||||
messageId?: string;
|
||||
|
||||
@ApiProperty({
|
||||
example: '11111111111@c.us',
|
||||
required: false,
|
||||
description:
|
||||
'NOWEB engine only - the ID of the user that sent the message (undefined for individual chats)',
|
||||
})
|
||||
participant?: string;
|
||||
}
|
||||
|
||||
export class MessageRequest extends SessionBaseRequest {
|
||||
@ApiProperty({
|
||||
example: 'false_11111111111@c.us_AAAAAAAAAAAAAAAAAAAA',
|
||||
})
|
||||
messageId: string;
|
||||
}
|
||||
|
||||
export class MessageContactVcardRequest extends ChatRequest {
|
||||
contactsId: string;
|
||||
name: string
|
||||
contactsId: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export class MessageTextRequest extends ChatRequest {
|
||||
text = "Hi there!"
|
||||
text = 'Hi there!';
|
||||
@ApiProperty({
|
||||
description:
|
||||
'Mention contact in the message. ' +
|
||||
"The message MUST contain '@123456789' text in order to work with 'mentions' field.",
|
||||
example: ['123456789@c.us'],
|
||||
})
|
||||
mentions?: string[];
|
||||
}
|
||||
|
||||
export class Button {
|
||||
id: string
|
||||
body: string
|
||||
id: string;
|
||||
text: string;
|
||||
}
|
||||
|
||||
export class MessageTextButtonsRequest extends ChatRequest {
|
||||
title: string;
|
||||
text: string;
|
||||
buttons?: Array<Button>;
|
||||
title: string;
|
||||
footer?: string;
|
||||
buttons?: Array<Button>;
|
||||
}
|
||||
|
||||
export class MessageReplyRequest extends ChatRequest {
|
||||
text = "Reply text"
|
||||
@ApiProperty({
|
||||
example: 'false_11111111111@c.us_AAAAAAAAAAAAAAAAAAAA',
|
||||
})
|
||||
reply_to: string;
|
||||
export class MessageReplyRequest extends MessageTextRequest {
|
||||
text = 'Reply text';
|
||||
@ApiProperty({
|
||||
example: 'false_11111111111@c.us_AAAAAAAAAAAAAAAAAAAA',
|
||||
})
|
||||
reply_to: string;
|
||||
}
|
||||
|
||||
export class MessageLocationRequest extends ChatRequest {
|
||||
latitude: string;
|
||||
longitude: string;
|
||||
title: string;
|
||||
}
|
||||
|
||||
export class RemoteFile {
|
||||
@ApiProperty({
|
||||
description: "MIME type of the attachment.",
|
||||
example: 'image/jpeg',
|
||||
})
|
||||
mimetype: string
|
||||
|
||||
@ApiProperty({
|
||||
description: "Document file name. Value can be null",
|
||||
example: 'filename.jpg',
|
||||
})
|
||||
filename?: string | null
|
||||
|
||||
@ApiProperty({
|
||||
example: 'https://github.com/devlikeapro/whatsapp-http-api/raw/core/examples/dev.likeapro.jpg',
|
||||
})
|
||||
url: string
|
||||
}
|
||||
|
||||
export class BinaryFile {
|
||||
@ApiProperty({
|
||||
description: "MIME type of the attachment.",
|
||||
example: 'image/jpeg',
|
||||
})
|
||||
mimetype: string
|
||||
|
||||
@ApiProperty({
|
||||
description: "Document file name. Optional",
|
||||
example: 'filename.jpeg',
|
||||
})
|
||||
filename?: string | null
|
||||
|
||||
@ApiProperty({
|
||||
description: "Base64-encoded data of the file",
|
||||
example: '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',
|
||||
})
|
||||
data: string
|
||||
latitude: number;
|
||||
longitude: number;
|
||||
title: string;
|
||||
}
|
||||
|
||||
@ApiExtraModels(BinaryFile, RemoteFile)
|
||||
class FileRequest extends ChatRequest {
|
||||
@ApiProperty({
|
||||
oneOf: [
|
||||
{$ref: getSchemaPath(BinaryFile)},
|
||||
{$ref: getSchemaPath(RemoteFile)},
|
||||
],
|
||||
})
|
||||
file: BinaryFile | RemoteFile
|
||||
@ApiProperty({
|
||||
oneOf: [
|
||||
{ $ref: getSchemaPath(BinaryFile) },
|
||||
{ $ref: getSchemaPath(RemoteFile) },
|
||||
],
|
||||
})
|
||||
file: BinaryFile | RemoteFile;
|
||||
}
|
||||
|
||||
export class MessageImageRequest extends FileRequest {
|
||||
caption: string;
|
||||
caption: string;
|
||||
}
|
||||
|
||||
export class MessageFileRequest extends FileRequest {
|
||||
}
|
||||
|
||||
export class VoiceBinaryFile {
|
||||
@ApiProperty({
|
||||
description: "MIME type of the attachment.",
|
||||
})
|
||||
mimetype = "audio/ogg; codecs=opus"
|
||||
|
||||
@ApiProperty({
|
||||
description: "Document file name. Optional",
|
||||
})
|
||||
filename = "voice-message.mp3"
|
||||
|
||||
@ApiProperty({
|
||||
description: "Base64-encoded data of the file",
|
||||
example: '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
Line truncated
|
||||
})
|
||||
data: string
|
||||
}
|
||||
|
||||
export class VoiceRemoteFile {
|
||||
@ApiProperty({
|
||||
description: "MIME type of the attachment.",
|
||||
})
|
||||
mimetype = "audio/ogg; codecs=opus"
|
||||
|
||||
@ApiProperty({
|
||||
description: "Document file name. Optional",
|
||||
})
|
||||
filename = "voice-message.mp3"
|
||||
|
||||
@ApiProperty({
|
||||
example: 'https://github.com/devlikeapro/whatsapp-http-api/raw/core/examples/dev.likeapro.mp3',
|
||||
})
|
||||
url: string
|
||||
|
||||
caption: string;
|
||||
}
|
||||
|
||||
@ApiExtraModels(VoiceBinaryFile, VoiceRemoteFile)
|
||||
export class MessageVoiceRequest extends ChatRequest {
|
||||
@ApiProperty({
|
||||
oneOf: [
|
||||
{$ref: getSchemaPath(VoiceBinaryFile)},
|
||||
{$ref: getSchemaPath(VoiceRemoteFile)},
|
||||
],
|
||||
})
|
||||
file: VoiceBinaryFile | VoiceRemoteFile
|
||||
@ApiProperty({
|
||||
oneOf: [
|
||||
{ $ref: getSchemaPath(VoiceBinaryFile) },
|
||||
{ $ref: getSchemaPath(VoiceRemoteFile) },
|
||||
],
|
||||
})
|
||||
file: VoiceBinaryFile | VoiceRemoteFile;
|
||||
}
|
||||
|
||||
export class MessageLinkPreviewRequest extends ChatRequest {
|
||||
url: string;
|
||||
title: string;
|
||||
url: string;
|
||||
title: string;
|
||||
}
|
||||
|
||||
export class MessageReactionRequest extends MessageRequest {
|
||||
@ApiProperty({
|
||||
description: "Emoji to react with. Send an empty string to remove the reaction",
|
||||
example: '👍',
|
||||
})
|
||||
reaction: string
|
||||
@ApiProperty({
|
||||
description:
|
||||
'Emoji to react with. Send an empty string to remove the reaction',
|
||||
example: '👍',
|
||||
})
|
||||
reaction: string;
|
||||
}
|
||||
|
||||
export class WANumberExistResult {
|
||||
numberExists: boolean;
|
||||
}
|
||||
|
||||
export class MessagePoll {
|
||||
@ApiProperty({
|
||||
example: 'How are you?',
|
||||
})
|
||||
name: string;
|
||||
|
||||
@ApiProperty({
|
||||
example: ['Awesome!', 'Good!', 'Not bad!'],
|
||||
})
|
||||
options: string[];
|
||||
|
||||
multipleAnswers = false;
|
||||
}
|
||||
|
||||
export class MessagePollRequest extends ChatRequest {
|
||||
poll: MessagePoll;
|
||||
}
|
||||
|
||||
export class MessageDestination {
|
||||
@ApiProperty({
|
||||
description: 'Message ID',
|
||||
example: 'false_11111111111@c.us_AAAAAAAAAAAAAAAAAAAA',
|
||||
})
|
||||
id: string;
|
||||
to: string;
|
||||
from: string;
|
||||
fromMe: boolean;
|
||||
}
|
||||
@@ -1,22 +1,21 @@
|
||||
import {IsString} from "class-validator";
|
||||
import {SessionBaseRequest, SessionQuery, WHATSAPP_DEFAULT_SESSION_NAME} from "./base.dto";
|
||||
import { IsString } from 'class-validator';
|
||||
|
||||
import { SessionBaseRequest, SessionQuery } from './base.dto';
|
||||
|
||||
/**
|
||||
* Queries
|
||||
*/
|
||||
|
||||
export class ContactQuery extends SessionQuery {
|
||||
@IsString()
|
||||
contactId: string
|
||||
@IsString()
|
||||
contactId: string;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Requests
|
||||
*/
|
||||
|
||||
export class ContactRequest extends SessionBaseRequest {
|
||||
@IsString()
|
||||
contactId: string
|
||||
@IsString()
|
||||
contactId: string;
|
||||
}
|
||||
+39
-15
@@ -1,22 +1,46 @@
|
||||
export const SECOND = 1000;
|
||||
|
||||
export enum WAEvents {
|
||||
MESSAGE = "message",
|
||||
MESSAGE_ANY = "message.any",
|
||||
MESSAGE_ACK = "message.ack",
|
||||
STATE_CHANGE = "state.change",
|
||||
GROUP_JOIN = "group.join",
|
||||
GROUP_LEAVE = "group.leave",
|
||||
export enum WAHAEvents {
|
||||
MESSAGE = 'message',
|
||||
MESSAGE_ANY = 'message.any',
|
||||
MESSAGE_ACK = 'message.ack',
|
||||
STATE_CHANGE = 'state.change',
|
||||
GROUP_JOIN = 'group.join',
|
||||
GROUP_LEAVE = 'group.leave',
|
||||
PRESENCE_UPDATE = 'presence.update',
|
||||
POLL_VOTE = 'poll.vote',
|
||||
POLL_VOTE_FAILED = 'poll.vote.failed',
|
||||
}
|
||||
|
||||
export enum WhatsappStatus {
|
||||
STARTING = "STARTING",
|
||||
SCAN_QR_CODE = "SCAN_QR_CODE",
|
||||
WORKING = "WORKING",
|
||||
FAILED = "FAILED",
|
||||
export enum WAHASessionStatus {
|
||||
STOPPED = 'STOPPED',
|
||||
STARTING = 'STARTING',
|
||||
SCAN_QR_CODE = 'SCAN_QR_CODE',
|
||||
WORKING = 'WORKING',
|
||||
FAILED = 'FAILED',
|
||||
}
|
||||
|
||||
export enum WhatsappEngine {
|
||||
VENOM = "VENOM",
|
||||
WEBJS = "WEBJS"
|
||||
export enum WAHAEngine {
|
||||
VENOM = 'VENOM',
|
||||
WEBJS = 'WEBJS',
|
||||
NOWEB = 'NOWEB',
|
||||
NOWEB_MOBILE = 'NOWEB_MOBILE',
|
||||
}
|
||||
|
||||
export enum WAHAPresenceStatus {
|
||||
OFFLINE = 'offline',
|
||||
ONLINE = 'online',
|
||||
TYPING = 'typing',
|
||||
RECORDING = 'recording',
|
||||
PAUSED = 'paused',
|
||||
}
|
||||
|
||||
export enum WAMessageAck {
|
||||
ERROR = -1,
|
||||
PENDING = 0,
|
||||
SERVER = 1,
|
||||
DEVICE = 2,
|
||||
READ = 3,
|
||||
PLAYED = 4,
|
||||
}
|
||||
export const ACK_UNKNOWN = 'UNKNOWN';
|
||||
@@ -0,0 +1,79 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
|
||||
export class RemoteFile {
|
||||
@ApiProperty({
|
||||
description: 'MIME type of the attachment.',
|
||||
example: 'image/jpeg',
|
||||
})
|
||||
mimetype: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'Document file name. Value can be null',
|
||||
example: 'filename.jpg',
|
||||
})
|
||||
filename?: string | null;
|
||||
|
||||
@ApiProperty({
|
||||
example:
|
||||
'https://github.com/devlikeapro/whatsapp-http-api/raw/core/examples/dev.likeapro.jpg',
|
||||
})
|
||||
url: string;
|
||||
}
|
||||
|
||||
export class BinaryFile {
|
||||
@ApiProperty({
|
||||
description: 'MIME type of the attachment.',
|
||||
example: 'image/jpeg',
|
||||
})
|
||||
mimetype: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'Document file name. Optional',
|
||||
example: 'filename.jpeg',
|
||||
})
|
||||
filename?: string | null;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'Base64-encoded data of the file',
|
||||
example:
|
||||
'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',
|
||||
})
|
||||
data: string;
|
||||
}
|
||||
|
||||
export class VoiceBinaryFile {
|
||||
@ApiProperty({
|
||||
description: 'MIME type of the attachment.',
|
||||
})
|
||||
mimetype = 'audio/ogg; codecs=opus';
|
||||
|
||||
@ApiProperty({
|
||||
description: 'Document file name. Optional',
|
||||
})
|
||||
filename = 'voice-message.mp3';
|
||||
|
||||
@ApiProperty({
|
||||
description: 'Base64-encoded data of the file',
|
||||
example:
|
||||
'SUQzBAAAAAAAI1RTU0UAAAAPAAADTGF2ZjU4Ljc2LjEwMAAAAAAAAAAAAAAA//NkxAAW0D4AVN4YAAYAKqTsSE2Z0IlkQMKANEGBUWH7a99t8eFgGQqfrfJxAsocfid8uizPyg535NQgTSs/z6xwJlJc/XKOE8uQtJsTP7ym4u/ky78nQzJw+t+4TvEiw/UftU4TymUVBiqj2qVLlF8X+bWGBLTBZi2+uOml4gA4HEVKHj2Fw89os529yuxD//NkxCcUAGI0FOJGKNWEHAhP3y5/+LZc5OU6X9RI+9M5IZc/fOaJf5y6cTjSBfLxrzjCnoSUCNUgnzokmHoBlzC6CGSrXGjEOPedHzBCkYcOBBpTTNe9/8tGfO5M4inBdS7PsvVusm91LKv4Ycftpwq4YATDG98/QyhKd3V7mZn7wZb2yrt3Y0fiGYpP4aUa//NkxFokCoIwNOpMRV7E1Kqm9vJ123c1F9bGVSKnasqqcezN/4ar26Uz3P8M/AgXq/+6ctsqX+fW6UmfCJ79o9g/CqpFrrpE78sS2V8ApIpklEE02r9II4IDbdNNnFC4dREiIgJFHHcOZjvE9lBMgBQUZzqdOmMDc97ku5pyFwNDph6+LaOyjKaRA6x4x2sc//NkxEwZkcZIVNpGCBophiL5tglY4Lj3qeQceTdr6ur/R11nlO/6KuHORu5SCMucIAApqsZ1I2CBCFxtWZ1tzE3pcvJD87eocViSFHDFuRXMs+q62dVX4O2GtELkoUrIfEbh7506+CEA0VUxhsBLvWABIfGtS8OCcALHMrWskk2pbXJuOEheYHvtaFW9arRY//NkxGgc+ZY8COJGVJsJX1dZg8q3QVzqEQCJapAB18VGV0Kcf+J8F8mNnEbyRRigCGBwCm4AQGjWWcBwIbSN5OhOjQgmU8f6ry3stZNsXzEDM1r44o0zCIuyJMgAejNzadpOkuIVTwXA/BS9ep5F8IvMiz9tM16cuR2SKoggy2MZQtg8BNAidnuNVpsFLpx5//NkxHcgEjJFdOGGtCyrFeW67L62epUITeB47MPJE/N7DOhVOlzQ3shzlhFM2gkw2MBQ0zxwHeTFmAKsdmTYnkf6VQlp4GCOOLPWCLF3sYHocCoKThOYp41Wo93UtOhKLnDyelr+f407RfQ0loWe5+a43SqraakgBnwG4POuQSELhVgoHwVB0OoeCgYj442U//NkxHkjKbZIyuZQTO/HTN86uNWKDHSjaBJ/RdqIU9N6adPSJcjBpAjd5ZQMRpirXYsZphSRhl2BiwAD9ZAHGGWBEzHAU3Ws11wH6d2fdF/sqG9P8pbEqpa2MNUcah+aizKGY8v4+WWNtiSTbbMmyf3K+U5Rez8xte6f9ZqzAIjDxVp4AEmjlm3KWLyoJw1r//NkxG8kSXpQMu5MWDLiYSGiEC1DUiwsLihhGVbL4DpupqbFV9eMCtpMfeuq2rdVeZVjIMHTE8nDqsWwcQxmk3BgCBxhAbhgGDBVBEP6XAIhRMwmTmYYwZ247Uc+cECwUeim6NFyz5cgE0SixFn+fd5Sb0kClO5up2jHtvXhqfSgK48ZRY+xgCaKhmJRI9lr//NkxGAhIT5UFO4MTFtqa6x7kuCp0aq5TakV0uFEJq1b3N9XYzKlRXJPaSjSxK16nqUZU4nVxqUFGJXeenWhgstGlJWAiyYfWYQCQYElQINEwKcFLFqIG1Zg8UvWrXmn1urARzCgoBTosSTkRcfBUVbsDXM62KmDEAkBJIYuNDHnGAAaYAyAwGwK6dYB7GPQ//NkxF4e4R5YXOMGiLWuWmL28y2xduEEPX16UzKUPFHrolYu8zXv879xhWqbVph6jGInGdqQwcFDlqOMrjUxIck9jAYAaslNTOm3mOyEoAq0pPsjKCa8QSZV7WmomOiijP1smCamch4MWEIfTcdiD/8MysS3MIj1h6NBhMoXeaAARIGjNLI4WOMDQOLS4+kR//NkxGUgaXZYVOGGlIXFBZoDP07hI3oN61pXNfrczNjykgKtFEu64tDaAKkkkkkkkiuSIFcvY9AuCM1psOiZD1e2RL5UFGSfVFOxKiSZcOB/rRbEIby4QS/l7HwSQ50PdnO5vF8uaYLg9T7o/0LGAVoE+AnnUhEqHqN+2EiZOousyjbkKCoGzIXFQGBGBcEg//NkxGY11Dq1vnpNhsAoeI90gZTMogfZUtHJiDaiLhMLEIFjgi/rrIXF+zNMlMwtIAFwQc+pTaGwtePD6+tl7j0comzEIh1EyQYLbHpcQ5SF7kZd1NIWQyNQKRC9JnDD6UFiJIGrIKBzkPl2Uq4cmamQz7+7tOxl7FtEH0h06g4OqqDJTBgSqEvrWRlEECjC//NkxBEhKYKUANPSnD4CG3gCwLkHoBMqTKUaUkraqCzctWV+m1bkb8u0ouTmawYa28AxqoQtKHeXrBv5cEpBP6IK/CcgM8Ey2hPrVRHqMT4cYXQFwbZAcIhcmTBOaMnhVz81zINiN///8sMPC3/7n/+qticPkhrRgq5w0OPcxplVBAMcSK16xyfBu6V5E4VO//NkxA8fQe7SVnsE3sqlEuhK9fjDZLxzi6SLBUEewl/scsTkeqdE9uFRUHGoaSs6r+2lvfUOiQ9kXdOx7B+1x2jPQvKC7RPl2PzZ7uzXe1f/5SkZoy2lzQ2FA6cWKCptRB0u3/6Vf+0SoKiglKox7AQEgJh8ISADd6fuBIAUaKk9cslsqjVbKmbz0PPY5Q1+//NkxBUfIaK01sPSlh5ZFGEiMvdwnWn2oMTLJTM2J8t0RhfRR2tLEUkrDGLOos4rOIYcCqAiKoiuom7r2xebLJIlTx44fjk7d8n83YRe957vMH/cwSKL6Ey973mSpMgGTlosv1ecaUut/hE0ILvvGRox6nk1AL2mPfUBUZaSvgWZbhpRdzWxrQ2qsfS4Qmi6//NkxBsgWWKwFsPMnLLjPbZm/+Hp/Fw44c4jYeLAJohxuQaL0ZST7Y3rG5bVb0kahY09mDWHbJTQ7lcxyAsgLkCImGpSeLFqjKWO+szt1WrMpSDqwjc80JbaLzItpImQmNBnf/t1/SBQZHiEsLNJUhlwbQoEgDWNNauCKdiupuITWqBhmGTVEDhJolUxTqPG//NkxBwc4Wq81nsMlh7aUSRnfU3mOSHKcXHggNr2WWu5xRtopJoCBLrRrL1WX2xrcpnD6LkCyUmR0yUWoJjCR77Sw9mBXULCT54Yu0Xrdb/hVS0I3/mTSw7/w6GyoBSMHjLXpqqgkAv1krkkRYNGdtnWV3uRCZjY3Di3rqbqSNhlzSWI00OhwAAcQvlyZaGo//NkxCsdK+rM9njE+zCBnEkZT8iKUmbJBEcUCMSNAxxiWZoqSN//tlV5mb7aKrPeulvuyohyLP////rv03ns6IrqtNN6p3LrKhFRn0akUwsm8ZkkyhudZqIlO/a1dASkgCFg25A5g4wH3DYgeONWRWR5F0rSuv35UUCWGdFkme7Wioowdlp+tKTOglxQIdDs//NkxDkcgZ7U/lmEllYr/sU44cWEhi+sCpF31D0rAKFKjD0tGAkIsfWlkkkieDodcZLB37qXxcNOSQIHaGhVNV6qqhR1SlSSUm1lRiYWROH6A2xUkPSoFND1ytKPu+bBqK93TKimC1i0iELlCdh2brUzdCKYEJBmO22uLhva2uLZ3EQvGqNqW2+KiYpnIZqf//NkxEodEoLEXmIE3uFdKU1ft6UWWu/NtOV2MFJBARFv915Fwi//ijKZ5QPtahDwbPvMOOPta9UJJad1sibIEcAnXUzOin78wDRaiSuCjubzcE4XF3jy82iLCgZdHNdXe0NttCoCkSCqQUsqtTX1rrQ0VUezN/9/F8WRyxaMdBFmkPGnrCA4XUpoG/xKGlVK//NkxFgdUa6w3nmQkoUGufspBpZEMpRb92LyRGYOuPLBUNETxFC+eZYI1QBiWo7bv9cbC4RBNgU2ZOzJlF2I6hncW0XFxQq5joOD4qV1k0mFqK+c0awzlTSR0s1t3Fwl6uZIKmCg7bWIdsjgSDKRpnRGr8vcxrIbKzUVl7316SOkxjs9CJen///Npyf///f///NkxGUc4869vkoEv/MjM0zQT+PLHf03+FAppQiO26Si0wExQTgQZNKKhYVXL/KdqhblIpuWqJEkZJao2zpmnQJzwo/KzF2SsevxgrWgg8ogPCHJC+ZGYI2ADMGCNweE3kigIuDQFGDEH4qBwcUYUz+bXbe4AMoPu/7s24QUHaPJvozFL7kkmPLhgUeLCPpU//NkxHQdQYKAPsmGkBUB40UDw1eB2YYqrAq9/o+y1xKjtHtsXBr0nSf2Vwmm+QRJlXz1m1IVzyOFGEgDDGZVaz3eRW2U4RFBamZ93qsjBtD5znsr2aIZnVX2RKpDvajaEnvzn37TvfX9f/faT/t///8i6rX3INPKHWGrJkprEf6xqTGg6UAvtPBqpnPTuNw1//NkxIIcA758FspEWAuYStUOFnAwRayKry+iKyESNAqfXOu5qY5aIXsruZdM/Z6b1jIKxB4uh2+ai4eSFYcK4fVqaeiZnNyp575zQoX5/c34fn8L/0fInJ48l+F09KZX4/8eN+rndzx47njOiPmj3Nd6SOQRWBYTMnDxEcIuFllGECkpxosYIVIcSiVJ51w+//NkxJUf9DqIFMGGMBolGBvMSYphcBJDXSmhMFFUsV27MeGBQHxCBzaaMCtwETeeCALA0JwkK1nBt2pRB7HrBckAWrpF4kJn0LYWQwa0URuPUN5l7LLlrWrvMhMuPBVwCCIFDLUqLslhZQTOEZoFjKZSQq07QHBQoX0DkWRTrgWmFVJSeW5NkVpKKXrclWbD//NkxJgcYPqSNoGGDBXTKSmTqRYo2Geff0vxs/Ve9dGEwGFWSYiWdaxBoqdDLZ+WPRaFSICcLmgghZVzUDgKz1IZYOfD3YlyEq1PNCo0XEoCUwGh0Ep0TLyRYDDhRQUDKSXe
Line truncated
|
||||
})
|
||||
data: string;
|
||||
}
|
||||
|
||||
export class VoiceRemoteFile {
|
||||
@ApiProperty({
|
||||
description: 'MIME type of the attachment.',
|
||||
})
|
||||
mimetype = 'audio/ogg; codecs=opus';
|
||||
|
||||
@ApiProperty({
|
||||
description: 'Document file name. Optional',
|
||||
})
|
||||
filename = 'voice-message.mp3';
|
||||
|
||||
@ApiProperty({
|
||||
example:
|
||||
'https://github.com/devlikeapro/whatsapp-http-api/raw/core/examples/dev.likeapro.mp3',
|
||||
})
|
||||
url: string;
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import { IsArray, IsString } from 'class-validator';
|
||||
|
||||
/**
|
||||
* Structures
|
||||
*/
|
||||
export class Participant {
|
||||
@IsString()
|
||||
id: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Queries
|
||||
*/
|
||||
|
||||
/**
|
||||
* Requests
|
||||
*/
|
||||
|
||||
export class ParticipantsRequest {
|
||||
@IsArray()
|
||||
participants: Array<Participant>;
|
||||
}
|
||||
|
||||
export class DescriptionRequest {
|
||||
@IsString()
|
||||
description: string;
|
||||
}
|
||||
export class SubjectRequest {
|
||||
@IsString()
|
||||
subject: string;
|
||||
}
|
||||
|
||||
export class CreateGroupRequest {
|
||||
@IsString()
|
||||
name: string;
|
||||
|
||||
@IsArray()
|
||||
participants: Array<Participant>;
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
|
||||
import { WAHAPresenceStatus } from './enums.dto';
|
||||
|
||||
export class WAHAPresenceData {
|
||||
@ApiProperty({
|
||||
description: 'Chat ID - participant or contact id',
|
||||
example: '11111111111@c.us',
|
||||
})
|
||||
participant: string;
|
||||
lastKnownPresence: WAHAPresenceStatus;
|
||||
@ApiProperty({
|
||||
example: 1686568773,
|
||||
})
|
||||
lastSeen?: number;
|
||||
}
|
||||
export class WAHAChatPresences {
|
||||
@ApiProperty({
|
||||
description: 'Chat ID - either group id or contact id',
|
||||
example: '11111111111@c.us',
|
||||
})
|
||||
id: string;
|
||||
presences: WAHAPresenceData[];
|
||||
}
|
||||
|
||||
export class WAHASessionPresence {
|
||||
presence: WAHAPresenceStatus;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'Chat ID - either group id or contact id',
|
||||
example: '11111111111@c.us',
|
||||
})
|
||||
chatId: string;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
|
||||
function ChatIdProperty(options = undefined) {
|
||||
options = options || {};
|
||||
if (!options.example) {
|
||||
options.example = '11111111111@c.us';
|
||||
}
|
||||
return ApiProperty(options);
|
||||
}
|
||||
|
||||
function MessageIdProperty() {
|
||||
return ApiProperty({
|
||||
description: 'Message ID',
|
||||
example: 'false_11111111111@c.us_AAAAAAAAAAAAAAAAAAAA',
|
||||
});
|
||||
}
|
||||
|
||||
export { ChatIdProperty, MessageIdProperty };
|
||||
+75
-115
@@ -1,138 +1,98 @@
|
||||
import {ApiProperty} from "@nestjs/swagger";
|
||||
import {WAEvents} from "./enums.dto";
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
|
||||
export class WANumberExistResult {
|
||||
numberExists: boolean
|
||||
}
|
||||
|
||||
|
||||
export class WAGroupNotification {
|
||||
@ApiProperty({
|
||||
description: "ID that represents the groupNotification",
|
||||
})
|
||||
id: any
|
||||
|
||||
@ApiProperty({
|
||||
description: "Unix timestamp for when the groupNotification was created",
|
||||
})
|
||||
timestamp: number
|
||||
|
||||
@ApiProperty({
|
||||
description: "ID for the Chat that this groupNotification was sent for",
|
||||
})
|
||||
chatId: string
|
||||
|
||||
|
||||
@ApiProperty({
|
||||
description: "ContactId for the user that produced the GroupNotification",
|
||||
})
|
||||
author: string
|
||||
|
||||
@ApiProperty({
|
||||
description: "Extra content",
|
||||
})
|
||||
body: string
|
||||
|
||||
@ApiProperty({
|
||||
description: "Contact IDs for the users that were affected by this GroupNotification",
|
||||
})
|
||||
recipientIds: string[]
|
||||
}
|
||||
|
||||
export enum WAMessageAck {
|
||||
ACK_ERROR = -1,
|
||||
ACK_PENDING = 0,
|
||||
ACK_SERVER = 1,
|
||||
ACK_DEVICE = 2,
|
||||
ACK_READ = 3,
|
||||
ACK_PLAYED = 4,
|
||||
}
|
||||
import { WAMessageAck } from './enums.dto';
|
||||
import { ChatIdProperty, MessageIdProperty } from './properties.dto';
|
||||
|
||||
export class WALocation {
|
||||
description?: string | null
|
||||
latitude: string
|
||||
longitude: string
|
||||
description?: string | null;
|
||||
latitude: string;
|
||||
longitude: string;
|
||||
}
|
||||
|
||||
|
||||
export class WAMessage {
|
||||
@ApiProperty({
|
||||
description: "Message ID",
|
||||
example: "false_11111111111@c.us_AAAAAAAAAAAAAAAAAAAA"
|
||||
})
|
||||
id: string
|
||||
@MessageIdProperty()
|
||||
id: string;
|
||||
|
||||
/** */
|
||||
@ApiProperty({
|
||||
description: "Unix timestamp for when the message was created",
|
||||
example: 1666943582
|
||||
})
|
||||
timestamp: number
|
||||
/** */
|
||||
@ApiProperty({
|
||||
description: 'Unix timestamp for when the message was created',
|
||||
example: 1666943582,
|
||||
})
|
||||
timestamp: number;
|
||||
|
||||
@ApiProperty({
|
||||
description: "ID for the Chat that this message was sent to, except if the message was sent by the current user "
|
||||
})
|
||||
from: string
|
||||
@ChatIdProperty({
|
||||
description:
|
||||
'ID for the Chat that this message was sent to, except if the message was sent by the current user ',
|
||||
})
|
||||
from: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: "Indicates if the message was sent by the current user"
|
||||
})
|
||||
fromMe: boolean
|
||||
@ApiProperty({
|
||||
description: 'Indicates if the message was sent by the current user',
|
||||
})
|
||||
fromMe: boolean;
|
||||
|
||||
@ApiProperty({
|
||||
description: `
|
||||
@ChatIdProperty({
|
||||
description: `
|
||||
* ID for who this message is for.
|
||||
* If the message is sent by the current user, it will be the Chat to which the message is being sent.
|
||||
* If the message is sent by another user, it will be the ID for the current user.
|
||||
`
|
||||
})
|
||||
to: string
|
||||
`,
|
||||
})
|
||||
to: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: "Message content"
|
||||
})
|
||||
body: string
|
||||
@ApiProperty({
|
||||
description: 'For groups - participant who sent the message',
|
||||
})
|
||||
participant: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: "Indicates if the message has media available for download"
|
||||
})
|
||||
hasMedia: boolean
|
||||
@ApiProperty({
|
||||
description: 'Message content',
|
||||
})
|
||||
body: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: "The URL for the media in the message if any",
|
||||
example: "http://localhost:3000/api/files/false_11111111111@c.us_AAAAAAAAAAAAAAAAAAAA.oga"
|
||||
})
|
||||
mediaUrl: string
|
||||
@ApiProperty({
|
||||
description: 'Indicates if the message has media available for download',
|
||||
})
|
||||
hasMedia: boolean;
|
||||
|
||||
@ApiProperty({
|
||||
description: "ACK status for the message"
|
||||
})
|
||||
ack: WAMessageAck
|
||||
@ApiProperty({
|
||||
description: 'The URL for the media in the message if any',
|
||||
example:
|
||||
'http://localhost:3000/api/files/false_11111111111@c.us_AAAAAAAAAAAAAAAAAAAA.oga',
|
||||
})
|
||||
mediaUrl: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: "If the message was sent to a group, this field will contain the user that sent the message."
|
||||
})
|
||||
author?: string;
|
||||
@ApiProperty({
|
||||
description: 'ACK status for the message',
|
||||
})
|
||||
ack: WAMessageAck;
|
||||
|
||||
@ApiProperty({
|
||||
description: "Location information contained in the message, if the message is type \"location\""
|
||||
})
|
||||
location?: WALocation
|
||||
@ApiProperty({
|
||||
description: 'ACK status name for the message',
|
||||
})
|
||||
ackName: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: "List of vCards contained in the message."
|
||||
})
|
||||
vCards?: string[]
|
||||
@ApiProperty({
|
||||
description:
|
||||
'If the message was sent to a group, this field will contain the user that sent the message.',
|
||||
})
|
||||
author?: string;
|
||||
|
||||
/** Returns message in a raw format */
|
||||
@ApiProperty({
|
||||
description: "Message in a raw format that we get from WhatsApp. May be changed anytime, use it with caution! It depends a lot on the underlying backend."
|
||||
})
|
||||
_data?: any
|
||||
}
|
||||
@ApiProperty({
|
||||
description:
|
||||
'Location information contained in the message, if the message is type "location"',
|
||||
})
|
||||
location?: WALocation;
|
||||
|
||||
export class WAWebhook {
|
||||
event: WAEvents
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||
payload: WAMessage | WAGroupNotification | object
|
||||
@ApiProperty({
|
||||
description: 'List of vCards contained in the message.',
|
||||
})
|
||||
vCards?: string[];
|
||||
|
||||
/** Returns message in a raw format */
|
||||
@ApiProperty({
|
||||
description:
|
||||
'Message in a raw format that we get from WhatsApp. May be changed anytime, use it with caution! It depends a lot on the underlying backend.',
|
||||
})
|
||||
_data?: any;
|
||||
}
|
||||
@@ -1,14 +1,79 @@
|
||||
import {WhatsappStatus} from "./enums.dto";
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
|
||||
import { WAHASessionStatus } from './enums.dto';
|
||||
import { WebhookConfig } from './webhooks.config.dto';
|
||||
import { ChatIdProperty } from './properties.dto';
|
||||
|
||||
/**
|
||||
* Queries
|
||||
*/
|
||||
export class ListSessionsQuery {
|
||||
@ApiProperty({
|
||||
example: false,
|
||||
required: false,
|
||||
description:
|
||||
'Return all sessions, including those that are in the STOPPED state.',
|
||||
})
|
||||
all: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests
|
||||
*/
|
||||
export class ProxyConfig {
|
||||
@ApiProperty({
|
||||
example: 'localhost:3128',
|
||||
})
|
||||
server: string;
|
||||
@ApiProperty({
|
||||
example: null,
|
||||
})
|
||||
username?: string;
|
||||
@ApiProperty({
|
||||
example: null,
|
||||
})
|
||||
password?: string;
|
||||
}
|
||||
|
||||
export class SessionConfig {
|
||||
webhooks?: WebhookConfig[];
|
||||
@ApiProperty({
|
||||
example: null,
|
||||
})
|
||||
proxy?: ProxyConfig;
|
||||
}
|
||||
|
||||
export class SessionStartRequest {
|
||||
name = "default"
|
||||
name = 'default';
|
||||
config?: SessionConfig;
|
||||
}
|
||||
|
||||
export class SessionStopRequest {
|
||||
name = "default"
|
||||
name = 'default';
|
||||
@ApiProperty({
|
||||
example: false,
|
||||
required: false,
|
||||
description: 'Stop and logout from the session.',
|
||||
})
|
||||
logout = false;
|
||||
}
|
||||
|
||||
export class SessionLogoutRequest {
|
||||
name = 'default';
|
||||
}
|
||||
|
||||
export class SessionDTO {
|
||||
name = "default"
|
||||
status: WhatsappStatus
|
||||
name = 'default';
|
||||
status: WAHASessionStatus;
|
||||
config: SessionConfig;
|
||||
}
|
||||
|
||||
export class MeInfo {
|
||||
@ChatIdProperty()
|
||||
id: string;
|
||||
pushName: string;
|
||||
}
|
||||
|
||||
export class SessionInfo extends SessionDTO {
|
||||
me: MeInfo | null;
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
import { ApiExtraModels, ApiProperty, getSchemaPath } from '@nestjs/swagger';
|
||||
import {
|
||||
BinaryFile,
|
||||
RemoteFile,
|
||||
VoiceBinaryFile,
|
||||
VoiceRemoteFile,
|
||||
} from './files.dto';
|
||||
|
||||
export const BROADCAST_ID = 'status@broadcast';
|
||||
|
||||
class StatusRequest {
|
||||
@ApiProperty({
|
||||
description:
|
||||
'it is always necessary to inform the list of contacts that will have access to the posted status',
|
||||
example: ['55xxxxxxxxxxx@c.us'],
|
||||
})
|
||||
contacts = ['55xxxxxxxxxxx@c.us'];
|
||||
}
|
||||
|
||||
export class TextStatus extends StatusRequest {
|
||||
text = 'Have a look! https://waha.devlike.pro/';
|
||||
backgroundColor = '#38b42f';
|
||||
font = 1;
|
||||
}
|
||||
|
||||
@ApiExtraModels(BinaryFile, RemoteFile)
|
||||
export class ImageStatus extends StatusRequest {
|
||||
@ApiProperty({
|
||||
oneOf: [
|
||||
{ $ref: getSchemaPath(BinaryFile) },
|
||||
{ $ref: getSchemaPath(RemoteFile) },
|
||||
],
|
||||
})
|
||||
file: BinaryFile | RemoteFile;
|
||||
caption: string;
|
||||
}
|
||||
|
||||
@ApiExtraModels(VoiceBinaryFile, VoiceRemoteFile)
|
||||
export class VoiceStatus extends StatusRequest {
|
||||
@ApiProperty({
|
||||
oneOf: [
|
||||
{ $ref: getSchemaPath(VoiceBinaryFile) },
|
||||
{ $ref: getSchemaPath(VoiceRemoteFile) },
|
||||
],
|
||||
})
|
||||
file: VoiceBinaryFile | VoiceRemoteFile;
|
||||
backgroundColor = '#38b42f';
|
||||
}
|
||||
|
||||
@ApiExtraModels(BinaryFile, RemoteFile)
|
||||
export class VideoStatus extends StatusRequest {
|
||||
@ApiProperty({
|
||||
oneOf: [
|
||||
{ $ref: getSchemaPath(BinaryFile) },
|
||||
{ $ref: getSchemaPath(RemoteFile) },
|
||||
],
|
||||
})
|
||||
file: BinaryFile | RemoteFile;
|
||||
caption: string;
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
|
||||
export class RetriesConfiguration {
|
||||
@ApiProperty({
|
||||
example: 2,
|
||||
})
|
||||
delaySeconds: number;
|
||||
|
||||
@ApiProperty({
|
||||
example: 15,
|
||||
})
|
||||
attempts: number;
|
||||
}
|
||||
export class CustomHeader {
|
||||
@ApiProperty({
|
||||
example: 'X-My-Custom-Header',
|
||||
})
|
||||
name: string;
|
||||
|
||||
@ApiProperty({
|
||||
example: 'Value',
|
||||
})
|
||||
value: string;
|
||||
}
|
||||
|
||||
export class HmacConfiguration {
|
||||
@ApiProperty({
|
||||
example: 'your-secret-key',
|
||||
})
|
||||
key: string;
|
||||
}
|
||||
|
||||
export class WebhookConfig {
|
||||
@ApiProperty({
|
||||
example: 'https://httpbin.org/post',
|
||||
required: true,
|
||||
})
|
||||
url: string;
|
||||
|
||||
@ApiProperty({
|
||||
example: ['message'],
|
||||
required: true,
|
||||
})
|
||||
events: string[];
|
||||
|
||||
@ApiProperty({
|
||||
example: null,
|
||||
})
|
||||
hmac?: HmacConfiguration;
|
||||
|
||||
@ApiProperty({
|
||||
example: null,
|
||||
})
|
||||
retries?: RetriesConfiguration;
|
||||
|
||||
@ApiProperty({
|
||||
example: null,
|
||||
})
|
||||
customHeaders?: CustomHeader[];
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
|
||||
import { WAHAEngine, WAHAEvents, WAMessageAck } from './enums.dto';
|
||||
import { WAMessage } from './responses.dto';
|
||||
import { MessageDestination } from './chatting.dto';
|
||||
import { ChatIdProperty, MessageIdProperty } from './properties.dto';
|
||||
|
||||
export class WAMessageAckBody {
|
||||
@MessageIdProperty()
|
||||
id: string;
|
||||
|
||||
@ChatIdProperty()
|
||||
from: string;
|
||||
|
||||
@ChatIdProperty()
|
||||
to: string;
|
||||
|
||||
@ChatIdProperty()
|
||||
participant: string;
|
||||
|
||||
fromMe: boolean;
|
||||
ack: WAMessageAck;
|
||||
ackName: string;
|
||||
}
|
||||
|
||||
export class WAGroupPayload {
|
||||
@ApiProperty({
|
||||
description: 'ID that represents the groupNotification',
|
||||
})
|
||||
id: any;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'Unix timestamp for when the groupNotification was created',
|
||||
})
|
||||
timestamp: number;
|
||||
|
||||
@ChatIdProperty({
|
||||
description: 'ID for the Chat that this groupNotification was sent for',
|
||||
})
|
||||
chatId: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'ContactId for the user that produced the GroupNotification',
|
||||
})
|
||||
author: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'Extra content',
|
||||
})
|
||||
body: string;
|
||||
|
||||
@ApiProperty({
|
||||
description:
|
||||
'Contact IDs for the users that were affected by this GroupNotification',
|
||||
})
|
||||
recipientIds: string[];
|
||||
}
|
||||
|
||||
export class PollVote extends MessageDestination {
|
||||
@ApiProperty({
|
||||
description: 'Option that user has selected',
|
||||
example: ['Awesome!'],
|
||||
})
|
||||
selectedOptions: string[];
|
||||
|
||||
@ApiProperty({
|
||||
description: 'Timestamp, ms',
|
||||
example: 1692861369,
|
||||
})
|
||||
timestamp: number;
|
||||
}
|
||||
|
||||
export class PollVotePayload {
|
||||
vote: PollVote;
|
||||
poll: MessageDestination;
|
||||
}
|
||||
|
||||
export class WAWebhook {
|
||||
event: WAHAEvents;
|
||||
session: string;
|
||||
engine: WAHAEngine;
|
||||
payload:
|
||||
| WAMessage
|
||||
| WAGroupPayload
|
||||
| WAMessageAckBody
|
||||
| PollVotePayload
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||
| object;
|
||||
}
|
||||
Loaded 100 of 104 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user