Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5c666c0dd4 | ||
|
|
3ccc0469c0 | ||
|
|
5ab3244d6a | ||
|
|
05bc8abbee | ||
|
|
8e9056a0cd | ||
|
|
ea75b357a7 | ||
|
|
2b607e9b44 | ||
|
|
57d3528a96 | ||
|
|
660a04aa52 | ||
|
|
d0a408001d | ||
|
|
48391533a6 | ||
|
|
f2113adbbc | ||
|
|
abfcbc7397 | ||
|
|
96f2982325 | ||
|
|
3a521bc06c | ||
|
|
6fbaec0083 | ||
|
|
cae94c7b5c |
No files matched your search
@@ -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 }}
|
||||
@@ -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.
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 44 KiB |
@@ -0,0 +1,219 @@
|
||||
---
|
||||
title: "Polls"
|
||||
description: "How to send polls and receive votes"
|
||||
lead: ""
|
||||
date: 2020-10-06T08:48:45+00:00
|
||||
lastmod: 2020-10-06T08:48:45+00:00
|
||||
draft: false
|
||||
images: [ ]
|
||||
weight: 126
|
||||
---
|
||||
|
||||
**Available on NOWEB engine only**.
|
||||
|
||||
Waiting for WEBJS engine to support it, and we'll add it ASAP!
|
||||
|
||||

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

