Compare commits

..
53 Commits
Author SHA1 Message Date
allburov 89a9ce87da [core] Use the main branch in whatsapp webjs
Release / deploy-docker (push) Waiting to run
2023-04-14 10:43:02 +07:00
allburov 52e469f6ac [core] Bump version - 2023.4.14 2023-04-14 10:38:58 +07:00
allburov 4f275de8d4 [core] close session properly
Fix #83
2023-04-14 10:38:56 +07:00
allburov d9de41e350 [core] bump version - 2023.3.28 2023-04-14 10:38:54 +07:00
allburov a4c279ba25 [core] Add short description about webhooks
Release / deploy-docker (push) Waiting to run
2023-04-01 19:04:13 +07:00
allburov e68282a9e4 [core] Add video on the index 2023-04-01 18:53:37 +07:00
allburov 8613206c3a [core] Move video to introduction page 2023-04-01 18:50:30 +07:00
allburov 31b8cde8bf [core] Add youtube video 2023-04-01 18:46:19 +07:00
allburov 2f2f9520db [core] add session to webhook data
fix #80
2023-03-28 15:37:53 +07:00
allburov 2501ae836e [core] bump version 2023-03-28 15:37:53 +07:00
allburov 2c8bf91e77 [plus] Increase body limit for files - fix #79 2023-03-28 15:37:52 +07:00
allburov 0ac063498e [core] Fix addParticipans issue - https://github.com/pedroslopez/whatsapp-web.js/pull/2087 2023-03-28 15:37:52 +07:00
allburov 2bf672f0cd [core] Downgrade venom-bot 2023-03-22 19:14:12 +07:00
allburov ca209adca0 [core] bump version - 2023.3.21 2023-03-22 19:14:12 +07:00
allburov dd10c82c5e [core] Update engines - venom, whatsapp webjs 2023-03-22 19:14:12 +07:00
allburov c3ad2a0931 [core] Make update-dependencies - use yarn 2023-03-22 19:14:12 +07:00
allburov 30aacc8946 [core] Fix features 2023-03-22 19:13:23 +07:00
allburov 723a644937 Use "" for * in env variable 2023-02-28 16:59:28 +07:00
allburov 165eeb5bf2 Add note how to start ARM docker image 2023-02-28 10:00:06 +07:00
allburov f1776d4a26 Support only amd64
Release / deploy-docker (push) Waiting to run
2023-02-28 09:38:48 +07:00
allburov 2f2a1b3182 Use yarn 2023-02-28 09:03:30 +07:00
allburov 0477cf7db5 Update build-and-push action 2023-02-27 19:47:10 +07:00
allburov 2d7c7986aa Add changelog for Chromium 2023-02-27 19:27:22 +07:00
allburov bf84ea8fca [core] fix chromium path 2023-02-27 19:15:37 +07:00
allburov f3ef8a7f96 [core] fix version file 2023-02-27 19:08:56 +07:00
allburov ec214f610a [core] Install chromium by default 2023-02-27 19:04:27 +07:00
allburov 013fd46722 [core] Install fonts separately 2023-02-27 18:51:50 +07:00
allburov 32d583b9ef [core] Skip chromium download 2023-02-27 18:51:27 +07:00
allburov df6e201ba5 [core] Choose between chrome and chromium 2023-02-27 18:47:02 +07:00
allburov 789b613b82 [core] Build docker for ARM 64 2023-02-27 18:13:53 +07:00
allburov b698342db4 [core] Build docker for ARM v7\v8 2023-02-27 18:09:07 +07:00
allburov d853f2fe99 [core] Build docker for ARM 2023-02-27 17:57:14 +07:00
allburov 20d66b5b31 [core][doc] Split examples to echo bot and download files bot 2023-02-27 11:00:53 +07:00
allburov da7264c5e8 [core] Bump version 2023-02-24 19:57:50 +07:00
allburov f3299f78d9 [core] Update swagger 2023-02-24 19:57:49 +07:00
allburov 7ef0805c91 [core] Add group endpoints docs and changelog 2023-02-24 19:57:48 +07:00
allburov ba1afa2569 [core] Add group endpoints 2023-02-24 19:57:48 +07:00
allburov 936e2d0863 [core] Add sendLocation to webjs.
It doesn't work tho because of https://github.com/pedroslopez/whatsapp-web.js/issues/1591
2023-02-24 19:57:41 +07:00
Aleksey 166410f66a Use main webjs
Release / deploy-docker (push) Waiting to run
2023-02-11 13:39:11 +00:00
Aleksey f8c9298694 Update package lock
Release / deploy-docker (push) Waiting to run
2023-02-11 12:58:38 +07:00
Aleksey b0b642b6bb ^1.19.3 2023-02-11 08:40:48 +07:00
allburov 5fa20343da [core] Remove one time mark 2023-01-22 14:18:36 +07:00
allburov bbbf181011 [core] does not cache layers to install new Chrome
Release / deploy-docker (push) Waiting to run
2023-01-22 14:07:19 +07:00
allburov 6af547d2af [core] Quick fix for webjs engine.
Release / deploy-docker (push) Waiting to run
Fix #46
2023-01-15 14:52:17 +07:00
allburov d72b1822cb [core][2023.1] Add checkNumberStatus for webjs engine 2023-01-15 14:37:15 +07:00
allburov d34df18da8 [core][2023.1] Add contacts endpoints 2023-01-15 14:37:14 +07:00
allburov 27cb247760 [core][docs] Add contact api changelog 2023-01-09 09:31:00 +07:00
allburov 7a66d6fccd [core][docs] Add changelog.md 2023-01-09 09:31:00 +07:00
allburov f1cb372339 [core][docs] Add contacts page 2023-01-09 09:31:00 +07:00
allburov 46d65e6fc0 [core][docs] Reorder guides 2023-01-09 09:31:00 +07:00
allburov 6168298b5e [core][docs] Move engines doc to config 2023-01-09 09:31:00 +07:00
allburov d5fef413ca [core] Add swagger authentication to changelog 2022-12-19 12:35:36 +07:00
allburov 0802ee6106 [core][docs] Add GET /messages request. Fix #31 2022-12-19 12:32:55 +07:00
47 changed files with 9946 additions and 33509 deletions

No files matched your search

