Compare commits

...
45 Commits
Author SHA1 Message Date
allburov 5c666c0dd4 Use buildjet with arm
Release / docker-amd64 (push) Waiting to run
Release / docker-arm (push) Waiting to run
2023-09-04 18:39:57 +07:00
allburov 3ccc0469c0 Use buildjet 2023-09-04 18:39:55 +07:00
allburov 5ab3244d6a adjust CI 2023-09-04 18:39:52 +07:00
allburov 05bc8abbee [core] Bump version to 2023.9.3 2023-09-03 19:30:47 +07:00
allburov 8e9056a0cd [core] Add /api/sessions/{session}/me endpoint
Fix #191
2023-09-03 19:30:16 +07:00
allburov ea75b357a7 [core] Log engine events if DEBUG enabled for NOWEB 2023-09-03 16:33:08 +07:00
allburov 2b607e9b44 [core] Bump version to 2023.8.29 2023-08-29 07:49:13 +03:00
allburov 57d3528a96 [core] Apply code review suggestions 2023-08-29 07:48:51 +03:00
allburov 660a04aa52 [core] Document polls 2023-08-29 07:48:51 +03:00
allburov d0a408001d [core] poll.vote.failed 2023-08-29 07:48:51 +03:00
allburov 48391533a6 [core] poll.vote 2023-08-29 07:48:51 +03:00
allburov f2113adbbc [core] Send poll 2023-08-29 07:48:51 +03:00
allburov abfcbc7397 [core] Update WEBJS
Release / deploy-docker (push) Waiting to run
2023-08-23 16:40:46 +03:00
allburov 96f2982325 [core] Bump to 2023.8.23 2023-08-23 12:15:06 +03:00
allburov 3a521bc06c [core] Update WEBJS engine 2023-08-23 12:14:51 +03:00
allburov 6fbaec0083 [core] Bump to 2023.8.21 2023-08-21 14:25:06 +03:00
allburov cae94c7b5c [core] catch error while handling incoming message NOWEB 2023-08-21 14:24:51 +03:00
allburov bce012f692 [core] Bump version to 2023.8.19
Release / deploy-docker (push) Waiting to run
2023-08-19 12:36:08 +03:00
allburov b23b639a8b [core] Support message.ack for NOWEB, add ackName 2023-08-19 12:35:44 +03:00
allburov 5a628907ed [core] Add pairing auth method for NOWEB, add QR endpoint 2023-08-13 16:08:56 +03:00
allburov 6da479c0d7 [core] Update WEBJS engine
Release / deploy-docker (push) Waiting to run
2023-08-10 10:45:40 +03:00
allburov cbb321df6d [core] Bump to 2023.8.10 2023-08-10 07:27:36 +03:00
allburov 0a7beb6909 [core] Update webjs engine
Fix TypeError: Cannot read properties of undefined (reading 'CallCollection')
2023-08-10 07:27:33 +03:00
allburov 2105094ac3 [core] Bump version to 2023.8.8
Release / deploy-docker (push) Waiting to run
2023-08-08 08:57:31 +03:00
allburov d8fee686f8 [core] Update WEBJS and NOWEB engines 2023-08-08 08:56:23 +03:00
allburov 75fc1fd7e0 [docs] Update swagger (openapi) 2023-08-07 08:06:36 +03:00
allburov e718652491 [docs] Keep only versions as headers 2023-08-07 08:05:01 +03:00
allburov bcb49f2be7 [docs] Fix changelog for stories 2023-08-06 18:52:56 +03:00
allburov dab63e4060 [example] Send message and reply 2023-08-06 18:02:36 +03:00
allburov 3fdacbb6ac [core] set defaultQueryTimeoutMs to undefined 2023-08-01 16:42:16 +07:00
allburov f02b62e620 [plus] Show macos by default 2023-08-01 15:42:59 +07:00
allburov 51acce14a0 [plus] Add session to what's next? 2023-08-01 15:28:26 +07:00
allburov b63f6ac39e [core] Add status endpoints 2023-07-25 17:42:44 +07:00
allburov 9e107a8a76 [core] Update noweb engine 2023-07-23 16:49:21 +07:00
allburov 8c083242d3 [core] add documentation and changelog for info-admin-only 2023-07-21 16:08:04 +07:00
gqlxj1987 2a068d561f [core] Add PUT /api/groups/:id/settings/security/info-admin-only endpoint 2023-07-21 13:24:23 +07:00
allburov ef96fbf8c5 [core] Fix chatId bracket 2023-07-16 16:51:50 +07:00
allburov 2c381a0b9c [core] Add openapi json link 2023-07-16 16:41:37 +07:00
allburov fef2bcb6e2 [core] create directory before saving file
Release / deploy-docker (push) Waiting to run
2023-07-16 16:25:41 +07:00
allburov 2f6ed2fb58 [core] Add chats endpoints
Release / deploy-docker (push) Waiting to run
2023-07-16 16:14:12 +07:00
allburov 4eb23d2c09 [core] Bump version to 2023.7.16 2023-07-16 16:14:08 +07:00
allburov ec3de8dbcf [core] Init folder for a session 2023-07-16 13:30:40 +07:00
allburov f6f09747dc [core] pin node:20-bullseye
Release / deploy-docker (push) Waiting to run
2023-07-14 10:10:28 +07:00
allburov 27a534a605 [core] add set session presence endpoint 2023-07-12 16:46:15 +07:00
allburov 3cbd940843 [core] Add swagger image at the top of quick start 2023-07-07 14:26:23 +07:00
57 changed files with 2183 additions and 336 deletions

No files matched your search