|
||||
|
||||
#### How to test poll.vote.failed events?
|
||||
|
||||
To receive `poll.vote.failed` events, follow these steps:
|
||||
|
||||
1. Start a session and authorize it with a QR code.
|
||||
2. Send a poll to a chat.
|
||||
3. Stop the session (logout is not required).
|
||||
4. Start the session again.
|
||||
5. Vote on the poll.
|
||||
6. You will receive a `poll.vote.failed` event.
|
||||
|
||||
#### Why does the poll.vote.failed event occur and when will it be fixed?
|
||||
|
||||
The issue occurs because WAHA does not have a proper storage system, but polls require proper storage in order to
|
||||
decrypt votes later.
|
||||
|
||||
There will be two fixes for this:
|
||||
|
||||
1. In the short term, a [local file storage](https://github.com/devlikeapro/whatsapp-http-api/issues/188) will be used
|
||||
to save poll keys.
|
||||
2. In the long term, work is being done
|
||||
on [remote storages](https://github.com/devlikeapro/whatsapp-http-api/issues/41).
|
||||
|
||||
Even after these fixes are implemented, it's better to handle `poll.vote.failed` events anyway, so your application is
|
||||
prepared for such cases!
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 50 KiB |
@@ -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.
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ When sending media (images, voice, files) you can either use:
|
||||
|
||||
## Endpoints
|
||||
|
||||
### Send text 
|
||||
### Send text
|
||||
To send text message - use `POST /api/sendText` with example payload.
|
||||
```json
|
||||
{
|
||||
@@ -58,7 +58,12 @@ also mention it in `mentions` in format `2132132130@c.us`
|
||||
}
|
||||
```
|
||||
|
||||
### Reply on message 
|
||||
### Send poll
|
||||
We have a dedicated page [how to send polls and receive votes]({{< relref "/docs/how-to/polls" >}})!
|
||||
|
||||

|
||||
|
||||
### Reply on message
|
||||
To reply on a message - use `POST /api/reply` with example payload.
|
||||
```json
|
||||
{
|
||||
@@ -72,7 +77,7 @@ To reply on a message - use `POST /api/reply` with example payload.
|
||||
#### Reply files 
|
||||
WAHA does not support reply with files (images, voice, etc). If you're interested in it - please create an issue in GitHub.
|
||||
|
||||
### Add a reaction 
|
||||
### Add a reaction
|
||||
Use `PUT /api/reaction` method to set reaction to a message.
|
||||
|
||||
{{< alert icon="👉" text="Reaction API uses PUT, not POST request! Please make sure you send right request." />}}
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 50 KiB |
@@ -128,6 +128,28 @@ 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
|
||||
|
||||
@@ -284,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 
|
||||
### HMAC authentication
|
||||
|
||||
|
||||
@@ -14,6 +14,8 @@ 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)
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
MessageImageRequest,
|
||||
MessageLinkPreviewRequest,
|
||||
MessageLocationRequest,
|
||||
MessagePollRequest,
|
||||
MessageReactionRequest,
|
||||
MessageReplyRequest,
|
||||
MessageTextButtonsRequest,
|
||||
@@ -67,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);
|
||||
|
||||
@@ -51,8 +51,14 @@ export class GroupsController {
|
||||
|
||||
@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) {
|
||||
@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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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 };
|
||||
@@ -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[]>;
|
||||
}
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
MessageImageRequest,
|
||||
MessageLinkPreviewRequest,
|
||||
MessageLocationRequest,
|
||||
MessagePollRequest,
|
||||
MessageReactionRequest,
|
||||
MessageReplyRequest,
|
||||
MessageTextButtonsRequest,
|
||||
@@ -30,7 +31,11 @@ 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';
|
||||
@@ -167,6 +172,10 @@ export abstract class WhatsappSession {
|
||||
|
||||
abstract getScreenshot(): Promise<Buffer | string>;
|
||||
|
||||
public getSessionMeInfo(): Promise<MeInfo | null> {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
/**
|
||||
* Other methods
|
||||
*/
|
||||
@@ -178,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);
|
||||
|
||||
@@ -8,7 +8,10 @@ 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';
|
||||
@@ -39,6 +42,7 @@ export const IMPORTS = [
|
||||
export const CONTROLLERS = [
|
||||
AuthController,
|
||||
SessionsController,
|
||||
SessionController,
|
||||
ChattingController,
|
||||
ChatsController,
|
||||
StatusController,
|
||||
|
||||
@@ -10,6 +10,7 @@ import { WAHAEngine, WAHASessionStatus } from '../structures/enums.dto';
|
||||
import {
|
||||
ProxyConfig,
|
||||
SessionDTO,
|
||||
SessionInfo,
|
||||
SessionLogoutRequest,
|
||||
SessionStartRequest,
|
||||
SessionStopRequest,
|
||||
@@ -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,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
+224
-19
@@ -1,15 +1,23 @@
|
||||
import makeWASocket, {
|
||||
Browsers,
|
||||
DisconnectReason,
|
||||
getAggregateVotesInPollMessage,
|
||||
getKeyAuthor,
|
||||
isJidGroup,
|
||||
jidNormalizedUser,
|
||||
makeInMemoryStore,
|
||||
PresenceData,
|
||||
proto,
|
||||
useMultiFileAuthState,
|
||||
WAMessageContent,
|
||||
WAMessageKey,
|
||||
} from '@adiwajshing/baileys';
|
||||
import { UnprocessableEntityException } from '@nestjs/common';
|
||||
import { request } from 'express';
|
||||
import * as fs from 'fs/promises';
|
||||
import { Agent } from 'https';
|
||||
import * as lodash from 'lodash';
|
||||
import { update } from 'lodash';
|
||||
import { PairingCodeResponse } from 'src/structures/auth.dto';
|
||||
import { Message } from 'whatsapp-web.js';
|
||||
|
||||
@@ -19,10 +27,12 @@ import {
|
||||
ChatRequest,
|
||||
CheckNumberStatusQuery,
|
||||
MessageContactVcardRequest,
|
||||
MessageDestination,
|
||||
MessageFileRequest,
|
||||
MessageImageRequest,
|
||||
MessageLinkPreviewRequest,
|
||||
MessageLocationRequest,
|
||||
MessagePollRequest,
|
||||
MessageReactionRequest,
|
||||
MessageReplyRequest,
|
||||
MessageTextButtonsRequest,
|
||||
@@ -51,6 +61,11 @@ import {
|
||||
} from '../structures/presence.dto';
|
||||
import { WAMessage } from '../structures/responses.dto';
|
||||
import { BROADCAST_ID, TextStatus } from '../structures/status.dto';
|
||||
import {
|
||||
PollVote,
|
||||
PollVotePayload,
|
||||
WAMessageAckBody,
|
||||
} from '../structures/webhooks.dto';
|
||||
import {
|
||||
ensureSuffix,
|
||||
WAHAInternalEvent,
|
||||
@@ -62,8 +77,7 @@ import {
|
||||
} from './exceptions';
|
||||
import { createAgentProxy } from './helpers.proxy';
|
||||
import { QR } from './QR';
|
||||
import { WAMessageAckBody } from '../structures/webhooks.dto';
|
||||
import { update } from 'lodash';
|
||||
import { MeInfo } from '../structures/sessions.dto';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const QRCode = require('qrcode');
|
||||
@@ -115,6 +129,7 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
|
||||
logger: logger,
|
||||
mobile: false,
|
||||
defaultQueryTimeoutMs: undefined,
|
||||
getMessage: (key) => this.getMessage(key),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -126,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;
|
||||
}
|
||||
|
||||
@@ -159,6 +175,16 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
|
||||
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) => {
|
||||
@@ -196,6 +222,19 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
|
||||
});
|
||||
}
|
||||
|
||||
protected debugLogEnginesEvents(sock) {
|
||||
if (!process.env.DEBUG) {
|
||||
return;
|
||||
}
|
||||
|
||||
sock.ev.process((events) => {
|
||||
this.log.debug('Engine events');
|
||||
this.log.debug('=============');
|
||||
console.log(JSON.stringify(events));
|
||||
this.log.debug('=============');
|
||||
});
|
||||
}
|
||||
|
||||
stop() {
|
||||
this.sock.ev.removeAllListeners();
|
||||
this.sock.ws.removeAllListeners();
|
||||
@@ -203,6 +242,19 @@ 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
|
||||
*/
|
||||
@@ -299,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();
|
||||
}
|
||||
@@ -523,9 +590,9 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
|
||||
|
||||
subscribe(event, handler) {
|
||||
if (event === WAHAEvents.MESSAGE) {
|
||||
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) {
|
||||
this.sock.ev.on(BaileysEvents.MESSAGES_UPSERT, ({ messages }) =>
|
||||
this.handleIncomingMessages(messages, handler, true),
|
||||
@@ -535,6 +602,7 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
|
||||
this.sock.ev.on(BaileysEvents.MESSAGES_UPDATE, (events) => {
|
||||
events
|
||||
.filter(isMine)
|
||||
.filter(isAckUpdateMessageEvent)
|
||||
.map(this.convertMessageUpdateToMessageAck)
|
||||
.forEach(handler);
|
||||
});
|
||||
@@ -553,6 +621,18 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
|
||||
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}`,
|
||||
@@ -565,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;
|
||||
@@ -574,11 +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 destination = getDestination(message);
|
||||
const fromToParticipant = getFromToParticipant(message);
|
||||
const id = buildMessageId(message.key);
|
||||
let body = message.message.conversation;
|
||||
if (!body) {
|
||||
@@ -589,11 +677,11 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
|
||||
return Promise.resolve({
|
||||
id: id,
|
||||
timestamp: message.messageTimestamp,
|
||||
from: toCusFormat(destination.from),
|
||||
from: toCusFormat(fromToParticipant.from),
|
||||
fromMe: message.key.fromMe,
|
||||
body: body,
|
||||
to: toCusFormat(destination.to),
|
||||
participant: toCusFormat(destination.participant),
|
||||
to: toCusFormat(fromToParticipant.to),
|
||||
participant: toCusFormat(fromToParticipant.participant),
|
||||
// @ts-ignore
|
||||
hasMedia: Boolean(message.mediaUrl),
|
||||
// @ts-ignore
|
||||
@@ -607,24 +695,26 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
|
||||
_data: message,
|
||||
});
|
||||
}
|
||||
|
||||
protected convertMessageUpdateToMessageAck(event): WAMessageAckBody {
|
||||
const message = event;
|
||||
const destination = getDestination(message);
|
||||
const fromToParticipant = getFromToParticipant(message);
|
||||
const id = buildMessageId(message.key);
|
||||
const ack = message.update.status - 1;
|
||||
const body: WAMessageAckBody = {
|
||||
id: id,
|
||||
from: toCusFormat(destination.from),
|
||||
to: toCusFormat(destination.to),
|
||||
participant: toCusFormat(destination.participant),
|
||||
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 destination = getDestination(event);
|
||||
const fromToParticipant = getFromToParticipant(event);
|
||||
const id = buildMessageId(event.key);
|
||||
|
||||
const receipt = event.receipt;
|
||||
@@ -638,9 +728,9 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
|
||||
}
|
||||
const body: WAMessageAckBody = {
|
||||
id: id,
|
||||
from: toCusFormat(destination.from),
|
||||
to: toCusFormat(destination.to),
|
||||
participant: toCusFormat(destination.participant),
|
||||
from: toCusFormat(fromToParticipant.from),
|
||||
to: toCusFormat(fromToParticipant.to),
|
||||
participant: toCusFormat(fromToParticipant.participant),
|
||||
fromMe: event.key.fromMe,
|
||||
ack: ack,
|
||||
ackName: WAMessageAck[ack] || ACK_UNKNOWN,
|
||||
@@ -648,6 +738,77 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
|
||||
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[],
|
||||
@@ -693,6 +854,9 @@ function toCusFormat(remoteJid) {
|
||||
if (!remoteJid) {
|
||||
return;
|
||||
}
|
||||
if (remoteJid == 'me') {
|
||||
return remoteJid;
|
||||
}
|
||||
const number = remoteJid.split('@')[0];
|
||||
return ensureSuffix(number);
|
||||
}
|
||||
@@ -746,7 +910,15 @@ function isMine(message) {
|
||||
return message?.key?.fromMe;
|
||||
}
|
||||
|
||||
function getDestination(message) {
|
||||
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;
|
||||
@@ -761,3 +933,36 @@ function getDestination(message) {
|
||||
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,
|
||||
};
|
||||
}
|
||||
@@ -50,6 +50,8 @@ import {
|
||||
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');
|
||||
@@ -113,6 +115,19 @@ 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');
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
VoiceBinaryFile,
|
||||
VoiceRemoteFile,
|
||||
} from './files.dto';
|
||||
import { ChatIdProperty } from './properties.dto';
|
||||
|
||||
/**
|
||||
* Queries
|
||||
@@ -25,9 +26,7 @@ export class MessageTextQuery extends SessionQuery {
|
||||
}
|
||||
|
||||
export class ChatQuery extends SessionQuery {
|
||||
@ApiProperty({
|
||||
example: '11111111111@c.us',
|
||||
})
|
||||
@ChatIdProperty()
|
||||
chatId: string;
|
||||
}
|
||||
|
||||
@@ -48,9 +47,7 @@ export class GetPresenceQuery extends ChatQuery {}
|
||||
* Requests
|
||||
*/
|
||||
export class ChatRequest extends SessionBaseRequest {
|
||||
@ApiProperty({
|
||||
example: '11111111111@c.us',
|
||||
})
|
||||
@ChatIdProperty()
|
||||
chatId: string;
|
||||
}
|
||||
|
||||
@@ -167,3 +164,32 @@ export class MessageReactionRequest extends MessageRequest {
|
||||
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;
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 };
|
||||
@@ -1,6 +1,7 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
|
||||
import { WAMessageAck } from './enums.dto';
|
||||
import { ChatIdProperty, MessageIdProperty } from './properties.dto';
|
||||
|
||||
export class WALocation {
|
||||
description?: string | null;
|
||||
@@ -9,10 +10,7 @@ export class WALocation {
|
||||
}
|
||||
|
||||
export class WAMessage {
|
||||
@ApiProperty({
|
||||
description: 'Message ID',
|
||||
example: 'false_11111111111@c.us_AAAAAAAAAAAAAAAAAAAA',
|
||||
})
|
||||
@MessageIdProperty()
|
||||
id: string;
|
||||
|
||||
/** */
|
||||
@@ -22,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 ',
|
||||
})
|
||||
@@ -33,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.
|
||||
|
||||
@@ -2,6 +2,7 @@ import { ApiProperty } from '@nestjs/swagger';
|
||||
|
||||
import { WAHASessionStatus } from './enums.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;
|
||||
}
|
||||
@@ -2,16 +2,22 @@ import { ApiProperty } from '@nestjs/swagger';
|
||||
|
||||
import { WAHAEngine, WAHAEvents, WAMessageAck } from './enums.dto';
|
||||
import { WAMessage } from './responses.dto';
|
||||
import { MessageDestination } from './chatting.dto';
|
||||
import { ChatIdProperty, MessageIdProperty } from './properties.dto';
|
||||
|
||||
export class WAMessageAckBody {
|
||||
@ApiProperty({
|
||||
description: 'Message ID',
|
||||
example: 'false_11111111111@c.us_AAAAAAAAAAAAAAAAAAAA',
|
||||
})
|
||||
@MessageIdProperty()
|
||||
id: string;
|
||||
|
||||
@ChatIdProperty()
|
||||
from: string;
|
||||
|
||||
@ChatIdProperty()
|
||||
to: string;
|
||||
|
||||
@ChatIdProperty()
|
||||
participant: string;
|
||||
|
||||
fromMe: boolean;
|
||||
ack: WAMessageAck;
|
||||
ackName: string;
|
||||
@@ -28,7 +34,7 @@ export class WAGroupPayload {
|
||||
})
|
||||
timestamp: number;
|
||||
|
||||
@ApiProperty({
|
||||
@ChatIdProperty({
|
||||
description: 'ID for the Chat that this groupNotification was sent for',
|
||||
})
|
||||
chatId: string;
|
||||
@@ -50,10 +56,34 @@ export class WAGroupPayload {
|
||||
recipientIds: string[];
|
||||
}
|
||||
|
||||
export class PollVote extends MessageDestination {
|
||||
@ApiProperty({
|
||||
description: 'Option that user has selected',
|
||||
example: ['Awesome!'],
|
||||
})
|
||||
selectedOptions: string[];
|
||||
|
||||
@ApiProperty({
|
||||
description: 'Timestamp, ms',
|
||||
example: 1692861369,
|
||||
})
|
||||
timestamp: number;
|
||||
}
|
||||
|
||||
export class PollVotePayload {
|
||||
vote: PollVote;
|
||||
poll: MessageDestination;
|
||||
}
|
||||
|
||||
export class WAWebhook {
|
||||
event: WAHAEvents;
|
||||
session: string;
|
||||
engine: WAHAEngine;
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||
payload: WAMessage | WAGroupPayload | WAMessageAckBody | object;
|
||||
payload:
|
||||
| WAMessage
|
||||
| WAGroupPayload
|
||||
| WAMessageAckBody
|
||||
| PollVotePayload
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||
| object;
|
||||
}
|
||||
+1
-1
@@ -26,7 +26,7 @@ export function getWAHAVersion(): WAHAVersion {
|
||||
}
|
||||
|
||||
export const VERSION = {
|
||||
version: '2023.8.19',
|
||||
version: '2023.9.3',
|
||||
tier: getWAHAVersion(),
|
||||
browser: getBrowserExecutablePath(),
|
||||
};
|
||||
@@ -13151,7 +13151,7 @@ __metadata:
|
||||
|
||||
"whatsapp-web.js@https://github.com/pedroslopez/whatsapp-web.js":
|
||||
version: 1.22.1
|
||||
resolution: "whatsapp-web.js@https://github.com/pedroslopez/whatsapp-web.js.git#commit=b47998878ae62732832986793f8e7ceae9ddb96f"
|
||||
resolution: "whatsapp-web.js@https://github.com/pedroslopez/whatsapp-web.js.git#commit=abac063b779570729476cf42e29dc694e5345ca6"
|
||||
dependencies:
|
||||
"@pedroslopez/moduleraid": ^5.0.2
|
||||
archiver: ^5.3.1
|
||||
@@ -13170,7 +13170,7 @@ __metadata:
|
||||
optional: true
|
||||
unzipper:
|
||||
optional: true
|
||||
checksum: 8084bde72fb558dbf541e51862c39c74f5dd0b29a99b753fd51589d066b305d05bb5a9593a86b2b20c49b38754e365443bc14be824134a4dc170985b6ed36d19
|
||||
checksum: 2100a4c826d25c5697fe304f6e036556b4c1c28f310262389c17b10344a182b0203eac6ac8823e3624c1e0c1b93187bd9447a1775aa69e01c7329659a6edcebc
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
||||
Reference in new issue
Block a user