+7 -3
View File
@@ -13,6 +13,11 @@ jobs:
- name: Checkout
uses: actions/checkout@v3
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
with:
platforms: 'amd64'
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
@@ -33,12 +38,11 @@ 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 }}
cache-from: type=gha
cache-to: type=gha
+1
View File
@@ -0,0 +1 @@
network-timeout 60000
+2
View File
@@ -0,0 +1,2 @@
# Changelog
You can find changelog on [the changelog page](https://waha.devlike.pro/docs/help/changelog/)
+28 -8
View File
@@ -2,12 +2,13 @@
# Build
#
FROM node:18 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 install
# App
WORKDIR /src
@@ -18,17 +19,36 @@ RUN npm run build && find ./dist -name "*.d.ts" -delete
# Final
#
FROM node:18 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 ./
+2 -1
View File
@@ -35,4 +35,5 @@ 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 add whatsapp-web.js@https://github.com/pedroslopez/whatsapp-web.js
. ${NVM_DIR}/nvm.sh && nvm exec yarn add venom-bot
+6 -8
View File
@@ -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)
@@ -56,6 +56,7 @@
- Multiple WhatsApp account running inside one container
## Security
- Swagger panel authentication
- API authentication
## Updates
@@ -63,22 +64,19 @@
- 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
# Quick start
+4 -38
View File
@@ -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 ![](/images/versions/plus.png)
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.
![](/images/versions/plus.png) If you try to send an image the "echo server" will send a path to the downloaded file.
+21 -3
View File
@@ -12,16 +12,34 @@ menu:
weight: 600
toc: true
---
## 2021.3
- Add [Groups API]({{< relref "/docs/how-to/groups" >}})
- Use Chromium by default instead of Chrome
## 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 ![](/images/versions/plus.png)
- Added `WHATSAPP_SWAGGER_USERNAME` and `WHATSAPP_SWAGGER_PASSWORD` to hide and protect swagger panel.
## 2022.11
**Please test changes in test environment before update production!!**
### Engine ![](/images/versions/core.png) ![](/images/versions/plus.png)
### Engine ![](/images/versions/core.png)
1. WAHA has changed its underlying engine from Venom to Whatsapp Web.JS. It might change the response and webhook's payloads.
2. Optimize CPU and memory consumption.
### Requests ![](/images/versions/core.png) ![](/images/versions/plus.png)
### Requests ![](/images/versions/core.png)
- For all `/api/session/` requests use `name` field instead of `sessionName`.
- For all "chatting" requests use `session` field instead of `sessionName`.
@@ -33,7 +51,7 @@ Now you can authenticate all requests for WAHA - use `WHATSAPP_API_KEY=secret` e
If `WHATSAPP_API_KEY` is set - requests must have `X-Api-Key` header with `secret` value, where `secret` - any random secret key.
### Webhooks ![](/images/versions/core.png) ![](/images/versions/plus.png)
### Webhooks ![](/images/versions/core.png)
#### Configuration
Instead of setting each webhook via environment variables - we use two environments variables:
@@ -6,32 +6,53 @@ date: 2020-10-06T08:48:45+00:00
lastmod: 2020-10-06T08:48:45+00:00
draft: false
images: []
weight: 400
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
```
## Environment variables
### Common
- `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_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
-
## File storage ![](/images/versions/plus.png)
- `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`)
## Engines
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.
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." />}}
### WhatsApp WebJS ![](/images/versions/core.png)
**WAHA** uses **WhatsApp WebJS** engine by default
- https://github.com/pedroslopez/whatsapp-web.js
- `WHATSAPP_DEFAULT_ENGINE=WEBJS`
### Venom ![](/images/versions/core.png)
- https://github.com/orkestral/venom
- `WHATSAPP_DEFAULT_ENGINE=VENOM`
@@ -0,0 +1,101 @@
---
title : "Contacts"
description: "Contacts"
lead: ""
date: 2020-10-06T08:48:45+00:00
lastmod: 2020-10-06T08:48:45+00:00
draft: false
images: []
weight: 130
---
Methods for contacts.
{{< alert icon="👉" text="WhatsApp Web does not support adding contacts, so the API doesn't support it too." />}}
## Get all contacts
Get your contacts - `GET /api/contacts/all`
```json
[
{
"id": "11231231231@c.us",
"number": "11231231231",
"name": "Contact Name",
"pushname": "Pushname",
"shortName": "Shortname",
"isMe": true,
"isGroup": false,
"isWAContact": true,
"isMyContact": true,
"isBlocked": false
}
]
```
## Get contact
Get contact
- `GET /api/contacts?contactId=11231231231&session=default`
- `GET /api/contacts?contactId=11231231231@c.us&session=default`
```json
{
"id": "11231231231@c.us",
"number": "11231231231",
"name": "Contact Name",
"pushname": "Pushname",
"shortName": "Shortname",
"isMe": true,
"isGroup": false,
"isWAContact": true,
"isMyContact": true,
"isBlocked": false
}
```
## Check phone number exists
If you want to check if phone number is registered in WhatsApp (even if the number is not in your contact list) - use
this endpoint for that.
- `GET /api/contacts/check-exists?phone=11231231231&session=default`
```json
{
"numberExists": true
}
```
## Get "about" contact
- `GET /api/contacts/about?contactId=11231231231&session=default`
```json
{
"about": "Hi, I use WhatsApp!"
}
```
## Get contact profile picture
- `GET /api/contacts/profile-picture?contactId=11231231231&session=default`
```json
{
"profilePictureURL": "https://example.com/profile.jpg"
}
```
## Block (unblock) contact
- To block contact - `POST /api/contacts/block`
- To unblock contact - `POST /api/contacts/unblock`
Request:
```json
{
"contactId": "11231231231",
"session": "default"
}
```
@@ -1,27 +0,0 @@
---
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: 700
---
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.
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." />}}
## WhatsApp WebJS ![](/images/versions/core.png)
**WAHA** uses **WhatsApp WebJS** engine by default
- https://github.com/pedroslopez/whatsapp-web.js
- `WHATSAPP_DEFAULT_ENGINE=WEBJS`
## Venom ![](/images/versions/core.png)
- https://github.com/orkestral/venom
- `WHATSAPP_DEFAULT_ENGINE=VENOM`
@@ -0,0 +1,168 @@
---
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"
}
```
## 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`
@@ -41,18 +41,19 @@ What is the difference between **WAHA Plus** and other SaaS solutions for WhatsA
| | WAHA Plus | Others |
|--------------------|:-------------------------------------------------:|----------------------------------------------------------------------------------:|
| **Sessions** | ✔️ No limits! | ➖ One account for $50/month. |
| **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 |
| **License Term** | ✔️ No license checks! | ➖ One month |
| **Message price** | ✔️ All messages are Free! | ➖ Some solutions have a price for messages - $1/100 messages. |
| **Expenses** | ✔️ one-time $19 **donation**! | ➖ Monthly payment starts from $50 |
| **Expenses** | ✔️ $19 **donation**! | ➖ Monthly payment starts from $50 |
## Donations
WAHA Plus version is available through **donations** - either one time or subscription.
WAHA Plus version is available through **donations** (subscriptions).
**It doesn't require monthly subscriptions, once installed on your server - it always works!**
(until WhatsApp made backward-incompatible changes, and you have to update the image)
{{< alert icon="💡" text="No licence checks or expiration on already installed instances!" />}}
We do not sell **WAHA**, it's not a purchase. It's a donation.
@@ -79,7 +80,7 @@ For $19 one-time donation you get:
### 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 - either subscribe or use one time donation!
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!" />}}
@@ -6,20 +6,29 @@ date: 2020-10-06T08:48:45+00:00
lastmod: 2020-10-06T08:48:45+00:00
draft: false
images: []
weight: 300
weight: 120
---
The project uses [Webhooks](https://en.wikipedia.org/wiki/Webhook) to send the messages or events from WhatsApp to your
The project uses [Webhooks](https://en.wikipedia.org/wiki/Webhook) to send the messages and events from WhatsApp to your
application.
## Webhooks ![](/images/versions/core.png) ![](/images/versions/plus.png)
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.
To receive incoming messages with webhooks,
you first need to set up a webhook URL in your application and pass it to `WHATSAPP_HOOK_URL` environment variable.
This URL is where WhatsApp will send incoming messages and other events -
define which events you want to receive with `WHATSAPP_HOOK_EVENTS` environment variable.
## Webhooks ![](/images/versions/core.png)
You can configure where you want to receive events in environment variables:
- `WHATSAPP_HOOK_URL=https://httpbin.org/post` - to set up a URL for the webhook
- `WHATSAPP_HOOK_EVENTS=message,message.any,state.change,group.join,group.leave` - specify events. DO NOT specify all of
them, it's too heavy payload, choose the right for you.
- `WHATSAPP_HOOK_EVENTS=*` - subscribe to all events. It's not recommended for production, but it's fine for
- `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:
@@ -27,6 +36,7 @@ On the URL that you set via `WHATSAPP_HOOK_URL` you receive JSON-data with follo
```json
{
"event": "message",
"session": "default",
"payload": {}
}
```
@@ -40,7 +50,7 @@ Below the list of all events that WhatsApp HTTP API sends to your.
Run the bellow command and see look at the logs - it prints body request for all events that happen in your WhatsApp!
```bash
docker run -it -e WHATSAPP_HOOK_EVENTS=* -e WHATSAPP_HOOK_URL=https://httpbin.org/post devlikeapro/whatsapp-http-api
docker run -it -e "WHATSAPP_HOOK_EVENTS=*" -e WHATSAPP_HOOK_URL=https://httpbin.org/post devlikeapro/whatsapp-http-api
```
### Examples
@@ -55,7 +65,7 @@ You can create a pull request with your favorite language in the [GitHub, in exa
Just put the code and **README.md** file in the folder - we'll do the rest!
## Events ![](/images/versions/core.png) ![](/images/versions/plus.png)
## Events ![](/images/versions/core.png)
### message
@@ -64,6 +74,7 @@ Incoming message (text/audio/files)
```json
{
"event": "message",
"session": "default",
"payload": {
"id": "true_11111111111@c.us_AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
"timestamp": 1667561485,
@@ -126,6 +137,7 @@ Fired on all message creations, including your own. The payload is the same as f
```json
{
"event": "message.any",
"session": "default",
"payload": {}
}
```
@@ -135,6 +147,7 @@ Fired on all message creations, including your own. The payload is the same as f
```json
{
"event": "message.ack",
"session": "default",
"payload": {}
}
```
@@ -144,6 +157,7 @@ Fired on all message creations, including your own. The payload is the same as f
```json
{
"event": "state.change",
"session": "default",
"payload": {}
}
```
@@ -153,6 +167,7 @@ Fired on all message creations, including your own. The payload is the same as f
```json
{
"event": "group.join",
"session": "default",
"payload": {}
}
```
@@ -162,6 +177,7 @@ Fired on all message creations, including your own. The payload is the same as f
```json
{
"event": "group.left",
"session": "default",
"payload": {}
}
```
@@ -176,6 +192,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,
@@ -193,3 +210,12 @@ To configure the url you can use environment variables `WHATSAPP_API_HOSTNAME` a
## Webhook retries ![](/images/versions/plus.png)
**WAHA** retries to reach your webhook URL 15 times and with 2 seconds delay between attempts.
## Get messages
You can read messages from the history by using `GET /api/messages` endpoint.
```bash
curl -X 'GET' \
'http://localhost:3000/api/messages?chatId=11111111111%40c.us&limit=1000&session=default' \
-H 'accept: application/json'
```
@@ -6,7 +6,7 @@ date: 2020-10-06T08:48:45+00:00
lastmod: 2020-10-06T08:48:45+00:00
draft: false
images: []
weight: 800
weight: 700
---
{{< alert icon="💡" text="Do not expose WhatsApp HTTP API on public networks!" />}}
@@ -6,7 +6,7 @@ date: 2020-10-06T08:48:45+00:00
lastmod: 2020-10-06T08:48:45+00:00
draft: false
images: []
weight: 200
weight: 110
---
On your swagger documentation [http://localhost:3000/](http://localhost:3000/)
or in [the example swagger](/swagger) you can find:
@@ -28,7 +28,7 @@ Read more about [multiple sessions →]({{< relref "/docs/how-to/sessions" >}})
For phone number `12132132131` the `chatId` is `12132132131@c.us`.
- **Groups** use random number with `@g.us` at the end.
## Send text ![](/images/versions/core.png) ![](/images/versions/plus.png)
## Send text ![](/images/versions/core.png)
To send text message - use `POST /api/sendText` with example payload.
```json
{
@@ -38,7 +38,7 @@ To send text message - use `POST /api/sendText` with example payload.
}
```
## Reply on message ![](/images/versions/core.png) ![](/images/versions/plus.png)
## Reply on message ![](/images/versions/core.png)
To reply on a message - use `POST /api/reply` with example payload.
```json
{
@@ -52,10 +52,10 @@ To reply on a message - use `POST /api/reply` with example payload.
### Reply files ![](/images/versions/plus-soon.png)
WAHA does not support reply with files (images, voice, etc). If you're interested in it - please create an issue in GitHub.
## Add a reaction ![](/images/versions/core.png) ![](/images/versions/plus.png)
## Add a reaction ![](/images/versions/core.png)
Use `PUT /api/reaction` method to set reaction to a message.
{{< alert icon="👉" text="Reactoin API uses PUT, not POST request! Please make sure you send right request." />}}
{{< alert icon="👉" text="Reaction API uses PUT, not POST request! Please make sure you send right request." />}}
```json
{
@@ -6,7 +6,7 @@ date: 2020-10-06T08:48:45+00:00
lastmod: 2020-10-06T08:48:45+00:00
draft: false
images: []
weight: 800
weight: 200
---
## Saving session ![](/images/versions/plus.png)
@@ -22,7 +22,22 @@ 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" >}}).
If you find it easier to follow a video tutorial rather than reading through text, we've got you covered!
A 3-minute video tutorial is available to help you navigate WhatsApp HTTP API smoothly.
<div class="d-flex justify-content-center my-4">
<iframe
width="560" height="315"
src="https://www.youtube.com/embed/RFerMyAUPRg"
title="YouTube video player"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
allowfullscreen
></iframe>
</div>
## Features
WAHA is distributed in two versions:
@@ -32,21 +47,21 @@ WAHA is distributed in two versions:
Here's the available features:
### Messages
- Send messages ![](/images/versions/core.png) ![](/images/versions/plus.png)
- Receive messages ![](/images/versions/core.png) ![](/images/versions/plus.png)
- Message replies ![](/images/versions/core.png) ![](/images/versions/plus.png)
- Send location ![](/images/versions/core.png) ![](/images/versions/plus.png)
- Send buttons ![](/images/versions/core.png) ![](/images/versions/plus.png)
- Receive location ![](/images/versions/core.png) ![](/images/versions/plus.png)
- React to messages ![](/images/versions/core.png) ![](/images/versions/plus.png)
- Send messages ![](/images/versions/core.png)
- Receive messages ![](/images/versions/core.png)
- Message replies ![](/images/versions/core.png)
- Send location ![](/images/versions/core.png)
- Send buttons ![](/images/versions/core.png)
- Receive location ![](/images/versions/core.png)
- React to messages ![](/images/versions/core.png)
- Send media (images/documents/files) ![](/images/versions/plus.png)
- Send voice messages ![](/images/versions/plus.png)
- Receive media (images/audio/video/documents) ![](/images/versions/plus.png)
- Receive messages - webhook retries ![](/images/versions/plus.png)
### Sessions
- Multi Device ![](/images/versions/core.png) ![](/images/versions/plus.png)
- Get the screenshot ![](/images/versions/core.png) ![](/images/versions/plus.png)
- Multi Device ![](/images/versions/core.png)
- Get the screenshot ![](/images/versions/core.png)
- Session saving (don't have to scan QR on every restart) ![](/images/versions/plus.png)
- Session saving on remote storage (database, redis) ![](/images/versions/plus-soon.png)
- Single WhatsApp account running inside one container ![](/images/versions/core.png)
@@ -54,26 +69,23 @@ Here's the available features:
### Security
- API authentication ![](/images/versions/plus.png)
- Swagger panel authentication ![](/images/versions/plus-soon.png)
- Swagger panel authentication ![](/images/versions/plus.png)
### Updates
- Bug fixes and updates come as soon as they are implemented. ![](/images/versions/plus.png)
- One month delay in bug fixes and updates. ![](/images/versions/core.png)
### Groups ![](/images/versions/soon.png)
- Join groups by invite
### Groups ![](/images/versions/core.png)
- 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 ![](/images/versions/soon.png)
- Mention users
### Contacts ![](/images/versions/core.png)
- Mute/unmute chats
- Block/unblock contacts
- Get contact info
- Get profile pictures
- Set user status message
@@ -13,7 +13,8 @@ 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 +32,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 rather than reading through text, we've got you covered!
A 3-minute video tutorial is available to help you navigate WhatsApp HTTP API smoothly.
<div class="d-flex justify-content-center my-4">
<iframe
width="560" height="315"
src="https://www.youtube.com/embed/RFerMyAUPRg"
title="YouTube video player"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
allowfullscreen
></iframe>
</div>
### 1. Download image
@@ -53,6 +68,28 @@ 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
![](/images/versions/core.png) 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
```
![](/images/versions/plus.png) 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:
@@ -83,8 +120,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 +130,8 @@ The example payload:
![](session-start.png)
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
![](/images/versions/plus.png) version and only one account in Core ![](/images/versions/core.png) version.
Read more about [multiple sessions →]({{< relref "/docs/how-to/sessions" >}})
@@ -123,6 +161,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",
@@ -142,6 +181,7 @@ 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" >}})
- Make sure your [API is properly protected ->]({{< relref "/docs/how-to/security" >}})
+27 -15
View File
@@ -15,6 +15,18 @@
<p class="meta"><a href="https://github.com/devlikeapro/whatsapp-http-api">Source code in GitHub.</a></p>
</div>
</div>
<div>
<div class="d-flex justify-content-center my-4">
<iframe
width="560" height="315"
src="https://www.youtube.com/embed/RFerMyAUPRg"
title="YouTube video player"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
allowfullscreen
></iframe>
</div>
</div>
</section>
{{ end }}
@@ -22,6 +34,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 +56,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 +76,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>
+1 -1
View File
@@ -1 +1 @@
{"openapi":"3.0.0","paths":{"/api/sessions/start":{"post":{"operationId":"SessionsController_start","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SessionStartRequest"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SessionDTO"}}}}},"tags":["sessions"],"security":[{"api_key":[]}]}},"/api/sessions/stop":{"post":{"operationId":"SessionsController_stop","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SessionStopRequest"}}}},"responses":{"201":{"description":""}},"tags":["sessions"],"security":[{"api_key":[]}]}},"/api/sessions":{"get":{"operationId":"SessionsController_list","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/SessionDTO"}}}}}},"tags":["sessions"],"security":[{"api_key":[]}]}},"/api/checkNumberStatus":{"get":{"operationId":"ChattingController_checkNumberStatus","summary":"Check number status","parameters":[{"name":"phone","required":true,"in":"query","schema":{"type":"string"}},{"name":"session","required":true,"in":"query","schema":{"default":"default","type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WANumberExistResult"}}}}},"tags":["chatting"],"security":[{"api_key":[]}]}},"/api/sendContactVcard":{"post":{"operationId":"ChattingController_sendContactVcard","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageContactVcardRequest"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["chatting"],"security":[{"api_key":[]}]}},"/api/sendText":{"get":{"operationId":"ChattingController_sendTextGet","summary":"Send a text message","parameters":[{"name":"phone","required":true,"in":"query","schema":{"type":"string"}},{"name":"text","required":true,"in":"query","schema":{"type":"string"}},{"name":"session","required":true,"in":"query","schema":{"default":"default","type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["chatting"],"security":[{"api_key":[]}]},"post":{"operationId":"ChattingController_sendText","summary":"Send a text message","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageTextRequest"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WAMessage"}}}}},"tags":["chatting"],"security":[{"api_key":[]}]}},"/api/sendTextButtons":{"post":{"operationId":"ChattingController_sendTextButtons","summary":"Send a text message with buttons","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageTextButtonsRequest"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["chatting"],"security":[{"api_key":[]}]}},"/api/sendLocation":{"post":{"operationId":"ChattingController_sendLocation","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageLocationRequest"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["chatting"],"security":[{"api_key":[]}]}},"/api/sendLinkPreview":{"post":{"operationId":"ChattingController_sendLinkPreview","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageLinkPreviewRequest"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["chatting"],"security":[{"api_key":[]}]}},"/api/sendImage":{"post":{"operationId":"ChattingController_sendImage","summary":"Send an image. Either from an URL or base64 data - look at the request schemas for details.","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageImageRequest"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["chatting"],"security":[{"api_key":[]}]}},"/api/sendFile":{"post":{"operationId":"ChattingController_sendFile","summary":"Send a file. Either from an URL or base64 data - look at the request schemas for details.","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageFileRequest"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["chatting"],"security":[{"api_key":[]}]}},"/api/sendVoice":{"post":{"operationId":"ChattingController_sendVoice","summary":"Send an voice message. Either from an URL or basLine truncated
{"openapi":"3.0.0","paths":{"/api/sessions/start":{"post":{"operationId":"SessionsController_start","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SessionStartRequest"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SessionDTO"}}}}},"tags":["sessions"],"security":[{"api_key":[]}]}},"/api/sessions/stop":{"post":{"operationId":"SessionsController_stop","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SessionStopRequest"}}}},"responses":{"201":{"description":""}},"tags":["sessions"],"security":[{"api_key":[]}]}},"/api/sessions":{"get":{"operationId":"SessionsController_list","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/SessionDTO"}}}}}},"tags":["sessions"],"security":[{"api_key":[]}]}},"/api/checkNumberStatus":{"get":{"operationId":"ChattingController_checkNumberStatus","summary":"Check number status","parameters":[{"name":"phone","required":true,"in":"query","schema":{"type":"string"}},{"name":"session","required":true,"in":"query","schema":{"default":"default","type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WANumberExistResult"}}}}},"tags":["chatting"],"security":[{"api_key":[]}]}},"/api/sendContactVcard":{"post":{"operationId":"ChattingController_sendContactVcard","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageContactVcardRequest"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["chatting"],"security":[{"api_key":[]}]}},"/api/sendText":{"get":{"operationId":"ChattingController_sendTextGet","summary":"Send a text message","parameters":[{"name":"phone","required":true,"in":"query","schema":{"type":"string"}},{"name":"text","required":true,"in":"query","schema":{"type":"string"}},{"name":"session","required":true,"in":"query","schema":{"default":"default","type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["chatting"],"security":[{"api_key":[]}]},"post":{"operationId":"ChattingController_sendText","summary":"Send a text message","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageTextRequest"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WAMessage"}}}}},"tags":["chatting"],"security":[{"api_key":[]}]}},"/api/sendTextButtons":{"post":{"operationId":"ChattingController_sendTextButtons","summary":"Send a text message with buttons","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageTextButtonsRequest"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["chatting"],"security":[{"api_key":[]}]}},"/api/sendLocation":{"post":{"operationId":"ChattingController_sendLocation","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageLocationRequest"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["chatting"],"security":[{"api_key":[]}]}},"/api/sendLinkPreview":{"post":{"operationId":"ChattingController_sendLinkPreview","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageLinkPreviewRequest"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["chatting"],"security":[{"api_key":[]}]}},"/api/sendImage":{"post":{"operationId":"ChattingController_sendImage","summary":"Send an image. Either from an URL or base64 data - look at the request schemas for details.","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageImageRequest"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["chatting"],"security":[{"api_key":[]}]}},"/api/sendFile":{"post":{"operationId":"ChattingController_sendFile","summary":"Send a file. Either from an URL or base64 data - look at the request schemas for details.","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageFileRequest"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["chatting"],"security":[{"api_key":[]}]}},"/api/sendVoice":{"post":{"operationId":"ChattingController_sendVoice","summary":"Send an voice message. Either from an URL or basLine truncated
+118
View File
@@ -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
![](/images/versions/core.png) 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!
-44
View File
@@ -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"
+2 -2
View File
@@ -1,2 +1,2 @@
flask
requests
flask~=1.0
requests~=2.0
@@ -0,0 +1,67 @@
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()
@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
from_ = payload["from"]
# 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=from_, text=text)
# Send OK back
return "OK"
+53
View File
@@ -0,0 +1,53 @@
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()
@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
from_ = payload["from"]
# Send a text back via WhatsApp HTTP API
send_message(chat_id=from_, text=text)
# Send OK back
return "OK"
-32623
View File
File diff suppressed because it is too large. Load diff
+2 -2
View File
@@ -43,7 +43,7 @@
"rxjs": "^7.1.0",
"swagger-ui-express": "^4.1.4",
"venom-bot": "^4.3.7",
"whatsapp-web.js": "^1.18.4"
"whatsapp-web.js": "https://github.com/pedroslopez/whatsapp-web.js"
},
"devDependencies": {
"@nestjs/cli": "^9.0.0",
@@ -51,7 +51,7 @@
"@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",
+63
View File
@@ -0,0 +1,63 @@
import {Body, Controller, Get, Post, Query} from '@nestjs/common';
import {ApiOperation, ApiSecurity, ApiTags} from "@nestjs/swagger";
import {SessionManager} from "../core/abc/manager.abc";
import {ContactQuery, ContactRequest} from "../structures/contacts.dto";
import {SessionQuery} from "../structures/base.dto";
import {CheckNumberStatusQuery} from "../structures/chatting.dto";
import {WANumberExistResult} from "../structures/responses.dto";
@ApiSecurity('api_key')
@Controller('api/contacts')
@ApiTags('contacts')
export class ContactsController {
constructor(private manager: SessionManager) {
}
@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('/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('/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)
}
}
+165
View File
@@ -0,0 +1,165 @@
import {Body, Controller, Delete, Get, Param, Post, Put} from '@nestjs/common';
import {ApiOperation, ApiSecurity, ApiTags} from "@nestjs/swagger";
import {SessionManager} from "../core/abc/manager.abc";
import {CreateGroupRequest, DescriptionRequest, ParticipantsRequest, SubjectRequest} from "../structures/groups.dto";
import {WhatsappSession} from "../core/abc/session.abc";
import {SessionApiParam, SessionParam} from "./helpers";
@ApiSecurity('api_key')
@Controller('api/:session/groups')
@ApiTags('groups')
export class GroupsController {
constructor(private manager: SessionManager) {
}
@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)
}
@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)
}
}
+35
View File
@@ -0,0 +1,35 @@
import {ApiParam} from "@nestjs/swagger";
import {Injectable, Param, PipeTransform} from "@nestjs/common";
import {SessionManager} from "../core/abc/manager.abc";
import {WhatsappSession} from "../core/abc/session.abc";
/**
* Get session name and return Whatsapp session back (if exists)
* 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)
+1 -1
View File
@@ -2,8 +2,8 @@ import {Controller, Get, Query, Res} from '@nestjs/common';
import {ApiSecurity, ApiTags} from "@nestjs/swagger";
import {Readable} from "stream";
import {Response} from 'express';
import {SessionQuery} from "../structures/chatting.dto";
import {SessionManager} from "../core/abc/manager.abc";
import {SessionQuery} from "../structures/base.dto";
@ApiSecurity('api_key')
+103 -2
View File
@@ -16,6 +16,20 @@ import {
} from "../../structures/chatting.dto";
import {MediaStorage} from "./storage.abc";
import {MessageId} from "whatsapp-web.js";
import {ContactQuery, ContactRequest} from "../../structures/contacts.dto";
import {NotImplementedByEngineError} from "../exceptions";
import {CreateGroupRequest, ParticipantsRequest} from "../../structures/groups.dto";
import * as fs from "fs";
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 abstract class WhatsappSession {
public status: WhatsappStatus;
@@ -26,8 +40,8 @@ export abstract class WhatsappSession {
this.log = log
}
getBrowserExecutablePath(){
return "/usr/bin/google-chrome-stable"
getBrowserExecutablePath() {
return getBrowserExecutablePath()
}
getBrowserArgsForPuppeteer() {
@@ -103,6 +117,93 @@ export abstract class WhatsappSession {
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()
}
/**
* Group methods
*/
public createGroup(request: CreateGroupRequest) {
throw new NotImplementedByEngineError()
}
public getGroups() {
throw new NotImplementedByEngineError()
}
public getGroup(id) {
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()
}
/**
* END - Methods for API
*/
+4
View File
@@ -5,10 +5,12 @@ import {WhatsappConfigService} from "../config.service";
import {ServeStaticModule} from "@nestjs/serve-static";
import {SessionsController} from "../api/sessions.controller";
import {ChattingController} from "../api/chatting.controller";
import {ContactsController} from "../api/contacts.controller";
import {VersionController} from "../api/version.controller";
import {PassportModule} from "@nestjs/passport";
import {SessionManager} from "./abc/manager.abc";
import {SessionManagerCore} from "./manager.core";
import {GroupsController} from "../api/groups.controller";
export const IMPORTS = [
ConfigModule.forRoot({
@@ -30,6 +32,8 @@ export const IMPORTS = [
export const CONTROLLERS = [
SessionsController,
ChattingController,
ContactsController,
GroupsController,
ScreenshotController,
VersionController,
]
+2 -1
View File
@@ -20,6 +20,7 @@ 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;
@@ -164,7 +165,7 @@ export class WhatsappSessionVenomCore extends WhatsappSession {
}
sendLocation(request: MessageLocationRequest) {
return this.whatsapp.sendLocation(request.chatId, request.latitude, request.longitude, request.title)
return this.whatsapp.sendLocation(request.chatId, String(request.latitude), String(request.longitude), request.title)
}
sendSeen(chat: ChatRequest) {
+130 -8
View File
@@ -1,5 +1,5 @@
import {UnprocessableEntityException} from "@nestjs/common/exceptions/unprocessable-entity.exception";
import {Buttons, Chat, Client, Events, Message} from "whatsapp-web.js";
import {Buttons, Chat, Client, Contact, Events, GroupChat, Location, Message} from "whatsapp-web.js";
import {Message as MessageInstance} from "whatsapp-web.js/src/structures"
import {WAEvents, WhatsappStatus} from "../structures/enums.dto";
import {WhatsappSession} from "./abc/session.abc";
@@ -19,6 +19,8 @@ import {
MessageVoiceRequest
} from "../structures/chatting.dto";
import {AvailableInPlusVersion, NotImplementedByEngineError} from "./exceptions";
import {ContactQuery, ContactRequest} from "../structures/contacts.dto";
import {CreateGroupRequest, ParticipantsRequest} from "../structures/groups.dto";
// eslint-disable-next-line @typescript-eslint/no-var-requires
const qrcode = require('qrcode-terminal');
@@ -60,7 +62,7 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
}
stop() {
return this.whatsapp.pupPage.close()
return this.whatsapp.destroy()
}
/**
@@ -73,8 +75,9 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
return await this.whatsapp.pupPage.screenshot()
}
checkNumberStatus(request: CheckNumberStatusQuery): Promise<WANumberExistResult> {
throw new NotImplementedByEngineError()
async checkNumberStatus(request: CheckNumberStatusQuery): Promise<WANumberExistResult> {
const exist = await this.whatsapp.isRegisteredUser(this.ensureSuffix(request.phone))
return {numberExists: exist}
}
sendText(request: MessageTextRequest) {
@@ -109,13 +112,19 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
throw new AvailableInPlusVersion()
}
async sendLocation(request: MessageLocationRequest) {
const location = new Location(
request.latitude,
request.longitude,
request.title,
)
return this.whatsapp.sendMessage(request.chatId, location)
}
sendLinkPreview(request: MessageLinkPreviewRequest) {
throw new NotImplementedByEngineError()
}
sendLocation(request: MessageLocationRequest) {
throw new NotImplementedByEngineError()
}
async sendSeen(request: ChatRequest) {
const chat: Chat = await this.whatsapp.getChatById(request.chatId)
@@ -144,6 +153,114 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
return message.react(request.reaction)
}
/**
* Contacts methods
*/
getContact(query: ContactQuery) {
return this.whatsapp.getContactById(this.ensureSuffix(query.contactId)).then(this.toWAContact)
}
getContacts() {
return this.whatsapp.getContacts().then(contacts => contacts.map(this.toWAContact))
}
public async getContactAbout(query: ContactQuery) {
const contact = await this.whatsapp.getContactById(this.ensureSuffix(query.contactId))
return {"about": await contact.getAbout()}
}
public async getContactProfilePicture(query: ContactQuery) {
const contact = await this.whatsapp.getContactById(this.ensureSuffix(query.contactId))
return {"profilePictureURL": await contact.getProfilePicUrl()}
}
public async blockContact(request: ContactRequest) {
const contact = await this.whatsapp.getContactById(this.ensureSuffix(request.contactId))
await contact.block()
}
public async unblockContact(request: ContactRequest) {
const contact = await this.whatsapp.getContactById(this.ensureSuffix(request.contactId))
await contact.unblock()
}
/**
* Group methods
*/
public createGroup(request: CreateGroupRequest) {
const participantIds = request.participants.map(participant => participant.id)
return this.whatsapp.createGroup(request.name, participantIds)
}
public getGroups() {
return this.whatsapp.getChats().then(chats => chats.filter(chat => chat.isGroup))
}
public getGroup(id) {
return this.whatsapp.getChatById(id)
}
public async deleteGroup(id) {
const groupChat = await this.whatsapp.getChatById(id) as GroupChat
return groupChat.delete()
}
public async leaveGroup(id) {
const groupChat = await this.whatsapp.getChatById(id) as GroupChat
return groupChat.leave()
}
public async setDescription(id, description) {
const groupChat = await this.whatsapp.getChatById(id) as GroupChat
return groupChat.setDescription(description)
}
public async setSubject(id, subject) {
const groupChat = await this.whatsapp.getChatById(id) as GroupChat
return groupChat.setSubject(subject)
}
public async getInviteCode(id): Promise<string> {
const groupChat = await this.whatsapp.getChatById(id) as GroupChat
return groupChat.getInviteCode()
}
public async revokeInviteCode(id): Promise<string> {
const groupChat = await this.whatsapp.getChatById(id) as GroupChat
await groupChat.revokeInvite()
return groupChat.getInviteCode()
}
public async getParticipants(id) {
const groupChat = await this.whatsapp.getChatById(id) as GroupChat
return groupChat.participants
}
public async addParticipants(id, request: ParticipantsRequest) {
const groupChat = await this.whatsapp.getChatById(id) as GroupChat
const participantIds = request.participants.map(participant => participant.id)
return groupChat.addParticipants(participantIds)
}
public async removeParticipants(id, request: ParticipantsRequest) {
const groupChat = await this.whatsapp.getChatById(id) as GroupChat
const participantIds = request.participants.map(participant => participant.id)
return groupChat.removeParticipants(participantIds)
}
public async promoteParticipantsToAdmin(id, request: ParticipantsRequest) {
const groupChat = await this.whatsapp.getChatById(id) as GroupChat
const participantIds = request.participants.map(participant => participant.id)
return groupChat.promoteParticipants(participantIds)
}
public async demoteParticipantsToUser(id, request: ParticipantsRequest) {
const groupChat = await this.whatsapp.getChatById(id) as GroupChat
const participantIds = request.participants.map(participant => participant.id)
return groupChat.demoteParticipants(participantIds)
}
/**
* END - Methods for API
*/
@@ -192,6 +309,12 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
})
}
protected toWAContact(contact: Contact) {
// @ts-ignore
contact.id = contact.id._serialized
return contact
}
protected async downloadMedia(message: Message) {
if (!message.hasMedia) {
return message
@@ -202,6 +325,5 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
return message
}
}
+14 -3
View File
@@ -3,7 +3,7 @@ import {INestApplication} from "@nestjs/common";
import {VERSION} from "../version";
export class SwaggerModuleCore {
configure(app: INestApplication){
configure(app: INestApplication) {
this.setUpAuth(app)
const options = new DocumentBuilder()
@@ -11,10 +11,20 @@ export class SwaggerModuleCore {
.setDescription('WhatsApp HTTP API that you can configure in a click!')
.setExternalDoc("Documentation", "https://waha.devlike.pro/")
.setVersion(VERSION.version)
.addTag('sessions', 'Control your WhatsApp sessions')
.addTag('sessions', 'Control WhatsApp sessions')
.addTag('screenshot', 'Get screenshot of WhatsApp and show QR code')
.addTag('chatting', 'Chat methods')
.addTag('other', 'Other endpoints')
.addTag(
'contacts',
`Contacts methods.<br>
Use phone number (without +) or phone number and \`@c.us\` at the end as \`contactId\`.<br>
'E.g: \`12312312310\` OR \`12312312310@c.us\`<br>`
)
.addTag(
'groups',
`Groups methods.<br>`
)
.addTag('other', 'Other methods')
.addApiKey({
type: 'apiKey',
description: 'Your secret api key',
@@ -27,6 +37,7 @@ export class SwaggerModuleCore {
SwaggerModule.setup('', app, document);
}
protected setUpAuth(app: INestApplication) {
return undefined
}
+3 -3
View File
@@ -40,7 +40,7 @@ export class WebhookConductorCore implements WebhookConductor {
this.log.log('Configuring webhooks...')
for (const event of this.events) {
try {
session.subscribe(event, (data: any) => this.callWebhook(event, data, this.url))
session.subscribe(event, (data: any) => this.callWebhook(event, session, data, this.url))
} catch (error) {
if (error instanceof NotImplementedByEngineError) {
this.log.error(error)
@@ -53,8 +53,8 @@ export class WebhookConductorCore implements WebhookConductor {
this.log.log('Webhooks were configured.')
}
public callWebhook(event, data: any, url) {
const json: WAWebhook = {event: event, payload: data}
public callWebhook(event, session: WhatsappSession, data: any, url) {
const json: WAWebhook = {event: event, session: session.name, payload: data}
this.log.log(`Sending POST to ${url}...`)
this.log.debug(`POST DATA: ${JSON.stringify(json)}`)
this.post(json, url)
+5
View File
@@ -4,6 +4,7 @@ import {AllExceptionsFilter} from "./api/exception.filter";
import {getWAHAVersion, VERSION, WAHAVersion} from "./version";
import {AppModuleCore} from "./core/app.module.core";
import {SwaggerModuleCore} from "./core/swagger.module.core";
import {json, urlencoded} from "express";
async function loadModules(): Promise<[typeof AppModuleCore, typeof SwaggerModuleCore]> {
const version = getWAHAVersion()
@@ -32,6 +33,10 @@ async function bootstrap() {
app.enableShutdownHooks();
app.useGlobalFilters(new AllExceptionsFilter());
// Allow to send big body - for images and attachments
app.use(json({limit: '50mb'}));
app.use(urlencoded({limit: '50mb'}));
// Configure swagger
const swagger = new SwaggerModule()
swagger.configure(app)
+19
View File
@@ -0,0 +1,19 @@
import {IsNotEmpty} from "class-validator";
export const WHATSAPP_DEFAULT_SESSION_NAME = "default"
/**
* Queries
*/
export class SessionQuery {
@IsNotEmpty()
session: string = WHATSAPP_DEFAULT_SESSION_NAME;
}
/**
* Requests
*/
export class SessionBaseRequest {
session = WHATSAPP_DEFAULT_SESSION_NAME
}
+3 -14
View File
@@ -1,17 +1,11 @@
import {IsNotEmpty, IsString} from "class-validator";
import {ApiExtraModels, ApiProperty, getSchemaPath} from "@nestjs/swagger";
export const WHATSAPP_DEFAULT_SESSION_NAME = "default"
import {SessionBaseRequest, SessionQuery} from "./base.dto";
/**
* Queries
*/
export class SessionQuery {
@IsNotEmpty()
session: string = WHATSAPP_DEFAULT_SESSION_NAME;
}
export class CheckNumberStatusQuery extends SessionQuery {
@IsString()
phone: string
@@ -28,11 +22,6 @@ export class MessageTextQuery extends SessionQuery {
/**
* Requests
*/
export class SessionBaseRequest {
session = WHATSAPP_DEFAULT_SESSION_NAME
}
export class ChatRequest extends SessionBaseRequest {
@ApiProperty({
example: '11111111111@c.us',
@@ -76,8 +65,8 @@ export class MessageReplyRequest extends ChatRequest {
}
export class MessageLocationRequest extends ChatRequest {
latitude: string;
longitude: string;
latitude: number;
longitude: number;
title: string;
}
+22
View File
@@ -0,0 +1,22 @@
import {IsString} from "class-validator";
import {SessionBaseRequest, SessionQuery} from "./base.dto";
/**
* Queries
*/
export class ContactQuery extends SessionQuery {
@IsString()
contactId: string
}
/**
* Requests
*/
export class ContactRequest extends SessionBaseRequest {
@IsString()
contactId: string
}
+40
View File
@@ -0,0 +1,40 @@
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>
}
+1
View File
@@ -133,6 +133,7 @@ export class WAMessage {
export class WAWebhook {
event: WAEvents
session: string
// eslint-disable-next-line @typescript-eslint/ban-types
payload: WAMessage | WAGroupNotification | object
}
+8 -1
View File
@@ -1,3 +1,6 @@
// eslint-disable-next-line @typescript-eslint/no-var-requires
import {getBrowserExecutablePath} from "./core/abc/session.abc";
// eslint-disable-next-line @typescript-eslint/no-var-requires
const fs = require('fs')
// eslint-disable-next-line @typescript-eslint/no-var-requires
@@ -24,4 +27,8 @@ export function getWAHAVersion(): WAHAVersion {
return WAHAVersion.CORE
}
export const VERSION = {version: "2022.12.11", tier: getWAHAVersion()}
export const VERSION = {
version: "2023.4.14",
tier: getWAHAVersion(),
browser: getBrowserExecutablePath()
}
-656
View File
@@ -1,656 +0,0 @@
{
"openapi": "3.0.0",
"info": {
"title": "WhatsApp HTTP API",
"description": "WhatsApp HTTP API that you can configure in a click",
"version": "1.0",
"contact": {}
},
"tags": [
{
"name": "device",
"description": "Device information"
},
{
"name": "chatting",
"description": "Chat methods"
}
],
"servers": [],
"components": {
"securitySchemes": {
"api_key": {
"type": "apiKey",
"in": "header",
"name": "X-VENOM-TOKEN",
"description": "Your secret key"
}
},
"schemas": {
"MessageContactVcard": {
"type": "object",
"properties": {
"chatId": {
"type": "string",
"example": "791231234567@c.us"
},
"contactsId": {
"type": "string"
},
"name": {
"type": "string"
}
},
"required": [
"chatId",
"contactsId",
"name"
]
},
"MessageText": {
"type": "object",
"properties": {
"chatId": {
"type": "string",
"example": "791231234567@c.us"
},
"text": {
"type": "string"
}
},
"required": [
"chatId",
"text"
]
},
"MessageLocation": {
"type": "object",
"properties": {
"chatId": {
"type": "string",
"example": "791231234567@c.us"
},
"latitude": {
"type": "string"
},
"longitude": {
"type": "string"
},
"title": {
"type": "string"
}
},
"required": [
"chatId",
"latitude",
"longitude",
"title"
]
},
"MessageLinkPreview": {
"type": "object",
"properties": {
"chatId": {
"type": "string",
"example": "791231234567@c.us"
},
"url": {
"type": "string"
},
"title": {
"type": "string"
}
},
"required": [
"chatId",
"url",
"title"
]
},
"MessageImage": {
"type": "object",
"properties": {
"chatId": {
"type": "string",
"example": "791231234567@c.us"
},
"path": {
"type": "string"
},
"filename": {
"type": "string"
},
"caption": {
"type": "string"
}
},
"required": [
"chatId",
"path",
"filename",
"caption"
]
},
"MessageFile": {
"type": "object",
"properties": {
"chatId": {
"type": "string",
"example": "791231234567@c.us"
},
"path": {
"type": "string"
},
"filename": {
"type": "string"
},
"caption": {
"type": "string"
}
},
"required": [
"chatId",
"path",
"filename",
"caption"
]
},
"MessageReply": {
"type": "object",
"properties": {
"chatId": {
"type": "string",
"example": "791231234567@c.us"
},
"reply_to": {
"type": "string",
"example": "message.id"
},
"text": {
"type": "string"
}
},
"required": [
"chatId",
"reply_to",
"text"
]
},
"Chat": {
"type": "object",
"properties": {
"chatId": {
"type": "string",
"example": "791231234567@c.us"
}
},
"required": [
"chatId"
]
}
}
},
"paths": {
"/api/sendContactVcard": {
"post": {
"operationId": "ChattingController_sendContactVcard",
"parameters": [],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MessageContactVcard"
}
}
}
},
"responses": {
"201": {
"description": "",
"content": {
"application/json": {
"schema": {
"type": "object"
}
}
}
}
},
"tags": [
"chatting"
]
}
},
"/api/sendText": {
"post": {
"operationId": "ChattingController_sendText",
"summary": "Send a text message",
"parameters": [],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MessageText"
}
}
}
},
"responses": {
"201": {
"description": "",
"content": {
"application/json": {
"schema": {
"type": "object"
}
}
}
}
},
"tags": [
"chatting"
]
}
},
"/api/sendLocation": {
"post": {
"operationId": "ChattingController_sendLocation",
"parameters": [],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MessageLocation"
}
}
}
},
"responses": {
"201": {
"description": "",
"content": {
"application/json": {
"schema": {
"type": "object"
}
}
}
}
},
"tags": [
"chatting"
]
}
},
"/api/sendLinkPreview": {
"post": {
"operationId": "ChattingController_sendLinkPreview",
"parameters": [],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MessageLinkPreview"
}
}
}
},
"responses": {
"201": {
"description": "",
"content": {
"application/json": {
"schema": {
"type": "object"
}
}
}
}
},
"tags": [
"chatting"
]
}
},
"/api/sendImage": {
"post": {
"operationId": "ChattingController_sendImage",
"summary": "NOT IMPLEMENTED YET",
"parameters": [],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MessageImage"
}
}
}
},
"responses": {
"201": {
"description": "",
"content": {
"application/json": {
"schema": {
"type": "object"
}
}
}
}
},
"tags": [
"chatting"
]
}
},
"/api/sendFile": {
"post": {
"operationId": "ChattingController_sendFile",
"summary": "NOT IMPLEMENTED YET",
"parameters": [],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MessageFile"
}
}
}
},
"responses": {
"201": {
"description": "",
"content": {
"application/json": {
"schema": {
"type": "object"
}
}
}
}
},
"tags": [
"chatting"
]
}
},
"/api/reply": {
"post": {
"operationId": "ChattingController_reply",
"summary": "Reply to a text message",
"parameters": [],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MessageReply"
}
}
}
},
"responses": {
"201": {
"description": "",
"content": {
"application/json": {
"schema": {
"type": "object"
}
}
}
}
},
"tags": [
"chatting"
]
}
},
"/api/sendSeen": {
"post": {
"operationId": "ChattingController_sendSeen",
"parameters": [],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Chat"
}
}
}
},
"responses": {
"201": {
"description": ""
}
},
"tags": [
"chatting"
]
}
},
"/api/startTyping": {
"post": {
"operationId": "ChattingController_startTyping",
"parameters": [],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Chat"
}
}
}
},
"responses": {
"201": {
"description": "",
"content": {
"application/json": {
"schema": {
"type": "boolean"
}
}
}
}
},
"tags": [
"chatting"
]
}
},
"/api/stopTyping": {
"post": {
"operationId": "ChattingController_stopTyping",
"parameters": [],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Chat"
}
}
}
},
"responses": {
"201": {
"description": "",
"content": {
"application/json": {
"schema": {
"type": "boolean"
}
}
}
}
},
"tags": [
"chatting"
]
}
},
"/api/killServiceWorker": {
"post": {
"operationId": "DeviceController_killServiceWorker",
"parameters": [],
"responses": {
"201": {
"description": "",
"content": {
"application/json": {
"schema": {
"type": "boolean"
}
}
}
}
},
"tags": [
"device"
]
}
},
"/api/restartService": {
"post": {
"operationId": "DeviceController_restartService",
"parameters": [],
"responses": {
"201": {
"description": "",
"content": {
"application/json": {
"schema": {
"type": "boolean"
}
}
}
}
},
"tags": [
"device"
]
}
},
"/api/getHostDevice": {
"get": {
"operationId": "DeviceController_getHostDevice",
"parameters": [],
"responses": {
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"type": "object"
}
}
}
}
},
"tags": [
"device"
]
}
},
"/api/getConnectionState": {
"get": {
"operationId": "DeviceController_getConnectionState",
"parameters": [],
"responses": {
"200": {
"description": ""
}
},
"tags": [
"device"
]
}
},
"/api/getBatteryLevel": {
"get": {
"operationId": "DeviceController_getBatteryLevel",
"parameters": [],
"responses": {
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"type": "number"
}
}
}
}
},
"tags": [
"device"
]
}
},
"/api/isConnected": {
"get": {
"operationId": "DeviceController_isConnected",
"parameters": [],
"responses": {
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"type": "boolean"
}
}
}
}
},
"tags": [
"device"
]
}
},
"/api/getWAVersion": {
"get": {
"operationId": "DeviceController_getWAVersion",
"parameters": [],
"responses": {
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"type": "string"
}
}
}
}
},
"tags": [
"device"
]
}
},
"/api/screenshot": {
"get": {
"operationId": "ScreenshotController_screenshot",
"parameters": [],
"responses": {
"200": {
"description": ""
}
},
"tags": [
"screenshot"
]
}
}
}
}
+8573
View File
File diff suppressed because it is too large. Load diff