+1
View File
@@ -25,6 +25,7 @@ module.exports = {
"@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",
+39 -9
View File
@@ -6,18 +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 QEMU
uses: docker/setup-qemu-action@v2
with:
platforms: 'amd64'
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
@@ -26,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'
@@ -46,3 +41,38 @@ jobs:
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 }}
+1
View File
@@ -1,4 +1,5 @@
.yarn
example.ts
.*sessions
tokens
files
+2 -2
View File
@@ -1,7 +1,7 @@
#
# Build
#
FROM node:18 as build
FROM node:20-bullseye as build
ENV PUPPETEER_SKIP_DOWNLOAD=True
# npm packages
@@ -20,7 +20,7 @@ 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
@@ -56,10 +56,16 @@
[[main]]
name = "Swagger ->"
weight = 99
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
@@ -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`
@@ -84,6 +84,17 @@ Request:
}
```
## Settings
### Security for group info
Updates the group settings to only allow admins to edit group info (title, description, photo).
`PUT /api/{session}/groups/{groupId}/settings/security/info-admin-only`
The request doesn't require any request's body and
**can't be used to disable the security setting (allow ANY to edit group info)**.
If you wish to have that ability - please [create an issue](https://github.com/devlikeapro/whatsapp-http-api/issues)
## Participants
### Get participants
Binary file not shown.

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!
![poll-example.jpg](poll-example.jpg)
## 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.
![handle-poll-vote-failed.jpg](handle-poll-vote-failed.jpg)
#### 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

@@ -12,7 +12,55 @@ weight: 801
You can get presence information (online, offline with last seen, typing status) for a contact if they share their
presence information.
{{< alert icon="👉" text="Presence endpoints and webhook is available only in **NOWEB** engine." />}}
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:
@@ -22,9 +70,6 @@ Here's few notes about fields:
- `lastKnownPresence` - contains the last known presence status, which can be
`offline`, `online`, `typing`, `recording`, or `paused`
## Endpoints
### Get all presence
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.
@@ -84,7 +129,8 @@ You can subscribe to presence information by calling `POST /api/{session}/presen
You can get later presence information for the chat with above `GET` endpoints or by listening to `presence.update`
webhook.
## Webhook
## Events
### presence.update
You can subscribe to `presence.update` webhook event to get the most recent presence information.
@@ -116,6 +116,9 @@ Fired on all message creations, including your own. The payload is the same as f
```
### message.ack
Receive events when server or recipient gets the message, read or played it.
[See details on Webhooks page ->]({{< relref "webhooks#messageack" >}}).
```json
{
@@ -155,7 +158,10 @@ If you want to limit files lifetime, specify file types for download or change d
## Endpoints
### Get messages
You can read messages from the history by using `GET /api/messages` endpoint.
Read messages from the history - use `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!" />}}
@@ -25,7 +25,13 @@ Read more about [multiple sessions →]({{< relref "/docs/how-to/sessions" >}})
For phone number `12132132131` the `chatId` is `12132132131@c.us`.
- **Groups** use random number with `@g.us` at the end.
## Send text ![](/images/versions/core.png)
When sending media (images, voice, files) you can either use:
- `file.data` field with base64 encoded file (https://base64.guru/converter/encode/file)
- `file.url` field with public available URL for that file
## Endpoints
### Send text
To send text message - use `POST /api/sendText` with example payload.
```json
{
@@ -34,7 +40,7 @@ To send text message - use `POST /api/sendText` with example payload.
"text": "Hi there!"
}
```
### Mention contact
#### 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.
@@ -52,7 +58,12 @@ also mention it in `mentions` in format `2132132130@c.us`
}
```
## Reply on message ![](/images/versions/core.png)
### Send poll
We have a dedicated page [how to send polls and receive votes]({{< relref "/docs/how-to/polls" >}})!
![](poll-example.jpg)
### Reply on message
To reply on a message - use `POST /api/reply` with example payload.
```json
{
@@ -63,10 +74,10 @@ To reply on a message - use `POST /api/reply` with example payload.
}
```
### Reply files ![](/images/versions/plus-soon.png)
#### Reply files ![](/images/versions/plus-soon.png)
WAHA does not support reply with files (images, voice, etc). If you're interested in it - please create an issue in GitHub.
## Add a reaction ![](/images/versions/core.png)
### Add a reaction
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." />}}
@@ -88,14 +99,14 @@ To remove reaction from a message - send empty string in the reaction request.
}
```
## Send image ![](/images/versions/plus.png)
### Send image ![](/images/versions/plus.png)
Use `POST /api/sendImage` to send images.
You can send images in two ways:
1. Provide a URL for the image.
2. Encode the whole file content into base64 and send it in the request body.
### URL
#### URL
```json
{
"session": "default",
@@ -109,7 +120,7 @@ You can send images in two ways:
}
```
### Base64
#### Base64
```json
{
"session": "default",
@@ -123,14 +134,14 @@ You can send images in two ways:
}
```
## Send voice ![](/images/versions/plus.png)
### Send voice ![](/images/versions/plus.png)
Use `POST /api/sendVoice` to send voice messages.
You can send voice messages in two ways:
1. Provide a URL for the voice.
2. Encode the whole file content into base64 and send it in the request body.
### URL
#### URL
```json
{
"session": "default",
@@ -144,7 +155,7 @@ You can send voice messages in two ways:
}
```
### Base64
#### Base64
```json
{
"chatId": "11111111111@c.us",
@@ -156,14 +167,15 @@ You can send voice messages in two ways:
"session": "default"
}
```
## Send file ![](/images/versions/plus.png)
### Send file ![](/images/versions/plus.png)
Use `POST /api/sendFile` to send a file as a document.
You can send files in two ways:
1. Provide a URL for the file.
2. Encode the whole file content into base64 and send it in the request body.
### URL
#### URL
```json
{
"chatId": "11111111111@c.us",
@@ -176,7 +188,7 @@ You can send files in two ways:
}
```
### Base64
#### Base64
```json
{
"session": "default",
@@ -190,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 ![](/images/versions/plus.png)
`POST /api/{session}/status/image`
```json
{
"contacts": [
"55xxxxxxxxxxx@c.us"
],
"file": {
"mimetype": "image/jpeg",
"filename": "filename.jpeg",
"data": "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"
},
"caption": "string"
}
```
#### Send voice status ![](/images/versions/plus.png)
`POST /api/{session}/status/voice`
```json
{
"contacts": [
"55xxxxxxxxxxx@c.us"
],
"file": {
"mimetype": "audio/mp4",
"filename": "voice-message.mp3",
"data": "T2dnUwACAAAAAAAAAAASgxpgAAAAAHB8DlwBE09wdXNIZWFkAQE4AcBdAAAAAABPZ2dTAAAAAAAAAAAAABKDGmABAAAATyrcUQE/T3B1c1RhZ3MNAAAATGF2ZjU4Ljc2LjEwMAEAAAAeAAAAZW5jb2Rlcj1MYXZjNTguMTM0LjEwMCBsaWJvcHVzT2dnUwAAgLsAAAAAAAASgxpgAgAAAAf0Bgo2/yaH/z2Gh/8igOHzzHp6fnvM/wqzs7S+uMGztre4wdfY+ciBeXvde3z8gndxbmxsbW1tbW1t2H/6wQbxCPBuFb1e6D6/STKCS8mcmYDGsEpkNj5o/3Dk0mzldZfsrMle4pNoErj35KH2YRHOly89zdCPPTyYOt814n+9gEBwC8B4cpoXpL6k/4GtX/b544TdyIThnpSSAtWdKEAkasngrAwGTxNVjxHWPjjf3cKN5pjU5zN1KJAvdXxxcpeL7rknh0+0d1t1WJyiqXwo6EK1FyXEwg1qhD/lnhAEXvrBYvodfLgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOXY0LALmG5Lsikw9IoA0o+jQ46rAe6YuOxUeeSjCCEN3XuHi85DrEjKfh1BZUzXH8QzxPU95vkX0BadsmMQ4TrR3sdykCyyfyeJ4jJF9AU8z/4H6w9JNRIL4ynYb9XyU77dpvXst1I1vOrp+fHXRC8vWkraBQ3BZ6hL+awQP+B+DwxiOyOTE6VzfBizhJ1rxCrWMJ4ajT0v8G+UnZthmv8thnt9E63G/ayKEJoRTDaFnqe6qxvp/UfhZe7scT2OJxTfAZOXZNM8n1FQaRb/hbcpvs3UPSk9hwXfIERCjHnrpWnYigaur47GKnqo7VsUocsJwmKyR83JIuGro3wguJ+lnpczd9sm/BEmZrmsKyhjh5ybVNbRVS8v/zFUuATiG2X6mf623bTjmfcWBjzkqNMgBNcE3L/FfNxID3UKkW0MsAt9osP1dHFRimA45mRJHIBSiMk20nzQctIIPEBTenC0jTmryhnC27ESBUReh5ggjxT2IZajZz+0h08L0hLxMebzoGKyhdh0VN3zVpJIAEuJS+g3zS+2Pv++1/Kq3SeipJiuv6jTWNanExjA5zGtUgnLJ3/E8+UcpyoM8r8IR6b6P4tNYwFaLTp5klvf2tXU30Rokpb1yz8IHOWwlOOQjx7vnmHu/7gMQk/sEy3QEHjD5DShH3BOCnEF+zH8IWK9SJ1Zr6KKZqZBz8Y+eocPYjIyeu8tNSjM/0axmrXg2ILx0JgqTMHsPplxNiSkW9Y0rhA/5ug48RR6j+bDowlS45KCgipixvxanZ7E0isxCoCrzY/WgRF4eQM9EdQ2CisYnegcokbSQs5bSzLElptBIvYpe7iwGPDRQ83nToFNU6JAik1+4n2i9psSHd5Fhz78rIoAVT7Qk/ly1Ss8leYBNR3OsWDYgmPihz4cQJilLoECBDqXWareqoluKWjU5Gf+5aBW98VMGpLE7vtecToVwl1If9z1iBVt+kObggkJoxkBVntvdolNzrpVR9sKE3JpZaLTMI01fwMsjg3LBSq+ZjbiKdIYDYNBDmIQpTwHqEjVsZzahbdkN1Z16uUNnZgwH8K/sRV5XLX8nGLYiZ9ZOWW7bG4wi5cgudmdLmSrdD7h9KWIGGJFKLoP+zqcLNNeAtJnIWdxdYZ/zBIMIitjZuV0BkrbXWDjaW1lR9OhQV28BsnyU3GJKozZAinIMx1hlDY5UHtySDTsBGF8rJZVlRFTmn3QZ3FIcMpx/OhZh2dzzQBqgGjHSrJyPDGfSIsmUt4Rdyb3KRGIBmwJKD/5yhyh1b/5qV3821sYArCvia6ydsQqzcYuRpynNaQr0iaKm2ZQlsMmWjN6jObKQSq+zx1mg+dOF0XSxezBtzdT3b/0oMK8pCA5PVljx6eOuKD80DBshcgYFAqMFULSOP111Q48X5r0Ke86y9d3a1miQnxCoPsY5eiX2nNlHMVfVVMSB4mJXzWCuSsJVQli2Ib4C/EkbDMGweg15GxmXx7iZkspv8rUinLMzh00ghftGppkF+cHTqRUoGBlzDwjmt9mHZJXP8Qe9W/WZmTrfgC9tjY2t3tHy9fcSbLWXWufJXrv3XpbbjrJab9U8aERYF/J94YfUyAn6GtASlU+I+RxQZvEOaZc4PGlHn9obkTYiecxTJvQVsHy8uHd0qlcqPpv6FXKxtNmSfzC1k9+avx9DA/7lIDM/hT0IE1hOaDl5T/ETJK3O5kp1sLgVWTxKcAxIhzLqSGLn5c59GEGbE/AS7GPdpLzJPaWRX1THRPHNFfE4OrqKp+ozs0GEBinwQ2AMyNPfu+u0UdlsiPrHsQRon9E+mdz3HnrZB5cbVLMzN7O8IOIdrGPk3Osg4O/PzYg9KZIY9Blaqy04AyPOGewGoRDxVRlBiKrbsBqlblhwu6hhMyrw/ZYZ3oH6fCKFxgEDd/qDxNs2y9EAZjvGkDY37VCuhUS1wIZM4TuZFVUytzOt5PlBNHelBLTj14q0aC0QYmBFALK6GmPb5c0L5P6itmM/9+lopZOnB5zBuuHBPCczKecFJXDx6H/uCNeFM5WszEDOKDBdaPnL9Czc0yIxqxnd3X2RjnY1xOB5pyPEqJFP9DihZt4EUgI6gQpLF8PXsqhGIowAM+wFnKEEINSnUJ0ImGwuSJ3Jrte/RWSMXlRIN08eHVBXP/z+ER4VXZcPK0aymtCLJFuZIsHbLK78WXw4P6ht+wMv933PX0WuUeNx/1ybKZy1Ke5SBbGbXUGNw1lPZkQvq2jsrAZe0mNpJXY3yvB48Oyrn0TVQB4TG5DGhcB5aBmdiHh6xjw1ivoSvhaJSTNl8xmrCsQmnE0FXk936IFC9kJ/lXVRDc+/nleToCvN4NVg6FqUwARpe367KHoE7rn4MEM7VGYv4+XolW2Z6+rurdXcMwUdAcb/oWp/r8a1pK3KqgVKZXgkA1MpSZEMcXMYNOvD+rj8F7eF1gndlg7ACVIYLrG7Qkd4FoswnsoEM0Q/V5QHKsEM7fz7sDW2c4cPdTclp0FzFaGkPob9BtwwFEwloNM3frY1f/FGnE+OvCMjiXVx01KKtnPUhcGq5jSeqGhRxwVMEg3mDZSLqxefUv493OGKFrgY9ERhh+EWntD9Q+jZXwBpyr5zw2YjipGevxvd+x/Sa0iJhzCUwOW8hcZrfZEIv5wWSIB9QUymkBS4qDueq+41007FtXA+uSV7tjrOY/8aeluNhFP+SVHsQfPIcSep4R4j1wMMnfhx2BXzYEXbcuYZ0XvH+SU2PEvV9lcANvjC5uqFHdkETszwGIWSVfyjpVwauxp7vgCp9m3/fDcQGMVxXYs8aaZ9grU1Wbm9ECTONc8/A7r188iKZhp5Cy7BY0grKwC2OvHk86EtQtnPfOMWlk615+1ey89W0EmnzlTjAZ8MefTy4/ydyzXyQqppb42qEkB7Itd3Y3lgB52zW7dmPWDFij5LEtMfot8wsZLZ2Otix+HKlPuUuOqCDzo0ESdHhERJLQctd/wjVX9LgeAx+T1VPRB/7Ro62mJf4AiAFwM2OxTe60Ko9+4AEXmP2w9vkEFcKMc2KB+WK3QG0hk61HZLPat3b+2SZJow+5AEyCV2P+KYtDpnQCEeN5eXBITlRB23IxttHmPoh1MhsJhHGs8XrpcjXZ99QqZrvlYbtjNiRimU0jNEgmuRXm64w+MF+IpdmQNA1J6GuwO2PSgbsDaiZHsOwd/u7Na3EgAJEsOq56ubhWxwjeZXjGhGv+TCtkgk6QfhRGJkVQqOQzgLwWt7UeRO0oLuN0SeaOLqNiNDK5zSNQdyPbbQzb+3PobulwT5wXda6OfZLBjI58JDH5CY6nBcszBe+GM2eyOuc511ayTOx7eoaAD2XiIyBbgvXNliKKggfyQTeEo+DGs+BHMVxj25W0ovJNmXKl6Tmu1SWXJiJENmp3Od3OEstBj+3D9/COoMoki211FjX31bYL8iW9YqugO2HxKIKjNyoHpvgdwIFVODmu4KArVF7kWa6CO0RBxif7ucVw3YKPTZ7XNd72O+05ZOVkK9pv5SKRYxlRJ/Y+0INI5kIN3isN1HOMtQkG8yJJMfNAm0+epMWwicvsvAwykdBdPjzCG2ck8z1LKIJpOe0t5NcvMLs3fOqP9v0xqYPNM6XFIBDzG7RqZ8kKb2bNbE2zbGnLWCr63833NcYZXQ7Oi+CKYqzuIq7TgKGieG1trLLl4v6Wzr+eb2Wtr9ArINDL7EpPH4s66cEtpEKDZTMEmRMIRBwKE4A6aeFHvJKqzdlMgiDFV0SIe9ST6qCdXMycDEHRjFSOFbJDnq4hBzUYhElt71yPSttjV0igcby3ulXxKe12rwkJyRWi8bQBYEPBu8jcHdOTP08Sayd2my/Z8FOXg4iDpqCRF2XuyVCm++RM2kyHmr152gJh+3h972rTyLpc2QDd1dxED9AeloJ8pEeh7lAIVCXjKhA3uTWFVtd6iFT5InLDLFb6wg9gTZg1r1JxxXGudwoXfYCZS66MFxaQeamEvsvSi1ez9ifSiOQRdDms39znSL/mgBb2vZboRJrxrD3zaoBzX2NiHOPSEAY43rlZ51VNZ2YwJ25HgjTb1tY10AfSmWNAC2VlJM3Um6psHbyn+02KmnDHJxjHpsQBnlaxc6DfDPmuNl63ntD8nxv7lvEFA0cFafqSIb/9J2SY5OSBs1OXBSgpP29fIRtBx7oKUmNVMk36/iAT7uMPML4YnCPk+53HrH6zLIOGY2Rd8wj5FJwNvAUw+UG9DrB3x5z4QuMGQX+nDtlcjCAqFIbzx/CLPho0cDNvY2PMtbYlqG7AUBaiEHqilSX1pgByRFgpE3RAdc8wSmHLNy4DKBlhz9JxuVMyExYg9lZx4kzrsnqwe7nKUcCH3J7I0qchP09c8nooyx2ByD93laIS+cuu1UQCkK3P0hk/gBXLine truncated
},
"backgroundColor": "#38b42f"
}
```
#### Send video status ![](/images/versions/plus.png)
`POST /api/{session}/status/video`
```json
{
"contacts": [
"55xxxxxxxxxxx@c.us"
],
"file": {
"mimetype": "audio/mp4",
"filename": "video-message.mp3",
"data": "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 truncated
},
"backgroundColor": "#38b42f"
}
```
Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

@@ -9,7 +9,7 @@ images: [ ]
weight: 125
---
## Endpoints
## Sessions
### Start
@@ -128,6 +128,60 @@ In order to log out the session - call `POST /api/sessions/logout`
}
```
### 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 ![](/images/versions/plus.png)
@@ -190,14 +190,32 @@ Fired on all message creations, including your own. The payload is the same as f
```
### message.ack
Receive events when server or recipient gets the message, read or played it.
`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"
}
}
```
@@ -266,6 +284,60 @@ It's an internal engine's state, not **session** `status`.
}
```
### 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 ![](/images/versions/plus.png)
### HMAC authentication
+30 -11
View File
@@ -12,6 +12,30 @@ menu:
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) -
@@ -93,40 +117,35 @@ January 2023
December 2023
- Added `GET /messages/` endpoint to get chat messages [#31](https://github.com/devlikeapro/whatsapp-http-api/issues/31)
### Security ![](/images/versions/plus.png)
- Added `WHATSAPP_SWAGGER_USERNAME` and `WHATSAPP_SWAGGER_PASSWORD` to hide and protect swagger panel.
## 2022.11
**Please test changes in test environment before update production!!**
### Engine ![](/images/versions/core.png)
**Engine** ![](/images/versions/core.png)
1. WAHA has changed its underlying engine from Venom to Whatsapp Web.JS. It might change the response and webhook's
payloads.
2. Optimize CPU and memory consumption.
### Requests ![](/images/versions/core.png)
**Requests** ![](/images/versions/core.png)
- For all `/api/session/` requests use `name` field instead of `sessionName`.
- For all "chatting" requests use `session` field instead of `sessionName`.
### Sessions ![](/images/versions/plus.png)
**Sessions** ![](/images/versions/plus.png)
Now you don't have to scan QR code each time you run WAHA, WAHA saves it for you! Available only in Plus version.
### Authentication ![](/images/versions/plus.png)
**Authentication** ![](/images/versions/plus.png)
Now you can authenticate all requests for WAHA - use `WHATSAPP_API_KEY=secret` environment variable to set "secret key".
If `WHATSAPP_API_KEY` is set - requests must have `X-Api-Key` header with `secret` value, where `secret` - any random
secret key.
### Webhooks ![](/images/versions/core.png)
#### Configuration
**Webhooks** ![](/images/versions/core.png)
Instead of setting each webhook via environment variables - we use two environments variables:
@@ -137,7 +156,7 @@ Instead of setting each webhook via environment variables - we use two environme
|----------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------|
| <pre>WHATSAPP_HOOK_ONMESSAGE=https://httpbin.org/post <br>WHATSAPP_HOOK_ONANYMESSAGE=https://httpbin.org/post </pre> | <pre>WHATSAPP_HOOK_URL=https://httpbin.org/post <br>WHATSAPP_HOOK_EVENTS=message,message.any</pre> |
#### Payload
** Webhook payload**
The data for webhooks are wrapped inside a new `WAWebhook` object with `event` and `payload` fields to help you identify
which handler you should call based on `event`.
@@ -16,6 +16,8 @@ toc: true
On this page you're going to install WAHA, run it, authenticate the client using QR code and send the first text message
to WhatsApp!
![](swagger.png)
## Requirements
Only thing that you must have - installed docker. Please follow the original
@@ -183,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" >}})
+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","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"}}}}},"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":"ChattingControllLine truncated
{"openapi":"3.0.0","paths":{"/api/{session}/auth/qr":{"get":{"operationId":"AuthController_getQR","summary":"Get QR code for pairing WhatsApp Web.","parameters":[{"name":"session","required":true,"in":"path","description":"WhatsApp session name","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["auth"],"security":[{"api_key":[]}]}},"/api/{session}/auth/request-code":{"post":{"operationId":"AuthController_requestCode","summary":"Request authentication code. NOWEB and NOWEB_MOBILE engines only","parameters":[{"name":"session","required":true,"in":"path","description":"WhatsApp session name","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RequestCodeRequest"}}}},"responses":{"201":{"description":""}},"tags":["auth"],"security":[{"api_key":[]}]}},"/api/{session}/auth/authorize-code":{"post":{"operationId":"AuthController_authorizeCode","summary":"Send OTP authentication code. NOWEB_MOBILE engine only","parameters":[{"name":"session","required":true,"in":"path","description":"WhatsApp session name","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OTPRequest"}}}},"responses":{"201":{"description":""}},"tags":["auth"],"security":[{"api_key":[]}]}},"/api/sessions/start":{"post":{"operationId":"SessionsController_start","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SessionStartRequest"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SessionDTO"}}}}},"tags":["sessions"],"security":[{"api_key":[]}]}},"/api/sessions/stop":{"post":{"operationId":"SessionsController_stop","summary":"Stop session","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SessionStopRequest"}}}},"responses":{"201":{"description":""}},"tags":["sessions"],"security":[{"api_key":[]}]}},"/api/sessions/logout":{"post":{"operationId":"SessionsController_clean","summary":"Logout from session.","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SessionLogoutRequest"}}}},"responses":{"201":{"description":""}},"tags":["sessions"],"security":[{"api_key":[]}]}},"/api/sessions":{"get":{"operationId":"SessionsController_list","parameters":[{"name":"all","required":false,"in":"query","example":false,"description":"Return all sessions, including those that are in the STOPPED state.","schema":{"type":"boolean"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/SessionDTO"}}}}}},"tags":["sessions"],"security":[{"api_key":[]}]}},"/api/checkNumberStatus":{"get":{"operationId":"ChattingController_checkNumberStatus","summary":"Check number status","parameters":[{"name":"phone","required":true,"in":"query","schema":{"type":"string"}},{"name":"session","required":true,"in":"query","schema":{"default":"default","type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WANumberExistResult"}}}}},"tags":["chatting"],"security":[{"api_key":[]}]}},"/api/sendContactVcard":{"post":{"operationId":"ChattingController_sendContactVcard","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageContactVcardRequest"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["chatting"],"security":[{"api_key":[]}]}},"/api/sendText":{"get":{"operationId":"ChattingController_sendTextGet","summary":"Send a text message","parameters":[{"name":"phone","required":true,"in":"query","schema":{"type":"string"}},{"name":"text","required":true,"in":"query","schema":{"type":"string"}},{"name":"session","required":true,"in":"query","schema":{"default":"default","type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["chatting"],"security":[{"api_key":[]}]},"post":{"operationId":"ChattingController_sendText","summary":"Send a text message","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageTextRequest"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WAMessage"}}}}},"tags":["chatting"],"security":[{"api_key":[]}]}},"/api/sendTextButtons":{"post":{"operationId":"ChattingController_sendTextButtons","summary":"Send a text message with buttons","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageTextButtonsRequest"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"taLine truncated
+1 -1
View File
@@ -82,7 +82,7 @@ def whatsapp_webhook():
# Send a text back via WhatsApp HTTP API
send_message(chat_id=chat_id, text=text)
# OR reply on the message
# reply(chat_id=chat_id, message_id=message_id, text=text)
reply(chat_id=chat_id, message_id=message_id, text=text)
# Send OK back
return "OK"
+1
View File
@@ -38,6 +38,7 @@
"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",
+67
View File
@@ -0,0 +1,67 @@
import { Body, Controller, Get, Post, Res } from '@nestjs/common';
import { ApiOperation, ApiSecurity, ApiTags } from '@nestjs/swagger';
import { SessionManager } from '../core/abc/manager.abc';
import { WhatsappSession } from '../core/abc/session.abc';
import { OTPRequest, RequestCodeRequest } from '../structures/auth.dto';
import { SessionApiParam, SessionParam } from './helpers';
import { WAHASessionStatus } from '../structures/enums.dto';
import { UnprocessableEntityException } from '@nestjs/common/exceptions/unprocessable-entity.exception';
import { Readable } from 'stream';
import { Response } from 'express';
@ApiSecurity('api_key')
@Controller('api/:session/auth')
@ApiTags('auth')
class AuthController {
constructor(private manager: SessionManager) {}
@Get('qr')
@SessionApiParam
@ApiOperation({
summary: 'Get QR code for pairing WhatsApp Web.',
})
async getQR(@Res() res: Response, @SessionParam session: WhatsappSession) {
if (session.status != WAHASessionStatus.SCAN_QR_CODE) {
const err = `Can get QR code only in SCAN_QR_CODE status. The current status is '${session.status}'`;
throw new UnprocessableEntityException(err);
}
const buffer = await session.getQR();
const stream = new Readable();
stream.push(buffer);
stream.push(null);
res.set({
'Content-Type': 'image/png',
'Content-Length': buffer.length,
});
stream.pipe(res);
}
@Post('request-code')
@SessionApiParam
@ApiOperation({
summary: 'Request authentication code. NOWEB and NOWEB_MOBILE engines only',
})
requestCode(
@SessionParam session: WhatsappSession,
@Body() request: RequestCodeRequest,
) {
return session.requestCode(request.phoneNumber, request.method);
}
@Post('authorize-code')
@SessionApiParam
@ApiOperation({
summary: 'Send OTP authentication code. NOWEB_MOBILE engine only',
})
authorizeCode(
@SessionParam session: WhatsappSession,
@Body() request: OTPRequest,
) {
return session.authorizeCode(request.code);
}
}
export { AuthController };
+56
View File
@@ -0,0 +1,56 @@
import { Controller, Delete, Get, Param, Post, Query } from '@nestjs/common';
import { ApiOperation, ApiSecurity, ApiTags } from '@nestjs/swagger';
import { SessionManager } from '../core/abc/manager.abc';
import { WhatsappSession } from '../core/abc/session.abc';
import { SessionApiParam, SessionParam } from './helpers';
import { GetChatMessagesQuery } from '../structures/chats.dto';
import { parseBool } from '../helpers';
@ApiSecurity('api_key')
@Controller('api/:session/chats')
@ApiTags('chats')
class ChatsController {
constructor(private manager: SessionManager) {}
@Get('')
@SessionApiParam
@ApiOperation({ summary: 'Get all chats' })
getAllChats(@SessionParam session: WhatsappSession) {
return session.getChats();
}
@Delete(':chatId')
@SessionApiParam
@ApiOperation({ summary: 'Deletes the chat' })
deleteChat(
@SessionParam session: WhatsappSession,
@Param('chatId') chatId: string,
) {
return session.deleteChat(chatId);
}
@Get(':chatId/messages')
@SessionApiParam
@ApiOperation({ summary: 'Gets messages in the chat' })
getChatMessages(
@Query() query: GetChatMessagesQuery,
@SessionParam session: WhatsappSession,
@Param('chatId') chatId: string,
) {
const downloadMedia = parseBool(query.downloadMedia);
return session.getChatMessages(chatId, query.limit, downloadMedia);
}
@Delete(':chatId/messages')
@SessionApiParam
@ApiOperation({ summary: 'Clears all messages from the chat' })
clearMessages(
@SessionParam session: WhatsappSession,
@Param('chatId') chatId: string,
) {
return session.clearMessages(chatId);
}
}
export { ChatsController };
+10 -1
View File
@@ -11,6 +11,7 @@ import {
MessageImageRequest,
MessageLinkPreviewRequest,
MessageLocationRequest,
MessagePollRequest,
MessageReactionRequest,
MessageReplyRequest,
MessageTextButtonsRequest,
@@ -18,8 +19,9 @@ import {
MessageTextRequest,
MessageVoiceRequest,
SendSeenRequest,
WANumberExistResult,
} from '../structures/chatting.dto';
import { WAMessage, WANumberExistResult } from '../structures/responses.dto';
import { WAMessage } from '../structures/responses.dto';
@ApiSecurity('api_key')
@Controller('api')
@@ -66,6 +68,13 @@ export class ChattingController {
return whatsapp.sendTextButtons(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('/sendLocation')
sendLocation(@Body() request: MessageLocationRequest) {
const whatsapp = this.manager.getSession(request.session);
+4 -2
View File
@@ -3,9 +3,11 @@ import { ApiOperation, ApiSecurity, ApiTags } from '@nestjs/swagger';
import { SessionManager } from '../core/abc/manager.abc';
import { SessionQuery } from '../structures/base.dto';
import { CheckNumberStatusQuery } from '../structures/chatting.dto';
import {
CheckNumberStatusQuery,
WANumberExistResult,
} from '../structures/chatting.dto';
import { ContactQuery, ContactRequest } from '../structures/contacts.dto';
import { WANumberExistResult } from '../structures/responses.dto';
@ApiSecurity('api_key')
@Controller('api/contacts')
+13
View File
@@ -49,6 +49,19 @@ export class GroupsController {
return session.getGroup(id);
}
@Put(':id/settings/security/info-admin-only')
@SessionApiParam
@ApiOperation({
summary:
'Updates the group settings to only allow admins to edit group info (title, description, photo).',
})
setGroupAdminOnly(
@SessionParam session: WhatsappSession,
@Param('id') id: string,
) {
return session.setInfoAdminsOnly(id, true);
}
@Delete(':id')
@SessionApiParam
@ApiOperation({ summary: 'Delete the group.' })
+43 -2
View File
@@ -1,10 +1,21 @@
import { Controller, Get, Param, Post } from '@nestjs/common';
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 } from '../structures/presence.dto';
import {
WAHAChatPresences,
WAHASessionPresence,
} from '../structures/presence.dto';
import { SessionApiParam, SessionParam } from './helpers';
import { WAHAPresenceStatus } from '../structures/enums.dto';
@ApiSecurity('api_key')
@Controller('api/:session/presence')
@@ -12,6 +23,36 @@ import { SessionApiParam, SessionParam } from './helpers';
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.' })
+23 -2
View File
@@ -6,16 +6,21 @@ 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 {
class SessionsController {
constructor(private manager: SessionManager) {}
@Post('/start/')
@@ -45,8 +50,24 @@ export class SessionsController {
}
@Get('/')
async list(@Query() query: ListSessionsQuery): Promise<SessionDTO[]> {
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 };
+69
View File
@@ -0,0 +1,69 @@
import {
Body,
Controller,
Delete,
Get,
Param,
Post,
Query,
} from '@nestjs/common';
import { ApiOperation, ApiSecurity, ApiTags } from '@nestjs/swagger';
import { SessionManager } from '../core/abc/manager.abc';
import { WhatsappSession } from '../core/abc/session.abc';
import { SessionApiParam, SessionParam } from './helpers';
import {
ImageStatus,
TextStatus,
VideoStatus,
VoiceStatus,
} from '../structures/status.dto';
@ApiSecurity('api_key')
@Controller('api/:session/status')
@ApiTags('status')
class StatusController {
constructor(private manager: SessionManager) {}
@Post('text')
@SessionApiParam
@ApiOperation({ summary: 'Send text status.' })
sendTextStatus(
@SessionParam session: WhatsappSession,
@Body() status: TextStatus,
) {
return session.sendTextStatus(status);
}
@Post('image')
@SessionApiParam
@ApiOperation({ summary: 'Send image status.' })
sendImageStatus(
@SessionParam session: WhatsappSession,
@Body() status: ImageStatus,
) {
return session.sendImageStatus(status);
}
@Post('voice')
@SessionApiParam
@ApiOperation({ summary: 'Send voice status.' })
sendVoiceStatus(
@SessionParam session: WhatsappSession,
@Body() status: VoiceStatus,
) {
return session.sendVoiceStatus(status);
}
@Post('video')
@SessionApiParam
@ApiOperation({ summary: 'Send video status.' })
sendVideoStatus(
@SessionParam session: WhatsappSession,
@Body() status: VideoStatus,
) {
return session.sendVideoStatus(status);
}
}
export { StatusController };
+1 -1
View File
@@ -3,7 +3,7 @@ import { ConfigService } from '@nestjs/config';
import { parseBool } from './helpers';
import { WAHAEngine } from './structures/enums.dto';
import { WebhookConfig } from './structures/webhooks.dto';
import { WebhookConfig } from './structures/webhooks.config.dto';
@Injectable()
export class WhatsappConfigService {
+2 -1
View File
@@ -3,6 +3,7 @@ import { OnApplicationShutdown } from '@nestjs/common';
import { WAHAEngine } from '../../structures/enums.dto';
import {
SessionDTO,
SessionInfo,
SessionLogoutRequest,
SessionStartRequest,
SessionStopRequest,
@@ -35,5 +36,5 @@ export abstract class SessionManager implements OnApplicationShutdown {
abstract getSession(name: string, error?: boolean): WhatsappSession;
abstract getSessions(all: boolean): Promise<SessionDTO[]>;
abstract getSessions(all: boolean): Promise<SessionInfo[]>;
}
+96 -2
View File
@@ -12,6 +12,7 @@ import {
MessageImageRequest,
MessageLinkPreviewRequest,
MessageLocationRequest,
MessagePollRequest,
MessageReactionRequest,
MessageReplyRequest,
MessageTextButtonsRequest,
@@ -22,6 +23,7 @@ import { ContactQuery, ContactRequest } from '../../structures/contacts.dto';
import {
WAHAEngine,
WAHAEvents,
WAHAPresenceStatus,
WAHASessionStatus,
} from '../../structures/enums.dto';
import {
@@ -29,9 +31,20 @@ import {
ParticipantsRequest,
} from '../../structures/groups.dto';
import { WAHAChatPresences } from '../../structures/presence.dto';
import { ProxyConfig, SessionConfig } from '../../structures/sessions.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';
const CHROME_PATH = '/usr/bin/google-chrome-stable';
const CHROMIUM_PATH = '/usr/bin/chromium';
@@ -42,6 +55,7 @@ export function getBrowserExecutablePath() {
}
return CHROMIUM_PATH;
}
export function ensureSuffix(phone) {
const suffix = '@c.us';
if (phone.includes('@')) {
@@ -139,8 +153,32 @@ export abstract class WhatsappSession {
/**
* 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);
@@ -149,6 +187,10 @@ export abstract class WhatsappSession {
abstract sendTextButtons(request: MessageTextButtonsRequest);
sendPoll(request: MessagePollRequest) {
throw new NotImplementedByEngineError();
}
abstract sendLocation(request: MessageLocationRequest);
abstract sendLinkPreview(request: MessageLinkPreviewRequest);
@@ -167,10 +209,35 @@ export abstract class WhatsappSession {
abstract stopTyping(chat: ChatRequest);
abstract getMessages(query: GetMessageQuery);
getMessages(query: GetMessageQuery) {
throw new NotImplementedByEngineError();
}
abstract setReaction(request: MessageReactionRequest);
/**
* Chats methods
*/
public getChats() {
throw new NotImplementedByEngineError();
}
public deleteChat(chatId) {
throw new NotImplementedByEngineError();
}
public getChatMessages(
chatId: string,
limit: number,
downloadMedia: boolean,
) {
throw new NotImplementedByEngineError();
}
public clearMessages(chatId) {
throw new NotImplementedByEngineError();
}
/**
* Contacts methods
*/
@@ -213,6 +280,10 @@ export abstract class WhatsappSession {
throw new NotImplementedByEngineError();
}
public setInfoAdminsOnly(id, value) {
throw new NotImplementedByEngineError();
}
public deleteGroup(id) {
throw new NotImplementedByEngineError();
}
@@ -257,6 +328,10 @@ export abstract class WhatsappSession {
throw new NotImplementedByEngineError();
}
public setPresence(presence: WAHAPresenceStatus, chatId?: string) {
throw new NotImplementedByEngineError();
}
public getPresences(): Promise<WAHAChatPresences[]> {
throw new NotImplementedByEngineError();
}
@@ -269,6 +344,25 @@ export abstract class WhatsappSession {
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
*/
+1 -1
View File
@@ -19,7 +19,7 @@ abstract class SessionStorage {
this.engine = engine;
}
abstract init(): Promise<void>;
abstract init(sessionName?: string): Promise<void>;
abstract clean(sessionName: string): Promise<void>;
+1 -1
View File
@@ -1,6 +1,6 @@
import { ConsoleLogger } from '@nestjs/common';
import { WebhookConfig } from '../../structures/webhooks.dto';
import { WebhookConfig } from '../../structures/webhooks.config.dto';
import { WhatsappSession } from './session.abc';
export abstract class WebhookSender {
+11 -1
View File
@@ -8,11 +8,17 @@ import { ContactsController } from '../api/contacts.controller';
import { GroupsController } from '../api/groups.controller';
import { PresenceController } from '../api/presence.controller';
import { ScreenshotController } from '../api/screenshot.controller';
import { SessionsController } from '../api/sessions.controller';
import {
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({
@@ -34,8 +40,12 @@ export const IMPORTS = [
PassportModule,
];
export const CONTROLLERS = [
AuthController,
SessionsController,
SessionController,
ChattingController,
ChatsController,
StatusController,
ContactsController,
GroupsController,
PresenceController,
+7 -5
View File
@@ -10,11 +10,12 @@ import { WAHAEngine, WAHASessionStatus } from '../structures/enums.dto';
import {
ProxyConfig,
SessionDTO,
SessionInfo,
SessionLogoutRequest,
SessionStartRequest,
SessionStopRequest,
} from '../structures/sessions.dto';
import { WebhookConfig } from '../structures/webhooks.dto';
import { WebhookConfig } from '../structures/webhooks.config.dto';
import { SessionManager } from './abc/manager.abc';
import {
SessionParams,
@@ -107,9 +108,6 @@ export class SessionManagerCore extends SessionManager {
const storage = new this.MediaStorageClass();
const webhookLog = new ConsoleLogger(`Webhook - ${name}`);
const webhook = new this.WebhookConductorClass(webhookLog);
await this.sessionStorage.init();
const proxyConfig = this.getProxyConfig(request);
const sessionConfig: SessionParams = {
name,
@@ -119,6 +117,7 @@ export class SessionManagerCore extends SessionManager {
proxyConfig: proxyConfig,
sessionConfig: request.config,
};
await this.sessionStorage.init(name);
// @ts-ignore
const session = new this.EngineClass(sessionConfig);
this.session = session;
@@ -193,7 +192,7 @@ export class SessionManagerCore extends SessionManager {
return session;
}
async getSessions(all: boolean): Promise<SessionDTO[]> {
async getSessions(all: boolean): Promise<SessionInfo[]> {
if (!this.session) {
if (!all) {
return [];
@@ -203,14 +202,17 @@ export class SessionManagerCore extends SessionManager {
name: this.DEFAULT,
status: WAHASessionStatus.STOPPED,
config: undefined,
me: null,
},
];
}
const me = await this.session.getSessionMeInfo().catch((err) => null);
return [
{
name: this.session.name,
status: this.session.status,
config: this.session.sessionConfig,
me: me,
},
];
}
+381 -29
View File
@@ -1,41 +1,55 @@
import makeWASocket, {
Browsers,
DisconnectReason,
getAggregateVotesInPollMessage,
getKeyAuthor,
isJidGroup,
jidNormalizedUser,
makeInMemoryStore,
PresenceData,
proto,
useMultiFileAuthState,
WAMessageContent,
WAMessageKey,
} from '@adiwajshing/baileys';
import { WABrowserDescription } 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,
GetMessageQuery,
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,
@@ -45,7 +59,13 @@ import {
WAHAChatPresences,
WAHAPresenceData,
} from '../structures/presence.dto';
import { WAMessage, WANumberExistResult } from '../structures/responses.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,
@@ -57,6 +77,7 @@ import {
} from './exceptions';
import { createAgentProxy } from './helpers.proxy';
import { QR } from './QR';
import { MeInfo } from '../structures/sessions.dto';
// eslint-disable-next-line @typescript-eslint/no-var-requires
const QRCode = require('qrcode');
@@ -66,7 +87,9 @@ 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',
};
@@ -78,6 +101,7 @@ const PresenceStatuses = {
recording: WAHAPresenceStatus.RECORDING,
paused: WAHAPresenceStatus.PAUSED,
};
const ToEnginePresenceStatus = flipObject(PresenceStatuses);
export class WhatsappSessionNoWebCore extends WhatsappSession {
engine = WAHAEngine.NOWEB;
@@ -101,9 +125,11 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
fetchAgent: agent,
auth: state,
printQRInTerminal: true,
browser: ['Linux', 'Chrome', '111.0.5563.64'] as WABrowserDescription,
browser: Browsers.macOS('Chrome'),
logger: logger,
mobile: false,
defaultQueryTimeoutMs: undefined,
getMessage: (key) => this.getMessage(key),
};
}
@@ -115,6 +141,7 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
const socketConfig = this.getSocketConfig(agent, state);
const sock: any = makeWASocket(socketConfig);
sock.ev.on(BaileysEvents.CREDS_UPDATE, saveCreds);
this.debugLogEnginesEvents(sock);
return sock;
}
@@ -126,9 +153,12 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
}
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);
}
@@ -141,6 +171,22 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
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') {
@@ -174,7 +220,19 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
});
}
});
this.events.emit(WAHAInternalEvent.engine_start);
}
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() {
@@ -184,9 +242,58 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
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(
@@ -203,6 +310,9 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
}
}
/**
* Other methods
*/
async checkNumberStatus(
request: CheckNumberStatusQuery,
): Promise<WANumberExistResult> {
@@ -241,6 +351,21 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
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();
}
@@ -305,10 +430,6 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
return this.sock.sendPresenceUpdate('paused', request.chatId);
}
async getMessages(query: GetMessageQuery) {
throw new NotImplementedByEngineError();
}
async setReaction(request: MessageReactionRequest) {
const key = parseMessageId(request.messageId);
const reactionMessage = {
@@ -414,6 +535,16 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
return this.sock.groupParticipantsUpdate(id, participants, 'demote');
}
public async setPresence(presence: WAHAPresenceStatus, chatId?: string) {
const enginePresence = ToEnginePresenceStatus[presence];
if (!enginePresence) {
throw new NotImplementedByEngineError(
`NOWEB engine doesn't support '${presence}' presence.`,
);
}
await this.sock.sendPresenceUpdate(enginePresence, chatId);
}
public async getPresences(): Promise<WAHAChatPresences[]> {
const result: WAHAChatPresences[] = [];
for (const remoteJid in this.store.presences) {
@@ -440,27 +571,68 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
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) {
return this.sock.ev.on(BaileysEvents.MESSAGES_UPSERT, ({ messages }) =>
this.handleIncomingMessages(messages, handler, false),
);
this.sock.ev.on(BaileysEvents.MESSAGES_UPSERT, ({ messages }) => {
this.handleIncomingMessages(messages, handler, false);
});
} else if (event === WAHAEvents.MESSAGE_ANY) {
return this.sock.ev.on(BaileysEvents.MESSAGES_UPSERT, ({ messages }) =>
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) {
return this.sock.ev.on(BaileysEvents.CONNECTION_UPDATE, handler);
this.sock.ev.on(BaileysEvents.CONNECTION_UPDATE, handler);
} else if (event === WAHAEvents.GROUP_JOIN) {
return this.sock.ev.on(BaileysEvents.GROUPS_UPSERT, handler);
this.sock.ev.on(BaileysEvents.GROUPS_UPSERT, handler);
} else if (event === WAHAEvents.PRESENCE_UPDATE) {
return this.sock.ev.on(BaileysEvents.PRESENCE_UPDATE, (data) =>
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}`,
@@ -473,6 +645,8 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
// 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;
@@ -482,18 +656,17 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
}
private processIncomingMessage(message) {
return this.downloadMedia(message).then(this.toWAMessage);
return this.downloadMedia(message)
.then(this.toWAMessage)
.catch((error) => {
this.log.error('Failed to process incoming message');
this.log.error(error);
console.trace(error);
});
}
protected toWAMessage(message): Promise<WAMessage> {
const isGroupMessage = Boolean(message.key.participant);
let participant: string;
let to: string;
if (isGroupMessage) {
participant = message.key.participant;
to = message.key.remoteJid;
}
const from = message.key.remoteJid;
const fromToParticipant = getFromToParticipant(message);
const id = buildMessageId(message.key);
let body = message.message.conversation;
if (!body) {
@@ -504,23 +677,138 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
return Promise.resolve({
id: id,
timestamp: message.messageTimestamp,
from: toCusFormat(from),
from: toCusFormat(fromToParticipant.from),
fromMe: message.key.fromMe,
body: body,
to: toCusFormat(to),
participant: toCusFormat(participant),
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[],
@@ -566,6 +854,9 @@ function toCusFormat(remoteJid) {
if (!remoteJid) {
return;
}
if (remoteJid == 'me') {
return remoteJid;
}
const number = remoteJid.split('@')[0];
return ensureSuffix(number);
}
@@ -574,7 +865,7 @@ function toCusFormat(remoteJid) {
* Convert from 11111111111@c.us to 11111111111@s.whatsapp.net
* @param chatId
*/
function toJID(chatId) {
export function toJID(chatId) {
if (isJidGroup(chatId)) {
return chatId;
}
@@ -614,3 +905,64 @@ function parseMessageId(messageId) {
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,
};
}
+9 -4
View File
@@ -15,13 +15,14 @@ import {
MessageReplyRequest,
MessageTextButtonsRequest,
MessageTextRequest,
WANumberExistResult,
} from '../structures/chatting.dto';
import {
WAHAEngine,
WAHAEvents,
WAHASessionStatus,
} from '../structures/enums.dto';
import { WAMessage, WANumberExistResult } from '../structures/responses.dto';
import { WAMessage } from '../structures/responses.dto';
import { WAHAInternalEvent, WhatsappSession } from './abc/session.abc';
import { NotImplementedByEngineError } from './exceptions';
import { QR } from './QR';
@@ -228,16 +229,16 @@ export class WhatsappSessionVenomCore extends WhatsappSession {
// return this.whatsapp.stopTyping(chat.chatId);
}
async getMessages(query: GetMessageQuery) {
async getChatMessages(chatId: string, limit: number) {
const messages = await this.whatsapp.getAllMessagesInChat(
query.chatId,
chatId,
true,
false,
);
// Go over messages, download media, and convert to right format.
const result = [];
for (const [count, message] of messages.entries()) {
if (count > query.limit) {
if (count > limit) {
// Have enough in the list, stop processing
break;
}
@@ -246,6 +247,10 @@ export class WhatsappSessionVenomCore extends WhatsappSession {
return result;
}
async getMessages(query: GetMessageQuery) {
return this.getChatMessages(query.chatId, query.limit);
}
setReaction(request: MessageReactionRequest) {
throw new NotImplementedByEngineError();
}
+136 -28
View File
@@ -28,23 +28,33 @@ import {
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, WANumberExistResult } from '../structures/responses.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');
@@ -53,6 +63,12 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
engine = WAHAEngine.WEBJS;
whatsapp: Client;
protected qr: QR;
public constructor(config) {
super(config);
this.qr = new QR();
}
protected buildClient() {
const clientOptions: ClientOptions = {
@@ -85,23 +101,12 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
async start() {
this.whatsapp = this.buildClient();
this.whatsapp.initialize().catch((error) => {
this.status = WAHASessionStatus.FAILED;
this.log.error(error);
return;
});
// Connect events
this.whatsapp.on(Events.QR_RECEIVED, (qr) => {
qrcode.generate(qr, { small: true });
this.status = WAHASessionStatus.SCAN_QR_CODE;
});
this.whatsapp.on(Events.AUTHENTICATED, () => {
this.status = WAHASessionStatus.WORKING;
this.log.log(`Session '${this.name}' has been authenticated!`);
});
this.listenConnectionEvents();
this.events.emit(WAHAInternalEvent.engine_start);
return this;
}
@@ -110,9 +115,49 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
return this.whatsapp.destroy();
}
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;
}
protected listenConnectionEvents() {
this.whatsapp.on(Events.QR_RECEIVED, (qr) => {
this.log.debug('QR received');
// Convert to image and save
QRCode.toDataURL(qr).then((url) => {
this.qr.save(url);
});
// Print in terminal
qrcode.generate(qr, { small: true });
this.status = WAHASessionStatus.SCAN_QR_CODE;
});
this.whatsapp.on(Events.AUTHENTICATED, () => {
this.status = WAHASessionStatus.WORKING;
this.qr.save('');
this.log.log(`Session '${this.name}' has been authenticated!`);
});
}
/**
* START - Methods for API
*/
/**
* Auth methods
*/
public async getQR(): Promise<Buffer> {
return Promise.resolve(this.qr.get());
}
async getScreenshot(): Promise<Buffer | string> {
if (this.status === WAHASessionStatus.FAILED) {
throw new UnprocessableEntityException(
@@ -211,18 +256,7 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
}
async getMessages(query: GetMessageQuery) {
const chat: Chat = await this.whatsapp.getChatById(
this.ensureSuffix(query.chatId),
);
const messages = await chat.fetchMessages({
limit: query.limit,
});
// Go over messages, download media, and convert to right format.
const result = [];
for (const message of messages) {
result.push(await this.processIncomingMessage(message));
}
return result;
return this.getChatMessages(query.chatId, query.limit, query.downloadMedia);
}
async setReaction(request: MessageReactionRequest) {
@@ -235,6 +269,39 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
return message.react(request.reaction);
}
/**
* Chats methods
*/
getChats() {
return this.whatsapp.getChats();
}
async getChatMessages(chatId: string, limit: number, downloadMedia: boolean) {
const chat: Chat = await this.whatsapp.getChatById(
this.ensureSuffix(chatId),
);
const messages = await chat.fetchMessages({
limit: limit,
});
// Go over messages, download media, and convert to right format.
const result = [];
for (const message of messages) {
const msg = await this.processIncomingMessage(message, downloadMedia);
result.push(msg);
}
return result;
}
async deleteChat(chatId) {
const chat = await this.whatsapp.getChatById(chatId);
return chat.delete();
}
async clearMessages(chatId) {
const chat = await this.whatsapp.getChatById(chatId);
return chat.clearMessages();
}
/**
* Contacts methods
*/
@@ -288,6 +355,11 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
return this.whatsapp.createGroup(request.name, participantIds);
}
public async setInfoAdminsOnly(id, value) {
const groupChat = (await this.whatsapp.getChatById(id)) as GroupChat;
return groupChat.setInfoAdminsOnly(value);
}
public getGroups() {
return this.whatsapp
.getChats()
@@ -366,6 +438,34 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
return groupChat.demoteParticipants(participantIds);
}
public async setPresence(presence: WAHAPresenceStatus, chatId?: string) {
let chat: Chat;
switch (presence) {
case WAHAPresenceStatus.ONLINE:
await this.whatsapp.sendPresenceAvailable();
break;
case WAHAPresenceStatus.OFFLINE:
await this.whatsapp.sendPresenceUnavailable();
break;
case WAHAPresenceStatus.TYPING:
chat = await this.whatsapp.getChatById(chatId);
await chat.sendStateTyping();
break;
case WAHAPresenceStatus.RECORDING:
chat = await this.whatsapp.getChatById(chatId);
await chat.sendStateRecording();
break;
case WAHAPresenceStatus.PAUSED:
chat = await this.whatsapp.getChatById(chatId);
await chat.clearState();
break;
default:
throw new NotImplementedByEngineError(
`WEBJS engine doesn't support '${presence}' presence.`,
);
}
}
/**
* END - Methods for API
*/
@@ -397,8 +497,16 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
}
}
private processIncomingMessage(message: Message) {
return this.downloadMedia(message).then(this.toWAMessage);
private async processIncomingMessage(message: Message, downloadMedia = true) {
if (downloadMedia) {
try {
message = await this.downloadMedia(message);
} catch (e) {
this.log.error('Failed when tried to download media for a message');
this.log.error(e, e.stack);
}
}
return await this.toWAMessage(message);
}
protected toWAMessage(message: Message): Promise<WAMessage> {
@@ -414,8 +522,8 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
hasMedia: Boolean(message.mediaUrl),
// @ts-ignore
mediaUrl: message.mediaUrl,
// @ts-ignore
ack: message.ack,
ackName: WAMessageAck[message.ack] || ACK_UNKNOWN,
location: message.location,
vCards: message.vCards,
_data: message.rawData,
+17 -3
View File
@@ -39,11 +39,22 @@ class LocalSessionConfigRepository extends SessionConfigRepository {
return undefined;
}
const content = await fs.readFile(filepath, 'utf-8');
// 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);
@@ -67,8 +78,11 @@ export class SessionStorageCore extends LocalSessionStorage {
return path.join(this.sessionsFolder, this.engine);
}
async init() {
await fs.mkdir(this.engineFolder, { recursive: true });
async init(sessionName?: string) {
const folder = sessionName
? this.getFolderPath(sessionName)
: this.engineFolder;
await fs.mkdir(folder, { recursive: true });
}
getFolderPath(name: string): string {
+8 -5
View File
@@ -15,8 +15,14 @@ export class SwaggerModuleCore {
.setExternalDoc('Documentation', 'https://waha.devlike.pro/')
.setVersion(VERSION.version)
.addTag('sessions', 'Control WhatsApp sessions')
.addTag('auth', 'Authentication')
.addTag('screenshot', 'Get screenshot of WhatsApp and show QR code')
.addTag('chatting', 'Chat methods')
.addTag('chatting', 'Chatting methods')
.addTag(
'status',
'Status (aka stories) methods. <b>NOWEB</b> engine only!',
)
.addTag('chats', `Chats methods`)
.addTag(
'contacts',
`Contacts methods.<br>
@@ -24,10 +30,7 @@ export class SwaggerModuleCore {
'E.g: \`12312312310\` OR \`12312312310@c.us\`<br>`,
)
.addTag('groups', `Groups methods.<br>`)
.addTag(
'presence',
`Presence information. Available in <b>NOWEB</b> engine only.<br>`,
)
.addTag('presence', `Presence information`)
.addTag('other', 'Other methods')
.addApiKey({
type: 'apiKey',
+2 -2
View File
@@ -1,12 +1,12 @@
import { ConsoleLogger } from '@nestjs/common';
import { WAHAEvents } from '../structures/enums.dto';
import { WAWebhook } from '../structures/responses.dto';
import { WebhookConfig } from '../structures/webhooks.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) {
+15 -1
View File
@@ -1,4 +1,4 @@
export function parseBool(value: any): boolean {
function parseBool(value: any): boolean {
if (!value) {
return false;
}
@@ -18,3 +18,17 @@ export function parseBool(value: any): boolean {
'Error: parseBool got unexpected value - use "true" or "false" values',
);
}
function flipObject(object) {
return Object.fromEntries(
Object.entries(object).map(([key, value]) => [value, key]),
);
}
function splitAt(str: string, index) {
const fst = [...str];
const snd = fst.splice(index);
return [fst.join(''), snd.join('')];
}
export { flipObject, parseBool, splitAt };
+24
View File
@@ -0,0 +1,24 @@
import { ApiProperty } from '@nestjs/swagger';
export class RequestCodeRequest {
@ApiProperty({
description: 'Mobile phone number in international format',
example: '12132132130',
})
phoneNumber: string;
@ApiProperty({
description:
'How would you like to receive the one time code for registration? |sms|voice. Leave empty for Web pairing.',
example: null,
required: false,
})
method: string;
}
export class OTPRequest {
code: string;
}
export class PairingCodeResponse {
code: string;
}
+21
View File
@@ -0,0 +1,21 @@
import { ApiProperty } from '@nestjs/swagger';
import { SessionBaseRequest, SessionQuery } from './base.dto';
/**
* Queries
*/
export class GetChatMessagesQuery extends SessionQuery {
limit: number = 100;
@ApiProperty({
example: true,
required: false,
description: 'Download media for messages',
})
downloadMedia: boolean = true;
}
/**
* Requests
*/
+48 -84
View File
@@ -2,6 +2,13 @@ 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
@@ -19,15 +26,19 @@ export class MessageTextQuery extends SessionQuery {
}
export class ChatQuery extends SessionQuery {
@ApiProperty({
example: '11111111111@c.us',
})
@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 {}
@@ -36,9 +47,7 @@ export class GetPresenceQuery extends ChatQuery {}
* Requests
*/
export class ChatRequest extends SessionBaseRequest {
@ApiProperty({
example: '11111111111@c.us',
})
@ChatIdProperty()
chatId: string;
}
@@ -108,47 +117,6 @@ export class MessageLocationRequest extends ChatRequest {
title: string;
}
export class RemoteFile {
@ApiProperty({
description: 'MIME type of the attachment.',
example: 'image/jpeg',
})
mimetype: string;
@ApiProperty({
description: 'Document file name. Value can be null',
example: 'filename.jpg',
})
filename?: string | null;
@ApiProperty({
example:
'https://github.com/devlikeapro/whatsapp-http-api/raw/core/examples/dev.likeapro.jpg',
})
url: string;
}
export class BinaryFile {
@ApiProperty({
description: 'MIME type of the attachment.',
example: 'image/jpeg',
})
mimetype: string;
@ApiProperty({
description: 'Document file name. Optional',
example: 'filename.jpeg',
})
filename?: string | null;
@ApiProperty({
description: 'Base64-encoded data of the file',
example:
'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',
})
data: string;
}
@ApiExtraModels(BinaryFile, RemoteFile)
class FileRequest extends ChatRequest {
@ApiProperty({
@@ -168,43 +136,6 @@ export class MessageFileRequest extends FileRequest {
caption: string;
}
export class VoiceBinaryFile {
@ApiProperty({
description: 'MIME type of the attachment.',
})
mimetype = 'audio/ogg; codecs=opus';
@ApiProperty({
description: 'Document file name. Optional',
})
filename = 'voice-message.mp3';
@ApiProperty({
description: 'Base64-encoded data of the file',
example:
'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 Line truncated
})
data: string;
}
export class VoiceRemoteFile {
@ApiProperty({
description: 'MIME type of the attachment.',
})
mimetype = 'audio/ogg; codecs=opus';
@ApiProperty({
description: 'Document file name. Optional',
})
filename = 'voice-message.mp3';
@ApiProperty({
example:
'https://github.com/devlikeapro/whatsapp-http-api/raw/core/examples/dev.likeapro.mp3',
})
url: string;
}
@ApiExtraModels(VoiceBinaryFile, VoiceRemoteFile)
export class MessageVoiceRequest extends ChatRequest {
@ApiProperty({
@@ -229,3 +160,36 @@ export class MessageReactionRequest extends MessageRequest {
})
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;
}
+13
View File
@@ -8,6 +8,8 @@ export enum WAHAEvents {
GROUP_JOIN = 'group.join',
GROUP_LEAVE = 'group.leave',
PRESENCE_UPDATE = 'presence.update',
POLL_VOTE = 'poll.vote',
POLL_VOTE_FAILED = 'poll.vote.failed',
}
export enum WAHASessionStatus {
@@ -22,6 +24,7 @@ export enum WAHAEngine {
VENOM = 'VENOM',
WEBJS = 'WEBJS',
NOWEB = 'NOWEB',
NOWEB_MOBILE = 'NOWEB_MOBILE',
}
export enum WAHAPresenceStatus {
@@ -31,3 +34,13 @@ export enum WAHAPresenceStatus {
RECORDING = 'recording',
PAUSED = 'paused',
}
export enum WAMessageAck {
ERROR = -1,
PENDING = 0,
SERVER = 1,
DEVICE = 2,
READ = 3,
PLAYED = 4,
}
export const ACK_UNKNOWN = 'UNKNOWN';
+79
View File
@@ -0,0 +1,79 @@
import { ApiProperty } from '@nestjs/swagger';
export class RemoteFile {
@ApiProperty({
description: 'MIME type of the attachment.',
example: 'image/jpeg',
})
mimetype: string;
@ApiProperty({
description: 'Document file name. Value can be null',
example: 'filename.jpg',
})
filename?: string | null;
@ApiProperty({
example:
'https://github.com/devlikeapro/whatsapp-http-api/raw/core/examples/dev.likeapro.jpg',
})
url: string;
}
export class BinaryFile {
@ApiProperty({
description: 'MIME type of the attachment.',
example: 'image/jpeg',
})
mimetype: string;
@ApiProperty({
description: 'Document file name. Optional',
example: 'filename.jpeg',
})
filename?: string | null;
@ApiProperty({
description: 'Base64-encoded data of the file',
example:
'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',
})
data: string;
}
export class VoiceBinaryFile {
@ApiProperty({
description: 'MIME type of the attachment.',
})
mimetype = 'audio/ogg; codecs=opus';
@ApiProperty({
description: 'Document file name. Optional',
})
filename = 'voice-message.mp3';
@ApiProperty({
description: 'Base64-encoded data of the file',
example:
'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 Line truncated
})
data: string;
}
export class VoiceRemoteFile {
@ApiProperty({
description: 'MIME type of the attachment.',
})
mimetype = 'audio/ogg; codecs=opus';
@ApiProperty({
description: 'Document file name. Optional',
})
filename = 'voice-message.mp3';
@ApiProperty({
example:
'https://github.com/devlikeapro/whatsapp-http-api/raw/core/examples/dev.likeapro.mp3',
})
url: string;
}
+10
View File
@@ -22,3 +22,13 @@ export class WAHAChatPresences {
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;
}
+18
View File
@@ -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 };
+10 -61
View File
@@ -1,52 +1,7 @@
import { ApiProperty } from '@nestjs/swagger';
import { WAHAEngine, WAHAEvents } from './enums.dto';
export class WANumberExistResult {
numberExists: boolean;
}
export class WAGroupNotification {
@ApiProperty({
description: 'ID that represents the groupNotification',
})
id: any;
@ApiProperty({
description: 'Unix timestamp for when the groupNotification was created',
})
timestamp: number;
@ApiProperty({
description: 'ID for the Chat that this groupNotification was sent for',
})
chatId: string;
@ApiProperty({
description: 'ContactId for the user that produced the GroupNotification',
})
author: string;
@ApiProperty({
description: 'Extra content',
})
body: string;
@ApiProperty({
description:
'Contact IDs for the users that were affected by this GroupNotification',
})
recipientIds: string[];
}
export enum WAMessageAck {
ACK_ERROR = -1,
ACK_PENDING = 0,
ACK_SERVER = 1,
ACK_DEVICE = 2,
ACK_READ = 3,
ACK_PLAYED = 4,
}
import { WAMessageAck } from './enums.dto';
import { ChatIdProperty, MessageIdProperty } from './properties.dto';
export class WALocation {
description?: string | null;
@@ -55,10 +10,7 @@ export class WALocation {
}
export class WAMessage {
@ApiProperty({
description: 'Message ID',
example: 'false_11111111111@c.us_AAAAAAAAAAAAAAAAAAAA',
})
@MessageIdProperty()
id: string;
/** */
@@ -68,7 +20,7 @@ export class WAMessage {
})
timestamp: number;
@ApiProperty({
@ChatIdProperty({
description:
'ID for the Chat that this message was sent to, except if the message was sent by the current user ',
})
@@ -79,7 +31,7 @@ export class WAMessage {
})
fromMe: boolean;
@ApiProperty({
@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.
@@ -115,6 +67,11 @@ export class WAMessage {
})
ack: WAMessageAck;
@ApiProperty({
description: 'ACK status name for the message',
})
ackName: string;
@ApiProperty({
description:
'If the message was sent to a group, this field will contain the user that sent the message.',
@@ -139,11 +96,3 @@ export class WAMessage {
})
_data?: any;
}
export class WAWebhook {
event: WAHAEvents;
session: string;
engine: WAHAEngine;
// eslint-disable-next-line @typescript-eslint/ban-types
payload: WAMessage | WAGroupNotification | object;
}
+12 -1
View File
@@ -1,7 +1,8 @@
import { ApiProperty } from '@nestjs/swagger';
import { WAHASessionStatus } from './enums.dto';
import { WebhookConfig } from './webhooks.dto';
import { WebhookConfig } from './webhooks.config.dto';
import { ChatIdProperty } from './properties.dto';
/**
* Queries
@@ -66,3 +67,13 @@ export class SessionDTO {
status: WAHASessionStatus;
config: SessionConfig;
}
export class MeInfo {
@ChatIdProperty()
id: string;
pushName: string;
}
export class SessionInfo extends SessionDTO {
me: MeInfo | null;
}
+60
View File
@@ -0,0 +1,60 @@
import { ApiExtraModels, ApiProperty, getSchemaPath } from '@nestjs/swagger';
import {
BinaryFile,
RemoteFile,
VoiceBinaryFile,
VoiceRemoteFile,
} from './files.dto';
export const BROADCAST_ID = 'status@broadcast';
class StatusRequest {
@ApiProperty({
description:
'it is always necessary to inform the list of contacts that will have access to the posted status',
example: ['55xxxxxxxxxxx@c.us'],
})
contacts = ['55xxxxxxxxxxx@c.us'];
}
export class TextStatus extends StatusRequest {
text = 'Have a look! https://waha.devlike.pro/';
backgroundColor = '#38b42f';
font = 1;
}
@ApiExtraModels(BinaryFile, RemoteFile)
export class ImageStatus extends StatusRequest {
@ApiProperty({
oneOf: [
{ $ref: getSchemaPath(BinaryFile) },
{ $ref: getSchemaPath(RemoteFile) },
],
})
file: BinaryFile | RemoteFile;
caption: string;
}
@ApiExtraModels(VoiceBinaryFile, VoiceRemoteFile)
export class VoiceStatus extends StatusRequest {
@ApiProperty({
oneOf: [
{ $ref: getSchemaPath(VoiceBinaryFile) },
{ $ref: getSchemaPath(VoiceRemoteFile) },
],
})
file: VoiceBinaryFile | VoiceRemoteFile;
backgroundColor = '#38b42f';
}
@ApiExtraModels(BinaryFile, RemoteFile)
export class VideoStatus extends StatusRequest {
@ApiProperty({
oneOf: [
{ $ref: getSchemaPath(BinaryFile) },
{ $ref: getSchemaPath(RemoteFile) },
],
})
file: BinaryFile | RemoteFile;
caption: string;
}
+60
View File
@@ -0,0 +1,60 @@
import { ApiProperty } from '@nestjs/swagger';
export class RetriesConfiguration {
@ApiProperty({
example: 2,
})
delaySeconds: number;
@ApiProperty({
example: 15,
})
attempts: number;
}
export class CustomHeader {
@ApiProperty({
example: 'X-My-Custom-Header',
})
name: string;
@ApiProperty({
example: 'Value',
})
value: string;
}
export class HmacConfiguration {
@ApiProperty({
example: 'your-secret-key',
})
key: string;
}
export class WebhookConfig {
@ApiProperty({
example: 'https://httpbin.org/post',
required: true,
})
url: string;
@ApiProperty({
example: ['message'],
required: true,
})
events: string[];
@ApiProperty({
example: null,
})
hmac?: HmacConfiguration;
@ApiProperty({
example: null,
})
retries?: RetriesConfiguration;
@ApiProperty({
example: null,
})
customHeaders?: CustomHeader[];
}
+71 -42
View File
@@ -1,60 +1,89 @@
import { ApiProperty } from '@nestjs/swagger';
export class RetriesConfiguration {
@ApiProperty({
example: 2,
})
delaySeconds: number;
import { WAHAEngine, WAHAEvents, WAMessageAck } from './enums.dto';
import { WAMessage } from './responses.dto';
import { MessageDestination } from './chatting.dto';
import { ChatIdProperty, MessageIdProperty } from './properties.dto';
@ApiProperty({
example: 15,
})
attempts: number;
}
export class CustomHeader {
@ApiProperty({
example: 'X-My-Custom-Header',
})
name: string;
export class WAMessageAckBody {
@MessageIdProperty()
id: string;
@ApiProperty({
example: 'Value',
})
value: string;
@ChatIdProperty()
from: string;
@ChatIdProperty()
to: string;
@ChatIdProperty()
participant: string;
fromMe: boolean;
ack: WAMessageAck;
ackName: string;
}
export class HmacConfiguration {
export class WAGroupPayload {
@ApiProperty({
example: 'your-secret-key',
description: 'ID that represents the groupNotification',
})
key: string;
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 WebhookConfig {
export class PollVote extends MessageDestination {
@ApiProperty({
example: 'https://httpbin.org/post',
required: true,
description: 'Option that user has selected',
example: ['Awesome!'],
})
url: string;
selectedOptions: string[];
@ApiProperty({
example: ['message'],
required: true,
description: 'Timestamp, ms',
example: 1692861369,
})
events: string[];
timestamp: number;
}
@ApiProperty({
example: null,
})
hmac?: HmacConfiguration;
export class PollVotePayload {
vote: PollVote;
poll: MessageDestination;
}
@ApiProperty({
example: null,
})
retries?: RetriesConfiguration;
@ApiProperty({
example: null,
})
customHeaders?: CustomHeader[];
export class WAWebhook {
event: WAHAEvents;
session: string;
engine: WAHAEngine;
payload:
| WAMessage
| WAGroupPayload
| WAMessageAckBody
| PollVotePayload
// eslint-disable-next-line @typescript-eslint/ban-types
| object;
}
+1 -1
View File
@@ -26,7 +26,7 @@ export function getWAHAVersion(): WAHAVersion {
}
export const VERSION = {
version: '2023.7.1',
version: '2023.9.3',
tier: getWAHAVersion(),
browser: getBrowserExecutablePath(),
};
+145 -9
View File
@@ -6,16 +6,17 @@ __metadata:
cacheKey: 8
"@adiwajshing/baileys@github:WhiskeySockets/Baileys":
version: 6.3.0
resolution: "@adiwajshing/baileys@https://github.com/WhiskeySockets/Baileys.git#commit=2dad94b1f032d38b0965402dee5c3c39b12fa7f4"
version: 6.4.0
resolution: "@adiwajshing/baileys@https://github.com/WhiskeySockets/Baileys.git#commit=49ab16f5925789b85219bec2f35686234dab08a0"
dependencies:
"@adiwajshing/keyed-db": ^0.2.4
"@hapi/boom": ^9.1.3
audio-decode: ^2.1.3
axios: ^1.3.3
cache-manager: ^5.2.2
futoin-hkdf: ^1.5.1
libphonenumber-js: ^1.10.20
libsignal: "https://github.com/adiwajshing/libsignal-node.git"
libsignal: "github:adiwajshing/libsignal-node"
music-metadata: ^7.12.3
node-cache: ^5.1.2
pino: ^7.0.0
@@ -36,7 +37,7 @@ __metadata:
optional: true
sharp:
optional: true
checksum: f563f3384be16d4a16f9f27c300a3e289fbb9375dcab518fab4312ce71a860236ea697fb0b511e84141c8bc2d3542ab56e5d96c43dd5a46ca38a19cac0c7b4f0
checksum: 0faf172db4dcfb9d6df9ff2ef7d2fe5d66aeb7c24e516197a59cbf2bb54648734d5601b992986b9be71e0774bab552be279633d79ea1fc9c8b02426b788973a1
languageName: node
linkType: hard
@@ -634,6 +635,13 @@ __metadata:
languageName: node
linkType: hard
"@eshaz/web-worker@npm:1.2.1":
version: 1.2.1
resolution: "@eshaz/web-worker@npm:1.2.1"
checksum: 515dd7d4ad28116c843e79f9092b673de5c0414739f95b095338db50d136eb912ec6a8b9e3e0ec336e8e9880a52aa2081a564a9e13dfc343c5e80b367b7c44d6
languageName: node
linkType: hard
"@eslint-community/eslint-utils@npm:^4.2.0":
version: 4.3.0
resolution: "@eslint-community/eslint-utils@npm:4.3.0"
@@ -1397,6 +1405,22 @@ __metadata:
languageName: node
linkType: hard
"@thi.ng/bitstream@npm:^2.2.12":
version: 2.2.22
resolution: "@thi.ng/bitstream@npm:2.2.22"
dependencies:
"@thi.ng/errors": ^2.2.17
checksum: d93ff9c099d72cab46cac1beaee0b6c7478a8b0dddf8915bd8f288fed37e3a28d382c3a8e019504e951c9252fdd27e3097c9774621f51a08b4380add20d2e8e3
languageName: node
linkType: hard
"@thi.ng/errors@npm:^2.2.17":
version: 2.2.17
resolution: "@thi.ng/errors@npm:2.2.17"
checksum: 3ad87cb5d04d811bf69c1caefd2443e8c0327014f028e989fef4ee30c2cce831066487a4773ec078b61298a7d6395a4c3b3dcd1de9d14e56fc03b4fab2142bef
languageName: node
linkType: hard
"@tokenizer/token@npm:^0.3.0":
version: 0.3.0
resolution: "@tokenizer/token@npm:0.3.0"
@@ -2118,6 +2142,35 @@ __metadata:
languageName: node
linkType: hard
"@wasm-audio-decoders/common@npm:9.0.1":
version: 9.0.1
resolution: "@wasm-audio-decoders/common@npm:9.0.1"
dependencies:
"@eshaz/web-worker": 1.2.1
checksum: 69cdd2e422f2792c21ca836ead308f7f7c778a3451d741aa3aba7c07f721995c3b870c7d25deec6559a4ce0d8d372b2688e5ec3a0b0193e8cd0149564704172f
languageName: node
linkType: hard
"@wasm-audio-decoders/flac@npm:^0.1.12":
version: 0.1.12
resolution: "@wasm-audio-decoders/flac@npm:0.1.12"
dependencies:
"@wasm-audio-decoders/common": 9.0.1
codec-parser: 2.4.2
checksum: c7b4793476233146de640add2edc6a40cc29f02d077ebc327f9f897f24e36b587cadeddc7342e6a58ea6bff6cb73e2e279374d958e3034e47a23c9786a5e45ce
languageName: node
linkType: hard
"@wasm-audio-decoders/ogg-vorbis@npm:^0.1.7":
version: 0.1.7
resolution: "@wasm-audio-decoders/ogg-vorbis@npm:0.1.7"
dependencies:
"@wasm-audio-decoders/common": 9.0.1
codec-parser: 2.4.2
checksum: c467e1d81848cb77f9745e9c6258f4f9928089491f68d7c17a4eb852685024e2e5b1873b4f56f3e3215261c6da567cba9f4b4c357f33dabe607c8c06971fc721
languageName: node
linkType: hard
"@webassemblyjs/ast@npm:1.11.1":
version: 1.11.1
resolution: "@webassemblyjs/ast@npm:1.11.1"
@@ -2830,6 +2883,36 @@ __metadata:
languageName: node
linkType: hard
"audio-buffer@npm:^5.0.0":
version: 5.0.0
resolution: "audio-buffer@npm:5.0.0"
checksum: cba9c85611ff28d4ab238dfadc698bd7d6c9b1c25bf6710b0289f645afb23c512a93bcbe99115d5f50412d450797a669e0ef14c45738943cef914bda1d226c60
languageName: node
linkType: hard
"audio-decode@npm:^2.1.3":
version: 2.1.4
resolution: "audio-decode@npm:2.1.4"
dependencies:
"@wasm-audio-decoders/flac": ^0.1.12
"@wasm-audio-decoders/ogg-vorbis": ^0.1.7
audio-buffer: ^5.0.0
audio-type: ^2.2.0
mpg123-decoder: ^0.4.8
node-wav: ^0.0.2
ogg-opus-decoder: ^1.6.4
qoa-format: ^1.0.0
checksum: f467a980c8eab8a2b0120b21a12dcdc7e43c3af4b9aef5ea8ce220a1c842d756b7e46c4fd5ae58f26a7cdf44eceea35ad176ff3228bcbc27c20bfef405cc29ef
languageName: node
linkType: hard
"audio-type@npm:^2.2.0":
version: 2.2.1
resolution: "audio-type@npm:2.2.1"
checksum: da588e69383321321eb951055436795906ed916a6cc10ea8c7eca82531d8a1a17d4cf287e9ca3aa3cc0932724b81f3e639da42dd358ee6f87818b9ced1db97ea
languageName: node
linkType: hard
"available-typed-arrays@npm:^1.0.5":
version: 1.0.5
resolution: "available-typed-arrays@npm:1.0.5"
@@ -3727,6 +3810,13 @@ __metadata:
languageName: node
linkType: hard
"codec-parser@npm:2.4.2":
version: 2.4.2
resolution: "codec-parser@npm:2.4.2"
checksum: 5bab5db5020ca11debc989d6539bf464325a90913cb049df1b74bfc513a9f4558871cbb340c024273939969e9fe68c3693593f154345423187c3da4e3d9af86f
languageName: node
linkType: hard
"collect-v8-coverage@npm:^1.0.0":
version: 1.0.1
resolution: "collect-v8-coverage@npm:1.0.1"
@@ -8185,14 +8275,14 @@ __metadata:
languageName: node
linkType: hard
"libphonenumber-js@npm:^1.10.20":
"libphonenumber-js@npm:^1.10.20, libphonenumber-js@npm:^1.10.36":
version: 1.10.36
resolution: "libphonenumber-js@npm:1.10.36"
checksum: e5a52e13add03db3cb8c0b7401f4cc5a4167a78965954a6e61b64bf412aacd5424e37ea6ab6ef9f77fc8994e7812bd758585f78b8fb871ca75e2c6b6507982fb
languageName: node
linkType: hard
"libsignal@https://github.com/adiwajshing/libsignal-node.git":
"libsignal@github:adiwajshing/libsignal-node":
version: 2.0.1
resolution: "libsignal@https://github.com/adiwajshing/libsignal-node.git#commit=11dbd962ea108187c79a7c46fe4d6f790e23da97"
dependencies:
@@ -8948,6 +9038,15 @@ __metadata:
languageName: node
linkType: hard
"mpg123-decoder@npm:^0.4.8":
version: 0.4.8
resolution: "mpg123-decoder@npm:0.4.8"
dependencies:
"@wasm-audio-decoders/common": 9.0.1
checksum: 66d02e91d8b68c2f66ef3740f7288d08f0cdbfed466b9966ad0f3374a6054b2e5b1c2481b3e872e16aa09cfe822859ff1d64d7bdd74347138a2105508953c771
languageName: node
linkType: hard
"ms@npm:2.0.0":
version: 2.0.0
resolution: "ms@npm:2.0.0"
@@ -9217,6 +9316,13 @@ __metadata:
languageName: node
linkType: hard
"node-wav@npm:^0.0.2":
version: 0.0.2
resolution: "node-wav@npm:0.0.2"
checksum: b2c855d3dc9b9a31cb9e5d6fb5cb852c540d084ac0a9b9174023406fb98e9c23b9327f699923a839a6c9a423a36d310a7c8382375fd9f2d41c0720c125b47195
languageName: node
linkType: hard
"node-webpmux@npm:^3.1.0":
version: 3.1.7
resolution: "node-webpmux@npm:3.1.7"
@@ -9396,6 +9502,17 @@ __metadata:
languageName: node
linkType: hard
"ogg-opus-decoder@npm:^1.6.4":
version: 1.6.4
resolution: "ogg-opus-decoder@npm:1.6.4"
dependencies:
"@wasm-audio-decoders/common": 9.0.1
codec-parser: 2.4.2
opus-decoder: 0.7.1
checksum: d3207ce61332aa972431c1ca4fa633c859538f41d0dd79cab0b57cbebc94b17aa8228f3afc9036b0d219abe6a435595a25b588ec052ca1bcfefeb64680816cc3
languageName: node
linkType: hard
"on-exit-leak-free@npm:^0.2.0":
version: 0.2.0
resolution: "on-exit-leak-free@npm:0.2.0"
@@ -9467,6 +9584,15 @@ __metadata:
languageName: node
linkType: hard
"opus-decoder@npm:0.7.1":
version: 0.7.1
resolution: "opus-decoder@npm:0.7.1"
dependencies:
"@wasm-audio-decoders/common": 9.0.1
checksum: 015b6a16d4dd8764bd6ef3b2f0010fd7553fe8d7d32aadb2154543563bd68a8bb7df7fe7407ec8b58e6451b241750f8f5a110928130012ec948f68ca4f4ab426
languageName: node
linkType: hard
"ora@npm:5.4.1, ora@npm:^5.4.1":
version: 5.4.1
resolution: "ora@npm:5.4.1"
@@ -10283,6 +10409,15 @@ __metadata:
languageName: node
linkType: hard
"qoa-format@npm:^1.0.0":
version: 1.0.0
resolution: "qoa-format@npm:1.0.0"
dependencies:
"@thi.ng/bitstream": ^2.2.12
checksum: 666fbaa73b6bdbd25d4729557d15d3412195bb51667477556f0c6d93ff9b1e099c6983de20f601423ef206b0bcc87eb7542f19a36f8754ab6ff92d5c2cfc2f0b
languageName: node
linkType: hard
"qrcode-terminal@npm:^0.12.0":
version: 0.12.0
resolution: "qrcode-terminal@npm:0.12.0"
@@ -12988,6 +13123,7 @@ __metadata:
file-type: 16.5.4
https-proxy-agent: ^7.0.0
jest: 26.4.2
libphonenumber-js: ^1.10.36
link-preview-js: ^3.0.4
lodash: ^4.17.21
mime-types: ^2.1.27
@@ -13014,8 +13150,8 @@ __metadata:
linkType: soft
"whatsapp-web.js@https://github.com/pedroslopez/whatsapp-web.js":
version: 1.21.0
resolution: "whatsapp-web.js@https://github.com/pedroslopez/whatsapp-web.js.git#commit=8fc6e6098c88e7280074e4a9bdaabedc0646589e"
version: 1.22.1
resolution: "whatsapp-web.js@https://github.com/pedroslopez/whatsapp-web.js.git#commit=abac063b779570729476cf42e29dc694e5345ca6"
dependencies:
"@pedroslopez/moduleraid": ^5.0.2
archiver: ^5.3.1
@@ -13034,7 +13170,7 @@ __metadata:
optional: true
unzipper:
optional: true
checksum: 76e6defad7b2e5b8ce69771f7cefab33c5a911d71ac056b4e0c92c9cf4f2e19be5cb85932bce8141e89dd3592325010e7f93093ab2794a3a186d7e4910a69a7b
checksum: 2100a4c826d25c5697fe304f6e036556b4c1c28f310262389c17b10344a182b0203eac6ac8823e3624c1e0c1b93187bd9447a1775aa69e01c7329659a6edcebc
languageName: node
linkType: hard