Compare commits

..
39 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
50 changed files with 1796 additions and 298 deletions

No files matched your search

+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
@@ -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
@@ -6,7 +6,7 @@ date: 2020-10-06T08:48:45+00:00
lastmod: 2020-10-06T08:48:45+00:00
draft: false
images: []
weight: 126
weight: 127
---
Chats methods.
@@ -23,12 +23,12 @@ Get all chats
### Get messages from chat
Get 100 messages from the chat
`GET /api/{session}/chats/{chatId/messages?limit=100`
`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`
`GET /api/{session}/chats/{chatId}/messages?limit=100&downloadMedia=false`
### Delete chat
Use the method to delete chat
@@ -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

@@ -129,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
{
@@ -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": "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 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
+18 -11
View File
@@ -12,8 +12,19 @@ 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}`
@@ -23,6 +34,7 @@ August 2023
- 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
@@ -105,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:
@@ -149,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`.
@@ -185,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 };
+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.' })
+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[]>;
}
+64 -1
View File
@@ -12,6 +12,7 @@ import {
MessageImageRequest,
MessageLinkPreviewRequest,
MessageLocationRequest,
MessagePollRequest,
MessageReactionRequest,
MessageReplyRequest,
MessageTextButtonsRequest,
@@ -30,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';
@@ -141,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);
@@ -151,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);
@@ -240,6 +280,10 @@ export abstract class WhatsappSession {
throw new NotImplementedByEngineError();
}
public setInfoAdminsOnly(id, value) {
throw new NotImplementedByEngineError();
}
public deleteGroup(id) {
throw new NotImplementedByEngineError();
}
@@ -300,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
@@ -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 {
+9 -1
View File
@@ -8,12 +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({
@@ -35,9 +40,12 @@ export const IMPORTS = [
PassportModule,
];
export const CONTROLLERS = [
AuthController,
SessionsController,
SessionController,
ChattingController,
ChatsController,
StatusController,
ContactsController,
GroupsController,
PresenceController,
+6 -2
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,
@@ -191,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 [];
@@ -201,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,
},
];
}
+370 -26
View File
@@ -1,42 +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 } from '../helpers';
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,
@@ -46,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,
@@ -58,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');
@@ -67,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',
};
@@ -103,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),
};
}
@@ -117,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;
}
@@ -128,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);
}
@@ -143,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') {
@@ -176,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() {
@@ -186,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(
@@ -205,6 +310,9 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
}
}
/**
* Other methods
*/
async checkNumberStatus(
request: CheckNumberStatusQuery,
): Promise<WANumberExistResult> {
@@ -243,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();
}
@@ -448,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}`,
@@ -481,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;
@@ -490,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) {
@@ -512,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[],
@@ -574,6 +854,9 @@ function toCusFormat(remoteJid) {
if (!remoteJid) {
return;
}
if (remoteJid == 'me') {
return remoteJid;
}
const number = remoteJid.split('@')[0];
return ensureSuffix(number);
}
@@ -582,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;
}
@@ -622,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,
};
}
+2 -1
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';
+63 -14
View File
@@ -28,24 +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');
@@ -54,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 = {
@@ -86,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;
}
@@ -111,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(
@@ -311,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()
@@ -473,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,
+12 -1
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);
+5
View File
@@ -15,8 +15,13 @@ 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', 'Chatting methods')
.addTag(
'status',
'Status (aka stories) methods. <b>NOWEB</b> engine only!',
)
.addTag('chats', `Chats methods`)
.addTag(
'contacts',
+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) {
+7 -1
View File
@@ -25,4 +25,10 @@ function flipObject(object) {
);
}
export { flipObject, parseBool };
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;
}
+42 -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,9 +26,7 @@ export class MessageTextQuery extends SessionQuery {
}
export class ChatQuery extends SessionQuery {
@ApiProperty({
example: '11111111111@c.us',
})
@ChatIdProperty()
chatId: string;
}
@@ -42,9 +47,7 @@ export class GetPresenceQuery extends ChatQuery {}
* Requests
*/
export class ChatRequest extends SessionBaseRequest {
@ApiProperty({
example: '11111111111@c.us',
})
@ChatIdProperty()
chatId: string;
}
@@ -114,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({
@@ -174,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({
@@ -235,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;
}
+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.16',
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