Compare commits

..
77 Commits
Author SHA1 Message Date
devlikepro c8f48dd62b [core] 11.8
Release / amd64 - chrome - chrome (push) Waiting to run
Release / amd64 - chromium - latest (push) Waiting to run
Release / linux/arm64 - chromium - arm (push) Waiting to run
Release / amd64 - none - noweb (push) Waiting to run
Release / linux/arm64 - none - noweb-arm (push) Waiting to run
2024-11-25 12:57:23 +07:00
devlikepro aed157697a [core] WEBJS - Fix QR code
fix #659
2024-11-25 12:56:51 +07:00
devlikepro 913b8606d9 [core] 2024.11.7
Release / amd64 - chrome - chrome (push) Waiting to run
Release / amd64 - chromium - latest (push) Waiting to run
Release / linux/arm64 - chromium - arm (push) Waiting to run
Release / amd64 - none - noweb (push) Waiting to run
Release / linux/arm64 - none - noweb-arm (push) Waiting to run
2024-11-24 14:06:33 +07:00
devlikepro 7cadbad36e [core] Add WebJSEngineConfigService.ts
fix #654 fix #653
2024-11-24 14:06:28 +07:00
devlikepro abb5bdffcc [core] 2024.11.6
Release / amd64 - chrome - chrome (push) Waiting to run
Release / amd64 - chromium - latest (push) Waiting to run
Release / linux/arm64 - chromium - arm (push) Waiting to run
Release / amd64 - none - noweb (push) Waiting to run
Release / linux/arm64 - none - noweb-arm (push) Waiting to run
2024-11-20 16:38:59 +07:00
devlikepro 6a44c15dd3 [core] fix undefined.get
fix #645
2024-11-20 16:38:43 +07:00
devlikepro e669dfe533 [core] Update dashboard
Release / amd64 - chrome - chrome (push) Waiting to run
Release / amd64 - chromium - latest (push) Waiting to run
Release / linux/arm64 - chromium - arm (push) Waiting to run
Release / amd64 - none - noweb (push) Waiting to run
Release / linux/arm64 - none - noweb-arm (push) Waiting to run
2024-11-19 13:17:40 +07:00
devlikepro be888f195d [core] NOWEB - fix timestamp 2024-11-19 13:17:39 +07:00
devlikepro d87cdef311 [core] Add participant for group 2024-11-19 13:17:39 +07:00
devlikepro ecf07cb520 [core] Pin Unpin message
fix #613
2024-11-19 13:17:39 +07:00
devlikepro 10829d53b9 [core] Check quoted message better 2024-11-19 13:17:38 +07:00
devlikepro 71093f2f31 [core] Rm unused code 2024-11-19 13:17:38 +07:00
devlikepro e88192002a [core] Fix log ctx 2024-11-19 13:17:38 +07:00
devlikepro 3e14282603 [core] 2024.11.5 2024-11-19 13:17:37 +07:00
devlikepro 5f16f4ca77 [core] Update dashboard 2024-11-19 13:17:37 +07:00
devlikepro 7b4fa4331f [core] Websockets 2024-11-19 13:17:36 +07:00
devlikepro 3ad0a463b5 [core] Lazy message and message.any 2024-11-19 13:17:36 +07:00
devlikepro d28d640f77 [core] Use EventWildUmask 2024-11-19 13:17:35 +07:00
devlikepro 594c0a6122 [core] Emit events in engine 2024-11-19 13:17:35 +07:00
devlikepro 843a826437 [core] Merge plus webhooks to core 2024-11-19 13:17:35 +07:00
devlikepro c4660bc824 [core] 2024.11.4
Release / amd64 - chrome - chrome (push) Waiting to run
Release / amd64 - chromium - latest (push) Waiting to run
Release / amd64 - none - noweb (push) Waiting to run
Release / linux/arm64 - none - noweb-arm (push) Waiting to run
Release / linux/arm64 - chromium - arm (push) Waiting to run
2024-11-10 12:48:54 +07:00
devlikepro 6053d38e4d [core] Handle WEBJS 429 error
fix #633
2024-11-10 12:48:54 +07:00
devlikepro 3865254a1c [core] NOWEB - get contacts list for status from store if not provided 2024-11-10 12:48:54 +07:00
devlikepro 65b61c09cb [core] Fix 10K status problem
fix #390 fix #413
2024-11-10 12:48:54 +07:00
devlikepro cce1618117 [core] add mongo auth by default 2024-11-08 18:14:35 +07:00
devlikepro 8b4b49a34b [core] waha-plus image 2024-11-08 17:19:50 +07:00
devlikepro 048c5dc2ec [core] send video back
Release / amd64 - chrome - chrome (push) Waiting to run
Release / amd64 - chromium - latest (push) Waiting to run
Release / linux/arm64 - chromium - arm (push) Waiting to run
Release / amd64 - none - noweb (push) Waiting to run
Release / linux/arm64 - none - noweb-arm (push) Waiting to run
fix #629
2024-11-07 11:43:42 +07:00
devlikepro eb218da8fb [core] linkPreview: false|true
fix #596 #598
2024-11-06 15:46:55 +07:00
devlikepro 65942a4ed4 [core] 2024.11.3 2024-11-06 15:46:55 +07:00
devlikepro 1444155c09 [core] Use WAL for sqlite3
fix #628
2024-11-06 15:46:55 +07:00
devlikepro 84ac1d8cfc [core] 2024.11.2
Release / amd64 - chrome - chrome (push) Waiting to run
Release / amd64 - chromium - latest (push) Waiting to run
Release / linux/arm64 - chromium - arm (push) Waiting to run
Release / amd64 - none - noweb (push) Waiting to run
Release / linux/arm64 - none - noweb-arm (push) Waiting to run
2024-11-05 18:39:40 +07:00
devlikepro 440e1e879a [core] WEBJS - fix injection 2024-11-05 18:39:40 +07:00
devlikepro bcecf793d4 [core] WEBJS - only issue WORKING on READY event 2024-11-05 18:39:40 +07:00
devlikepro 16c0d827e1 [core] Add some fonts 2024-11-05 18:39:40 +07:00
devlikepro 94f16a9583 [core] WEBJS - --disable-software-rasterizer 2024-11-05 18:39:40 +07:00
devlikepro 4bdac3bbc2 [core] use sessions folder (no dot) so it's visible via ls 2024-11-05 18:39:40 +07:00
devlikepro 2bc4a59aaf [core] 2024.11.1
Release / amd64 - chrome - chrome (push) Waiting to run
Release / amd64 - chromium - latest (push) Waiting to run
Release / linux/arm64 - chromium - arm (push) Waiting to run
Release / amd64 - none - noweb (push) Waiting to run
Release / linux/arm64 - none - noweb-arm (push) Waiting to run
2024-11-04 12:08:16 +07:00
devlikepro cf886de5ce [core] Add status for WEBJS 2024-11-04 12:08:15 +07:00
devlikepro 9969966ac6 [core] Add labels CRUD
fix #607
2024-11-04 12:08:15 +07:00
devlikepro fb809338e3 [core] 2024.10.14
Release / amd64 - chrome - chrome (push) Waiting to run
Release / amd64 - chromium - latest (push) Waiting to run
Release / linux/arm64 - chromium - arm (push) Waiting to run
Release / amd64 - none - noweb (push) Waiting to run
Release / linux/arm64 - none - noweb-arm (push) Waiting to run
2024-10-31 16:45:38 +07:00
devlikepro c6284eb994 [core] fix /api/messages
fix #622
2024-10-31 16:45:15 +07:00
devlikepro 454bda4c82 [core] fix /api/messages
Release / amd64 - chrome - chrome (push) Waiting to run
Release / amd64 - chromium - latest (push) Waiting to run
Release / linux/arm64 - chromium - arm (push) Waiting to run
Release / amd64 - none - noweb (push) Waiting to run
Release / linux/arm64 - none - noweb-arm (push) Waiting to run
fix #620
2024-10-31 13:07:30 +07:00
devlikepro 26e21441a0 [core] 2024.10.13 2024-10-31 13:04:25 +07:00
devlikepro 2edef0166e [core] add waves on voice back
fix #621
2024-10-31 13:03:46 +07:00
devlikepro 05cb89779b [core] 2024.10.12
Release / amd64 - chrome - chrome (push) Waiting to run
Release / amd64 - chromium - latest (push) Waiting to run
Release / linux/arm64 - chromium - arm (push) Waiting to run
Release / amd64 - none - noweb (push) Waiting to run
Release / linux/arm64 - none - noweb-arm (push) Waiting to run
2024-10-30 16:56:44 +07:00
devlikepro c0b2524952 [core] remove single process 2024-10-30 16:56:30 +07:00
devlikepro f1bb445593 [core] 2024.10.11
Release / amd64 - chrome - chrome (push) Waiting to run
Release / amd64 - chromium - latest (push) Waiting to run
Release / linux/arm64 - chromium - arm (push) Waiting to run
Release / amd64 - none - noweb (push) Waiting to run
Release / linux/arm64 - none - noweb-arm (push) Waiting to run
2024-10-29 17:09:58 +07:00
devlikepro 7f777f338f [core] WEBJS - window size 1280x720
Release / amd64 - chrome - chrome (push) Waiting to run
Release / amd64 - chromium - latest (push) Waiting to run
Release / linux/arm64 - chromium - arm (push) Waiting to run
Release / amd64 - none - noweb (push) Waiting to run
Release / linux/arm64 - none - noweb-arm (push) Waiting to run
2024-10-28 16:37:26 +07:00
devlikepro 4449dfdbd6 [core] 2024.10.10 2024-10-28 15:58:01 +07:00
devlikepro 6226e65e97 [core] Fix Singleton Issues 2024-10-28 15:57:39 +07:00
devlikepro be1783c029 [core] Fix Singleton 2024-10-28 15:57:36 +07:00
devlikepro 83a002ae5b [core] format question.md 2024-10-28 15:57:36 +07:00
devlikepro f0fcb2b329 [core] Update chrome version 2024-10-28 15:57:36 +07:00
devlikepro 5ff695a5b3 [core] Install zip unzip only for WEBJS 2024-10-28 15:57:36 +07:00
devlikepro 43fe1f076e [core] Update puppeter 2024-10-28 15:57:36 +07:00
devlikepro 1afd7e042c [core] fix channels swagger 2024-10-27 17:34:41 +07:00
devlikepro ba6474f97e [core] bufferutil is optional (no arm)
Release / amd64 - chrome - chrome (push) Waiting to run
Release / amd64 - chromium - latest (push) Waiting to run
Release / linux/arm64 - chromium - arm (push) Waiting to run
Release / amd64 - none - noweb (push) Waiting to run
Release / linux/arm64 - none - noweb-arm (push) Waiting to run
2024-10-27 16:52:03 +07:00
devlikepro d8eed114aa Merge remote-tracking branch 'core/core' into core 2024-10-27 15:18:07 +07:00
devlikepro e61ad6f79c [core] Up dashboard 2024-10-27 14:48:32 +07:00
devlikepro 9d27616003 [core] Add UNPAIRING status 2024-10-27 14:47:59 +07:00
devlikepro 0f2641909f [core] Adjust logs 2024-10-27 14:47:58 +07:00
devlikepro 9ca96acda2 [core] Update WEBJS 2024-10-25 17:15:39 +07:00
devlikepro 77db6c5e98 [core] Fix logout (unpair) from device
fix #148 fix #327 fix #194 fix #211
2024-10-25 16:56:06 +07:00
devlikepro c4aa9804d2 [core] 2024.10.9 2024-10-25 15:18:56 +07:00
devlikepro eb7bfe3a62 [core] API - messages filters
fix #449, fix #140, fix #458
2024-10-25 15:18:55 +07:00
devlikepro 01fd7a8d56 [core] WEBJS --disable-features=site-per-process 2024-10-25 15:18:55 +07:00
devlikepro 81fb93e5d6 [core] WEBJS puppeteer headless: new 2024-10-25 15:18:54 +07:00
devlikepro 64716ec6e6 [core] poll message attributes 2024-10-25 15:18:54 +07:00
devlikepro e0f787be32 [core] Update NOWEB
fix #586
2024-10-25 15:18:54 +07:00
devlikepro 138da79599 [core] Pin whatwg-url 2024-10-25 15:18:54 +07:00
devlikepro b8b290c2a5 [core] Update puppeter and ws 2024-10-25 15:18:53 +07:00
devlikepro f35dcc0ec6 [core] Get message by id
fix #159 #484
2024-10-25 15:18:52 +07:00
devlikepro 62704e261a [core] Lint templates 2024-10-25 15:18:52 +07:00
devlikepro f16ace6169 [core] API limit, offset, sort 2024-10-25 15:18:52 +07:00
devlikepro 35628aa93b [core] Fix WEBJS restart on state change 2024-10-25 15:18:52 +07:00
devlikepro 64bf42dc1c [core] Add chat unread
fix #498
2024-10-25 15:18:52 +07:00
devlikeapro 55fc6a4282 Create question.md 2024-10-23 18:23:25 +07:00
65 changed files with 21216 additions and 1144 deletions

No files matched your search

+1
View File
@@ -1,5 +1,6 @@
Dockerfile
.*sessions
sessions
files
venv
.wwebjs_auth
+1 -1
View File
@@ -172,5 +172,5 @@ WHATSAPP_FILES_FOLDER=/app/.media
#
# MongoDB Configuration
# https://waha.devlike.pro/docs/how-to/storages/#sessions---mongodb
#WHATSAPP_SESSIONS_MONGO_URL=mongodb://mongodb:27017
#WHATSAPP_SESSIONS_MONGO_URL=mongodb://mongouser:mongopassword@mongodb:27017
#
+6 -2
View File
@@ -8,7 +8,8 @@ assignees: ''
### Describe the bug
A clear and concise description of what the bug is. Feel free to remove sections that you don't feel to make the text shorter!
A clear and concise description of what the bug is. Feel free to remove sections
that you don't feel to make the text shorter!
### Version
@@ -22,9 +23,12 @@ Get the WAHA version by calling `GET /api/version`
}
```
Try to update to [the latest version](https://github.com/devlikeapro/waha/releases) before creating an issue!
Try to update to
[the latest version](https://github.com/devlikeapro/waha/releases) before
creating an issue!
## Steps
**To Reproduce** Steps to reproduce the behavior:
1. Go to '...'
+5 -5
View File
@@ -6,19 +6,19 @@ labels: ''
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
I'm always frustrated when [...]
**Is your feature request related to a problem? Please describe.** I'm always
frustrated when [...]
A clear and concise description of what the problem is. Ex.
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features
you've considered.
A clear and concise description of any alternative solutions or features you've
considered.
**Additional context** Add any other context or screenshots about the feature
request here.
+11
View File
@@ -0,0 +1,11 @@
---
name: Question
about: I have a question about the project or feature
title: '[Question] '
labels: 'question'
assignees: ''
---
👉 Kindly use
[GitHub Discussions](https://github.com/devlikeapro/waha/discussions) to ask
questions!
+1
View File
@@ -3,6 +3,7 @@
.yarn
example.ts
.*sessions
sessions
.secrets
.minio
.media
+1
View File
@@ -72,4 +72,5 @@ repos:
entrypoint.sh|
.pre-commit-config.yaml|
^src/core/engines/webjs/session.webjs.core.ts|
^src/core/engines/webjs/_lodash.js|
)$
+22 -5
View File
@@ -26,7 +26,7 @@ RUN yarn build && find ./dist -name "*.d.ts" -delete
FROM node:${NODE_VERSION} AS dashboard
# Download WAHA Dashboard
ENV WAHA_DASHBOARD_SHA 5199c68522e81e911b440834fa7adc15ff4a1cf9
ENV WAHA_DASHBOARD_SHA 45db5b46a944f4320136f2409d8b970483c8bd9d
RUN \
wget https://github.com/devlikeapro/dashboard/archive/${WAHA_DASHBOARD_SHA}.zip \
&& unzip ${WAHA_DASHBOARD_SHA}.zip -d /tmp/dashboard \
@@ -50,13 +50,30 @@ RUN echo "USE_BROWSER=$USE_BROWSER"
# Install ffmpeg to generate previews for videos
RUN apt-get update && apt-get install -y ffmpeg --no-install-recommends && rm -rf /var/lib/apt/lists/*
# Install zip and unzip
RUN apt-get update && apt-get install -y zip unzip --no-install-recommends && rm -rf /var/lib/apt/lists/*
# Install zip and unzip - either for chromium or chrome
RUN if [ "$USE_BROWSER" = "chromium" ] || [ "$USE_BROWSER" = "chrome" ]; then \
apt-get update \
&& apt-get install -y zip unzip \
&& rm -rf /var/lib/apt/lists/*; \
fi
# Install fonts if using either chromium or chrome
RUN if [ "$USE_BROWSER" = "chromium" ] || [ "$USE_BROWSER" = "chrome" ]; then \
apt-get update \
&& apt-get install -y fonts-ipafont-gothic fonts-wqy-zenhei fonts-thai-tlwg fonts-kacst fonts-freefont-ttf \
&& apt-get install -y \
fontconfig \
fonts-freefont-ttf \
fonts-gfs-neohellenic \
fonts-indic \
fonts-ipafont-gothic \
fonts-kacst \
fonts-liberation \
fonts-noto-cjk \
fonts-noto-color-emoji \
fonts-roboto \
fonts-thai-tlwg \
fonts-wqy-zenhei \
fonts-open-sans \
--no-install-recommends \
&& rm -rf /var/lib/apt/lists/*; \
fi
@@ -73,7 +90,7 @@ RUN if [ "$USE_BROWSER" = "chromium" ]; then \
# Install Chrome
# Available versions:
# https://www.ubuntuupdates.org/package/google_chrome/stable/main/base/google-chrome-stable
ARG CHROME_VERSION="126.0.6478.182-1"
ARG CHROME_VERSION="130.0.6723.69-1"
RUN if [ "$USE_BROWSER" = "chrome" ]; then \
wget --no-verbose -O /tmp/chrome.deb https://dl.google.com/linux/chrome/deb/pool/main/g/google-chrome-stable/google-chrome-stable_${CHROME_VERSION}_amd64.deb \
&& apt-get update \
+7 -5
View File
@@ -4,7 +4,7 @@ services:
restart: always
# https://waha.devlike.pro/docs/how-to/engines/#docker-images
# https://portal.devlike.pro/docker-image
image: devlikeapro/waha-plus:latest
image: devlikeapro/waha-plus
# WAHA Core
# image: devlikeapro/waha:latest
@@ -13,7 +13,7 @@ services:
volumes:
# Store sessions in the .sessions folder (comment it if you're using MongoDB)
- './.sessions:/app/.sessions'
- './sessions:/app/.sessions'
# Save media files
# https://waha.devlike.pro/docs/how-to/storages/#save-media-files-between-the-container-restarts
@@ -24,7 +24,7 @@ services:
# NOTE: Only if you're using MongoDB
# https://waha.devlike.pro/docs/how-to/storages/#sessions---mongodb
# Comment this block if you're using MongoDB
# Uncomment this block if you're using MongoDB
# mongodb:
# image: mongo
# container_name: mongodb
@@ -32,11 +32,13 @@ services:
# - '127.0.0.1:27017:27017/tcp'
# volumes:
# - mongodb_data:/data/db
# restart: always
# environment:
# - MONGO_INITDB_ROOT_USERNAME=mongouser
# - MONGO_INITDB_ROOT_PASSWORD=mongopassword
# NOTE: Only if you're using S3 to save media files
# https://waha.devlike.pro/docs/how-to/storages/#media---s3
# Comment this block if you're using AWS S3
# Uncomment this block if you're using AWS S3
# minio:
# image: quay.io/minio/minio
# container_name: minio
+12 -2
View File
@@ -70,16 +70,26 @@
"pino-pretty": "^11.2.1",
"pretty-bytes": "5.6.0",
"promise-retry": "^2.0.1",
"puppeteer": "^21.0.0",
"puppeteer": "^23.6.0",
"qrcode": "^1.5.1",
"qrcode-terminal": "^0.12.0",
"reflect-metadata": "^0.1.13",
"rimraf": "^3.0.2",
"rxjs": "^7.1.0",
"rxjs": "^7.8.1",
"sharp": "^0.33.4",
"swagger-ui-express": "^4.1.4",
"ulid": "^2.3.0",
"whatsapp-web.js": "github:devlikeapro/whatsapp-web.js#fork-main-channels"
},
"optionalDependencies": {
"bufferutil": "^4.0.8"
},
"resolutions": {
"bufferutil": "^4.0.8",
"ws": "^8.18.0",
"puppeteer": "^23.6.0",
"whatwg-url": "13.0.0"
},
"devDependencies": {
"@nestjs/cli": "^9.0.0",
"@nestjs/schematics": "^9.0.1",
+4 -4
View File
@@ -82,7 +82,7 @@ export class ChannelsController {
}
}
@Post('follow')
@Post(':id/follow')
@SessionApiParam
@NewsletterIdApiParam
@ApiOperation({ summary: 'Follow the channel.' })
@@ -93,7 +93,7 @@ export class ChannelsController {
return session.channelsFollowChannel(id);
}
@Post('unfollow')
@Post(':id/unfollow')
@SessionApiParam
@NewsletterIdApiParam
@ApiOperation({ summary: 'Unfollow the channel.' })
@@ -104,7 +104,7 @@ export class ChannelsController {
return session.channelsUnfollowChannel(id);
}
@Post('mute')
@Post(':id/mute')
@SessionApiParam
@NewsletterIdApiParam
@ApiOperation({ summary: 'Mute the channel.' })
@@ -115,7 +115,7 @@ export class ChannelsController {
return session.channelsMuteChannel(id);
}
@Post('unmute')
@Post(':id/unmute')
@SessionApiParam
@NewsletterIdApiParam
@ApiOperation({ summary: 'Unmute the channel.' })
+68 -5
View File
@@ -3,6 +3,7 @@ import {
Controller,
Delete,
Get,
NotFoundException,
Param,
Post,
Put,
@@ -21,7 +22,13 @@ import {
import { SessionManager } from '../core/abc/manager.abc';
import { WhatsappSession } from '../core/abc/session.abc';
import { parseBool } from '../helpers';
import { GetChatMessagesQuery, GetChatsQuery } from '../structures/chats.dto';
import {
ChatsPaginationParams,
GetChatMessageQuery,
GetChatMessagesFilter,
GetChatMessagesQuery,
PinMessageRequest,
} from '../structures/chats.dto';
import { EditMessageRequest } from '../structures/chatting.dto';
@ApiSecurity('api_key')
@@ -36,9 +43,9 @@ class ChatsController {
@ApiOperation({ summary: 'Get chats' })
getChats(
@WorkingSessionParam session: WhatsappSession,
@Query() query: GetChatsQuery,
@Query() pagination: ChatsPaginationParams,
) {
return session.getChats(query);
return session.getChats(pagination);
}
@Delete(':chatId')
@@ -56,13 +63,58 @@ class ChatsController {
@SessionApiParam
@ApiOperation({ summary: 'Gets messages in the chat' })
@ChatIdApiParam
@UsePipes(new ValidationPipe({ transform: true, whitelist: true }))
getChatMessages(
@Query() query: GetChatMessagesQuery,
@Query() filter: GetChatMessagesFilter,
@WorkingSessionParam session: WhatsappSession,
@Param('chatId') chatId: string,
) {
const downloadMedia = parseBool(query.downloadMedia);
return session.getChatMessages(chatId, query.limit, downloadMedia);
return session.getChatMessages(chatId, query, filter);
}
@Get(':chatId/messages/:messageId')
@SessionApiParam
@ApiOperation({ summary: 'Gets message by id' })
@ChatIdApiParam
async getChatMessage(
@Query() query: GetChatMessageQuery,
@WorkingSessionParam session: WhatsappSession,
@Param('chatId') chatId: string,
@Param('messageId') messageId: string,
) {
const message = await session.getChatMessage(chatId, messageId, query);
if (!message) {
throw new NotFoundException('Message not found');
}
return message;
}
@Post(':chatId/messages/:messageId/pin')
@SessionApiParam
@ApiOperation({ summary: 'Pins a message in the chat' })
@ChatIdApiParam
async pinMessage(
@WorkingSessionParam session: WhatsappSession,
@Param('chatId') chatId: string,
@Param('messageId') messageId: string,
@Body() body: PinMessageRequest,
) {
const result = await session.pinMessage(chatId, messageId, body.duration);
return { success: result };
}
@Post(':chatId/messages/:messageId/unpin')
@SessionApiParam
@ApiOperation({ summary: 'Unpins a message in the chat' })
@ChatIdApiParam
async unpinMessage(
@WorkingSessionParam session: WhatsappSession,
@Param('chatId') chatId: string,
@Param('messageId') messageId: string,
) {
const result = await session.unpinMessage(chatId, messageId);
return { success: result };
}
@Delete(':chatId/messages')
@@ -124,6 +176,17 @@ class ChatsController {
) {
return session.chatsUnarchiveChat(chatId);
}
@Post(':chatId/unread')
@SessionApiParam
@ChatIdApiParam
@ApiOperation({ summary: 'Unread the chat' })
unreadChat(
@WorkingSessionParam session: WhatsappSession,
@Param('chatId') chatId: string,
) {
return session.chatsUnreadChat(chatId);
}
}
export { ChatsController };
+8 -2
View File
@@ -6,9 +6,11 @@ import {
Put,
Query,
UsePipes,
ValidationPipe,
} from '@nestjs/common';
import { ApiOperation, ApiSecurity, ApiTags } from '@nestjs/swagger';
import { WAHAValidationPipe } from '@waha/nestjs/pipes/WAHAValidationPipe';
import { GetChatMessagesFilter } from '@waha/structures/chats.dto';
import { SendButtonsRequest } from '@waha/structures/chatting.buttons.dto';
import { SessionManager } from '../core/abc/manager.abc';
@@ -171,9 +173,13 @@ export class ChattingController {
@Get('/messages')
@ApiOperation({ summary: 'Get messages in a chat' })
async getMessages(@Query() query: GetMessageQuery) {
@UsePipes(new ValidationPipe({ transform: true, whitelist: true }))
async getMessages(
@Query() query: GetMessageQuery,
@Query() filter: GetChatMessagesFilter,
) {
const whatsapp = await this.manager.getWorkingSession(query.session);
return whatsapp.getMessages(query);
return whatsapp.getChatMessages(query.chatId, query, filter);
}
@Get('/sendText')
+25 -9
View File
@@ -1,4 +1,12 @@
import { Body, Controller, Get, Post, Query } from '@nestjs/common';
import {
Body,
Controller,
Get,
Post,
Query,
UsePipes,
ValidationPipe,
} from '@nestjs/common';
import { ApiOperation, ApiSecurity, ApiTags } from '@nestjs/swagger';
import { SessionManager } from '../core/abc/manager.abc';
@@ -7,7 +15,11 @@ import {
CheckNumberStatusQuery,
WANumberExistResult,
} from '../structures/chatting.dto';
import { ContactQuery, ContactRequest } from '../structures/contacts.dto';
import {
ContactQuery,
ContactRequest,
ContactsPaginationParams,
} from '../structures/contacts.dto';
@ApiSecurity('api_key')
@Controller('api/contacts')
@@ -15,6 +27,17 @@ import { ContactQuery, ContactRequest } from '../structures/contacts.dto';
export class ContactsController {
constructor(private manager: SessionManager) {}
@Get('/all')
@ApiOperation({ summary: 'Get all contacts' })
@UsePipes(new ValidationPipe({ transform: true, whitelist: true }))
async getAll(
@Query() query: SessionQuery,
@Query() pagination: ContactsPaginationParams,
) {
const whatsapp = await this.manager.getWorkingSession(query.session);
return whatsapp.getContacts(pagination);
}
@Get('/')
@ApiOperation({
summary: 'Get contact basic info',
@@ -26,13 +49,6 @@ export class ContactsController {
return whatsapp.getContact(query);
}
@Get('/all')
@ApiOperation({ summary: 'Get all contacts' })
async getAll(@Query() query: SessionQuery) {
const whatsapp = await this.manager.getWorkingSession(query.session);
return whatsapp.getContacts();
}
@Get('/check-exists')
@ApiOperation({ summary: 'Check phone number is registered in WhatsApp.' })
async checkExists(
+91 -2
View File
@@ -1,4 +1,16 @@
import { Body, Controller, Get, Param, Put } from '@nestjs/common';
import {
Body,
Controller,
Delete,
Get,
NotFoundException,
Param,
Post,
Put,
UnprocessableEntityException,
UsePipes,
ValidationPipe,
} from '@nestjs/common';
import { ApiOperation, ApiSecurity, ApiTags } from '@nestjs/swagger';
import { WhatsappSession } from '@waha/core/abc/session.abc';
import { ChatIdApiParam } from '@waha/nestjs/params/ChatIdApiParam';
@@ -6,7 +18,12 @@ import {
SessionApiParam,
WorkingSessionParam,
} from '@waha/nestjs/params/SessionApiParam';
import { Label, SetLabelsRequest } from '@waha/structures/labels.dto';
import {
Label,
LabelBody,
SetLabelsRequest,
} from '@waha/structures/labels.dto';
import * as lodash from 'lodash';
import { SessionManager } from '../core/abc/manager.abc';
@@ -23,6 +40,78 @@ export class LabelsController {
return session.getLabels();
}
@Post('/')
@SessionApiParam
@ApiOperation({ summary: 'Create a new label' })
@UsePipes(new ValidationPipe({ transform: true, whitelist: true }))
async create(
@WorkingSessionParam session: WhatsappSession,
@Body() body: LabelBody,
): Promise<Label> {
const labelDto = body.toDTO();
const labels = await session.getLabels();
if (labels.length >= 20) {
throw new UnprocessableEntityException('Maximum 20 labels allowed');
}
const labelByName = lodash.find(labels, { name: labelDto.name });
if (labelByName) {
throw new UnprocessableEntityException(
`Label with '${labelByName.name}' already exists, id: ${labelByName.id}`,
);
}
return session.createLabel(labelDto);
}
@Put('/:labelId')
@SessionApiParam
@ApiOperation({ summary: 'Update a label' })
@UsePipes(new ValidationPipe({ transform: true, whitelist: true }))
async update(
@WorkingSessionParam session: WhatsappSession,
@Param('labelId') labelId: string,
@Body() body: LabelBody,
): Promise<Label> {
const label = await session.getLabel(labelId);
if (!label) {
throw new NotFoundException('Label not found');
}
const labelDto = body.toDTO();
const labels = await session.getLabels();
const labelByName = lodash.find(labels, { name: labelDto.name });
if (labelByName) {
throw new UnprocessableEntityException(
`Label with '${labelByName.name}' already exists, id: ${labelByName.id}`,
);
}
label.name = labelDto.name;
label.color = labelDto.color;
label.colorHex = Label.toHex(label.color);
await session.updateLabel(label);
return label;
}
@Delete('/:labelId')
@SessionApiParam
@ApiOperation({ summary: 'Delete a label' })
@UsePipes(new ValidationPipe({ transform: true, whitelist: true }))
async delete(
@WorkingSessionParam session: WhatsappSession,
@Param('labelId') labelId: string,
): Promise<any> {
const label = await session.getLabel(labelId);
if (!label) {
throw new NotFoundException('Label not found');
}
await session.deleteLabel(label);
return { result: true };
}
@Get('/chats/:chatId')
@SessionApiParam
@ChatIdApiParam
+4
View File
@@ -135,6 +135,7 @@ class SessionsController {
async delete(@Param('session') name: string): Promise<void> {
await this.withLock(name, async () => {
await this.manager.unassign(name);
await this.manager.unpair(name);
await this.manager.stop(name, true);
await this.manager.logout(name);
await this.manager.delete(name);
@@ -190,6 +191,7 @@ class SessionsController {
throw new NotFoundException('Session not found');
}
const isRunning = this.manager.isRunning(name);
await this.manager.unpair(name);
await this.manager.stop(name, true);
await this.manager.logout(name);
if (isRunning) {
@@ -258,6 +260,7 @@ class SessionsController {
// Old API did remove the session complete
await this.withLock(name, async () => {
await this.manager.unassign(name);
await this.manager.unpair(name);
await this.manager.stop(name, true);
await this.manager.logout(name);
await this.manager.delete(name);
@@ -283,6 +286,7 @@ class SessionsController {
const name = request.name;
await this.withLock(name, async () => {
await this.manager.unassign(name);
await this.manager.unpair(name);
await this.manager.stop(name, true);
await this.manager.logout(name);
await this.manager.delete(name);
+38 -22
View File
@@ -8,8 +8,8 @@ import { ISessionWorkerRepository } from '@waha/core/storage/ISessionWorkerRepos
import { WAHAWebhook } from '@waha/structures/webhooks.dto';
import { waitUntil } from '@waha/utils/promiseTimeout';
import { VERSION } from '@waha/version';
import { EventEmitter } from 'events';
import { PinoLogger } from 'nestjs-pino';
import { merge, Observable, of } from 'rxjs';
import {
WAHAEngine,
@@ -25,7 +25,6 @@ import {
import { ISessionAuthRepository } from '../storage/ISessionAuthRepository';
import { ISessionConfigRepository } from '../storage/ISessionConfigRepository';
import { WhatsappSession } from './session.abc';
import { WebhookConductor } from './webhooks.abc';
// eslint-disable-next-line @typescript-eslint/no-var-requires
const AsyncLock = require('async-lock');
@@ -36,7 +35,6 @@ export abstract class SessionManager implements BeforeApplicationShutdown {
public sessionConfigRepository: ISessionConfigRepository;
protected sessionMeRepository: ISessionMeRepository;
protected sessionWorkerRepository: ISessionWorkerRepository;
public events: EventEmitter;
private lock: any;
WAIT_STATUS_INTERVAL = 500;
@@ -72,7 +70,18 @@ export abstract class SessionManager implements BeforeApplicationShutdown {
protected abstract get EngineClass(): typeof WhatsappSession;
protected abstract get WebhookConductorClass(): typeof WebhookConductor;
public getSessionEvent(session: string, event: WAHAEvents): Observable<any> {
return of();
}
public getSessionEvents(
session: string,
events: WAHAEvents[],
): Observable<any> {
return merge(
...events.map((event) => this.getSessionEvent(session, event)),
);
}
//
// API Methods
@@ -94,6 +103,8 @@ export abstract class SessionManager implements BeforeApplicationShutdown {
abstract logout(name: string): Promise<void>;
abstract unpair(name: string): Promise<void>;
abstract getSession(name: string): WhatsappSession;
abstract getSessionInfo(name: string): Promise<SessionDetailedInfo | null>;
@@ -112,22 +123,6 @@ export abstract class SessionManager implements BeforeApplicationShutdown {
await this.sessionWorkerRepository?.unassign(name, this.workerId);
}
protected handleSessionEvent(event: WAHAEvents, session: WhatsappSession) {
return (payload: any) => {
const me = session.getSessionMeInfo();
const data: WAHAWebhook = {
event: event,
session: session.name,
metadata: session.sessionConfig?.metadata,
me: me,
payload: payload,
engine: session.engine,
environment: VERSION,
};
this.events.emit(event, data);
};
}
async getWorkingSession(sessionName: string): Promise<WhatsappSession> {
return this.waitUntilStatus(sessionName, [WAHASessionStatus.WORKING]);
}
@@ -158,7 +153,28 @@ export abstract class SessionManager implements BeforeApplicationShutdown {
return session;
}
async beforeApplicationShutdown(signal?: string) {
this.events.removeAllListeners();
beforeApplicationShutdown(signal?: string) {
return;
}
}
export function populateSessionInfo(
event: WAHAEvents,
session: WhatsappSession,
) {
return (payload: any): WAHAWebhook => {
const id = payload._eventId;
const data = { ...payload };
delete data._eventId;
const me = session.getSessionMeInfo();
return {
id: id,
event: event,
session: session.name,
metadata: session.sessionConfig?.metadata,
me: me,
payload: data,
engine: session.engine,
environment: VERSION,
};
};
}
+147 -38
View File
@@ -3,21 +3,41 @@ import {
CreateChannelRequest,
ListChannelsQuery,
} from '@waha/structures/channels.dto';
import { GetChatsQuery } from '@waha/structures/chats.dto';
import {
GetChatMessageQuery,
GetChatMessagesFilter,
GetChatMessagesQuery,
} from '@waha/structures/chats.dto';
import { SendButtonsRequest } from '@waha/structures/chatting.buttons.dto';
import { Label, LabelID } from '@waha/structures/labels.dto';
import { Label, LabelDTO, LabelID } from '@waha/structures/labels.dto';
import { PaginationParams } from '@waha/structures/pagination.dto';
import { WAMessage } from '@waha/structures/responses.dto';
import { DefaultMap } from '@waha/utils/DefaultMap';
import { generatePrefixedId } from '@waha/utils/ids';
import { LoggerBuilder } from '@waha/utils/logging';
import { EventEmitter } from 'events';
import { complete } from '@waha/utils/reactive/complete';
import { SwitchObservable } from '@waha/utils/reactive/SwitchObservable';
import * as fs from 'fs';
import * as lodash from 'lodash';
import { Logger } from 'pino';
import {
BehaviorSubject,
catchError,
delay,
filter,
of,
retry,
share,
Subject,
switchMap,
} from 'rxjs';
import { distinctUntilChanged, map } from 'rxjs/operators';
import { MessageId } from 'whatsapp-web.js';
import {
ChatRequest,
CheckNumberStatusQuery,
EditMessageRequest,
GetMessageQuery,
MessageContactVcardRequest,
MessageFileRequest,
MessageForwardRequest,
@@ -62,7 +82,6 @@ import { NotImplementedByEngineError } from '../exceptions';
import { IMediaManager } from '../media/IMediaManager';
import { QR } from '../QR';
import { DataStore } from './DataStore';
// eslint-disable-next-line @typescript-eslint/no-var-requires
const qrcode = require('qrcode-terminal');
@@ -84,10 +103,6 @@ export function ensureSuffix(phone) {
return phone + suffix;
}
export enum WAHAInternalEvent {
ENGINE_START = 'engine.start',
}
export interface SessionParams {
name: string;
printQR: boolean;
@@ -100,7 +115,6 @@ export interface SessionParams {
}
export abstract class WhatsappSession {
public events: EventEmitter;
public engine: WAHAEngine;
public name: string;
@@ -111,9 +125,12 @@ export abstract class WhatsappSession {
protected proxyConfig?: ProxyConfig;
public sessionConfig?: SessionConfig;
protected engineConfig?: any;
protected unpairing: boolean = false;
private _status: WAHASessionStatus;
private shouldPrintQR: boolean;
protected events2: DefaultMap<WAHAEvents, SwitchObservable<any>>;
private status$: Subject<WAHASessionStatus>;
public constructor({
name,
@@ -125,11 +142,64 @@ export abstract class WhatsappSession {
sessionConfig,
engineConfig,
}: SessionParams) {
this.events = new EventEmitter();
this.status$ = new BehaviorSubject(null);
this.name = name;
this.proxyConfig = proxyConfig;
this.loggerBuilder = loggerBuilder;
this.logger = loggerBuilder.child({ name: 'WhatsappSession' });
this.events2 = new DefaultMap<WAHAEvents, SwitchObservable<any>>(
(key) =>
new SwitchObservable((obs$) => {
return obs$.pipe(
catchError((err) => {
this.logger.error(
`Caught error, dropping value from, event: '${key}'`,
);
this.logger.error(err, err.stack);
throw err;
}),
filter(Boolean),
map((data) => {
data._eventId = generatePrefixedId('evt');
return data;
}),
retry(),
share(),
);
}),
);
this.events2.get(WAHAEvents.SESSION_STATUS).switch(
this.status$
// initial value is null
.pipe(filter(Boolean))
// Wait for WORKING status to get all the info
// https://github.com/devlikeapro/waha/issues/409
.pipe(
switchMap((status: WAHASessionStatus) => {
const me = this.getSessionMeInfo();
const hasMe = !!me?.pushName && !!me?.id;
// Delay WORKING by 1 second if condition is met
// Usually we get WORKING with all the info after
if (status === WAHASessionStatus.WORKING && !hasMe) {
return of(status).pipe(delay(2000));
}
return of(status);
}),
// Remove consecutive duplicate WORKING statuses
distinctUntilChanged(
(prev, curr) => prev === curr && curr === WAHASessionStatus.WORKING,
),
)
// Populate the session info
.pipe(
map<WAHASessionStatus, WASessionStatusBody>((status) => {
return { name: this.name, status: status };
}),
),
);
this.sessionStore = sessionStore;
this.mediaManager = mediaManager;
this.sessionConfig = sessionConfig;
@@ -137,10 +207,18 @@ export abstract class WhatsappSession {
this.shouldPrintQR = printQR;
}
public getEventObservable(event: WAHAEvents) {
return this.events2.get(event);
}
protected set status(value: WAHASessionStatus) {
if (this.unpairing && value !== WAHASessionStatus.STOPPED) {
// In case of unpairing
// wait for STOPPED event, ignore the rest
return;
}
this._status = value;
const body: WASessionStatusBody = { name: this.name, status: value };
this.events.emit(WAHAEvents.SESSION_STATUS, body);
this.status$.next(value);
}
public get status() {
@@ -162,12 +240,16 @@ export abstract class WhatsappSession {
'--disable-accelerated-2d-canvas',
'--disable-application-cache',
'--disable-background-networking',
// DO NOT disable software rasterizer, it will break the video
// https://github.com/devlikeapro/waha/issues/629
// '--disable-software-rasterizer',
'--disable-cache',
'--disable-client-side-phishing-detection',
'--disable-component-update',
'--disable-default-apps',
'--disable-dev-shm-usage',
'--disable-extensions',
'--disable-features=site-per-process',
'--disable-gpu',
'--disable-offer-store-unmasked-wallet-cards',
'--disable-offline-load-stale-cache',
@@ -193,8 +275,9 @@ export abstract class WhatsappSession {
'--password-store=basic',
'--renderer-process-limit=2', // https://superuser.com/questions/654565/how-to-run-google-chrome-in-a-single-process
'--safebrowsing-disable-auto-update',
'--single-process',
// '--single-process',
'--use-mock-keychain',
'--window-size=1280,720',
];
}
@@ -208,24 +291,14 @@ export abstract class WhatsappSession {
/** Stop the session */
abstract stop(): Promise<void>;
/** Subscribe the handler to specific hook */
subscribeSessionEvent(
hook: WAHAEvents | string,
handler: (message) => void,
): boolean {
switch (hook) {
case WAHAEvents.SESSION_STATUS:
this.events.on(WAHAEvents.SESSION_STATUS, handler);
return true;
default:
return false;
}
protected stopEvents() {
complete(this.events2);
}
abstract subscribeEngineEvent(
hook: WAHAEvents | string,
handler: (message) => void,
): boolean;
/* Unpair the account */
async unpair(): Promise<void> {
return;
}
/**
* START - Methods for API
@@ -306,10 +379,6 @@ export abstract class WhatsappSession {
abstract stopTyping(chat: ChatRequest);
getMessages(query: GetMessageQuery) {
throw new NotImplementedByEngineError();
}
abstract setReaction(request: MessageReactionRequest);
setStar(request: MessageStarRequest): Promise<void> {
@@ -319,7 +388,7 @@ export abstract class WhatsappSession {
/**
* Chats methods
*/
public getChats(query: GetChatsQuery) {
public getChats(pagination: PaginationParams) {
throw new NotImplementedByEngineError();
}
@@ -329,12 +398,32 @@ export abstract class WhatsappSession {
public getChatMessages(
chatId: string,
limit: number,
downloadMedia: boolean,
query: GetChatMessagesQuery,
filter: GetChatMessagesFilter,
) {
throw new NotImplementedByEngineError();
}
public getChatMessage(
chatId: string,
messageId: string,
query: GetChatMessageQuery,
): Promise<null | WAMessage> {
throw new NotImplementedByEngineError();
}
public pinMessage(
chatId: string,
messageId: string,
duration: number,
): Promise<boolean> {
throw new NotImplementedByEngineError();
}
public unpinMessage(chatId: string, messageId: string): Promise<boolean> {
throw new NotImplementedByEngineError();
}
public deleteMessage(chatId: string, messageId: string) {
throw new NotImplementedByEngineError();
}
@@ -359,14 +448,34 @@ export abstract class WhatsappSession {
throw new NotImplementedByEngineError();
}
public chatsUnreadChat(chatId: string): Promise<any> {
throw new NotImplementedByEngineError();
}
/**
* Labels methods
*/
public async getLabel(labelId: string): Promise<Label | undefined> {
const labels = await this.getLabels();
return lodash.find(labels, { id: labelId });
}
public getLabels(): Promise<Label[]> {
throw new NotImplementedByEngineError();
}
public async createLabel(label: LabelDTO): Promise<Label> {
throw new NotImplementedByEngineError();
}
public async updateLabel(label: Label): Promise<Label> {
throw new NotImplementedByEngineError();
}
public async deleteLabel(label: Label): Promise<void> {
throw new NotImplementedByEngineError();
}
public getChatsByLabelId(labelId: string) {
throw new NotImplementedByEngineError();
}
@@ -386,7 +495,7 @@ export abstract class WhatsappSession {
throw new NotImplementedByEngineError();
}
public getContacts() {
public getContacts(pagination: PaginationParams) {
throw new NotImplementedByEngineError();
}
-26
View File
@@ -1,26 +0,0 @@
import { LoggerBuilder } from '@waha/utils/logging';
import { Logger } from 'pino';
import { WebhookConfig } from '../../structures/webhooks.config.dto';
import { WhatsappSession } from './session.abc';
export abstract class WebhookSender {
protected url: string;
protected logger: Logger;
protected readonly config: WebhookConfig;
constructor(
loggerBuilder: LoggerBuilder,
protected webhookConfig: WebhookConfig,
) {
this.url = webhookConfig.url;
this.logger = loggerBuilder.child({ name: WebhookSender.name });
this.config = webhookConfig;
}
abstract send(json: any): void;
}
export abstract class WebhookConductor {
abstract configure(session: WhatsappSession, webhooks: WebhookConfig[]);
}
+23 -50
View File
@@ -14,10 +14,10 @@ import {
import { SessionManager } from '@waha/core/abc/manager.abc';
import { WebsocketHeartbeatJob } from '@waha/nestjs/ws/WebsocketHeartbeatJob';
import { WebSocket } from '@waha/nestjs/ws/ws';
import { WAHAEvents } from '@waha/structures/enums.dto';
import { WAHAEvents, WAHAEventsWild } from '@waha/structures/enums.dto';
import { EventWildUnmask } from '@waha/utils/events';
import { generatePrefixedId } from '@waha/utils/ids';
import { IncomingMessage } from 'http';
import * as lodash from 'lodash';
import * as url from 'url';
import { Server } from 'ws';
@@ -37,11 +37,9 @@ export class WebsocketGatewayCore
@WebSocketServer()
server: Server;
private listeners: Map<WebSocket, { session: string; events: string[] }> =
new Map();
private readonly logger: LoggerService;
private heartbeat: WebsocketHeartbeatJob;
private eventUnmask = new EventWildUnmask(WAHAEvents, WAHAEventsWild);
constructor(private manager: SessionManager) {
this.logger = new Logger('WebsocketGateway');
@@ -56,49 +54,39 @@ export class WebsocketGatewayCore
const id = generatePrefixedId('wsc');
socket.id = id;
this.logger.debug(`New client connected: ${request.url}`);
const params = this.getParams(id, request, socket);
if (!params) {
return;
}
const { session, events } = params;
const params = this.getParams(request);
const session: string = params.session;
const events: WAHAEvents[] = params.events;
this.logger.debug(
`Client connected to session: '${session}', events: ${events}, ${id}`,
);
this.listeners.set(socket, { session, events });
const sub = this.manager
.getSessionEvents(session, events)
.subscribe((data) => {
this.logger.debug(`Sending data to client, event.id: ${data.id}`, data);
socket.send(JSON.stringify(data));
});
socket.on('close', () => {
this.logger.debug(`Client disconnected - ${socket.id}`);
sub.unsubscribe();
});
}
private getParams(id: string, request: IncomingMessage, socket: WebSocket) {
private getParams(request: IncomingMessage) {
const query = url.parse(request.url, true).query;
const session = (query.session as string) || '*';
if (session !== '*') {
this.logger.warn(
`Only connecting to all sessions is allowed for now, use session=*, ${id}`,
);
const error =
'Only connecting to all sessions is allowed for now, use session=*';
socket.close(4001, JSON.stringify({ error }));
return null;
}
const events = ((query.events as string) || '*').split(',');
if (
!lodash.isEqual(events, ['*']) &&
!lodash.isEqual(events, [WAHAEvents.SESSION_STATUS])
) {
this.logger.warn(
`Only \'session.status\' event is allowed for now, use events=session.status or events=*, ${id}`,
);
const error =
"Only 'session.status' event is allowed for now, use events=session.status or events=*";
socket.close(4001, JSON.stringify({ error }));
return null;
let paramsEvents = (query.events as string[]) || '*';
// if params events string - split by ","
if (typeof paramsEvents === 'string') {
paramsEvents = paramsEvents.split(',');
}
const events = this.eventUnmask.unmask(paramsEvents);
return { session, events };
}
handleDisconnect(socket: WebSocket): any {
this.logger.debug(`Client disconnected - ${socket.id}`);
this.listeners.delete(socket);
}
async beforeApplicationShutdown(signal?: string) {
@@ -118,24 +106,9 @@ export class WebsocketGatewayCore
afterInit(server: Server) {
this.logger.debug('Websocket server initialized');
this.manager.events.on(
WAHAEvents.SESSION_STATUS,
this.sendToAll.bind(this),
);
this.logger.debug('Subscribed to manager events');
this.logger.debug('Starting heartbeat service...');
this.heartbeat.start(server);
this.logger.debug('Heartbeat service started');
}
sendToAll(data: any) {
this.listeners.forEach((options, client) => {
if (options.events.length === 0) {
return;
}
this.logger.debug('Sending data to client', data);
client.send(JSON.stringify(data));
});
}
}
+2
View File
@@ -10,6 +10,7 @@ import {
ServerDebugController,
} from '@waha/api/server.controller';
import { WebsocketGatewayCore } from '@waha/core/api/websocket.gateway.core';
import { WebJSEngineConfigService } from '@waha/core/config/WebJSEngineConfigService';
import { MediaLocalStorageModule } from '@waha/core/media/local/media.local.storage.module';
import { MediaLocalStorageConfig } from '@waha/core/media/local/MediaLocalStorageConfig';
import { BufferJsonReplacerInterceptor } from '@waha/nestjs/BufferJsonReplacerInterceptor';
@@ -146,6 +147,7 @@ const PROVIDERS = [
},
DashboardConfigServiceCore,
SwaggerConfigServiceCore,
WebJSEngineConfigService,
WhatsappConfigService,
EngineConfigService,
WebsocketGatewayCore,
@@ -0,0 +1,37 @@
import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { WebJSConfig } from '../../core/engines/webjs/session.webjs.core';
@Injectable()
export class WebJSEngineConfigService {
constructor(protected configService: ConfigService) {}
getConfig(): WebJSConfig {
let webVersion = this.configService.get<string>(
'WAHA_WEBJS_WEB_VERSION',
undefined,
);
if (webVersion === '2.2412.54-videofix') {
// Deprecated version
webVersion = undefined;
}
return {
webVersion: webVersion,
cacheType: this.getCacheType(),
};
}
getCacheType(): 'local' | 'none' {
const cacheType = this.configService
.get<string>('WAHA_WEBJS_CACHE_TYPE', 'none')
.toLowerCase();
if (cacheType != 'local' && cacheType != 'none') {
throw new Error(
'Invalid cache type, only "local" and "none" are allowed',
);
}
return cacheType;
}
}
File diff suppressed because it is too large. Load diff
@@ -1,11 +1,12 @@
import { Chat } from '@adiwajshing/baileys';
import { PaginationParams } from '@waha/structures/pagination.dto';
export interface IChatRepository {
getAll(): Promise<Chat[]>;
getAllByIds(ids: string[]): Promise<Chat[]>;
getAllWithMessages(limit?: number, offset?: number): Promise<Chat[]>;
getAllWithMessages(pagination: PaginationParams): Promise<Chat[]>;
getById(id: string): Promise<Chat | null>;
@@ -1,7 +1,8 @@
import { Contact } from '@adiwajshing/baileys';
import { PaginationParams } from '@waha/structures/pagination.dto';
export interface IContactRepository {
getAll(): Promise<Contact[]>;
getAll(pagination?: PaginationParams): Promise<Contact[]>;
getById(id: string): Promise<Contact | null>;
@@ -1,3 +1,6 @@
import { GetChatMessagesFilter } from '@waha/structures/chats.dto';
import { PaginationParams } from '@waha/structures/pagination.dto';
export interface IMessagesRepository {
deleteAll(): Promise<void>;
@@ -5,7 +8,11 @@ export interface IMessagesRepository {
upsertOne(message: any): Promise<void>;
getAllByJid(jid: string, limit: number): Promise<any[]>;
getAllByJid(
jid: string,
filter: GetChatMessagesFilter,
pagination: PaginationParams,
): Promise<any[]>;
getByJidById(jid: string, id: string): Promise<any | null>;
+11 -3
View File
@@ -5,6 +5,8 @@ import {
proto,
} from '@adiwajshing/baileys';
import { Label } from '@adiwajshing/baileys/lib/Types/Label';
import { GetChatMessagesFilter } from '@waha/structures/chats.dto';
import { PaginationParams } from '@waha/structures/pagination.dto';
export interface INowebStore {
presences: any;
@@ -17,11 +19,17 @@ export interface INowebStore {
loadMessage(jid: string, id: string): Promise<proto.IWebMessageInfo>;
getMessagesByJid(chatId: string, limit: number): Promise<any>;
getMessagesByJid(
chatId: string,
filter: GetChatMessagesFilter,
pagination: PaginationParams,
): Promise<any>;
getChats(limit?: number, offset?: number): Promise<Chat[]>;
getMessageById(chatId: string, messageId: string): Promise<any>;
getContacts(): Promise<Contact[]>;
getChats(pagination: PaginationParams): Promise<Chat[]>;
getContacts(pagination: PaginationParams): Promise<Contact[]>;
getContactById(jid: string): Promise<Contact>;
@@ -1,6 +1,8 @@
import { Chat, Contact, makeInMemoryStore, proto } from '@adiwajshing/baileys';
import { Label } from '@adiwajshing/baileys/lib/Types/Label';
import { BadRequestException } from '@nestjs/common';
import { GetChatMessagesFilter } from '@waha/structures/chats.dto';
import { PaginationParams } from '@waha/structures/pagination.dto';
import { INowebStore } from './INowebStore';
@@ -37,15 +39,23 @@ export class NowebInMemoryStore implements INowebStore {
return this.store.loadMessage(jid, id);
}
getMessagesByJid(chatId: string, limit: number): Promise<any> {
getMessagesByJid(
chatId: string,
filter: GetChatMessagesFilter,
pagination: PaginationParams,
): Promise<any> {
throw new BadRequestException(this.errorMessage);
}
getChats(limit?: number, offset?: number): Promise<Chat[]> {
getMessageById(chatId: string, messageId: string): Promise<any> {
throw new BadRequestException(this.errorMessage);
}
getContacts(): Promise<Contact[]> {
getChats(pagination: PaginationParams): Promise<Chat[]> {
throw new BadRequestException(this.errorMessage);
}
getContacts(pagination: PaginationParams): Promise<Contact[]> {
throw new BadRequestException(this.errorMessage);
}
@@ -16,6 +16,8 @@ import {
} from '@adiwajshing/baileys/lib/Types/LabelAssociation';
import { ILabelAssociationRepository } from '@waha/core/engines/noweb/store/ILabelAssociationsRepository';
import { ILabelsRepository } from '@waha/core/engines/noweb/store/ILabelsRepository';
import { GetChatMessagesFilter } from '@waha/structures/chats.dto';
import { PaginationParams, SortOrder } from '@waha/structures/pagination.dto';
import { toNumber } from 'lodash';
import { Logger } from 'pino';
@@ -322,20 +324,32 @@ export class NowebPersistentStore implements INowebStore {
return proto.WebMessageInfo.fromObject(data);
}
getMessagesByJid(chatId: string, limit: number) {
return this.messagesRepo.getAllByJid(toJID(chatId), toNumber(limit));
getMessagesByJid(
chatId: string,
filter: GetChatMessagesFilter,
pagination: PaginationParams,
): Promise<any> {
pagination.sortBy = 'messageTimestamp';
pagination.sortOrder = SortOrder.DESC;
return this.messagesRepo.getAllByJid(chatId, filter, pagination);
}
getChats(limit?: number, offset?: number): Promise<Chat[]> {
return this.chatRepo.getAllWithMessages(limit, offset);
getMessageById(chatId: string, messageId: string): Promise<any> {
return this.messagesRepo.getByJidById(chatId, messageId);
}
getChats(pagination: PaginationParams): Promise<Chat[]> {
pagination.sortBy ||= 'conversationTimestamp';
pagination.sortOrder ||= SortOrder.DESC;
return this.chatRepo.getAllWithMessages(pagination);
}
getContactById(jid) {
return this.contactRepo.getById(jid);
}
getContacts() {
return this.contactRepo.getAll();
getContacts(pagination: PaginationParams) {
return this.contactRepo.getAll(pagination);
}
getLabels(): Promise<Label[]> {
@@ -1,23 +1,24 @@
import { Chat } from '@adiwajshing/baileys';
import { PaginationParams } from '@waha/structures/pagination.dto';
import { KnexPaginator } from '@waha/utils/Paginator';
import { IChatRepository } from '../IChatRepository';
import { NOWEBSqlite3KVRepository } from './NOWEBSqlite3KVRepository';
class ChatPaginator extends KnexPaginator {
indexes = ['id', 'conversationTimestamp'];
}
export class Sqlite3ChatRepository
extends NOWEBSqlite3KVRepository<Chat>
implements IChatRepository
{
async getAllWithMessages(limit?: number, offset?: number): Promise<Chat[]> {
protected Paginator = ChatPaginator;
async getAllWithMessages(pagination: PaginationParams): Promise<Chat[]> {
// Get chats with conversationTimestamp is not Null
let query = this.select()
.whereNotNull('conversationTimestamp')
.orderBy('conversationTimestamp', 'desc');
if (limit != null) {
query = query.limit(limit);
}
if (offset != null) {
query = query.offset(offset);
}
let query = this.select().whereNotNull('conversationTimestamp');
query = this.pagination(query, pagination);
return await this.all(query);
}
}
@@ -1,8 +1,16 @@
import { Contact } from '@adiwajshing/baileys';
import { KnexPaginator } from '@waha/utils/Paginator';
import { IContactRepository } from '../IContactRepository';
import { NOWEBSqlite3KVRepository } from './NOWEBSqlite3KVRepository';
class ContactPaginator extends KnexPaginator {
indexes = ['id'];
}
export class Sqlite3ContactRepository
extends NOWEBSqlite3KVRepository<Contact>
implements IContactRepository {}
implements IContactRepository
{
protected Paginator = ContactPaginator;
}
@@ -1,3 +1,7 @@
import { ALL_JID } from '@waha/core/engines/noweb/session.noweb.core';
import { GetChatMessagesFilter } from '@waha/structures/chats.dto';
import { PaginationParams } from '@waha/structures/pagination.dto';
import { IMessagesRepository } from '../IMessagesRepository';
import { NOWEBSqlite3KVRepository } from './NOWEBSqlite3KVRepository';
@@ -9,11 +13,36 @@ export class Sqlite3MessagesRepository
return this.upsertMany(messages);
}
getAllByJid(jid: string, limit: number): Promise<any[]> {
const query = this.select()
.where({ jid: jid })
.limit(limit)
.orderBy('messageTimestamp', 'DESC');
async getAllByJid(
jid: string,
filter: GetChatMessagesFilter,
pagination: PaginationParams,
): Promise<any[]> {
let query = this.select();
if (jid !== ALL_JID) {
query = this.select().where({ jid: jid });
}
if (filter['filter.timestamp.lte'] != null) {
query = query.where(
'messageTimestamp',
'<=',
filter['filter.timestamp.lte'],
);
}
if (filter['filter.timestamp.gte'] != null) {
query = query.where(
'messageTimestamp',
'>=',
filter['filter.timestamp.gte'],
);
}
if (filter['filter.fromMe'] != null) {
// filter by data->"$.key.fromMe"
query = query.whereRaw("data->'$.key.fromMe' = ?", [
filter['filter.fromMe'] ? 'true' : 'false',
]);
}
query = this.pagination(query, pagination);
return this.all(query);
}
+8
View File
@@ -78,6 +78,14 @@ const isObjectALong = (value: any): value is Long => {
);
};
export function ensureNumber(value: number | Long): number {
if (!value) {
// @ts-ignore
return value;
}
return typeof value === 'number' ? value : toNumber(value);
}
const toNumber = (longValue: Long): number => {
const { low, high, unsigned } = longValue;
const result = unsigned ? low >>> 0 : low + high * 0x100000000;
@@ -0,0 +1,52 @@
<!DOCTYPE html><html id="whatsapp-web" class="" lang="en" dir="ltr"><head><link data-default-icon="https://static.whatsapp.net/rsrc.php/v3/yP/r/rYZqPCBaG70.png" rel="shortcut icon" href="https://static.whatsapp.net/rsrc.php/v3/yP/r/rYZqPCBaG70.png" /><meta name="bingbot" content="noarchive" /><meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=2,shrink-to-fit=no" /><link rel="manifest" href="/data/manifest.json" crossorigin="use-credentials" /><meta name="google" content="notranslate" /><meta name="format-detection" content="telephone=no" /><meta name="description" content="Quickly send and receive WhatsApp messages right from your computer." /><meta name="og:description" content="Quickly send and receive WhatsApp messages right from your computer." /><meta name="og:url" content="https://web.whatsapp.com/" /><meta name="og:title" content="WhatsApp Web" /><meta name="og:image" content="https://static.whatsapp.net/rsrc.php/v3/yR/r/y8-PTBaP90a.png" /><meta name="theme-color" content="#111b21" media="(prefers-color-scheme: dark)" /><meta name="theme-color" content="#f0f2f5" /><link rel="apple-touch-icon" sizes="192x192" href="https://static.whatsapp.net/rsrc.php/v3/yc/r/hUUuVTz6ZVi.png" /><style data-btmanifest="1018072227_main" nonce="o8S8zE9I">:root,.__fb-light-mode:root,.__fb-light-mode{--splashscreen-primary-title:#4125d;--splashscreen-progress-background:#e9edef;--splashscreen-progress-primary:#00c298;--splashscreen-secondary-lighter:#8696a0;--splashscreen-startup-background:#f0f2f5;--splashscreen-startup-background-rgb:240, 242, 245;--splashscreen-startup-icon:#bbc5cb}</style><style data-btmanifest="1018072227_main" nonce="o8S8zE9I">.__fb-dark-mode{--splashscreen-primary-title:rgba(233, 237, 239, .88);--splashscreen-progress-background:#233138;--splashscreen-progress-primary:#0b846d;--splashscreen-secondary-lighter:#667781;--splashscreen-startup-background:#111b21;--splashscreen-startup-background-rgb:17, 27, 33;--splashscreen-startup-icon:#676f73}@media (prefers-color-scheme: dark){:root{--splashscreen-primary-title:rgba(233, 237, 239, .88);--splashscreen-progress-background:#233138;--splashscreen-progress-primary:#0b846d;--splashscreen-secondary-lighter:#667781;--splashscreen-startup-background:#111b21;--splashscreen-startup-background-rgb:17, 27, 33;--splashscreen-startup-icon:#676f73}}</style><script id="envjson" type="application/json" nonce="o8S8zE9I">{"useTrustedTypes":false,"isTrustedTypesReportOnly":false,"use_fbt_virtual_modules":true,"compat_iframe_token":null,"clear_js_factory_after_used":true,"brsid":"7435115086220858941"}</script><script src="data:application/x-javascript; charset=utf-8;base64,Oy8qRkJfUEtHX0RFTElNKi8KCnZhciBkYXRhRWxlbWVudD1kb2N1bWVudC5nZXRFbGVtZW50QnlJZCgiZW52anNvbiIpO2lmKGRhdGFFbGVtZW50IT1udWxsKXt2YXIgY29weVZhcmlhYmxlcz1mdW5jdGlvbihhKXtmb3IodmFyIGIgaW4gdmFyaWFibGVzKWFbYl09dmFyaWFibGVzW2JdfSx2YXJpYWJsZXM9SlNPTi5wYXJzZShkYXRhRWxlbWVudC50ZXh0Q29udGVudCk7d2luZG93LnJlcXVpcmVMYXp5P3dpbmRvdy5yZXF1aXJlTGF6eShbIkVudiJdLGNvcHlWYXJpYWJsZXMpOih3aW5kb3cuRW52PXdpbmRvdy5FbnZ8fHt9LGNvcHlWYXJpYWJsZXMod2luZG93LkVudikpfQovLyMgc291cmNlVVJMPWh0dHBzOi8vc3RhdGljLndoYXRzYXBwLm5ldC9yc3JjLnBocC92My95TC9yL2RiZ3JNc25EMThwLmpzCg==" data-bootloader-hash="nxRKQO6" crossorigin="anonymous" data-p=":0" data-btmanifest="1018072227_main" data-c="1"></script><script src="data:application/x-javascript; charset=utf-8;base64,Oy8qRkJfUEtHX0RFTElNKi8KCl9fYW5ub3RhdG9yPWZ1bmN0aW9uKGEpe3JldHVybiBhfSxfX2Rfc3R1Yj1bXSxfX2Q9ZnVuY3Rpb24oYSxiLGMsZCl7X19kX3N0dWIucHVzaChbYSxiLGMsZF0pfSxfX3JsX3N0dWI9W10scmVxdWlyZUxhenk9ZnVuY3Rpb24oKXtfX3JsX3N0dWIucHVzaChhcmd1bWVudHMpfTsKLy8jIHNvdXJjZVVSTD1odHRwczovL3N0YXRpYy53aGF0c2FwcC5uZXQvcnNyYy5waHAvdjMveTIvci9jTWRfU1pvdjlFaS5qcwo=" data-bootloader-hash="W7CcKyc" crossorigin="anonymous" data-p=":0" data-btmanifest="1018072227_main" data-c="1"></script><script src="data:application/x-javascript; charset=utf-8;base64,Oy8qRkJfUEtHX0RFTElNKi8KCl9idGxkcj17fTsKLy8jIHNvdXJjZVVSTD1odHRwczovL3N0YXRpYy53aGF0c2FwcC5uZXQvcnNyYy5waHAvdjMveUgvci9aTGNLT2JOdU8xYi5qcwo=" data-bootloader-hash="678jA6i" crossorigin="anonymous" data-p=":0" data-btmanifest="1018072227_main" data-c="1"></script><script src="data:application/x-javascript; charset=utf-8;base64,Oy8qRkJfUEtHX0RFTElNKi8KCndpbmRvdy5vcGVuRGF0YWJhc2UmJih3aW5kb3cub3BlbkRhdGFiYXNlPWZ1bmN0aW9uKCl7dGhyb3cgbmV3IEVycm9yKCl9KTsKLy8jIHNvdXJjZVVSTD1odHRwczovL3N0YXRpYy53aGF0c2FwcC5uZXQvcnNyYy5waHAvdjMveVkvci85Qk95ZURLTC1kQy5qcwo=" data-bootloader-hash="k6/EjOG" crossorigin="anonymous" data-p=":0" data-btmanifest="1018072227_main" data-c="1"></script><script src="data:application/x-javascript; charset=utf-8;base64,Oy8qRkJfUEtHX0RFTElNKi8KCmZ1bmN0aW9uIHBhcmVudElzTm90SGVhZE5vckJvZHkoYSl7cmV0dXJuIGEucGFyZW50RWxlbWVudCE9PWRvY3VtZW50LmJvZHkmJmEucGFyZW50RWxlbWVudCE9PWRvY3VtZW50LmhlYWR9ZnVuY3Rpb24gaXNUYWdTdXBwb3J0ZWQoYSl7cmV0dXJuIGEubm9kZU5hbWU9PT0iU0NSSVBUInx8YS5ub2RlTmFtZT09PSJMSU5LIiYmKChhPWdldE5vZGVEYXRhU2V0KGEpKT09bnVsbD92b2lkIDA6YS5hc3Line truncated
<script type="application/json" data-content-len="92" data-sjs>{"require":[["qplTimingsServerJS",null,null,["7435115086220858941-server","htmlStart",23]]]}</script>
<link type="text/css" rel="stylesheet" href="https://static.whatsapp.net/rsrc.php/v4/y4/l/0,cross/nJSuJRxe0LRIuQQZ3xdyKtes83aNhixgx.css" data-bootloader-hash="L0/3ndb" crossorigin="anonymous" data-p=":3,27,19" data-btmanifest="1018072227_main" data-c="1" />
<link rel="preload" href="https://static.whatsapp.net/rsrc.php/v3/yl/r/HOQztTu5JYT.js" as="script" crossorigin="anonymous" nonce="o8S8zE9I" /><script src="https://static.whatsapp.net/rsrc.php/v3/yl/r/HOQztTu5JYT.js" data-bootloader-hash="A8pgoR4" async="1" crossorigin="anonymous" data-p=":1" data-btmanifest="1018072227_main" data-c="1"></script>
<link rel="preload" href="https://static.whatsapp.net/rsrc.php/v3/yw/r/dYGBdB10S1Z.js" as="script" crossorigin="anonymous" nonce="o8S8zE9I" /><script src="https://static.whatsapp.net/rsrc.php/v3/yw/r/dYGBdB10S1Z.js" data-bootloader-hash="Bbw1TQP" async="1" crossorigin="anonymous" data-p=":2" data-btmanifest="1018072227_main" data-c="1"></script>
<link rel="preload" href="https://static.whatsapp.net/rsrc.php/v3/yS/r/UhwbwVdbQX0.js" as="script" crossorigin="anonymous" nonce="o8S8zE9I" /><script src="https://static.whatsapp.net/rsrc.php/v3/yS/r/UhwbwVdbQX0.js" data-bootloader-hash="F/l6arV" async="1" crossorigin="anonymous" data-p=":14" data-btmanifest="1018072227_main" data-c="1"></script>
<link rel="preload" href="https://static.whatsapp.net/rsrc.php/v3/yx/r/ryQSgyNOW8X.js" as="script" crossorigin="anonymous" nonce="o8S8zE9I" /><script src="https://static.whatsapp.net/rsrc.php/v3/yx/r/ryQSgyNOW8X.js" data-bootloader-hash="krMlcAs" async="1" crossorigin="anonymous" data-p=":8" data-btmanifest="1018072227_main" data-c="1"></script>
<link rel="preload" href="https://static.whatsapp.net/rsrc.php/v3iWBv4/yk/l/rt/IN3s4Kru2-t2RSfFSjWLjPTFOQq3__cln_2SsOE-ssqJx0Eef7DOYopYnK0rykQBR7vsWqehdEtWw.js" as="script" crossorigin="anonymous" nonce="o8S8zE9I" /><script src="https://static.whatsapp.net/rsrc.php/v3iWBv4/yk/l/rt/IN3s4Kru2-t2RSfFSjWLjPTFOQq3__cln_2SsOE-ssqJx0Eef7DOYopYnK0rykQBR7vsWqehdEtWw.js" data-bootloader-hash="U5nfRMR" async="1" crossorigin="anonymous" data-tsrc="https://static.whatsapp.net/rsrc-translations.php/v7iWBv4/yV/l/en_US/IN3s4Kru2-t2RSfFSjWLjPTFOQq3__cln_2SsOE-ssqJx0Eef7DOYopYnK0rykQBR7vsWqehdEtWw.js" data-p=":25,5,13,23,20,22,7" data-btmanifest="1018072227_main" data-c="1"></script><link rel="preload" href="https://static.whatsapp.net/rsrc-translations.php/v7iWBv4/yV/l/en_US/IN3s4Kru2-t2RSfFSjWLjPTFOQq3__cln_2SsOE-ssqJx0Eef7DOYopYnK0rykQBR7vsWqehdEtWw.js" as="fetch" crossorigin="anonymous" />
<link rel="preload" href="https://static.whatsapp.net/rsrc.php/v3iHCc4/yu/l/rt/rW3lOI79YLL.js" as="script" crossorigin="anonymous" nonce="o8S8zE9I" /><script src="https://static.whatsapp.net/rsrc.php/v3iHCc4/yu/l/rt/rW3lOI79YLL.js" data-bootloader-hash="Bv8Xq6G" async="1" crossorigin="anonymous" data-tsrc="https://static.whatsapp.net/rsrc-translations.php/v7iHCc4/ys/l/en_US/rW3lOI79YLL.js" data-p=":16" data-btmanifest="1018072227_main" data-c="1"></script><link rel="preload" href="https://static.whatsapp.net/rsrc-translations.php/v7iHCc4/ys/l/en_US/rW3lOI79YLL.js" as="fetch" crossorigin="anonymous" />
<link rel="preload" href="https://static.whatsapp.net/rsrc.php/v3/yR/r/h4Qy0FiYsZ_.js" as="script" crossorigin="anonymous" nonce="o8S8zE9I" /><script src="https://static.whatsapp.net/rsrc.php/v3/yR/r/h4Qy0FiYsZ_.js" data-bootloader-hash="bUA3nqa" async="1" crossorigin="anonymous" data-p=":4" data-btmanifest="1018072227_main" data-c="1"></script>
<link rel="preload" href="https://static.whatsapp.net/rsrc.php/v3iuY-4/ym/l/rt/yTGZCBMSfU8.js" as="script" crossorigin="anonymous" nonce="o8S8zE9I" /><script src="https://static.whatsapp.net/rsrc.php/v3iuY-4/ym/l/rt/yTGZCBMSfU8.js" data-bootloader-hash="VS6S+f6" async="1" crossorigin="anonymous" data-tsrc="https://static.whatsapp.net/rsrc-translations.php/v7iuY-4/ye/l/en_US/yTGZCBMSfU8.js" data-p=":9" data-btmanifest="1018072227_main" data-c="1"></script><link rel="preload" href="https://static.whatsapp.net/rsrc-translations.php/v7iuY-4/ye/l/en_US/yTGZCBMSfU8.js" as="fetch" crossorigin="anonymous" />
<link rel="preload" href="https://static.whatsapp.net/rsrc.php/v3/y6/r/OicDJTSlcdh.js" as="script" crossorigin="anonymous" nonce="o8S8zE9I" /><script src="https://static.whatsapp.net/rsrc.php/v3/y6/r/OicDJTSlcdh.js" data-bootloader-hash="hER3bup" async="1" crossorigin="anonymous" data-p=":6" data-btmanifest="1018072227_main" data-c="1"></script>
<script type="application/json" data-content-len="459" data-sjs>{"require":[["MakeHasteTranslations","fetchTranslationsForEarlyFlush",null,[{"U5nfRMR":"https:\/\/static.whatsapp.net\/rsrc-translations.php\/v7iWBv4\/yV\/l\/en_US\/IN3s4Kru2-t2RSfFSjWLjPTFOQq3__cln_2SsOE-ssqJx0Eef7DOYopYnK0rykQBR7vsWqehdEtWw.js","Bv8Xq6G":"https:\/\/static.whatsapp.net\/rsrc-translations.php\/v7iHCc4\/ys\/l\/en_US\/rW3lOI79YLL.js","VS6S+f6":"https:\/\/static.whatsapp.net\/rsrc-translations.php\/v7iuY-4\/ye\/l\/en_US\/yTGZCBMSfU8.js"}]]]}</script>
<!--EF-->
<title>WhatsApp Web</title>
<script type="application/json" data-content-len="1247" data-sjs>{"require":[["HasteSupportData","handle",null,[{"gkxData":{"2199":{"result":false,"hash":null},"7742":{"result":false,"hash":null},"8068":{"result":true,"hash":null},"20935":{"result":true,"hash":null},"20936":{"result":false,"hash":null},"1393":{"result":false,"hash":null},"3485":{"result":false,"hash":null},"5918":{"result":false,"hash":null},"5971":{"result":false,"hash":null},"7686":{"result":false,"hash":null},"7687":{"result":true,"hash":null},"21050":{"result":false,"hash":null},"21075":{"result":false,"hash":null},"21076":{"result":true,"hash":null},"22792":{"result":false,"hash":null},"23433":{"result":false,"hash":null},"4341":{"result":false,"hash":null},"6323":{"result":false,"hash":null},"8523":{"result":false,"hash":null},"9861":{"result":false,"hash":null},"21062":{"result":false,"hash":null},"21063":{"result":false,"hash":null},"21069":{"result":false,"hash":null},"21071":{"result":false,"hash":null},"21072":{"result":false,"hash":null},"33056":{"result":false,"hash":null}},"qexData":{"526":{"r":null},"538":{"r":null},"543":{"r":null},"362":{"r":null},"1028":{"r":null},"104":{"r":null},"128":{"r":null},"344":{"r":null},"388":{"r":null}},"justknobxData":{"3323":{"r":true},"1806":{"r":true},"2819":{"r":true}}}]]]}</script>
</head><body class=""><div id="wa_web_initial_startup" class="_apdl"><div style="transform: translate(calc(50% - 52px / 2))" class="_apdm"><span><svg width="250" height="52" xmlns="http://www.w3.org/2000/svg"><path d="M37.7 31.2c-.6-.4-3.8-2-4.4-2.1-.6-.2-1-.4-1.4.3l-2 2.5c-.4.4-.8.5-1.5.2-.6-.3-2.7-1-5.1-3.2-2-1.7-3.2-3.8-3.6-4.5-.4-.6 0-1 .3-1.3l1-1.1.6-1.1c.2-.4 0-.8 0-1.1l-2-4.8c-.6-1.3-1.1-1-1.5-1.1h-1.2c-.5 0-1.2.1-1.8.8-.5.6-2.2 2.2-2.2 5.3 0 3.2 2.3 6.3 2.6 6.7.3.4 4.6 7 11 9.7l3.7 1.4c1.5.5 3 .4 4 .2 1.3-.1 3.9-1.5 4.4-3 .5-1.5.5-2.8.4-3-.2-.4-.6-.5-1.3-.8M26 47.2c-3.9 0-7.6-1-11-3l-.7-.4-8.1 2L8.4 38l-.6-.8A21.4 21.4 0 0126 4.4a21.3 21.3 0 0121.4 21.4c0 11.8-9.6 21.4-21.4 21.4M44.2 7.6a25.8 25.8 0 00-40.6 31L0 52l13.7-3.6A25.8 25.8 0 0044.3 7.5" fill="currentColor"></path></svg></span></div><div class="_apdq"><progress value="0" max="100" dir="ltr"></progress></div><div class="_apdr">WhatsApp</div><div class="_apds"><span><svg width="10" height="12" xmlns="http://www.w3.org/2000/svg"><path d="M5 1.6c1.4 0 2.5 1 2.6 2.4v1.5h.2c.5 0 1 .4 1 1V10c0 .6-.5 1-1 1H2.3a1 1 0 01-1.1-1V6.5c0-.6.5-1 1-1h.2V4.2c0-1.4 1-2.5 2.4-2.6H5zm0 1.2c-.7 0-1.3.6-1.3 1.3v1.4h2.6V4.2c0-.7-.4-1.2-1-1.3H5z" fill="currentColor"></path></svg></span><span> </span>End-to-end encrypted</div></div><script type="application/json" data-content-len="91" data-sjs>{"require":[["qplTimingsServerJS",null,null,["7435115086220858941","splash_screen_show"]]]}</script>
<div id="mount_0_0_kK"></div>
<script type="application/json" data-content-len="53" data-sjs>{"require":[["maybeDisableAnimations",null,null,[]]]}</script>
<script type="application/json" data-content-len="49" data-sjs>{"require":[["replaceNativeTimer",null,null,[]]]}</script>
<script type="application/json" data-content-len="60" data-sjs>{"require":[["bootstrapWebSession",null,null,[1731122631]]]}</script>
<script type="application/json" data-content-len="109" data-sjs>{"require":[["qplTagServerJS",null,null,[["comet_aa_coinflip:false","should_use_bt_safe_js","logged_out"]]]]}</script>
<script type="application/json" data-content-len="157" data-sjs>{"require":[["qplTimingsServerJS",null,null,["7435115086220858941","tierOne"]],["qplTimingsServerJS",null,null,["7435115086220858941-server","tierOne",36]]]}</script>
<script type="application/json" data-content-len="82" data-sjs>{"require":[["JSScheduler","makeSchedulerGlobalEntry",null,["null",false,false]]]}</script>
<script type="application/json" data-content-len="7774" data-sjs>{"require":[["ScheduledServerJS","handle",null,[{"__bbox":{"define":[["cr:310",["RunWWW"],{"__rc":["RunWWW",null]},-1],["cr:1078",[],{"__rc":[null,null]},-1],["cr:1080",["unexpectedUseInComet"],{"__rc":["unexpectedUseInComet",null]},-1],["cr:1126",["TimeSliceSham"],{"__rc":["TimeSliceSham",null]},-1],["cr:3725",["clearTimeoutWWWOrMobile"],{"__rc":["clearTimeoutWWWOrMobile",null]},-1],["cr:4344",["setTimeoutWWWOrMobile"],{"__rc":["setTimeoutWWWOrMobile",null]},-1],["cr:6640",[],{"__rc":[null,null]},-1],["cr:7385",["clearIntervalWWW"],{"__rc":["clearIntervalWWW",null]},-1],["cr:7386",["clearTimeoutWWW"],{"__rc":["clearTimeoutWWW",null]},-1],["cr:7388",["setIntervalWWW"],{"__rc":["setIntervalWWW",null]},-1],["cr:7389",["setIntervalAcrossTransitionsWWW"],{"__rc":["setIntervalAcrossTransitionsWWW",null]},-1],["cr:7390",["setTimeoutWWW"],{"__rc":["setTimeoutWWW",null]},-1],["cr:7391",["setTimeoutAcrossTransitionsWWW"],{"__rc":["setTimeoutAcrossTransitionsWWW",null]},-1],["cr:8959",["DTSG"],{"__rc":["DTSG",null]},-1],["cr:8960",["DTSG_ASYNC"],{"__rc":["DTSG_ASYNC",null]},-1],["cr:696703",["JSScheduler"],{"__rc":["JSScheduler",null]},-1],["cr:806696",["clearTimeoutComet"],{"__rc":["clearTimeoutComet",null]},-1],["cr:807042",["setTimeoutComet"],{"__rc":["setTimeoutComet",null]},-1],["cr:896461",["setIntervalComet"],{"__rc":["setIntervalComet",null]},-1],["cr:896462",["setIntervalComet"],{"__rc":["setIntervalComet",null]},-1],["cr:925100",["RunComet"],{"__rc":["RunComet",null]},-1],["cr:986633",["setTimeoutComet"],{"__rc":["setTimeoutComet",null]},-1],["cr:1003267",["clearIntervalComet"],{"__rc":["clearIntervalComet",null]},-1],["cr:734",[],{"__rc":[null,null]},-1],["cr:1293",["ReactDOM.classic"],{"__rc":["ReactDOM.classic",null]},-1],["cr:4874",[],{"__rc":[null,null]},-1],["cr:7162",["ReactDOMCompatibilityLayer"],{"__rc":["ReactDOMCompatibilityLayer",null]},-1],["cr:7451",[],{"__rc":[null,null]},-1],["cr:1064332",[],{"__rc":[null,null]},-1],["cr:1106516",[],{"__rc":[null,null]},-1],["cr:1108857",[],{"__rc":[null,null]},-1],["cr:1294158",["React.classic"],{"__rc":["React.classic",null]},-1],["cr:1294159",["ReactDOM.classic"],{"__rc":["ReactDOM.classic",null]},-1],["CometPersistQueryParams",[],{"relative":{},"domain":{}},6231],["GetAsyncParamsExtraData",[],{"extra_data":{}},7511],["BootloaderConfig",[],{"deferBootloads":false,"jsRetries":[200,500],"jsRetryAbortNum":2,"jsRetryAbortTime":5,"silentDups":true,"timeout":60000,"tieredLoadingFromTier":100,"hypStep4":true,"phdOn":false,"btCutoffIndex":302,"fastPathForAlreadyRequired":true,"earlyRequireLazy":false,"enableTimeoutLoggingForNonComet":false,"deferLongTailManifest":true,"lazySoT":false,"translationRetries":[200,500],"translationRetryAbortNum":3,"translationRetryAbortTime":50},329],["CSSLoaderConfig",[],{"timeout":5000,"loadEventSupported":true},619],["CurrentUserInitialData",[],{"ACCOUNT_ID":"0","USER_ID":"0","NAME":"","SHORT_NAME":null,"IS_BUSINESS_PERSON_ACCOUNT":false,"HAS_SECONDARY_BUSINESS_PERSON":false,"IS_FACEBOOK_WORK_ACCOUNT":false,"IS_INSTAGRAM_BUSINESS_PERSON":false,"IS_MESSENGER_ONLY_USER":false,"IS_DEACTIVATED_ALLOWED_ON_MESSENGER":false,"IS_MESSENGER_CALL_GUEST_USER":false,"IS_WORK_MESSENGER_CALL_GUEST_USER":false,"IS_WORKROOMS_USER":false,"APP_ID":"2220391788200892","IS_BUSINESS_DOMAIN":false,"NON_FACEBOOK_USER_ID":"0"},270],["DTSGInitialData",[],{},258],["LSD",[],{"token":"wEKbRN9wZlWrDjkzN7hiVV"},323],["ServerNonce",[],{"ServerNonce":"EXAGZxp_fullevDJmcCA-j"},141],["SiteData",[],{"server_revision":1018072227,"client_revision":1018072227,"push_phase":"C3","pkg_cohort":"HYP:whatsapp_comet_pkg","haste_session":"20036.HYP:whatsapp_comet_pkg.2.1..0.1","pr":1,"manifest_base_uri":"https:\/\/static.whatsapp.net","manifest_origin":"whatsapp","manifest_version_prefix":"","be_one_ahead":true,"is_rtl":false,"is_experimental_tier":false,"is_jit_warmed_up":true,"hsi":"7435115086220858941","semr_host_bucket":"5","bl_hash_version":2,"comet_env":17,"wbloks_env":false,"ef_page":"comet.whatsapp.web.\/","compose_bootloads":false,"spin":4,"__spin_r":1018072227,"__spin_b":"trunk","__spin_t":1731122631,"vip":"157.240.229.60"},317],["SprinkleConfig",[],{"param_name":"jazoest","version":2,"should_randomize":false},2111],["PromiseUsePolyfillSetImmediateGK",[],{"www_always_use_polyfill_setimmediate":false},2190],["JSErrorLoggingConfig",[],{"appId":2220391788200892,"extra":[],"reportInterval":50,"sampleWeight":null,"sampleWeightKey":"__jssesw","projectBlocklist":[]},2776],["ImmediateImplementationExperiments",[],{"prefer_message_channel":true},3419],["DTSGInitData",[],{"token":"","async_get_token":""},3515],["UriNeedRawQuerySVConfig",[],{"uris":["dms.netmng.com","doubleclick.net","r.msn.com","watchit.sky.com","graphite.instagram.com","www.kfc.co.th","learn.pantheon.io","www.landmarkshops.in","www.ncl.com","s0.wp.com","www.tatacliq.com","bs.serving-sys.com","kohls.com","lazada.co.th","xg4ken.com","technopark.ruLine truncated
<script type="application/json" data-content-len="83" data-sjs>{"require":[["qplTimingsServerJS",null,null,["7435115086220858941","tierOneEnd"]]]}</script>
<script type="application/json" data-content-len="157" data-sjs>{"require":[["qplTimingsServerJS",null,null,["7435115086220858941","tierTwo"]],["qplTimingsServerJS",null,null,["7435115086220858941-server","tierTwo",80]]]}</script>
<script type="application/json" data-content-len="3594" data-sjs>{"require":[["HasteSupportData","handle",null,[{"bxData":{"9547":{"uri":"https:\/\/static.whatsapp.net\/rsrc.php\/v3\/yl\/r\/gi_DckOUM5a.png"},"9548":{"uri":"https:\/\/static.whatsapp.net\/rsrc.php\/v3\/y_\/r\/TPtFe9SpDSX.png"},"9555":{"uri":"https:\/\/static.whatsapp.net\/rsrc.php\/v3\/y4\/r\/XEPQuYy90FJ.png"},"9556":{"uri":"https:\/\/static.whatsapp.net\/rsrc.php\/v3\/ye\/r\/fWOSgX_xpHG.png"},"9561":{"uri":"https:\/\/static.whatsapp.net\/rsrc.php\/yW\/r\/BS_BUUXbKq5.mp3"},"9562":{"uri":"https:\/\/static.whatsapp.net\/rsrc.php\/yu\/r\/lsK0FC6wi8C.m4a"},"10639":{"uri":"https:\/\/static.whatsapp.net\/rsrc.php\/v3\/y3\/r\/AZwgE-H9Ubq.png"},"11319":{"uri":"https:\/\/static.whatsapp.net\/rsrc.php\/v3\/yX\/r\/JYPizEwERE4.png"},"11370":{"uri":"https:\/\/static.whatsapp.net\/rsrc.php\/v3\/y-\/r\/_8J_xqERSbv.png"},"31401":{"uri":"https:\/\/static.whatsapp.net\/rsrc.php\/v3\/y1\/r\/m5BEg2K4OR4.png"},"31402":{"uri":"https:\/\/static.whatsapp.net\/rsrc.php\/v3\/yb\/r\/uspVxRFx2oU.png"},"31422":{"uri":"https:\/\/static.whatsapp.net\/rsrc.php\/v3\/yq\/r\/MHVytaGe3gh.png"},"9544":{"uri":"https:\/\/static.whatsapp.net\/rsrc.php\/yv\/r\/6Ib-MkrweJ6.ttf"},"9545":{"uri":"https:\/\/static.whatsapp.net\/rsrc.php\/yk\/r\/ln-uwGsChQW.ttf"},"9551":{"uri":"https:\/\/static.whatsapp.net\/rsrc.php\/v3\/yq\/r\/xCHrap4ZvgY.png"},"9552":{"uri":"https:\/\/static.whatsapp.net\/rsrc.php\/v3\/y6\/r\/R6k6NrF2Cbl.png"},"9553":{"uri":"https:\/\/static.whatsapp.net\/rsrc.php\/v3\/yd\/r\/jLlIEwASpgj.png"},"9554":{"uri":"https:\/\/static.whatsapp.net\/rsrc.php\/v3\/y5\/r\/_U7GO8EnPFR.png"},"9559":{"uri":"https:\/\/static.whatsapp.net\/rsrc.php\/v3\/yy\/r\/noBSk9ItwJJ.png"},"9583":{"uri":"https:\/\/static.whatsapp.net\/rsrc.php\/v3\/yP\/r\/re6de1QF8pQ.png"},"10591":{"uri":"https:\/\/static.whatsapp.net\/rsrc.php\/v3\/yp\/r\/ReKDgNfJEFB.png"}},"clpData":{"6476":{"r":1000,"s":1},"1744178":{"r":1,"s":1},"1838142":{"r":1,"s":1}},"gkxData":{"1624":{"result":false,"hash":null},"2160":{"result":false,"hash":null},"4112":{"result":false,"hash":null},"5134":{"result":true,"hash":null},"5434":{"result":false,"hash":null},"5679":{"result":false,"hash":null},"5943":{"result":false,"hash":null},"6748":{"result":true,"hash":null},"6969":{"result":false,"hash":null},"7541":{"result":false,"hash":null},"7685":{"result":false,"hash":null},"20836":{"result":false,"hash":null},"20940":{"result":false,"hash":null},"21043":{"result":false,"hash":null},"21051":{"result":false,"hash":null},"21052":{"result":false,"hash":null},"21053":{"result":false,"hash":null},"21054":{"result":false,"hash":null},"21055":{"result":false,"hash":null},"21056":{"result":false,"hash":null},"21057":{"result":false,"hash":null},"21058":{"result":true,"hash":null},"21106":{"result":true,"hash":null},"21107":{"result":false,"hash":null},"21112":{"result":true,"hash":null},"26256":{"result":false,"hash":null},"26258":{"result":true,"hash":null},"26259":{"result":false,"hash":null},"26262":{"result":true,"hash":null},"24344":{"result":false,"hash":null},"24351":{"result":false,"hash":null},"24383":{"result":false,"hash":null},"30214":{"result":false,"hash":null}},"qplData":{"106":{}},"justknobxData":{"371":{"r":true},"819":{"r":true},"822":{"r":true},"860":{"r":100},"1617":{"r":160},"1618":{"r":8},"1619":{"r":1},"1620":{"r":2},"1621":{"r":4},"1622":{"r":0},"1623":{"r":6},"1624":{"r":1},"1662":{"r":2},"1663":{"r":14},"1664":{"r":2},"1784":{"r":false},"1854":{"r":false},"2086":{"r":4},"2148":{"r":false},"2157":{"r":true},"2237":{"r":false},"2268":{"r":true},"2269":{"r":true},"2552":{"r":false},"2702":{"r":true}}}]]]}</script>
<script type="application/json" data-content-len="40671" data-sjs>{"require":[["Bootloader","handlePayload",null,[{"consistency":{"rev":1018072227},"rsrcMap":{"xFqg8AH":{"type":"js","src":"https:\/\/static.whatsapp.net\/rsrc.php\/v3\/yL\/r\/BlIuoNYtWPV.js","c":1,"p":":195","m":"1018072227_main"},"AJOaQIc":{"type":"js","src":"https:\/\/static.whatsapp.net\/rsrc.php\/v3\/y8\/r\/G_AUzITVDxz.js","c":1,"p":":11","m":"1018072227_main"},"a\/ErpYx":{"type":"js","src":"https:\/\/static.whatsapp.net\/rsrc.php\/v3\/y_\/r\/Erq9JIEfuV6.js","c":1,"p":":10","m":"1018072227_main"},"a5fZ54F":{"type":"js","src":"https:\/\/static.whatsapp.net\/rsrc.php\/v3iekP4\/yH\/l\/rt\/NXnawuAbdD4.js","tsrc":"https:\/\/static.whatsapp.net\/rsrc-translations.php\/v7iekP4\/yP\/l\/en_US\/NXnawuAbdD4.js","c":1,"p":":21","m":"1018072227_main"},"EeF59cM":{"type":"js","src":"https:\/\/static.whatsapp.net\/rsrc.php\/v3i9cC4\/yD\/l\/rt\/mzFfMLLw6HK.js","tsrc":"https:\/\/static.whatsapp.net\/rsrc-translations.php\/v7i9cC4\/yX\/l\/en_US\/mzFfMLLw6HK.js","c":1,"p":":36","m":"1018072227_main"},"Xkc0WVi":{"type":"js","src":"https:\/\/static.whatsapp.net\/rsrc.php\/v3iREA4\/yI\/l\/rt\/8Aolmhvy8LU.js","tsrc":"https:\/\/static.whatsapp.net\/rsrc-translations.php\/v7iREA4\/yN\/l\/en_US\/8Aolmhvy8LU.js","c":1,"p":":34","m":"1018072227_main"},"vR+ju+x":{"type":"js","src":"https:\/\/static.whatsapp.net\/rsrc.php\/v3i91J4\/yt\/l\/rt\/dNU4SErCrmv.js","tsrc":"https:\/\/static.whatsapp.net\/rsrc-translations.php\/v7i91J4\/ya\/l\/en_US\/dNU4SErCrmv.js","c":1,"p":":28","m":"1018072227_main"},"XRiLJ5h":{"type":"css","src":"https:\/\/static.whatsapp.net\/rsrc.php\/v4\/y7\/l\/0,cross\/WfrI-AAXpPh.css","c":1,"p":":18","m":"1018072227_main"},"4zHjo\/t":{"type":"js","src":"https:\/\/static.whatsapp.net\/rsrc.php\/v3\/yq\/r\/sMiW0MeCLnG.js","c":1,"p":":15","m":"1018072227_main"},"zBLIrrt":{"type":"js","src":"https:\/\/static.whatsapp.net\/rsrc.php\/v3\/yq\/r\/hmLfv8YDyld.js","c":1,"p":":194","m":"1018072227_main"},"gXHW2I\/":{"type":"js","src":"https:\/\/static.whatsapp.net\/rsrc.php\/v3\/yw\/r\/-LbRSVJO6sn.js","c":1,"p":":40","m":"1018072227_main"},"Kyj0DuM":{"type":"js","src":"https:\/\/static.whatsapp.net\/rsrc.php\/v3\/yE\/r\/CtlyoX5avh8.js","c":1,"p":":39","m":"1018072227_main"},"zzVLof4":{"type":"js","src":"https:\/\/static.whatsapp.net\/rsrc.php\/v3i6XI4\/y-\/l\/rt\/AObUHhfOII7.js","tsrc":"https:\/\/static.whatsapp.net\/rsrc-translations.php\/v7i6XI4\/yf\/l\/en_US\/AObUHhfOII7.js","c":1,"p":":149","m":"1018072227_main"},"080D39R":{"type":"js","src":"https:\/\/static.whatsapp.net\/rsrc.php\/v3\/yX\/r\/ctArhl7hZ9J.js","c":1,"p":":219","m":"1018072227_main"},"zZM5CG8":{"type":"js","src":"https:\/\/static.whatsapp.net\/rsrc.php\/v3\/y0\/r\/qV4CPdytvui.js","c":1,"p":":80","m":"1018072227_main"},"Fw+YAB0":{"type":"js","src":"https:\/\/static.whatsapp.net\/rsrc.php\/v3\/yJ\/r\/41rr4p6vs7j.js","c":1,"p":":147","m":"1018072227_main"},"7jdcV+B":{"type":"js","src":"https:\/\/static.whatsapp.net\/rsrc.php\/v3\/yf\/r\/fNcbr8WPj3N.js","c":1,"p":":228","m":"1018072227_main"},"tTfYNAo":{"type":"js","src":"https:\/\/static.whatsapp.net\/rsrc.php\/v3\/yv\/r\/OtiwkkrbnEg.js","c":1,"p":":67","m":"1018072227_main"},"9YKdw6R":{"type":"js","src":"https:\/\/static.whatsapp.net\/rsrc.php\/v3\/yI\/r\/SMKPIPut05F.js","c":1,"p":":192","m":"1018072227_main"},"lz41h9C":{"type":"js","src":"https:\/\/static.whatsapp.net\/rsrc.php\/v3\/yT\/r\/sqn5gRbbgEd.js","c":1,"p":":142","m":"1018072227_main"},"UcOlgtU":{"type":"js","src":"https:\/\/static.whatsapp.net\/rsrc.php\/v3\/yR\/r\/xjqPhXjJVzw.js","c":1,"p":":172","m":"1018072227_main"},"K4DN3ov":{"type":"js","src":"https:\/\/static.whatsapp.net\/rsrc.php\/v3\/yH\/r\/YUTlfecSbQp.js","c":1,"p":":218","m":"1018072227_main"},"WhncEwJ":{"type":"js","src":"https:\/\/static.whatsapp.net\/rsrc.php\/v3\/yy\/r\/jTuTGnMJXdo.js","c":1,"p":":90","m":"1018072227_main"},"Fr6Lkg3":{"type":"js","src":"https:\/\/static.whatsapp.net\/rsrc.php\/v3\/yu\/r\/D_fZidk8XTz.js","c":1,"p":":198","m":"1018072227_main"},"dzSjMyH":{"type":"js","src":"https:\/\/static.whatsapp.net\/rsrc.php\/v3\/y4\/r\/pWX5sxLaZTx.js","c":1,"p":":48","m":"1018072227_main"},"l9YeuzY":{"type":"js","src":"https:\/\/static.whatsapp.net\/rsrc.php\/v3\/ya\/r\/mEsaFp2_2Sa.js","c":1,"p":":50","m":"1018072227_main"},"pZbl5CZ":{"type":"js","src":"https:\/\/static.whatsapp.net\/rsrc.php\/v3\/yD\/r\/Hfkat6cPNHo.js","c":1,"p":":241","m":"1018072227_main"},"j65SaHn":{"type":"js","src":"https:\/\/static.whatsapp.net\/rsrc.php\/v3\/yC\/r\/sDl5KW9QJ97.js","c":1,"p":":150","m":"1018072227_main"},"bRKmx2D":{"type":"js","src":"https:\/\/static.whatsapp.net\/rsrc.php\/v3\/yj\/r\/wLUf1sFtuSI.js","c":1,"p":":163","m":"1018072227_main"},"wDNapDv":{"type":"js","src":"https:\/\/static.whatsapp.net\/rsrc.php\/v3\/yP\/r\/xc6XM-JdW6p.js","c":1,"p":":274","m":"1018072227_main"},"rc5Kz5Y":{"type":"js","src":"https:\/\/static.whatsapp.net\/rsrc.php\/v3\/y7\/r\/EZZ9HoHBlyi.js","c":1,"p":":79","m":"1018072227_main"},"IkfPTaq":{"tLine truncated
<script type="application/json" data-content-len="65298" data-sjs>{"require":[["ScheduledServerJS","handle",null,[{"__bbox":{"define":[["cr:686",[],{"__rc":[null,null]},-1],["cr:755",["warningWWW"],{"__rc":["warningWWW",null]},-1],["cr:3128",[],{"__rc":[null,null]},-1],["cr:4122",[],{"__rc":[null,null]},-1],["cr:4324",[],{"__rc":[null,null]},-1],["cr:4533",[],{"__rc":[null,null]},-1],["cr:4722",[],{"__rc":[null,null]},-1],["cr:4857",[],{"__rc":[null,null]},-1],["cr:4940",[],{"__rc":[null,null]},-1],["cr:4941",[],{"__rc":[null,null]},-1],["cr:5151",[],{"__rc":[null,null]},-1],["cr:5292",[],{"__rc":[null,null]},-1],["cr:5411",[],{"__rc":[null,null]},-1],["cr:5624",["WAWebCleanAssets"],{"__rc":["WAWebCleanAssets",null]},-1],["cr:5664",[],{"__rc":[null,null]},-1],["cr:5981",[],{"__rc":[null,null]},-1],["cr:6000",[],{"__rc":[null,null]},-1],["cr:6009",[],{"__rc":[null,null]},-1],["cr:6022",["WAWebFeatureDetectionSwSupport"],{"__rc":["WAWebFeatureDetectionSwSupport",null]},-1],["cr:6167",[],{"__rc":[null,null]},-1],["cr:7730",["getFbtResult"],{"__rc":["getFbtResult",null]},-1],["cr:7907",[],{"__rc":[null,null]},-1],["cr:8958",["FBJSON"],{"__rc":["FBJSON",null]},-1],["cr:9859",[],{"__rc":[null,null]},-1],["cr:10195",[],{"__rc":[null,null]},-1],["cr:10196",[],{"__rc":[null,null]},-1],["cr:10197",[],{"__rc":[null,null]},-1],["cr:10198",[],{"__rc":[null,null]},-1],["cr:10199",[],{"__rc":[null,null]},-1],["cr:10201",[],{"__rc":[null,null]},-1],["cr:10212",[],{"__rc":[null,null]},-1],["cr:10234",[],{"__rc":[null,null]},-1],["cr:951783",[],{"__rc":[null,null]},-1],["cr:1984081",[],{"__rc":[null,null]},-1],["IntlCurrentLocale",[],{"code":"en_US"},5954],["WAMediaWasmWorkerResource",[],{"url":"https:\/\/static.whatsapp.net\/rsrc.php\/v3\/y0\/r\/nM6SFfo6YdP.js","name":"WAMediaWasmWorkerBundle","dynamicModules":{"cr:6640":{"__bbox":{"__rc":[null,null]}}},"hsdp":{"bxData":{"236":{"uri":"https:\/\/static.whatsapp.net\/rsrc.php\/y_\/r\/IbbOXFaNJTy.wasm"},"237":{"uri":"https:\/\/static.whatsapp.net\/rsrc.php\/yR\/r\/ayqr5HQtlkb.wasm"},"6945":{"uri":"https:\/\/static.whatsapp.net\/rsrc.php\/yO\/r\/tL59TT3QfUA.wasm"},"6946":{"uri":"https:\/\/static.whatsapp.net\/rsrc.php\/yf\/r\/rogm88TRRiw.wasm"},"31390":{"uri":"https:\/\/static.whatsapp.net\/rsrc.php\/yk\/r\/jtnU4lyaNTU.wasm"}},"gkxData":{"8169":{"result":false,"hash":null}},"qplData":{"106":{}}}},7438],["CookiePrivacySandboxConfig",[],{"is_affected_by_samesite_lax":false},7723],["WAWebMediaWorkerResource",[],{"url":"https:\/\/static.whatsapp.net\/rsrc.php\/v3\/yd\/r\/TWBPKyJqVOD.js","name":"WAMediaWorkerBundle","useInitScript":true},7034],["CookieDomain",[],{"domain":"whatsapp.com"},6421],["IntlVariationHoldout",[],{"disable_variation":false},6533],["IntlNumberTypeProps",["IntlCLDRNumberType05"],{"module":{"__m":"IntlCLDRNumberType05"}},7027],["WAWebRgbaToWebpWorkerResource",[],{"url":"https:\/\/static.whatsapp.net\/rsrc.php\/v3\/yx\/r\/qFfYRbc9sfE.js","name":"WAWebRgbaToWebpWorkerBundle","dynamicModules":{"cr:6640":{"__bbox":{"__rc":[null,null]}}},"hsdp":{}},7480],["WAWebFtsWorkerResource",[],{"url":"https:\/\/static.whatsapp.net\/rsrc.php\/v3\/ya\/r\/UWqggsl4Tnn.js","name":"WAWebFtsWorkerBundle","dynamicModules":{"cr:6640":{"__bbox":{"__rc":[null,null]}}},"hsdp":{"gkxData":{"5943":{"result":false,"hash":null},"26258":{"result":true,"hash":null},"26259":{"result":false,"hash":null}},"justknobxData":{"1617":{"r":160},"1618":{"r":8},"1619":{"r":1},"1620":{"r":2},"1621":{"r":4},"1622":{"r":0},"1623":{"r":6},"1624":{"r":1},"1662":{"r":2},"1663":{"r":14},"1664":{"r":2},"1854":{"r":false}}}},7301],["WAWebOpusRecorderWorkerResource",[],{"url":"https:\/\/static.whatsapp.net\/rsrc.php\/v3\/yg\/r\/LgrJOIYRuL4.js","name":"WAWebOpusRecorderWorkerBundle","hsdp":{}},7347],["WAWebWebpToRgbaWorkerResource",[],{"url":"https:\/\/static.whatsapp.net\/rsrc.php\/v3\/yF\/r\/HOvMdg5_EUB.js","name":"WAWebWebpToRgbaWorkerBundle","dynamicModules":{"cr:6640":{"__bbox":{"__rc":[null,null]}}},"hsdp":{}},7481],["CookieCoreConfig",[],{"dpr":{"t":604800,"s":"None"},"locale":{"t":604800,"s":"Lax"},"wa_lang_pref":{"t":604800,"s":"None"},"wa_web_lang_pref":{"t":2419200,"s":"Lax"}},2104],["IntlPhonologicalRules",[],{"meta":{"\/_B\/":"([.,!?\\s]|^)","\/_E\/":"([.,!?\\s]|$)"},"patterns":{"\/\u0001(.*)('|&#039;)s\u0001(?:'|&#039;)s(.*)\/":"\u0001$1$2s\u0001$3","\/_\u0001([^\u0001]*)\u0001\/":"javascript"}},1496],["IntlViewerContext",[],{"GENDER":3,"regionalLocale":null},772],["NumberFormatConfig",[],{"decimalSeparator":".","numberDelimiter":",","minDigitsForThousandsSeparator":4,"standardDecimalPatternInfo":{"primaryGroupSize":3,"secondaryGroupSize":3},"numberingSystemData":null},54],["WebWorkerConfig",[],{"logging":{"enabled":false,"config":"WebWorkerLoggerConfig"},"evalWorkerURL":"\/rsrc.php\/v3\/y0\/r\/ypsvPwCD3ku.js"},297],["ZeroCategoryHeader",[],{},1127],["ZeroRewriteRules",[],{"rewrite_rules":{},"whitelist":{"\/hr\/r":1,"\/hr\/p":1,"\/zero\/unsupported_browser\/":1,"\/zero\/policy\/optin":1,"\/zero\/optin\Line truncated
<script type="application/json" data-content-len="83" data-sjs>{"require":[["qplTimingsServerJS",null,null,["7435115086220858941","tierTwoEnd"]]]}</script>
<script type="application/json" data-content-len="162" data-sjs>{"require":[["qplTimingsServerJS",null,null,["7435115086220858941","tierThree"]],["qplTimingsServerJS",null,null,["7435115086220858941-server","tierThree",108]]]}</script>
<link rel="preload" href="https://static.whatsapp.net/rsrc.php/v4/y7/l/0,cross/WfrI-AAXpPh.css" as="style" crossorigin="anonymous" />
<script type="application/json" data-content-len="1277" data-sjs>{"require":[["HasteSupportData","handle",null,[{"bxData":{"482":{"uri":"https:\/\/static.whatsapp.net\/rsrc.php\/v3\/yS\/r\/aGcVD59xVTb.png"},"484":{"uri":"https:\/\/static.whatsapp.net\/rsrc.php\/v3\/y6\/r\/wa669aeJeom.png"},"9546":{"uri":"https:\/\/static.whatsapp.net\/rsrc.php\/v3\/yf\/r\/4l5Jzvrg84p.png"},"9563":{"uri":"https:\/\/static.whatsapp.net\/rsrc.php\/yQ\/r\/WElxnwAGYY2.mp3"},"9564":{"uri":"https:\/\/static.whatsapp.net\/rsrc.php\/yv\/r\/ze2kHBOq8T0.mp3"},"10047":{"uri":"https:\/\/static.whatsapp.net\/rsrc.php\/v1\/y6\/r\/kNeGAbVVWRf.jpg"},"14943":{"uri":"https:\/\/static.whatsapp.net\/rsrc.php\/v3\/ye\/r\/W2MDyeo0zkf.png"},"9557":{"uri":"https:\/\/static.whatsapp.net\/rsrc.php\/v3\/y5\/r\/cJXjAoffU5Q.png"},"9558":{"uri":"https:\/\/static.whatsapp.net\/rsrc.php\/v3\/yB\/r\/7Y1jh45L_8V.png"},"9565":{"uri":"https:\/\/static.whatsapp.net\/rsrc.php\/yn\/r\/6swO8kJM8z2.mp4"},"9566":{"uri":"https:\/\/static.whatsapp.net\/rsrc.php\/yk\/r\/GoZyw2bTK6s.mp4"},"9560":{"uri":"https:\/\/static.whatsapp.net\/rsrc.php\/v3\/yo\/r\/Oh4t5oP-H5G.png"}},"clpData":{"1848815":{"r":10000,"s":1},"1828945":{"r":100,"s":1},"5779":{"r":100,"s":1}},"gkxData":{"22826":{"result":false,"hash":null}},"justknobxData":{"2450":{"r":true},"2451":{"r":true},"770":{"r":false}}}]]]}</script>
<script type="application/json" data-content-len="43795" data-sjs>{"require":[["Bootloader","handlePayload",null,[{"consistency":{"rev":1018072227},"rsrcMap":{"D9ar2nt":{"type":"js","src":"https:\/\/static.whatsapp.net\/rsrc.php\/v3i6sf4\/y_\/l\/rt\/65HPwmPTxvE.js","tsrc":"https:\/\/static.whatsapp.net\/rsrc-translations.php\/v7i6sf4\/yW\/l\/en_US\/65HPwmPTxvE.js","c":1,"p":":31","m":"1018072227_main"},"hnModTo":{"type":"js","src":"https:\/\/static.whatsapp.net\/rsrc.php\/v3iis_4\/yd\/l\/rt\/ZqmYe3zRs7GwaJyZPA-vVYdNU4SErCrmvFPlNguXyB9esMiW0MeCLnGG_AUzITVDxz.js","tsrc":"https:\/\/static.whatsapp.net\/rsrc-translations.php\/v7iis_4\/ye\/l\/en_US\/ZqmYe3zRs7GwaJyZPA-vVYdNU4SErCrmvFPlNguXyB9esMiW0MeCLnGG_AUzITVDxz.js","c":1,"p":":17,29,28,30,15,11","m":"1018072227_main"},"rtkaIRw":{"type":"js","src":"https:\/\/static.whatsapp.net\/rsrc.php\/v3iXrJ4\/yS\/l\/rt\/KpdmaCxXrbk.js","tsrc":"https:\/\/static.whatsapp.net\/rsrc-translations.php\/v7iXrJ4\/yF\/l\/en_US\/KpdmaCxXrbk.js","c":1,"p":":33","m":"1018072227_main"},"nH8nCaN":{"type":"js","src":"https:\/\/static.whatsapp.net\/rsrc.php\/v3i4114\/y9\/l\/rt\/TPp4r4DTd6U.js","tsrc":"https:\/\/static.whatsapp.net\/rsrc-translations.php\/v7i4114\/y6\/l\/en_US\/TPp4r4DTd6U.js","c":1,"p":":12","m":"1018072227_main"},"NQLlfG2":{"type":"js","src":"https:\/\/static.whatsapp.net\/rsrc.php\/v3iY-Y4\/yl\/l\/rt\/E4aGTVxC2nj.js","tsrc":"https:\/\/static.whatsapp.net\/rsrc-translations.php\/v7iY-Y4\/yw\/l\/en_US\/E4aGTVxC2nj.js","c":1,"p":":24","m":"1018072227_main"},"0DhT4t6":{"type":"js","src":"https:\/\/static.whatsapp.net\/rsrc.php\/v3iCUV4\/yP\/l\/rt\/eT8o1iCEdXa.js","tsrc":"https:\/\/static.whatsapp.net\/rsrc-translations.php\/v7iCUV4\/ym\/l\/en_US\/eT8o1iCEdXa.js","c":1,"p":":35","m":"1018072227_main"},"qWX8sWF":{"type":"js","src":"https:\/\/static.whatsapp.net\/rsrc.php\/v3iJ3t4\/ys\/l\/rt\/jwMe24ZeyPw.js","tsrc":"https:\/\/static.whatsapp.net\/rsrc-translations.php\/v7iJ3t4\/yo\/l\/en_US\/jwMe24ZeyPw.js","c":1,"p":":128","m":"1018072227_main"},"Ni4Wnff":{"type":"js","src":"https:\/\/static.whatsapp.net\/rsrc.php\/v3iXNj4\/yi\/l\/rt\/j3PVDVEGfbS.js","tsrc":"https:\/\/static.whatsapp.net\/rsrc-translations.php\/v7iXNj4\/y-\/l\/en_US\/j3PVDVEGfbS.js","c":1,"p":":26","m":"1018072227_main"},"HxQDhQJ":{"type":"js","src":"https:\/\/static.whatsapp.net\/rsrc.php\/v3i3uu4\/y2\/l\/rt\/I6OEshxaJLb.js","tsrc":"https:\/\/static.whatsapp.net\/rsrc-translations.php\/v7i3uu4\/yo\/l\/en_US\/I6OEshxaJLb.js","c":1,"p":":154","m":"1018072227_main"},"4hny5Ac":{"type":"js","src":"https:\/\/static.whatsapp.net\/rsrc.php\/v3iOEA4\/yi\/l\/rt\/8JxXFm1twal.js","tsrc":"https:\/\/static.whatsapp.net\/rsrc-translations.php\/v7iOEA4\/yc\/l\/en_US\/8JxXFm1twal.js","c":1,"p":":182","m":"1018072227_main"},"VmsME0b":{"type":"js","src":"https:\/\/static.whatsapp.net\/rsrc.php\/v3iB5w4\/y2\/l\/rt\/zTPZwgAxofs.js","tsrc":"https:\/\/static.whatsapp.net\/rsrc-translations.php\/v7iB5w4\/yE\/l\/en_US\/zTPZwgAxofs.js","c":1,"p":":53","m":"1018072227_main"},"IZZqytq":{"type":"js","src":"https:\/\/static.whatsapp.net\/rsrc.php\/v3iw2n4\/yT\/l\/rt\/JMYlCfvGpMJ.js","tsrc":"https:\/\/static.whatsapp.net\/rsrc-translations.php\/v7iw2n4\/yd\/l\/en_US\/JMYlCfvGpMJ.js","c":1,"p":":122","m":"1018072227_main"},"\/9QLqMI":{"type":"js","src":"https:\/\/static.whatsapp.net\/rsrc.php\/v3\/yb\/r\/qXYvgBWQS1q.js","c":1,"p":":114","m":"1018072227_main"},"WL4swjd":{"type":"js","src":"https:\/\/static.whatsapp.net\/rsrc.php\/v3iNrs4\/yY\/l\/rt\/donqKK_Dv61.js","tsrc":"https:\/\/static.whatsapp.net\/rsrc-translations.php\/v7iNrs4\/y0\/l\/en_US\/donqKK_Dv61.js","c":1,"p":":116","m":"1018072227_main"},"YWTJlBx":{"type":"js","src":"https:\/\/static.whatsapp.net\/rsrc.php\/v3\/yA\/r\/GSkPF-VZFTk.js","c":1,"p":":206","m":"1018072227_main"},"kfiiT2r":{"type":"js","src":"https:\/\/static.whatsapp.net\/rsrc.php\/v3iq274\/y4\/l\/rt\/KtBBEKCYzsi.js","tsrc":"https:\/\/static.whatsapp.net\/rsrc-translations.php\/v7iq274\/yR\/l\/en_US\/KtBBEKCYzsi.js","c":1,"p":":176","m":"1018072227_main"},"hUx8D1I":{"type":"js","src":"https:\/\/static.whatsapp.net\/rsrc.php\/v3inCB4\/yG\/l\/rt\/VzcTp4tGD9e.js","tsrc":"https:\/\/static.whatsapp.net\/rsrc-translations.php\/v7inCB4\/yz\/l\/en_US\/VzcTp4tGD9e.js","c":1,"p":":201","m":"1018072227_main"},"wCz0Cyh":{"type":"js","src":"https:\/\/static.whatsapp.net\/rsrc.php\/v3iiDn4\/yv\/l\/rt\/duJxhZWl9PJ.js","tsrc":"https:\/\/static.whatsapp.net\/rsrc-translations.php\/v7iiDn4\/y8\/l\/en_US\/duJxhZWl9PJ.js","c":1,"p":":103","m":"1018072227_main"},"H0SsTnU":{"type":"js","src":"https:\/\/static.whatsapp.net\/rsrc.php\/v3itu04\/yb\/l\/rt\/2b-Iw6nVyr-.js","tsrc":"https:\/\/static.whatsapp.net\/rsrc-translations.php\/v7itu04\/yg\/l\/en_US\/2b-Iw6nVyr-.js","c":1,"p":":178","m":"1018072227_main"},"mpVqeE4":{"type":"js","src":"https:\/\/static.whatsapp.net\/rsrc.php\/v3\/yO\/r\/yPowwKlCoGk.js","c":1,"p":":101","m":"1018072227_main"},"RIMg8Zt":{"type":"js","src":"https:\/\/static.whatsapp.net\/rsrc.php\/v3\/yM\/r\/Lz7N8MRDWLine truncated
<script type="application/json" data-content-len="130" data-sjs>{"require":[["DeferredJSResourceScheduler",null,null,[["D9ar2nt","a\/ErpYx","hnModTo","rtkaIRw","a5fZ54F","nH8nCaN","NQLlfG2"]]]]}</script>
<script type="application/json" data-content-len="6173" data-sjs>{"require":[["ScheduledServerJSWithCSS","handle",null,[{"__bbox":{"define":[["cr:104",[],{"__rc":[null,null]},-1],["cr:1066",[],{"__rc":[null,null]},-1],["cr:2015",[],{"__rc":[null,null]},-1],["cr:4937",[],{"__rc":[null,null]},-1],["cr:5037",[],{"__rc":[null,null]},-1],["cr:5735",[],{"__rc":[null,null]},-1],["cr:5736",[],{"__rc":[null,null]},-1],["cr:5923",[],{"__rc":[null,null]},-1],["cr:5932",[],{"__rc":[null,null]},-1],["cr:5959",[],{"__rc":[null,null]},-1],["cr:5993",[],{"__rc":[null,null]},-1],["cr:5997",[],{"__rc":[null,null]},-1],["cr:6003",[],{"__rc":[null,null]},-1],["cr:6004",[],{"__rc":[null,null]},-1],["cr:6005",[],{"__rc":[null,null]},-1],["cr:6018",[],{"__rc":[null,null]},-1],["cr:7004",["ReactReconcilerConstants.classic"],{"__rc":["ReactReconcilerConstants.classic",null]},-1],["cr:7007",["ReactReconciler.classic"],{"__rc":["ReactReconciler.classic",null]},-1],["cr:7456",[],{"__rc":[null,null]},-1],["cr:8750",[],{"__rc":[null,null]},-1],["cr:8751",[],{"__rc":[null,null]},-1],["cr:8752",[],{"__rc":[null,null]},-1],["cr:8753",[],{"__rc":[null,null]},-1],["cr:10202",[],{"__rc":[null,null]},-1],["cr:10204",[],{"__rc":[null,null]},-1],["cr:10207",[],{"__rc":[null,null]},-1],["cr:10208",[],{"__rc":[null,null]},-1],["cr:10209",[],{"__rc":[null,null]},-1],["cr:10210",[],{"__rc":[null,null]},-1],["cr:10211",[],{"__rc":[null,null]},-1],["cr:12061",["useWAWebNoop"],{"__rc":["useWAWebNoop",null]},-1],["cr:12093",[],{"__rc":[null,null]},-1],["cr:8828",[],{"__rc":[null,null]},-1],["cr:1094907",[],{"__rc":[null,null]},-1],["cr:921407",["useNoopDebuggingInfoComponent"],{"__rc":["useNoopDebuggingInfoComponent",null]},-1],["cr:1634616",["CometUserActivity"],{"__rc":["CometUserActivity",null]},-1],["cr:2695",[],{"__rc":[null,null]},-1],["cr:6008",[],{"__rc":[null,null]},-1],["cr:7002",["ReactReconcilerConstants-prod.classic"],{"__rc":["ReactReconcilerConstants-prod.classic",null]},-1],["cr:6994",["ReactReconciler-prod.classic"],{"__rc":["ReactReconciler-prod.classic",null]},-1]],"require":[["CometSuspenseFalcoEvent"],["WAWebMainWrapper"],["WAFtsMultiLangTokenizer"],["WAWebEmojiAssetLoaderConfig"],["asn1js-2.1.1"],["WAWebEmojiAssetLoader"],["WAWebSetWorkerSafeHandlerApi"],["WAWebSetFrontendHandlerApi"],["WAWebPipVideoStreaming"],["WAWebClearAppStates"],["WAWebLinkDeviceScreen.react"],["FbtLogging"],["IntlQtEventFalcoEvent"],["emptyFunction","thatReturns",["RequireDeferredReference"],[[{"__dr":"CometSuspenseFalcoEvent"},{"__dr":"WAWebMainWrapper"},{"__dr":"WAFtsMultiLangTokenizer"},{"__dr":"WAWebEmojiAssetLoaderConfig"},{"__dr":"asn1js-2.1.1"},{"__dr":"WAWebEmojiAssetLoader"},{"__dr":"WAWebSetWorkerSafeHandlerApi"},{"__dr":"WAWebSetFrontendHandlerApi"},{"__dr":"WAWebPipVideoStreaming"},{"__dr":"WAWebClearAppStates"},{"__dr":"WAWebLinkDeviceScreen.react"},{"__dr":"FbtLogging"},{"__dr":"IntlQtEventFalcoEvent"}]]],["CometBTManifestLoader","init",[],[]],["WAWebEmojiSetup"],["Bootloader","markComponentsAsImmediate",[],[["WAWebNewsletterContextMenu","WAWebAddToFavoritesModal.react","WAWebCaptureMediaDrawer.react","WAWebContactUsModal.react","WAWebCommunityFlow.react","WAWebProfileDrawer.react","pkijs","WAWebSendLogsPopup.react","WAWebSendMsgMultiModal.react","WAWebStarredDrawerV2.react","WAWebNewSubgroupSuggestionFlow.react","WAWebCreateTextStatusFlow.react","WAWebNewsletterTabFlow.react","WAWebVerificationDrawer.react","WAWebAddContactToGroupFlow.react","WAWebArchivedFlow.react","WAWebAttachMediaFlow.react","WAWebAttachVcardsFlow.react","WAWebContactMsgInfoFlow.react","WAWebEditCaptureFlow.react","WAWebGroupMsgInfoFlow.react","WAWebGroupsV4InviteFlow.react","WAWebInfoFlow.react","WAWebMediaViewerFlow.react","WAWebStatusFlow.react","WAWebStatusQuotedFlow.react","WAWebNewChatFlow.react","WAWebNewGroupFlow.react","WAWebSendGroupInviteFlow.react","WAWebSettingsFlow.react","WAWebDirectConnectionCaBundle","WAWebBizCartFlow","relay-runtime","WAWebPhotoPicker.react","WAWebLottieAnimation.react","WAWebStickerStoreFlow","WAWebBizOrderStatusUpdateFlow.react","WAWebDeactivateCommunityDrawer.react","WAWebCommunityAdminPromotionNotificationFlow.react","WAWebManageCommunityGroupsFlow.react","WAWebBizCatalogManagementFlow.react","WAWebBizLabelFlow.react","WAWebBizManageLabelFlow.react","WAWebBizMerchantDetailsFormFlow.react","WAWebBizOrderManagementFlow.react","WAWebBizProductDetailsFlow.react","WAWebBizQuickRepliesFlow.react","WAWebBizSendProductModal.react","WAWebBizAttachProductModal.react","WAWebBizRemoveDirectConnectionKeysBridge","WAWebGroupInviteLinkDrawer.react","WAWebForwardMessageModal.react","WAWebBizMerchantDetailsDrawer.react","WAWebViewAllRepliesFlow.react","WAWebStatusDrawerFlow.react","WAWebAdsManagementDrawer.react","pdfjs-dist","WAWebSeeReviewDetailScreenDrawer.react","WAWebBizToolsFlow.react","WAWebStatusPrivacySettingsModalFlow.react","WAWebNewsletterDSAReportDetailsDrawer.react","WAWeb-lottie-light-canvas","WAWebEventInfoFlow.react","WAWebNewsletteLine truncated
<script type="application/json" data-content-len="85" data-sjs>{"require":[["qplTimingsServerJS",null,null,["7435115086220858941","tierThreeEnd"]]]}</script>
<script type="application/json" data-content-len="3280" data-sjs>{"require":[["CometQPLPayloadStore","storePayloadMap",null,[{"7435115086220858941":{"css\/cgu0bcly5o8w04ww.pkg,css\/8ssldt5oxp8g4o80.pkg,css\/77bvrgeabmo0ccc4.pkg.__composite__.css":{"url":"https:\/\/static.whatsapp.net\/rsrc.php\/v4\/y4\/l\/0,cross\/nJSuJRxe0LRIuQQZ3xdyKtes83aNhixgx.css","refs":["htmlStart","tierOne","tierTwo","tierThree"]},"js\/adwjvc6bn6ogsocw.pkg.js":{"url":"https:\/\/static.whatsapp.net\/rsrc.php\/v3\/yl\/r\/HOQztTu5JYT.js","refs":["htmlStart","tierOne","tierTwo","tierThree"]},"js\/c3ax5tl3nrc4kc88.pkg.js":{"url":"https:\/\/static.whatsapp.net\/rsrc.php\/v3\/yw\/r\/dYGBdB10S1Z.js","refs":["htmlStart","tierOne","tierTwo","tierThree"]},"js\/94dcx39n0x8ooocc.pkg.js":{"url":"https:\/\/static.whatsapp.net\/rsrc.php\/v3\/yS\/r\/UhwbwVdbQX0.js","refs":["htmlStart","tierOne","tierTwo","tierThree"]},"js\/boe4n1nexrwc0ws8.pkg.js":{"url":"https:\/\/static.whatsapp.net\/rsrc.php\/v3\/yx\/r\/ryQSgyNOW8X.js","refs":["htmlStart","tierOne","tierTwo","tierThree"]},"js\/11h6rv0quww0048k.pkg,js\/ajfm1uik02woo48s.pkg,js\/3q7umf15ec00c8og.pkg,js\/4x9cumf05twko8s8.pkg,js\/4sx05anpb4ao84gw.pkg,js\/btvqx8uxs9sgkw4s.pkg,js\/csa9qir353c4goc0.pkg.__composite__.js":{"url":"https:\/\/static.whatsapp.net\/rsrc.php\/v3iWBv4\/yk\/l\/rt\/IN3s4Kru2-t2RSfFSjWLjPTFOQq3__cln_2SsOE-ssqJx0Eef7DOYopYnK0rykQBR7vsWqehdEtWw.js","refs":["htmlStart","tierTwo","tierThree"]},"js\/bls5blk1p3k8gc04.pkg.js":{"url":"https:\/\/static.whatsapp.net\/rsrc.php\/v3iHCc4\/yu\/l\/rt\/rW3lOI79YLL.js","refs":["htmlStart","tierTwo","tierThree"]},"js\/6lvucz4kv18g0og0.pkg.js":{"url":"https:\/\/static.whatsapp.net\/rsrc.php\/v3\/yR\/r\/h4Qy0FiYsZ_.js","refs":["htmlStart","tierTwo","tierThree"]},"js\/ci5trb6air4sskow.pkg.js":{"url":"https:\/\/static.whatsapp.net\/rsrc.php\/v3iuY-4\/ym\/l\/rt\/yTGZCBMSfU8.js","refs":["htmlStart","tierTwo","tierThree"]},"js\/7smu524m1iko4ogs.pkg.js":{"url":"https:\/\/static.whatsapp.net\/rsrc.php\/v3\/y6\/r\/OicDJTSlcdh.js","refs":["htmlStart","tierTwo","tierThree"]},"js\/7ir3e8fs6rokcc0w.pkg.js":{"url":"https:\/\/static.whatsapp.net\/rsrc.php\/v3i6sf4\/y_\/l\/rt\/65HPwmPTxvE.js","refs":["tierThree"]},"js\/6h6os3dm0kg0cog0.pkg.js":{"url":"https:\/\/static.whatsapp.net\/rsrc.php\/v3\/y_\/r\/Erq9JIEfuV6.js","refs":["tierThree"]},"js\/dt5kj0sja4o4kwos.pkg,js\/bqvubmtfy1w08804.pkg,js\/25aaww03azr4soww.pkg,js\/cdkr9wbfnr40ssg0.pkg,js\/7z0ji06w2wgskkwc.pkg,js\/6h5n7x3sc2ccgsoc.pkg.__composite__.js":{"url":"https:\/\/static.whatsapp.net\/rsrc.php\/v3iis_4\/yd\/l\/rt\/ZqmYe3zRs7GwaJyZPA-vVYdNU4SErCrmvFPlNguXyB9esMiW0MeCLnGG_AUzITVDxz.js","refs":["tierThree"]},"js\/cqp6eiggu00ksgww.pkg.js":{"url":"https:\/\/static.whatsapp.net\/rsrc.php\/v3iXrJ4\/yS\/l\/rt\/KpdmaCxXrbk.js","refs":["tierThree"]},"js\/mzhqcy7m8ao0oocw.pkg.js":{"url":"https:\/\/static.whatsapp.net\/rsrc.php\/v3iekP4\/yH\/l\/rt\/NXnawuAbdD4.js","refs":["tierThree"]},"js\/8etvpcy3s2kggwkg.pkg.js":{"url":"https:\/\/static.whatsapp.net\/rsrc.php\/v3i4114\/y9\/l\/rt\/TPp4r4DTd6U.js","refs":["tierThree"]},"js\/6h1am4ng39s800kg.pkg.js":{"url":"https:\/\/static.whatsapp.net\/rsrc.php\/v3iY-Y4\/yl\/l\/rt\/E4aGTVxC2nj.js","refs":["tierThree"]},"css\/dp3ugr89mbwo4osk.pkg.css":{"url":"https:\/\/static.whatsapp.net\/rsrc.php\/v4\/y7\/l\/0,cross\/WfrI-AAXpPh.css","refs":["tierThree"]}}}]]]}</script>
<script type="application/json" data-content-len="69" data-sjs>{"require":[["qplTagServerJS",null,null,[["lastServerTagFlushed"]]]]}</script>
<script type="application/json" data-content-len="1583" data-sjs>{"require":[["qplTimingsServerJS",null,null,["7435115086220858941-server","earlyFlushStart",11]],["qplTimingsServerJS",null,null,["7435115086220858941-server","genEFRsrcStart",15]],["qplTimingsServerJS",null,null,["7435115086220858941-server","genEFRsrcEnd",19]],["qplTimingsServerJS",null,null,["7435115086220858941-server","earlyFlushEnd",23]],["qplTimingsServerJS",null,null,["7435115086220858941-server","cometPageRender",25]],["qplTimingsServerJS",null,null,["7435115086220858941-server","genTierOneStart",25]],["qplTimingsServerJS",null,null,["7435115086220858941-server","genRenderSplashScreenStart",25]],["qplTimingsServerJS",null,null,["7435115086220858941-server","genRenderSplashScreenEnd",26]],["qplTimingsServerJS",null,null,["7435115086220858941-server","genTierOneEnd",36]],["qplTimingsServerJS",null,null,["7435115086220858941-server","tierOnePreloadersFinished",37]],["qplTimingsServerJS",null,null,["7435115086220858941-server","genTierTwoStart",37]],["qplTimingsServerJS",null,null,["7435115086220858941-server","genTierTwoEnd",78]],["qplTimingsServerJS",null,null,["7435115086220858941-server","tierTwoPreloadersFinished",83]],["qplTimingsServerJS",null,null,["7435115086220858941-server","genTierThreeStart",83]],["qplTimingsServerJS",null,null,["7435115086220858941-server","genTierThreeEnd",107]],["qplTimingsServerJS",null,null,["7435115086220858941-server","tierThreePreloadersFinished",109]],["qplTimingsServerJS",null,null,["7435115086220858941-server","responseStart",1]],["qplTimingsServerJS",null,null,["7435115086220858941-server","responseEnd",109]]]}</script>
<script type="application/json" data-content-len="2" data-sjs>{}</script>
<script type="application/json" data-content-len="169" data-sjs>{"require":[["CometQPLPayloadStore","storePayloadBytesSent",null,[{"htmlStart":[14607,4599],"tierOne":[11806,4255],"tierTwo":[110183,31134],"tierThree":[52210,7574]}]]]}</script>
<div id='has-finished-comet-page' hidden style='display: none;'></div></body></html>
+141
View File
@@ -0,0 +1,141 @@
import * as fs from 'fs';
import * as path from 'path';
import pino, { Logger } from 'pino';
import { AuthStrategy, Client } from 'whatsapp-web.js';
async function isValidPath(path: string) {
try {
await fs.promises.access(path);
return true;
} catch {
return false;
}
}
export class LocalAuth implements AuthStrategy {
private readonly logger: Logger;
private client: Client;
private readonly clientId: string;
private readonly dataPath: string;
private readonly rmMaxRetries: number;
private userDataDir: string;
setup(client: Client) {
this.client = client;
}
constructor({ clientId, dataPath, rmMaxRetries, logger }) {
const idRegex = /^[-_\w]+$/i;
if (clientId && !idRegex.test(clientId)) {
throw new Error(
'Invalid clientId. Only alphanumeric characters, underscores and hyphens are allowed.',
);
}
this.dataPath = path.resolve(dataPath || './.wwebjs_auth/');
this.clientId = clientId;
this.rmMaxRetries = rmMaxRetries ?? 4;
this.logger = logger || pino({ name: LocalAuth.name });
}
async beforeBrowserInitialized() {
// @ts-ignore
const puppeteerOpts = this.client.options.puppeteer;
const sessionDirName = this.clientId
? `session-${this.clientId}`
: 'session';
const dirPath = path.join(this.dataPath, sessionDirName);
if (puppeteerOpts.userDataDir && puppeteerOpts.userDataDir !== dirPath) {
throw new Error(
'LocalAuth is not compatible with a user-supplied userDataDir.',
);
}
fs.mkdirSync(dirPath, { recursive: true });
// @ts-ignore
this.client.options.puppeteer = {
...puppeteerOpts,
userDataDir: dirPath,
};
this.userDataDir = dirPath;
await this.removeSingletonFiles(dirPath);
}
/**
* Find in direction Singleton* files and try to remove it
* Fix for SingletonLock and other files
*/
private async removeSingletonFiles(dir: string) {
const files = await fs.promises.readdir(dir);
for (const file of files) {
if (file.startsWith('Singleton')) {
const filePath = path.join(dir, file);
try {
await fs.promises.rm(filePath, {
maxRetries: this.rmMaxRetries,
recursive: true,
force: true,
});
} catch (err) {
this.logger.error(err, `Error deleting: ${filePath}`);
}
}
}
}
async logout() {
if (this.userDataDir) {
await this.removePathSilently(this.userDataDir);
}
}
private async removePathSilently(path: string) {
const exists = await isValidPath(path);
if (!exists) {
return;
}
try {
await fs.promises.rm(path, {
maxRetries: this.rmMaxRetries,
recursive: true,
force: true,
});
} catch (err) {
this.logger.error(err, `Error deleting: ${path}`);
}
}
async afterBrowserInitialized() {
return;
}
async onAuthenticationNeeded() {
return {
failed: false,
restart: false,
failureEventPayload: undefined,
};
}
async getAuthEventPayload() {
return;
}
async afterAuthReady() {
return;
}
async disconnect() {
return;
}
async destroy() {
return;
}
}
-15
View File
@@ -1,15 +0,0 @@
exports.LoadUtils = () => {
window.WAHA = {};
window.WAHA.getChats = async (limit, offset) => {
let chats = window.Store.Chat.getModelsArray();
if (limit || offset) {
offset = offset || 0;
limit = limit || Infinity;
chats = chats.slice(offset, offset + limit);
}
const chatPromises = chats.map((chat) => window.WWebJS.getChatModel(chat));
return await Promise.all(chatPromises);
};
};
+184 -22
View File
@@ -1,38 +1,200 @@
import { Client } from 'whatsapp-web.js';
import { GetChatMessagesFilter } from '@waha/structures/chats.dto';
import { Label } from '@waha/structures/labels.dto';
import { PaginationParams } from '@waha/structures/pagination.dto';
import { TextStatus } from '@waha/structures/status.dto';
import * as lodash from 'lodash';
import { Client, Events } from 'whatsapp-web.js';
import { Message } from 'whatsapp-web.js/src/structures';
// eslint-disable-next-line @typescript-eslint/no-var-requires
const { LoadUtils } = require('./WahaInjected');
const { LoadWAHA } = require('./_WAHA.js');
// eslint-disable-next-line @typescript-eslint/no-var-requires
const { LoadLodash } = require('./_lodash.js');
// eslint-disable-next-line @typescript-eslint/no-var-requires
const { LoadPaginator } = require('./_Paginator.js');
// eslint-disable-next-line @typescript-eslint/no-var-requires
const ChatFactory = require('whatsapp-web.js/src/factories/ChatFactory');
export class WebjsClient extends Client {
async inject() {
// Even tho this.inject is not defined as interface in Client.ts
// We can still call and override it
// @ts-ignore
await super.inject();
//Load util functions (serializers, helper functions)
await this.pupPage.evaluate(LoadUtils);
constructor(options) {
super(options);
// Wait until it's READY and inject more utils
this.on(Events.READY, async () => {
await this.pupPage.evaluate(LoadLodash);
await this.pupPage.evaluate(LoadPaginator);
await this.pupPage.evaluate(LoadWAHA);
});
}
async getChats(limit?: number, offset?: number) {
if (limit == null && offset == null) {
async unpair() {
await this.pupPage.evaluate(async () => {
if (
// @ts-ignore
window.Store &&
// @ts-ignore
window.Store.AppState &&
// @ts-ignore
typeof window.Store.AppState.logout === 'function'
) {
// @ts-ignore
await window.Store.AppState.logout();
}
});
}
async createLabel(name: string, color: number): Promise<number> {
const labelId: number = await this.pupPage.evaluate(
async (name, color) => {
// @ts-ignore
return await window.WAHA.WAWebBizLabelEditingAction.labelAddAction(
name,
color,
);
},
name,
color,
);
return labelId;
}
async deleteLabel(label: Label) {
return await this.pupPage.evaluate(async (label) => {
// @ts-ignore
return await window.WAHA.WAWebBizLabelEditingAction.labelDeleteAction(
label.id,
label.name,
label.color,
);
}, label);
}
async updateLabel(label: Label) {
return await this.pupPage.evaluate(async (label) => {
// @ts-ignore
return await window.WAHA.WAWebBizLabelEditingAction.labelEditAction(
label.id,
label.name,
undefined, // predefinedId
label.color,
);
}, label);
}
async getChats(pagination?: PaginationParams) {
if (lodash.isEmpty(pagination)) {
return await super.getChats();
}
return await this.getChats2(limit, offset);
}
private async getChats2(limit?: number, offset?: number) {
const chats = await this.pupPage.evaluate(
async (limit, offset) => {
// @ts-ignore
return await window.WAHA.getChats(limit, offset);
},
limit,
offset,
);
// Get paginated chats
pagination.limit ||= Infinity;
pagination.offset ||= 0;
const chats = await this.pupPage.evaluate(async (pagination) => {
// @ts-ignore
return await window.WAHA.getChats(pagination);
}, pagination);
return chats.map((chat) => ChatFactory.create(this, chat));
}
async sendTextStatus(status: TextStatus) {
// Convert from hex to number
const waColor = 'FF' + status.backgroundColor.replace('#', '');
const color = parseInt(waColor, 16);
const textStatus = {
text: status.text,
color: color,
font: status.font,
};
const sentMsg = await this.pupPage.evaluate(async (status) => {
// @ts-ignore
await window.Store.SendStatus.sendStatusTextMsgAction(status);
// @ts-ignore
const meUser = window.Store.User.getMeUser();
// @ts-ignore
const myStatus = window.Store.Status.getModelsArray().findLast(
(x) => x.id == meUser,
);
if (!myStatus) {
return undefined;
}
// @ts-ignore
const msg = myStatus.msgs.last();
// @ts-ignore
return msg ? window.WWebJS.getMessageModel(msg) : undefined;
}, textStatus);
return sentMsg ? new Message(this, sentMsg) : undefined;
}
async getMessages(
chatId: string,
filter: GetChatMessagesFilter,
pagination: PaginationParams,
) {
const messages = await this.pupPage.evaluate(
async (chatId, filter, pagination) => {
pagination.limit ||= Infinity;
pagination.offset ||= 0;
const msgFilter = (m) => {
if (m.isNotification) {
return false;
}
if (
filter['filter.fromMe'] != null &&
m.id.fromMe !== filter['filter.fromMe']
) {
return false;
}
if (
filter['filter.timestamp.gte'] != null &&
m.t < filter['filter.timestamp.gte']
) {
return false;
}
if (
filter['filter.timestamp.lte'] != null &&
m.t > filter['filter.timestamp.lte']
) {
return false;
}
return true;
};
// @ts-ignore
const chat = await window.WWebJS.getChat(chatId, { getAsModel: false });
let msgs = chat.msgs.getModelsArray().filter(msgFilter);
while (msgs.length < pagination.limit + pagination.offset) {
const loadedMessages =
// @ts-ignore
await window.Store.ConversationMsgs.loadEarlierMsgs(chat);
if (!loadedMessages || !loadedMessages.length) break;
msgs = [...loadedMessages.filter(msgFilter), ...msgs];
}
if (msgs.length > pagination.limit + pagination.offset) {
// sort by t - new first
msgs = msgs.sort((a, b) => b.t - a.t);
msgs = msgs.slice(
pagination.offset,
pagination.limit + pagination.offset,
);
}
// @ts-ignore
return msgs.map((m) => window.WWebJS.getMessageModel(m));
},
chatId,
filter,
pagination,
);
return messages.map((m) => new Message(this, m));
}
}
+35
View File
@@ -0,0 +1,35 @@
exports.LoadPaginator = () => {
class Paginator {
constructor(pagination) {
this.pagination = pagination;
}
apply(data) {
if (window.lodash.isEmpty(this.pagination)) {
return data;
}
return this.limit(this.sort(data));
}
sort(data) {
if (!this.pagination?.sortBy) {
return data;
}
return window.lodash.orderBy(
data,
[this.pagination.sortBy],
[this.pagination.sortOrder || 'asc'],
);
}
limit(data) {
if (!this.pagination?.limit) {
return data;
}
const offset = this.pagination.offset || 0;
const limit = this.pagination.limit || Infinity;
return data.slice(offset, offset + limit);
}
}
window.Paginator = Paginator;
};
+14
View File
@@ -0,0 +1,14 @@
exports.LoadWAHA = () => {
window.WAHA = {};
window.WAHA.WAWebBizLabelEditingAction = window.require(
'WAWebBizLabelEditingAction',
);
window.WAHA.getChats = async (pagination) => {
let chats = window.Store.Chat.getModelsArray().slice();
const paginator = new window.Paginator(pagination);
chats = paginator.apply(chats);
const chatPromises = chats.map((chat) => window.WWebJS.getChatModel(chat));
return await Promise.all(chatPromises);
};
};
File diff suppressed because it is too large. Load diff
+306 -123
View File
@@ -1,8 +1,9 @@
import { UnprocessableEntityException } from '@nestjs/common';
import {
getChannelInviteLink,
WAHAInternalEvent,
WhatsappSession,
} from '@waha/core/abc/session.abc';
import { LocalAuth } from '@waha/core/engines/webjs/LocalAuth';
import { WebjsClient } from '@waha/core/engines/webjs/WebjsClient';
import {
AvailableInPlusVersion,
@@ -10,7 +11,7 @@ import {
} from '@waha/core/exceptions';
import { IMediaEngineProcessor } from '@waha/core/media/IMediaEngineProcessor';
import { QR } from '@waha/core/QR';
import { parseBool, splitAt } from '@waha/helpers';
import { splitAt } from '@waha/helpers';
import { PairingCodeResponse } from '@waha/structures/auth.dto';
import { CallData } from '@waha/structures/calls.dto';
import {
@@ -19,12 +20,17 @@ import {
CreateChannelRequest,
ListChannelsQuery,
} from '@waha/structures/channels.dto';
import { ChatArchiveEvent, GetChatsQuery } from '@waha/structures/chats.dto';
import {
ChatArchiveEvent,
ChatSortField,
GetChatMessageQuery,
GetChatMessagesFilter,
GetChatMessagesQuery,
} from '@waha/structures/chats.dto';
import {
ChatRequest,
CheckNumberStatusQuery,
EditMessageRequest,
GetMessageQuery,
MessageFileRequest,
MessageForwardRequest,
MessageImageRequest,
@@ -52,13 +58,25 @@ import {
ParticipantsRequest,
SettingsSecurityChangeInfo,
} from '@waha/structures/groups.dto';
import { Label, LabelID } from '@waha/structures/labels.dto';
import { Label, LabelDTO, LabelID } from '@waha/structures/labels.dto';
import { ReplyToMessage } from '@waha/structures/message.dto';
import { PaginationParams } from '@waha/structures/pagination.dto';
import { WAMessage, WAMessageReaction } from '@waha/structures/responses.dto';
import { MeInfo } from '@waha/structures/sessions.dto';
import { WAMessageRevokedBody } from '@waha/structures/webhooks.dto';
import { waitUntil } from '@waha/utils/promiseTimeout';
import {
BROADCAST_ID,
StatusRequest,
TextStatus,
} from '@waha/structures/status.dto';
import {
EnginePayload,
WAMessageRevokedBody,
} from '@waha/structures/webhooks.dto';
import { PaginatorInMemory } from '@waha/utils/Paginator';
import { sleep, waitUntil } from '@waha/utils/promiseTimeout';
import { SingleDelayedJobRunner } from '@waha/utils/SingleDelayedJobRunner';
import { fromEvent, merge, mergeMap, Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import {
Call,
Channel as WEBJSChannel,
@@ -81,6 +99,7 @@ const QRCode = require('qrcode');
export interface WebJSConfig {
webVersion?: string;
cacheType: 'local' | 'none';
}
export class WhatsappSessionWebJSCore extends WhatsappSession {
@@ -124,8 +143,12 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
protected getClientOptions(): ClientOptions {
const path = this.getClassDirName();
const webVersion =
this.engineConfig?.webVersion || '2.3000.1014522270-alpha';
// this.log.debug(`Using web version: '${webVersion}'`);
this.engineConfig?.webVersion || '2.3000.1018072227-alpha';
const cacheType = this.engineConfig?.cacheType || 'none';
this.logger.info(`Using cache type: '${cacheType}'`);
if (cacheType === 'local') {
this.logger.info(`Using web version: '${webVersion}'`);
}
return {
puppeteer: {
headless: true,
@@ -135,15 +158,21 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
},
webVersion: webVersion,
webVersionCache: {
type: 'none',
// path: path,
// strict: true,
type: cacheType,
path: path,
strict: true,
},
};
}
protected async buildClient() {
const clientOptions = this.getClientOptions();
clientOptions.authStrategy = new LocalAuth({
clientId: this.name,
dataPath: undefined,
logger: this.logger,
rmMaxRetries: undefined,
});
this.addProxyConfig(clientOptions);
return new WebjsClient(clientOptions);
}
@@ -193,15 +222,13 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
.then(() => {
// Listen for browser disconnected event
this.whatsapp.pupBrowser.on('disconnected', () => {
this.status = WAHASessionStatus.FAILED;
this.logger.error('The browser has been disconnected');
this.restartClient();
this.failed();
});
// Listen for page close event
this.whatsapp.pupPage.on('close', () => {
this.status = WAHASessionStatus.FAILED;
this.logger.error('The WhatsApp Web page has been closed');
this.restartClient();
this.failed();
});
// Listen for page error event
@@ -216,16 +243,15 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
}
})
.catch((error) => {
this.status = WAHASessionStatus.FAILED;
this.logger.error(error);
this.restartClient();
this.failed();
return;
});
if (this.isDebugEnabled()) {
this.listenEngineEventsInDebugMode();
}
this.listenConnectionEvents();
this.events.emit(WAHAInternalEvent.ENGINE_START);
this.subscribeEngineEvents2();
}
async start() {
@@ -233,8 +259,7 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
await this.init().catch((err) => {
this.logger.error('Failed to start the client');
this.logger.error(err, err.stack);
this.status = WAHASessionStatus.FAILED;
this.restartClient();
this.failed();
});
return this;
}
@@ -242,11 +267,25 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
async stop() {
this.shouldRestart = false;
this.status = WAHASessionStatus.STOPPED;
this.events.removeAllListeners();
this.stopEvents();
this.startDelayedJob.cancel();
await this.end();
}
protected failed() {
// We'll restart the client if it's in the process of unpairing
this.status = WAHASessionStatus.FAILED;
this.restartClient();
}
async unpair() {
this.unpairing = true;
this.shouldRestart = false;
await this.whatsapp.unpair();
// Wait for unpairing to complete
await sleep(2_000);
}
private async end() {
this.engineStateCheckDelayedJob.cancel();
try {
@@ -312,19 +351,18 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
});
this.whatsapp.on(Events.AUTHENTICATED, (args) => {
this.status = WAHASessionStatus.WORKING;
this.qr.save('');
this.logger.info({ args: args }, `Session has been authenticated!`);
});
this.whatsapp.on(Events.AUTHENTICATION_FAILURE, (args) => {
this.status = WAHASessionStatus.FAILED;
this.failed();
this.qr.save('');
this.logger.info({ args: args }, `Session has failed to authenticate!`);
});
this.whatsapp.on(Events.DISCONNECTED, (args) => {
this.status = WAHASessionStatus.FAILED;
this.failed();
this.qr.save('');
this.logger.info({ args: args }, `Session has been disconnected!`);
});
@@ -340,7 +378,7 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
log.info(`Session state changed to bad state, waiting for recovery...`);
this.engineStateCheckDelayedJob.schedule(async () => {
if (!this.startDelayedJob.scheduled) {
if (this.startDelayedJob.scheduled) {
log.info('Session is restarting already, skip check.');
return;
}
@@ -493,10 +531,6 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
await chat.clearState();
}
async getMessages(query: GetMessageQuery) {
return this.getChatMessages(query.chatId, query.limit, query.downloadMedia);
}
async setReaction(request: MessageReactionRequest) {
const message = this.recreateMessage(request.messageId);
return message.react(request.reaction);
@@ -525,18 +559,38 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
/**
* Chats methods
*/
getChats(query: GetChatsQuery) {
return this.whatsapp.getChats(query.limit, query.offset);
getChats(pagination: PaginationParams) {
switch (pagination.sortBy) {
case ChatSortField.ID:
pagination.sortBy = 'id._serialized';
break;
case ChatSortField.CONVERSATION_TIMESTAMP:
pagination.sortBy = 't';
break;
}
return this.whatsapp.getChats(pagination);
}
async getChatMessages(chatId: string, limit: number, downloadMedia: boolean) {
downloadMedia = parseBool(downloadMedia);
const chat: Chat = await this.whatsapp.getChatById(
public async getChatMessages(
chatId: string,
query: GetChatMessagesQuery,
filter: GetChatMessagesFilter,
) {
if (chatId == 'all') {
throw new NotImplementedByEngineError(
"Can not get messages from 'all' in WEBJS",
);
}
const downloadMedia = query.downloadMedia;
// Test there's chat with id
await this.whatsapp.getChatById(this.ensureSuffix(chatId));
const pagination: PaginationParams = query;
const messages = await this.whatsapp.getMessages(
this.ensureSuffix(chatId),
filter,
pagination,
);
const messages = await chat.fetchMessages({
limit: limit,
});
const promises = [];
for (const msg of messages) {
promises.push(this.processIncomingMessage(msg, downloadMedia));
@@ -546,6 +600,33 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
return result;
}
public async getChatMessage(
chatId: string,
messageId: string,
query: GetChatMessageQuery,
): Promise<null | WAMessage> {
const message = await this.whatsapp.getMessageById(messageId);
if (!message) return null;
return await this.processIncomingMessage(message, query.downloadMedia);
}
public async pinMessage(
chatId: string,
messageId: string,
duration: number,
): Promise<boolean> {
const message = await this.whatsapp.getMessageById(messageId);
return message.pin(duration);
}
public async unpinMessage(
chatId: string,
messageId: string,
): Promise<boolean> {
const message = await this.whatsapp.getMessageById(messageId);
return message.unpin();
}
async deleteChat(chatId) {
const chat = await this.whatsapp.getChatById(chatId);
return chat.delete();
@@ -566,6 +647,11 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
return this.whatsapp.unarchiveChat(id);
}
public chatsUnreadChat(chatId: string): Promise<any> {
const id = this.ensureSuffix(chatId);
return this.whatsapp.markChatUnread(id);
}
/**
*
* Label methods
@@ -576,6 +662,24 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
return labels.map(this.toLabel);
}
public async createLabel(label: LabelDTO): Promise<Label> {
const labelId = await this.whatsapp.createLabel(label.name, label.color);
return {
id: labelId.toString(),
name: label.name,
color: label.color,
colorHex: Label.toHex(label.color),
};
}
public async updateLabel(label: Label): Promise<Label> {
return await this.whatsapp.updateLabel(label);
}
public deleteLabel(label: Label): Promise<void> {
return this.whatsapp.deleteLabel(label);
}
public getChatsByLabelId(labelId: string) {
return this.whatsapp.getChatsByLabelId(labelId);
}
@@ -611,10 +715,11 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
.then(this.toWAContact);
}
getContacts() {
return this.whatsapp
.getContacts()
.then((contacts) => contacts.map(this.toWAContact));
async getContacts(pagination: PaginationParams) {
const contactsWEBJS = await this.whatsapp.getContacts();
const contacts = contactsWEBJS.map(this.toWAContact);
const paginator = new PaginatorInMemory(pagination);
return paginator.apply(contacts);
}
public async getContactAbout(query: ContactQuery) {
@@ -893,88 +998,165 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
}
}
/**
* Status methods
*/
protected checkStatusRequest(request: StatusRequest) {
if (request.contacts?.length > 0) {
const msg =
"WEBJS doesn't accept 'contacts'. Remove the field to send status to all contacts.";
throw new UnprocessableEntityException(msg);
}
}
public sendTextStatus(status: TextStatus) {
this.checkStatusRequest(status);
return this.whatsapp.sendTextStatus(status);
}
/**
* END - Methods for API
*/
subscribeEngineEvent(event, handler): boolean {
switch (event) {
case WAHAEvents.MESSAGE:
this.whatsapp.on(Events.MESSAGE_RECEIVED, (message) =>
this.processIncomingMessage(message).then(handler),
);
return true;
case WAHAEvents.MESSAGE_WAITING:
this.whatsapp.on(Events.MESSAGE_CIPHERTEXT, (message) =>
this.processIncomingMessage(message).then(handler),
);
return true;
case WAHAEvents.MESSAGE_REVOKED:
this.whatsapp.on(
Events.MESSAGE_REVOKED_EVERYONE,
async (after, before) => {
const afterMessage = after ? await this.toWAMessage(after) : null;
const beforeMessage = before
? await this.toWAMessage(before)
: null;
const body: WAMessageRevokedBody = {
after: afterMessage,
before: beforeMessage,
subscribeEngineEvents2() {
//
// All
//
const events: Observable<EnginePayload>[] = [];
for (const key in Events) {
const event = Events[key];
const event$ = fromEvent(this.whatsapp, event);
events.push(
event$.pipe(
map((data) => {
return {
event: event,
data: data,
};
handler(body);
},
);
return true;
case WAHAEvents.MESSAGE_REACTION:
this.whatsapp.on('message_reaction', (message) =>
handler(this.processMessageReaction(message)),
);
return true;
case WAHAEvents.MESSAGE_ANY:
this.whatsapp.on(Events.MESSAGE_CREATE, (message) =>
this.processIncomingMessage(message).then(handler),
);
return true;
case WAHAEvents.STATE_CHANGE:
this.whatsapp.on(Events.STATE_CHANGED, handler);
return true;
case WAHAEvents.MESSAGE_ACK:
// We do not download media here
this.whatsapp.on(Events.MESSAGE_ACK, (message) =>
this.toWAMessage(message).then(handler),
);
return true;
case WAHAEvents.GROUP_JOIN:
this.whatsapp.on(Events.GROUP_JOIN, handler);
return true;
case WAHAEvents.GROUP_LEAVE:
this.whatsapp.on(Events.GROUP_LEAVE, handler);
return true;
case WAHAEvents.CHAT_ARCHIVE:
this.whatsapp.on('chat_archived', (chat, archived, _) => {
const body: ChatArchiveEvent = {
id: chat.id._serialized,
archived: archived,
timestamp: chat.timestamp,
};
handler(body);
});
return true;
case WAHAEvents.CALL_RECEIVED:
this.whatsapp.on('call', (call: Call) => {
const body: CallData = {
id: call.id,
from: call.from,
timestamp: call.timestamp,
isVideo: call.isVideo,
isGroup: call.isGroup,
};
handler(body);
});
return true;
default:
return false;
}),
),
);
}
const all$ = merge(...events);
this.events2.get(WAHAEvents.ENGINE_EVENT).switch(all$);
//
// Messages
//
const messageReceived$ = fromEvent(this.whatsapp, Events.MESSAGE_RECEIVED);
const messagesFromOthers$ = messageReceived$.pipe(
mergeMap((msg: any) => this.processIncomingMessage(msg, true)),
);
this.events2.get(WAHAEvents.MESSAGE).switch(messagesFromOthers$);
const messageCreate$ = fromEvent(this.whatsapp, Events.MESSAGE_CREATE);
const messagesFromAll$ = messageCreate$.pipe(
mergeMap((msg: any) => this.processIncomingMessage(msg, true)),
);
this.events2.get(WAHAEvents.MESSAGE_ANY).switch(messagesFromAll$);
const messageCiphertext$ = fromEvent(
this.whatsapp,
Events.MESSAGE_CIPHERTEXT,
);
const messagesWaiting$ = messageCiphertext$.pipe(
mergeMap((msg: any) => this.processIncomingMessage(msg, true)),
);
this.events2.get(WAHAEvents.MESSAGE_WAITING).switch(messagesWaiting$);
const messageRevoked$ = fromEvent(
this.whatsapp,
Events.MESSAGE_REVOKED_EVERYONE,
(after, before) => {
return { after, before };
},
);
const messagesRevoked$ = messageRevoked$.pipe(
map((event): WAMessageRevokedBody => {
const afterMessage = event.after ? this.toWAMessage(event.after) : null;
const beforeMessage = event.before
? this.toWAMessage(event.before)
: null;
return {
after: afterMessage,
before: beforeMessage,
};
}),
);
this.events2.get(WAHAEvents.MESSAGE_REVOKED).switch(messagesRevoked$);
const messageReaction$ = fromEvent(this.whatsapp, 'message_reaction');
const messagesReaction$ = messageReaction$.pipe(
map(this.processMessageReaction.bind(this)),
);
this.events2.get(WAHAEvents.MESSAGE_REACTION).switch(messagesReaction$);
const messageAck$ = fromEvent(
this.whatsapp,
Events.MESSAGE_ACK,
(message, ack) => {
return { message, ack };
},
);
const messagesAck$ = messageAck$.pipe(
map((event) => event.message),
map(this.toWAMessage.bind(this)),
);
this.events2.get(WAHAEvents.MESSAGE_ACK).switch(messagesAck$);
//
// Others
//
const stateChanged$ = fromEvent(this.whatsapp, Events.STATE_CHANGED);
this.events2.get(WAHAEvents.STATE_CHANGE).switch(stateChanged$);
//
// Groups
//
const groupJoin$ = fromEvent(this.whatsapp, Events.GROUP_JOIN);
this.events2.get(WAHAEvents.GROUP_JOIN).switch(groupJoin$);
const groupLeave$ = fromEvent(this.whatsapp, Events.GROUP_LEAVE);
this.events2.get(WAHAEvents.GROUP_LEAVE).switch(groupLeave$);
//
// Chats
//
const chatArchived$ = fromEvent(
this.whatsapp,
'chat_archived',
(chat, archived, _) => {
return {
chat: chat,
archived: archived,
};
},
);
const chatsArchived$ = chatArchived$.pipe(
map((event) => {
return {
id: event.chat.id._serialized,
archived: event.archived,
timestamp: event.chat.timestamp,
};
}),
);
this.events2.get(WAHAEvents.CHAT_ARCHIVE).switch(chatsArchived$);
//
// Calls
//
const call$ = fromEvent(this.whatsapp, 'call');
const calls$ = call$.pipe(
map((call: Call) => {
return {
id: call.id,
from: call.from,
timestamp: call.timestamp,
isVideo: call.isVideo,
isGroup: call.isGroup,
};
}),
);
this.events2.get(WAHAEvents.CALL_RECEIVED).switch(calls$);
}
private async processIncomingMessage(message: Message, downloadMedia = true) {
@@ -986,7 +1168,7 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
this.logger.error(e, e.stack);
}
}
return await this.toWAMessage(message);
return this.toWAMessage(message);
}
private processMessageReaction(reaction: Reaction): WAMessageReaction {
@@ -1004,10 +1186,10 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
};
}
protected toWAMessage(message: Message): Promise<WAMessage> {
protected toWAMessage(message: Message): WAMessage {
const replyTo = this.extractReplyTo(message);
// @ts-ignore
return Promise.resolve({
return {
id: message.id._serialized,
timestamp: message.timestamp,
from: message.from,
@@ -1028,7 +1210,7 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
vCards: message.vCards,
replyTo: replyTo,
_data: message.rawData,
});
};
}
protected extractReplyTo(message: Message): ReplyToMessage | null {
@@ -1072,6 +1254,7 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
return {
mentions: mentions,
quotedMessageId: quotedMessageId,
linkPreview: request.linkPreview,
};
}
}
+1 -1
View File
@@ -6,7 +6,7 @@ export class NotImplementedByEngineError extends UnprocessableEntityException {
constructor(msg = '') {
let error = 'The method is not implemented by the engine.';
if (msg) {
error += ` ${msg}`;
error = `${msg} ${error}`;
}
super(error);
}
@@ -0,0 +1,54 @@
import { populateSessionInfo } from '@waha/core/abc/manager.abc';
import { WhatsappSession } from '@waha/core/abc/session.abc';
import { WebhookSender } from '@waha/core/integrations/webhooks/WebhookSender';
import { WAHAEvents, WAHAEventsWild } from '@waha/structures/enums.dto';
import { WebhookConfig } from '@waha/structures/webhooks.config.dto';
import { EventWildUnmask } from '@waha/utils/events';
import { LoggerBuilder } from '@waha/utils/logging';
import { Logger } from 'pino';
export class WebhookConductor {
private logger: Logger;
private eventUnmask = new EventWildUnmask(WAHAEvents, WAHAEventsWild);
constructor(protected loggerBuilder: LoggerBuilder) {
this.logger = loggerBuilder.child({ name: WebhookConductor.name });
}
protected buildSender(webhookConfig: WebhookConfig): WebhookSender {
return new WebhookSender(this.loggerBuilder, webhookConfig);
}
private getSuitableEvents(events: WAHAEvents[] | string[]): WAHAEvents[] {
return this.eventUnmask.unmask(events);
}
public configure(session: WhatsappSession, webhooks: WebhookConfig[]) {
for (const webhookConfig of webhooks) {
this.configureSingleWebhook(session, webhookConfig);
}
}
private configureSingleWebhook(
session: WhatsappSession,
webhook: WebhookConfig,
) {
if (!webhook || !webhook.url || webhook.events.length === 0) {
return;
}
const url = webhook.url;
this.logger.info(`Configuring webhooks for ${url}...`);
const events = this.getSuitableEvents(webhook.events);
const sender = this.buildSender(webhook);
for (const event of events) {
const obs$ = session.getEventObservable(event);
obs$.subscribe((payload) => {
const data = populateSessionInfo(event, session)(payload);
sender.send(data);
});
this.logger.debug(`Event '${event}' is enabled for url: ${url}`);
}
this.logger.info(`Webhooks were configured for ${url}.`);
}
}
@@ -0,0 +1,203 @@
import { SECOND } from '@waha/structures/enums.dto';
import {
RetryPolicy,
WebhookConfig,
} from '@waha/structures/webhooks.config.dto';
import { LoggerBuilder } from '@waha/utils/logging';
import { VERSION } from '@waha/version';
import axios, { AxiosInstance } from 'axios';
import axiosRetry, { retryAfter } from 'axios-retry';
import * as crypto from 'crypto';
import { Logger } from 'pino';
import { ulid } from 'ulid';
// eslint-disable-next-line @typescript-eslint/no-var-requires
const HttpAgent = require('agentkeepalive');
// eslint-disable-next-line @typescript-eslint/no-var-requires
const HttpsAgent = require('agentkeepalive').HttpsAgent;
const DEFAULT_RETRY_DELAY_SECONDS = 2;
const DEFAULT_RETRY_ATTEMPTS = 15;
const DEFAULT_HMAC_ALGORITHM = 'sha512';
function noDelay(_retryNumber = 0, error: any) {
return Math.max(0, retryAfter(error));
}
function constantDelay(delayFactor: number) {
return (_retryNumber = 0, error = undefined) => {
return Math.max(delayFactor, retryAfter(error));
};
}
export function exponentialDelay(delayFactor: number) {
return (retryNumber = 0, error = undefined) => {
const calculatedDelay = 2 ** retryNumber * delayFactor;
const delay = Math.max(calculatedDelay, retryAfter(error));
const randomSum = delay * 0.2 * Math.random(); // 0-20% of the delay
return delay + randomSum;
};
}
export class WebhookSender {
protected static AGENTS = {
http: new HttpAgent({}),
https: new HttpsAgent({}),
};
protected url: string;
protected logger: Logger;
protected readonly config: WebhookConfig;
protected axios: AxiosInstance;
constructor(
loggerBuilder: LoggerBuilder,
protected webhookConfig: WebhookConfig,
) {
this.url = webhookConfig.url;
this.logger = loggerBuilder.child({ name: WebhookSender.name });
this.config = webhookConfig;
this.axios = this.buildAxiosInstance();
}
send(json: any) {
const body = JSON.stringify(json);
const headers = {
'content-type': 'application/json',
};
Object.assign(headers, this.getWebhookHeader());
Object.assign(headers, this.getHMACHeaders(body));
const ctx = {
id: headers['X-Webhook-Request-Id'],
['event.id']: json.id,
url: this.url,
};
this.logger.info(ctx, `Sending POST...`);
this.logger.debug(ctx, `POST DATA`);
this.axios
.post(this.url, body, { headers: headers })
.then((response) => {
this.logger.info(
ctx,
`POST request was sent with status code: ${response.status}`,
);
this.logger.debug(
{
...ctx,
body: response.data,
},
`Response`,
);
})
.catch((error) => {
this.logger.error(
{
...ctx,
error: error.message,
data: error.response?.data,
},
`POST request failed: ${error.message}`,
);
});
}
protected buildAxiosInstance(): AxiosInstance {
// configure headers
const customHeaders = this.config.customHeaders || [];
const headers = {
'content-type': 'application/json',
'User-Agent': `WAHA/${VERSION.version}`,
};
customHeaders.forEach((header) => {
headers[header.name] = header.value;
});
// configure retry
const attempts = this.config.retries?.attempts ?? DEFAULT_RETRY_ATTEMPTS;
const delaySeconds =
this.config.retries?.delaySeconds ?? DEFAULT_RETRY_DELAY_SECONDS;
const delayMs = delaySeconds * SECOND;
const policy = this.config.retries?.policy;
const retryDelay = this.buildRetryDelay(policy, delayMs);
const instance = axios.create({
headers: headers,
httpAgent: WebhookSender.AGENTS.http,
httpsAgent: WebhookSender.AGENTS.https,
});
axiosRetry(instance, {
retries: attempts,
retryDelay: retryDelay,
retryCondition: (error) => true,
onRetry: (retryCount, error, requestConfig) => {
this.logger.warn(
{ id: requestConfig.headers['X-Webhook-Request-Id'] },
`Error sending POST request: '${error.message}'. Retrying ${retryCount}/${attempts}...`,
);
},
});
return instance;
}
protected getHMACHeaders(body: string) {
// HMAC
const hmac = this.calculateHmac(body, DEFAULT_HMAC_ALGORITHM);
if (!hmac) {
return {};
}
return {
'X-Webhook-Hmac': hmac,
'X-Webhook-Hmac-Algorithm': DEFAULT_HMAC_ALGORITHM,
};
}
protected getWebhookHeader() {
return {
// UUID, no '-' in it
'X-Webhook-Request-Id': ulid(),
// unix timestamp with ms
'X-Webhook-Timestamp': Date.now().toString(),
};
}
private calculateHmac(body, algorithm) {
if (!this.config.hmac || !this.config.hmac.key) {
return undefined;
}
return crypto
.createHmac(algorithm, this.config.hmac.key)
.update(body)
.digest('hex');
}
private buildRetryDelay(
policy: RetryPolicy | null,
ms: number,
): (retryNumber: number, error: any) => number {
if (!ms) {
this.logger.debug(`Using no delay, because delaySeconds set to 0`);
return noDelay;
}
switch (policy) {
case RetryPolicy.CONSTANT:
this.logger.debug(`Using constant delay with '${ms}' ms factor`);
return constantDelay(ms);
case RetryPolicy.LINEAR:
this.logger.debug(`Using linear delay with '${ms}' ms factor`);
return axiosRetry.linearDelay(ms);
case RetryPolicy.EXPONENTIAL:
this.logger.debug(`Using exponential delay with '${ms}' ms factor`);
return exponentialDelay(ms);
default:
this.logger.debug('No delay policy specified, using constant delay');
return constantDelay(ms);
}
}
}
+65 -17
View File
@@ -3,11 +3,17 @@ import {
NotFoundException,
UnprocessableEntityException,
} from '@nestjs/common';
import { WebJSEngineConfigService } from '@waha/core/config/WebJSEngineConfigService';
import { WebhookConductor } from '@waha/core/integrations/webhooks/WebhookConductor';
import { MediaStorageFactory } from '@waha/core/media/MediaStorageFactory';
import { DefaultMap } from '@waha/utils/DefaultMap';
import { getPinoLogLevel, LoggerBuilder } from '@waha/utils/logging';
import { promiseTimeout, sleep } from '@waha/utils/promiseTimeout';
import { EventEmitter } from 'events';
import { complete } from '@waha/utils/reactive/complete';
import { SwitchObservable } from '@waha/utils/reactive/SwitchObservable';
import { PinoLogger } from 'nestjs-pino';
import { Observable, retry, share } from 'rxjs';
import { map } from 'rxjs/operators';
import { WhatsappConfigService } from '../config.service';
import {
@@ -23,7 +29,7 @@ import {
SessionInfo,
} from '../structures/sessions.dto';
import { WebhookConfig } from '../structures/webhooks.config.dto';
import { SessionManager } from './abc/manager.abc';
import { populateSessionInfo, SessionManager } from './abc/manager.abc';
import { SessionParams, WhatsappSession } from './abc/session.abc';
import { EngineConfigService } from './config/EngineConfigService';
import { WhatsappSessionNoWebCore } from './engines/noweb/session.noweb.core';
@@ -33,7 +39,6 @@ import { getProxyConfig } from './helpers.proxy';
import { MediaManager } from './media/MediaManager';
import { LocalSessionAuthRepository } from './storage/LocalSessionAuthRepository';
import { LocalStoreCore } from './storage/LocalStoreCore';
import { WebhookConductorCore } from './webhooks.core';
export class OnlyDefaultSessionIsAllowed extends UnprocessableEntityException {
constructor() {
@@ -59,22 +64,29 @@ export class SessionManagerCore extends SessionManager {
private sessionConfig?: SessionConfig;
DEFAULT = 'default';
// @ts-ignore
protected WebhookConductorClass = WebhookConductorCore;
protected readonly EngineClass: typeof WhatsappSession;
protected events2: DefaultMap<WAHAEvents, SwitchObservable<any>>;
constructor(
config: WhatsappConfigService,
private engineConfigService: EngineConfigService,
private webjsEngineConfigService: WebJSEngineConfigService,
log: PinoLogger,
private mediaStorageFactory: MediaStorageFactory,
) {
super(config, log);
this.events = new EventEmitter();
this.session = DefaultSessionStatus.STOPPED;
this.sessionConfig = null;
const engineName = this.engineConfigService.getDefaultEngineName();
this.EngineClass = this.getEngine(engineName);
this.events2 = new DefaultMap<WAHAEvents, SwitchObservable<any>>(
(key) =>
new SwitchObservable((obs$) => {
return obs$.pipe(retry(), share());
}),
);
this.store = new LocalStoreCore(engineName.toLowerCase());
this.sessionAuthRepository = new LocalSessionAuthRepository(this.store);
this.startPredefinedSessions();
@@ -100,6 +112,7 @@ export class SessionManagerCore extends SessionManager {
}
async beforeApplicationShutdown(signal?: string) {
this.stopEvents();
if (!this.session) {
return;
}
@@ -138,7 +151,7 @@ export class SessionManagerCore extends SessionManager {
`Session '${this.DEFAULT}' is already started.`,
);
}
this.log.info(`'${name}' - starting session...`);
this.log.info({ session: name }, `Starting session...`);
const logger = this.log.logger.child({ session: name });
logger.level = getPinoLogLevel(this.sessionConfig?.debug);
const loggerBuilder: LoggerBuilder = logger;
@@ -153,7 +166,7 @@ export class SessionManagerCore extends SessionManager {
loggerBuilder.child({ name: 'MediaManager' }),
);
const webhook = new this.WebhookConductorClass(loggerBuilder);
const webhook = new WebhookConductor(loggerBuilder);
const proxyConfig = this.getProxyConfig();
const sessionConfig: SessionParams = {
name,
@@ -164,21 +177,19 @@ export class SessionManagerCore extends SessionManager {
proxyConfig: proxyConfig,
sessionConfig: this.sessionConfig,
};
if (this.EngineClass === WhatsappSessionWebJSCore) {
sessionConfig.engineConfig = this.webjsEngineConfigService.getConfig();
}
await this.sessionAuthRepository.init(name);
// @ts-ignore
const session = new this.EngineClass(sessionConfig);
this.session = session;
this.updateSession();
// configure webhooks
const webhooks = this.getWebhooks();
webhook.configure(session, webhooks);
// configure events
session.events.on(
WAHAEvents.SESSION_STATUS,
this.handleSessionEvent(WAHAEvents.SESSION_STATUS, session),
);
// start session
await session.start();
logger.info('Session has been started.');
@@ -189,14 +200,32 @@ export class SessionManagerCore extends SessionManager {
};
}
private updateSession() {
if (!this.session) {
return;
}
const session: WhatsappSession = this.session as WhatsappSession;
for (const eventName in WAHAEvents) {
const event = WAHAEvents[eventName];
const stream$ = session
.getEventObservable(event)
.pipe(map(populateSessionInfo(event, session)));
this.events2.get(event).switch(stream$);
}
}
getSessionEvent(session: string, event: WAHAEvents): Observable<any> {
return this.events2.get(event);
}
async stop(name: string, silent: boolean): Promise<void> {
this.onlyDefault(name);
if (!this.isRunning(name)) {
this.log.debug(`Session is not running.`, { session: name });
this.log.debug({ session: name }, `Session is not running.`);
return;
}
this.log.info(`Stopping session...`, { session: name });
this.log.info({ session: name }, `Stopping session...`);
try {
const session = this.getSession(name);
await session.stop();
@@ -206,11 +235,25 @@ export class SessionManagerCore extends SessionManager {
throw err;
}
}
this.log.info(`Session has been stopped.`, { session: name });
this.log.info({ session: name }, `Session has been stopped.`);
this.session = DefaultSessionStatus.STOPPED;
this.updateSession();
await sleep(this.SESSION_STOP_TIMEOUT);
}
async unpair(name: string) {
if (!this.session) {
return;
}
const session = this.session as WhatsappSession;
this.log.info({ session: name }, 'Unpairing the device from account...');
await session.unpair().catch((err) => {
this.log.warn(`Error while unpairing from device: ${err}`);
});
await sleep(1000);
}
async logout(name: string): Promise<void> {
this.onlyDefault(name);
await this.sessionAuthRepository.clean(name);
@@ -219,6 +262,7 @@ export class SessionManagerCore extends SessionManager {
async delete(name: string): Promise<void> {
this.onlyDefault(name);
this.session = DefaultSessionStatus.REMOVED;
this.updateSession();
this.sessionConfig = undefined;
}
@@ -326,4 +370,8 @@ export class SessionManagerCore extends SessionManager {
const engine = await this.fetchEngineInfo();
return { ...session, engine: engine };
}
protected stopEvents() {
complete(this.events2);
}
}
+1
View File
@@ -63,6 +63,7 @@ export class LocalStoreCore extends LocalStore {
const engineDir = this.getEngineDirectory();
const database = path.join(engineDir, 'waha.sqlite3');
this.db = new Database(database);
this.db.pragma('journal_mode = WAL;');
}
return this.db;
}
@@ -1,5 +1,7 @@
import { PaginationParams } from '@waha/structures/pagination.dto';
import { KnexPaginator } from '@waha/utils/Paginator';
import { Database } from 'better-sqlite3';
import Knex from 'knex';
import Knex, { QueryBuilder } from 'knex';
import { Field, Schema } from './Schema';
@@ -15,6 +17,8 @@ export class Sqlite3KVRepository<Entity> {
private readonly columns: Field[];
private knex: Knex.Knex;
protected Paginator = KnexPaginator;
constructor(
db: Database,
schema: Schema,
@@ -29,8 +33,10 @@ export class Sqlite3KVRepository<Entity> {
this.knex = Knex({ client: 'better-sqlite3', useNullAsDefault: true });
}
getAll() {
return this.all(this.select());
getAll(pagination?: PaginationParams) {
let query = this.select();
query = this.pagination(query, pagination);
return this.all(query);
}
getAllByIds(ids: string[]) {
@@ -163,4 +169,9 @@ export class Sqlite3KVRepository<Entity> {
protected parse(row: any) {
return JSON.parse(row.data);
}
protected pagination(query: any, pagination?: PaginationParams) {
const paginator = new this.Paginator(pagination);
return paginator.apply(query);
}
}
-197
View File
@@ -1,197 +0,0 @@
import { LoggerBuilder } from '@waha/utils/logging';
import Agent from 'agentkeepalive';
import axios from 'axios';
import { AxiosInstance } from 'axios';
import { Logger } from 'pino';
import { WAHAEvents } from '../structures/enums.dto';
import { WebhookConfig } from '../structures/webhooks.config.dto';
import { WAHAWebhook } from '../structures/webhooks.dto';
import { VERSION } from '../version';
import { WAHAInternalEvent, WhatsappSession } from './abc/session.abc';
import { WebhookConductor, WebhookSender } from './abc/webhooks.abc';
// eslint-disable-next-line @typescript-eslint/no-var-requires
const HttpAgent = require('agentkeepalive');
// eslint-disable-next-line @typescript-eslint/no-var-requires
const HttpsAgent = require('agentkeepalive').HttpsAgent;
export class WebhookSenderCore extends WebhookSender {
protected static AGENTS = {
http: new HttpAgent({}),
https: new HttpsAgent({}),
};
protected axios: AxiosInstance;
constructor(loggerBuilder: LoggerBuilder, webhookConfig: WebhookConfig) {
super(loggerBuilder, webhookConfig);
this.axios = this.buildAxiosInstance();
}
protected buildAxiosInstance() {
const headers = {
'content-type': 'application/json',
'User-Agent': `WAHA/${VERSION.version}`,
};
return axios.create({
headers: headers,
httpAgent: WebhookSenderCore.AGENTS.http,
httpsAgent: WebhookSenderCore.AGENTS.https,
});
}
send(json: any, headers?: Record<string, string>) {
headers = headers || {};
this.logger.info(
{ id: headers['X-Webhook-Request-Id'], url: this.url },
`Sending POST...`,
);
this.logger.debug(
{ id: headers['X-Webhook-Request-Id'], data: json },
`POST DATA`,
);
this.axios
.post(this.url, json, { headers: headers })
.then((response) => {
this.logger.info(
{ id: headers['X-Webhook-Request-Id'] },
`POST request was sent with status code: ${response.status}`,
);
this.logger.debug(
{ id: headers['X-Webhook-Request-Id'], body: response.data },
`Response`,
);
})
.catch((error) => {
this.logger.error(
{
id: headers['X-Webhook-Request-Id'],
error: error.message,
data: error.response?.data,
},
`POST request failed: ${error.message}`,
);
});
}
}
export class WebhookConductorCore implements WebhookConductor {
private logger: Logger;
constructor(protected loggerBuilder: LoggerBuilder) {
this.logger = loggerBuilder.child({ name: WebhookConductor.name });
}
protected buildSender(webhookConfig: WebhookConfig): WebhookSender {
return new WebhookSenderCore(this.loggerBuilder, webhookConfig);
}
private getSuitableEvents(events: WAHAEvents[] | string[]): WAHAEvents[] {
const allEvents = Object.values(WAHAEvents);
// Enable all events if * in the events
// @ts-ignore
if (events.includes('*')) {
return allEvents;
}
// Get only known events, log and ignore others
const rightEvents = [];
for (const event of events) {
// @ts-ignore
if (!allEvents.includes(event)) {
this.logger.error(`Unknown event for webhook: '${event}'`);
continue;
}
rightEvents.push(event);
}
return rightEvents;
}
public configure(session: WhatsappSession, webhooks: WebhookConfig[]) {
for (const webhookConfig of webhooks) {
this.configureSingleWebhook(session, webhookConfig);
}
}
private configureSingleWebhook(
session: WhatsappSession,
webhook: WebhookConfig,
) {
if (!webhook || !webhook.url || webhook.events.length === 0) {
return;
}
const url = webhook.url;
this.logger.info(`Configuring webhooks for ${url}...`);
const events = this.getSuitableEvents(webhook.events);
const sender = this.buildSender(webhook);
for (const event of events) {
const found = this.configureSingleEvent(
session.subscribeSessionEvent,
session,
event,
sender,
url,
);
if (!found) {
// Postpone for ENGINE_START event and configure engine events
session.events.on(WAHAInternalEvent.ENGINE_START, () => {
const found = this.configureSingleEvent(
session.subscribeEngineEvent,
session,
event,
sender,
url,
);
if (!found) {
this.logger.error(
`Engine does not support webhook event: '${event}' for url '${url}'`,
);
}
});
}
}
this.logger.info(`Webhooks were configured for ${url}.`);
}
private configureSingleEvent(
subscribeMethod,
session: WhatsappSession,
event: WAHAEvents,
sender: WebhookSender,
url: string,
) {
const found = subscribeMethod.apply(session, [
event,
(data: any) => this.callWebhook(event, session, data, sender),
]);
if (!found) {
return false;
}
this.logger.debug(`Event '${event}' is enabled for url: ${url}`);
return true;
}
public async callWebhook(
event,
session: WhatsappSession,
data: any,
sender: WebhookSender,
) {
const me = session.getSessionMeInfo();
const json: WAHAWebhook = {
event: event,
session: session.name,
metadata: session.sessionConfig?.metadata,
me: me,
payload: data,
engine: session.engine,
environment: VERSION,
};
sender.send(json);
}
}
+85 -12
View File
@@ -1,35 +1,108 @@
import { ApiProperty } from '@nestjs/swagger';
import { BooleanString } from '@waha/nestjs/validation/BooleanString';
import { PaginationParams } from '@waha/structures/pagination.dto';
import { ChatIdProperty } from '@waha/structures/properties.dto';
import { Type } from 'class-transformer';
import { IsNumber, IsOptional } from 'class-validator';
import { SessionQuery } from './base.dto';
import { Transform, Type } from 'class-transformer';
import { IsBoolean, IsEnum, IsNumber, IsOptional } from 'class-validator';
/**
* Queries
*/
export class GetChatMessagesQuery extends SessionQuery {
export class GetChatMessagesFilter {
@ApiProperty({
required: false,
description: 'Filter messages before this timestamp (inclusive)',
})
@IsNumber()
@IsOptional()
@Type(() => Number)
'filter.timestamp.lte'?: number;
@ApiProperty({
required: false,
description: 'Filter messages after this timestamp (inclusive)',
})
@IsNumber()
@IsOptional()
@Type(() => Number)
'filter.timestamp.gte'?: number;
@ApiProperty({
required: false,
description: 'From me filter (by default shows all messages)',
})
@Transform(BooleanString)
@IsBoolean()
@IsOptional()
'filter.fromMe'?: boolean;
}
export class GetChatMessagesQuery {
@IsNumber()
@IsOptional()
@Type(() => Number)
limit: number = 100;
@IsNumber()
@IsOptional()
@Type(() => Number)
offset?: number;
@ApiProperty({
example: true,
required: false,
description: 'Download media for messages',
})
@Transform(BooleanString)
@IsBoolean()
@IsOptional()
downloadMedia: boolean = true;
}
export class GetChatsQuery {
@IsNumber()
export class GetChatMessageQuery {
@ApiProperty({
example: true,
required: false,
description: 'Download media for messages',
})
@Transform(BooleanString)
@IsBoolean()
@IsOptional()
@Type(() => Number)
limit?: number;
downloadMedia: boolean = true;
}
@IsNumber()
export enum ChatSortField {
CONVERSATION_TIMESTAMP = 'conversationTimestamp',
ID = 'id',
NAME = 'name',
}
export class ChatsPaginationParams extends PaginationParams {
@ApiProperty({
description: 'Sort by field',
enum: ChatSortField,
})
@IsOptional()
@Type(() => Number)
offset?: number;
@IsEnum(ChatSortField)
sortBy?: string;
}
export enum PinDuration {
DAY = 86400,
WEEK = 604800,
MONTH = 2592000,
}
export class PinMessageRequest {
@IsNumber()
@IsEnum(PinDuration)
@ApiProperty({
description:
'Duration in seconds. 24 hours (86400), 7 days (604800), 30 days (2592000)',
example: 86400,
})
duration: number;
}
/**
+16 -11
View File
@@ -4,9 +4,14 @@ import {
ApiProperty,
getSchemaPath,
} from '@nestjs/swagger';
import { IsNumber, IsString } from 'class-validator';
import { GetChatMessagesQuery } from '@waha/structures/chats.dto';
import { IsNotEmpty, IsNumber, IsString } from 'class-validator';
import { SessionBaseRequest, SessionQuery } from './base.dto';
import {
SessionBaseRequest,
SessionQuery,
WHATSAPP_DEFAULT_SESSION_NAME,
} from './base.dto';
import {
BinaryFile,
RemoteFile,
@@ -42,16 +47,14 @@ export class ChatQuery extends SessionQuery {
chatId: string;
}
export class GetMessageQuery extends ChatQuery {
@IsNumber()
limit: number;
export class GetMessageQuery extends GetChatMessagesQuery {
@IsNotEmpty()
@IsString()
session: string = WHATSAPP_DEFAULT_SESSION_NAME;
@ApiProperty({
example: true,
required: false,
description: 'Download media for messages',
})
downloadMedia: true;
@ChatIdProperty()
@IsString()
chatId: string;
}
export class GetPresenceQuery extends ChatQuery {}
@@ -153,6 +156,8 @@ export class MessageTextRequest extends ChatRequest {
@ReplyToProperty()
reply_to?: string;
linkPreview?: boolean = true;
}
export class EditMessageRequest {
+18 -1
View File
@@ -1,5 +1,7 @@
import { ApiProperty } from '@nestjs/swagger';
import { PaginationParams } from '@waha/structures/pagination.dto';
import { ChatIdProperty } from '@waha/structures/properties.dto';
import { IsString } from 'class-validator';
import { IsEnum, IsOptional, IsString } from 'class-validator';
import { SessionBaseRequest, SessionQuery } from './base.dto';
@@ -13,6 +15,21 @@ export class ContactQuery extends SessionQuery {
contactId: string;
}
enum ContactSortField {
ID = 'id',
NAME = 'name',
}
export class ContactsPaginationParams extends PaginationParams {
@ApiProperty({
description: 'Sort by field',
enum: ContactSortField,
})
@IsOptional()
@IsEnum(ContactSortField)
sortBy?: string;
}
/**
* Requests
*/
+7
View File
@@ -22,8 +22,14 @@ export enum WAHAEvents {
LABEL_DELETED = 'label.deleted',
LABEL_CHAT_ADDED = 'label.chat.added',
LABEL_CHAT_DELETED = 'label.chat.deleted',
ENGINE_EVENT = 'engine.event',
}
// All but no state.change, it's internal one
export const WAHAEventsWild = Object.values(WAHAEvents).filter(
(e) => e !== WAHAEvents.STATE_CHANGE && e !== WAHAEvents.ENGINE_EVENT,
);
export enum WAHASessionStatus {
STOPPED = 'STOPPED',
STARTING = 'STARTING',
@@ -53,4 +59,5 @@ export enum WAMessageAck {
READ = 3,
PLAYED = 4,
}
export const ACK_UNKNOWN = 'UNKNOWN';
+62 -1
View File
@@ -1,5 +1,7 @@
import { UnprocessableEntityException } from '@nestjs/common';
import { ApiProperty } from '@nestjs/swagger';
import { ChatIdProperty } from '@waha/structures/properties.dto';
import { IsHexColor, IsNumber, IsOptional, IsString } from 'class-validator';
const Colors = [
'#ff9485',
@@ -24,6 +26,65 @@ const Colors = [
'#9368cf',
];
export class LabelBody {
@ApiProperty({
example: 'Lead',
description: 'Label name',
})
@IsString()
name: string;
@ApiProperty({
example: '#ff9485',
description: 'Color in hex',
})
@IsHexColor()
@IsOptional()
colorHex?: string;
@ApiProperty({
example: null,
description: 'Color number, not hex',
})
@IsNumber()
@IsOptional()
color?: number;
toDTO(): LabelDTO {
if (this.color != null && this.colorHex != null) {
throw new UnprocessableEntityException(
"Use either 'color' or 'colorHex'",
);
}
if (this.color == null && this.colorHex == null) {
throw new UnprocessableEntityException(
"'color' or 'colorHex' is required",
);
}
if (this.colorHex) {
const color = Colors.indexOf(this.colorHex);
if (color == -1) {
throw new UnprocessableEntityException(
"Invalid 'colorHex'. Possible values: " + Colors.join(', '),
);
}
this.color = color;
}
return {
name: this.name,
color: this.color,
};
}
}
export class LabelDTO {
name: string;
color: number;
}
export class Label {
@ApiProperty({
example: '1',
@@ -39,7 +100,7 @@ export class Label {
@ApiProperty({
example: 0,
description: 'Internal color number, not hex',
description: 'Color number, not hex',
})
color: number;
+33
View File
@@ -0,0 +1,33 @@
import { ApiProperty } from '@nestjs/swagger';
import { Type } from 'class-transformer';
import { IsNumber, IsOptional, ValidateIf } from 'class-validator';
export enum SortOrder {
DESC = 'desc',
ASC = 'asc',
}
export class PaginationParams {
@IsNumber()
@IsOptional()
@Type(() => Number)
limit?: number;
@IsNumber()
@IsOptional()
@Type(() => Number)
offset?: number;
@ApiProperty({
description: 'Sort by field',
})
@IsOptional()
sortBy?: string;
@ApiProperty({
description:
'Sort order - <b>desc</b>ending (Z => A, New first) or <b>asc</b>ending (A => Z, Old first)',
})
@IsOptional()
sortOrder?: SortOrder;
}
+28 -12
View File
@@ -11,23 +11,27 @@ import {
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'];
const ContactsProperty = ApiProperty({
description: 'Contact list to send the status to.',
example: null,
required: false,
});
export interface StatusRequest {
contacts?: string[];
}
export class TextStatus extends StatusRequest {
export class TextStatus {
text: string = 'Have a look! https://github.com/';
backgroundColor: string = '#38b42f';
font: number = 1;
@ContactsProperty
contacts?: string[];
}
@ApiExtraModels(RemoteFile, BinaryFile)
export class ImageStatus extends StatusRequest {
export class ImageStatus {
@ApiProperty({
oneOf: [
{ $ref: getSchemaPath(RemoteFile) },
@@ -37,10 +41,13 @@ export class ImageStatus extends StatusRequest {
file: RemoteFile | BinaryFile;
caption?: string;
@ContactsProperty
contacts?: string[];
}
@ApiExtraModels(VoiceRemoteFile, VoiceBinaryFile)
export class VoiceStatus extends StatusRequest {
export class VoiceStatus {
@ApiProperty({
oneOf: [
{ $ref: getSchemaPath(VoiceRemoteFile) },
@@ -50,10 +57,13 @@ export class VoiceStatus extends StatusRequest {
file: VoiceRemoteFile | VoiceBinaryFile;
backgroundColor: string = '#38b42f';
@ContactsProperty
contacts?: string[];
}
@ApiExtraModels(VideoRemoteFile, VideoBinaryFile)
export class VideoStatus extends StatusRequest {
export class VideoStatus {
@ApiProperty({
oneOf: [
{ $ref: getSchemaPath(VideoRemoteFile) },
@@ -63,12 +73,18 @@ export class VideoStatus extends StatusRequest {
file: VideoRemoteFile | VideoBinaryFile;
caption?: string;
@ContactsProperty
contacts?: string[];
}
export class DeleteStatusRequest extends StatusRequest {
export class DeleteStatusRequest {
@ApiProperty({
description: 'status message id',
example: 'AAAAAAAAAAAAAAAAA',
})
id: string;
@ContactsProperty
contacts?: string[];
}
+20
View File
@@ -101,6 +101,11 @@ export class WASessionStatusBody {
}
export class WAHAWebhook {
@ApiProperty({
example: 'evt_01jcn4pjwwg47bwy2gsey6q5sx',
})
id: string;
@ApiProperty({
example: 'default',
})
@@ -320,6 +325,20 @@ class WAHAWebhookLabelChatDeleted extends WAHAWebhook {
payload: LabelChatAssociation;
}
export class EnginePayload {
event: string;
data: any;
}
class WAHAWebhookEngineEvent extends WAHAWebhook {
@ApiProperty({
description: 'Internal engine event.',
})
event = WAHAEvents.ENGINE_EVENT;
payload: EnginePayload;
}
const WAHA_WEBHOOKS = [
WAHAWebhookSessionStatus,
WAHAWebhookMessage,
@@ -341,5 +360,6 @@ const WAHA_WEBHOOKS = [
WAHAWebhookLabelDeleted,
WAHAWebhookLabelChatAdded,
WAHAWebhookLabelChatDeleted,
WAHAWebhookEngineEvent,
];
export { WAHA_WEBHOOKS };
+15
View File
@@ -0,0 +1,15 @@
export class DefaultMap<K, T> extends Map<K, T> {
private readonly factory: (key: K) => T;
constructor(factory: (key: K) => T) {
super();
this.factory = factory;
}
get(key: K): T {
if (!this.has(key)) {
this.set(key, this.factory(key));
}
return super.get(key);
}
}
+93
View File
@@ -0,0 +1,93 @@
import { PaginationParams } from '@waha/structures/pagination.dto';
import * as lodash from 'lodash';
export abstract class Paginator {
constructor(protected readonly pagination: PaginationParams) {}
apply<T>(data: T): T {
if (lodash.isEmpty(this.pagination)) {
return data;
}
return this.limit(this.sort(data));
}
protected abstract sort(data: any);
protected abstract limit(data: any);
}
export class PaginatorInMemory extends Paginator {
protected sort(data: any[]) {
if (!this.pagination?.sortBy) {
return data;
}
return lodash.orderBy(
data,
this.pagination.sortBy,
this.pagination.sortOrder || 'asc',
);
}
protected limit(data: any[]) {
if (!this.pagination?.limit) {
return data;
}
const offset = this.pagination.offset || 0;
const limit = this.pagination.limit || Infinity;
return data.slice(offset, offset + limit);
}
}
export class KnexPaginator extends Paginator {
indexes: string[] = [];
dataField: string = 'data';
protected sort(query: any) {
if (!this.pagination?.sortBy) {
return query;
}
const sortBy = this.pagination.sortBy;
const direction = this.pagination.sortOrder || 'asc';
if (this.indexes.includes(sortBy)) {
return query.orderBy(sortBy, direction);
}
// Use data->"$.{field}"
// Make sure to sanitize sortBy to prevent SQL injection
// sqlite3 doesn't support binding for column names and direction
return query.orderByRaw(`${this.dataField}->'$.${sortBy}' ${direction}`);
}
protected limit(query: any) {
const limit = this.pagination.limit;
const offset = this.pagination.offset;
if (limit != null) {
query = query.limit(limit);
}
if (offset != null) {
query = query.offset(offset);
}
return query;
}
}
export class MongoPaginator extends Paginator {
protected sort(query: any) {
if (!this.pagination?.sortBy) {
return query;
}
const mongoDirection = this.pagination.sortOrder === 'asc' ? 1 : -1;
return query.sort({ [this.pagination.sortBy]: mongoDirection });
}
protected limit(query: any) {
const limit = this.pagination.limit;
const offset = this.pagination.offset;
if (limit != null) {
query = query.limit(limit);
}
if (offset != null) {
query = query.skip(offset);
}
return query;
}
}
+31
View File
@@ -0,0 +1,31 @@
/**
* Unmask "*" in events list to exact values
* Remove duplicates if any
*/
export class EventWildUnmask {
constructor(
private readonly events: string[] | any,
private readonly all: string[] | any | null = null,
) {
// in case of enum - convert to array
this.events = Object.values(events);
this.all = all ? Object.values(all) : this.events;
}
unmask(events: string[]) {
const rightEvents = [];
if (events.includes('*')) {
rightEvents.push(...this.all);
}
// Get only known events, log and ignore others
for (const event of events) {
if (!this.events.includes(event)) {
continue;
}
rightEvents.push(event);
}
// return unique values
return [...new Set(rightEvents)];
}
}
+2 -3
View File
@@ -1,10 +1,9 @@
import { v4 as uuid4 } from 'uuid';
import { ulid } from 'ulid';
/**
* Generate prefix uuid (but remove -)
* @param prefix
*/
export function generatePrefixedId(prefix: string) {
const id = uuid4().replace(/-/g, '');
return `${prefix}_${id}`;
return `${prefix}_${ulid().toLowerCase()}`;
}
+34
View File
@@ -0,0 +1,34 @@
import { BehaviorSubject, EMPTY, Observable, share, switchMap } from 'rxjs';
const noop = (x: any) => x;
/**
* Observable that can easily be switched to new observable
* So you can change "source" of the observable,
* but clients will use the same observable (but consume NEW data from NEW source)
*/
export class SwitchObservable<T> extends Observable<T> {
private subject$: BehaviorSubject<Observable<T>>;
/**
* fn - Modify inner observable
*
*/
constructor(fn: (source: Observable<T>) => Observable<T> = noop) {
const subject$ = new BehaviorSubject<Observable<any>>(EMPTY);
let observable$ = subject$.pipe(switchMap((stream$) => stream$));
observable$ = fn(observable$);
super((subscriber) => observable$.subscribe(subscriber));
this.subject$ = subject$;
}
switch(newObservable$: Observable<T>) {
this.subject$.next(newObservable$);
}
complete() {
this.switch(EMPTY);
this.subject$.complete();
}
}
+19
View File
@@ -0,0 +1,19 @@
import { Subject } from 'rxjs';
interface WithValues<T> {
values(): IterableIterator<T>;
}
interface Completable {
complete(): void;
}
/**
* Go over collection and complete each item
* @param collection
*/
export function complete(collection: WithValues<Completable>) {
for (const item of collection.values()) {
item.complete();
}
}
+8
View File
@@ -0,0 +1,8 @@
import { filter } from 'rxjs';
/**
* Just as filter, but opposite
*/
export function exclude<T>(predicate: (...args) => boolean) {
return filter((...args) => !predicate(...args));
}
+1 -1
View File
@@ -33,7 +33,7 @@ export function getEngineName(): string {
}
export const VERSION: WAHAEnvironment = {
version: '2024.10.8',
version: '2024.11.8',
engine: getEngineName(),
tier: getWAHAVersion(),
browser: getBrowserExecutablePath(),
+257 -242
View File
@@ -7,7 +7,7 @@ __metadata:
"@adiwajshing/baileys@github:devlikeapro/Baileys#fork-master-channels-678":
version: 6.7.8
resolution: "@adiwajshing/baileys@https://github.com/devlikeapro/Baileys.git#commit=d0323b92eb8365e6f32a61e41dd00010602d07f1"
resolution: "@adiwajshing/baileys@https://github.com/devlikeapro/Baileys.git#commit=e2d6246da89a4775a8482fbe8de2a42b8421516d"
dependencies:
"@adiwajshing/keyed-db": ^0.2.4
"@hapi/boom": ^9.1.3
@@ -39,7 +39,7 @@ __metadata:
optional: true
sharp:
optional: true
checksum: a3b461006443713074401a17c3481aef571301e83f1fba193d06e7f9ea4c47359b5c973d0b3804e34d4e48c35bdc1a64d8c3e59a0a744e5bc9bcca591d180901
checksum: 43ac116fcc3eca176ff036a296c99d8be39bdcaafb788fb65305ee06d59060f0b8b8d5dee8596d9dca9794843dd76e90769e5acfa6d14ff55ad2a0f2616686e3
languageName: node
linkType: hard
@@ -2266,20 +2266,21 @@ __metadata:
languageName: node
linkType: hard
"@puppeteer/browsers@npm:1.9.1":
version: 1.9.1
resolution: "@puppeteer/browsers@npm:1.9.1"
"@puppeteer/browsers@npm:2.4.0":
version: 2.4.0
resolution: "@puppeteer/browsers@npm:2.4.0"
dependencies:
debug: 4.3.4
extract-zip: 2.0.1
progress: 2.0.3
proxy-agent: 6.3.1
tar-fs: 3.0.4
unbzip2-stream: 1.4.3
yargs: 17.7.2
debug: ^4.3.6
extract-zip: ^2.0.1
progress: ^2.0.3
proxy-agent: ^6.4.0
semver: ^7.6.3
tar-fs: ^3.0.6
unbzip2-stream: ^1.4.3
yargs: ^17.7.2
bin:
browsers: lib/cjs/main-cli.js
checksum: 1ea82e34af882dc6d7e8392a88ec4196e206a7f65743be39c196c7068d66b9bdfa370e28c6ab09946bd2baa2182adbcbf445e79cc9bcc5242f05878ae7045b27
checksum: c5f9890f1bf355783574c00b42e8a9bf9e4788d0dce76cc4cd50fcf80b5e5f99ae2b4325edd5350d4fb289f7dfa89f67edf8bc2da9abd53eba1f815caae97beb
languageName: node
linkType: hard
@@ -3752,15 +3753,6 @@ __metadata:
languageName: node
linkType: hard
"agent-base@npm:6":
version: 6.0.2
resolution: "agent-base@npm:6.0.2"
dependencies:
debug: 4
checksum: f52b6872cc96fd5f622071b71ef200e01c7c4c454ee68bc9accca90c98cfb39f2810e3e9aa330435835eedc8c23f4f8a15267f67c6e245d2b33757575bdac49d
languageName: node
linkType: hard
"agent-base@npm:^7.0.2, agent-base@npm:^7.1.0, agent-base@npm:^7.1.1":
version: 7.1.1
resolution: "agent-base@npm:7.1.1"
@@ -4315,6 +4307,13 @@ __metadata:
languageName: node
linkType: hard
"bare-events@npm:^2.0.0":
version: 2.5.0
resolution: "bare-events@npm:2.5.0"
checksum: 5aa10716e7f33c5dfc471fd657eee2a33f2db0f78b3c83b5cdd1a45a7e7871114a69460ea96cd838807c55eb470b9e53dd0dfda8c83cced1352cc8253cebff48
languageName: node
linkType: hard
"bare-events@npm:^2.2.0":
version: 2.4.2
resolution: "bare-events@npm:2.4.2"
@@ -4322,6 +4321,42 @@ __metadata:
languageName: node
linkType: hard
"bare-fs@npm:^2.1.1":
version: 2.3.5
resolution: "bare-fs@npm:2.3.5"
dependencies:
bare-events: ^2.0.0
bare-path: ^2.0.0
bare-stream: ^2.0.0
checksum: 071b1dff94a213eaf0b41693953959bf10af2deade597a56ff206a5d833579d56bc8530aa4614bb88bf39fd6d52f2404f7c36af4695109ffa756a13837ac3d91
languageName: node
linkType: hard
"bare-os@npm:^2.1.0":
version: 2.4.4
resolution: "bare-os@npm:2.4.4"
checksum: e90088a7dc0307c020350a28df8ec5564cae5a4b7a213d8509d70831d7064308e2ed31de801b68f474cb004ad3a0a66bd28c38374d270484d9025ee71af20396
languageName: node
linkType: hard
"bare-path@npm:^2.0.0, bare-path@npm:^2.1.0":
version: 2.1.3
resolution: "bare-path@npm:2.1.3"
dependencies:
bare-os: ^2.1.0
checksum: 20301aeb05b735852a396515464908e51e896922c3bb353ef2a09ff54e81ced94e6ad857bb0a36d2ce659c42bd43dd5c3d5643edd8faaf910ee9950c4e137b88
languageName: node
linkType: hard
"bare-stream@npm:^2.0.0":
version: 2.3.2
resolution: "bare-stream@npm:2.3.2"
dependencies:
streamx: ^2.20.0
checksum: 051c817f0b74617d99002a91c61e6fa2b56bff3bb329c5ab4a817287bc1f46f3aef5f13bc11201b344a91873782a45f755d3910012381f16e47feee2194fd792
languageName: node
linkType: hard
"base64-js@npm:^1.3.1":
version: 1.5.1
resolution: "base64-js@npm:1.5.1"
@@ -4599,6 +4634,16 @@ __metadata:
languageName: node
linkType: hard
"bufferutil@npm:^4.0.8":
version: 4.0.8
resolution: "bufferutil@npm:4.0.8"
dependencies:
node-gyp: latest
node-gyp-build: ^4.3.0
checksum: 7e9a46f1867dca72fda350966eb468eca77f4d623407b0650913fadf73d5750d883147d6e5e21c56f9d3b0bdc35d5474e80a600b9f31ec781315b4d2469ef087
languageName: node
linkType: hard
"busboy@npm:^1.0.0":
version: 1.6.0
resolution: "busboy@npm:1.6.0"
@@ -4838,15 +4883,16 @@ __metadata:
languageName: node
linkType: hard
"chromium-bidi@npm:0.5.8":
version: 0.5.8
resolution: "chromium-bidi@npm:0.5.8"
"chromium-bidi@npm:0.8.0":
version: 0.8.0
resolution: "chromium-bidi@npm:0.8.0"
dependencies:
mitt: 3.0.1
urlpattern-polyfill: 10.0.0
zod: 3.23.8
peerDependencies:
devtools-protocol: "*"
checksum: 4e4bb8d3907b3a17adf15e95220fc16733e0c4b03d0eccb071010a78ca85d122a0435da95ab657ade6d15e8c9b3cd917088b6128a0aec45e4444916625a4076f
checksum: 5c5b12e00564b6f145511ca4c159db24b07bfc6a1eb6add26d88c4331d74ef6cc8cd2f58bc169e0726c689910a1888313722f39ccab1bac14284d1918155d5e9
languageName: node
linkType: hard
@@ -5191,7 +5237,20 @@ __metadata:
languageName: node
linkType: hard
"cosmiconfig@npm:9.0.0":
"cosmiconfig@npm:^7.0.1":
version: 7.1.0
resolution: "cosmiconfig@npm:7.1.0"
dependencies:
"@types/parse-json": ^4.0.0
import-fresh: ^3.2.1
parse-json: ^5.0.0
path-type: ^4.0.0
yaml: ^1.10.0
checksum: c53bf7befc1591b2651a22414a5e786cd5f2eeaa87f3678a3d49d6069835a9d8d1aef223728e98aa8fec9a95bf831120d245096db12abe019fecb51f5696c96f
languageName: node
linkType: hard
"cosmiconfig@npm:^9.0.0":
version: 9.0.0
resolution: "cosmiconfig@npm:9.0.0"
dependencies:
@@ -5208,19 +5267,6 @@ __metadata:
languageName: node
linkType: hard
"cosmiconfig@npm:^7.0.1":
version: 7.1.0
resolution: "cosmiconfig@npm:7.1.0"
dependencies:
"@types/parse-json": ^4.0.0
import-fresh: ^3.2.1
parse-json: ^5.0.0
path-type: ^4.0.0
yaml: ^1.10.0
checksum: c53bf7befc1591b2651a22414a5e786cd5f2eeaa87f3678a3d49d6069835a9d8d1aef223728e98aa8fec9a95bf831120d245096db12abe019fecb51f5696c96f
languageName: node
linkType: hard
"crc-32@npm:^1.2.0":
version: 1.2.2
resolution: "crc-32@npm:1.2.2"
@@ -5273,15 +5319,6 @@ __metadata:
languageName: node
linkType: hard
"cross-fetch@npm:4.0.0":
version: 4.0.0
resolution: "cross-fetch@npm:4.0.0"
dependencies:
node-fetch: ^2.6.12
checksum: ecca4f37ffa0e8283e7a8a590926b66713a7ef7892757aa36c2d20ffa27b0ac5c60dcf453119c809abe5923fc0bae3702a4d896bfb406ef1077b0d0018213e24
languageName: node
linkType: hard
"cross-spawn@npm:^7.0.0, cross-spawn@npm:^7.0.2, cross-spawn@npm:^7.0.3":
version: 7.0.3
resolution: "cross-spawn@npm:7.0.3"
@@ -5409,6 +5446,18 @@ __metadata:
languageName: node
linkType: hard
"debug@npm:^4.3.6, debug@npm:^4.3.7":
version: 4.3.7
resolution: "debug@npm:4.3.7"
dependencies:
ms: ^2.1.3
peerDependenciesMeta:
supports-color:
optional: true
checksum: 822d74e209cd910ef0802d261b150314bbcf36c582ccdbb3e70f0894823c17e49a50d3e66d96b633524263975ca16b6a833f3e3b7e030c157169a5fabac63160
languageName: node
linkType: hard
"decamelize@npm:^1.2.0":
version: 1.2.0
resolution: "decamelize@npm:1.2.0"
@@ -5551,17 +5600,10 @@ __metadata:
languageName: node
linkType: hard
"devtools-protocol@npm:0.0.1045489":
version: 0.0.1045489
resolution: "devtools-protocol@npm:0.0.1045489"
checksum: 670b2ecc8e890358b08da3095b4c927ffc5ef846a39e1526ff99aecaa623b339450dfe308e421e64bf94308441832b8e9497ebda97fb02266fef3f455286742f
languageName: node
linkType: hard
"devtools-protocol@npm:0.0.1232444":
version: 0.0.1232444
resolution: "devtools-protocol@npm:0.0.1232444"
checksum: b421a3c20506d597211d101c3ed4e550db2ca195d59c3371b2e04a8d057f33be60e732bafc80dc53b34546593a7751857321971e235e9ce9f1c5a9523fd8fa20
"devtools-protocol@npm:0.0.1354347":
version: 0.0.1354347
resolution: "devtools-protocol@npm:0.0.1354347"
checksum: f3e29033319579f7e49c8fd05752faf811870cb6f4ec795b69b6f606984f11af52f317b98a229f39fe8e420346203a9f18df9c8053181e368e43440def1a9a5f
languageName: node
linkType: hard
@@ -6389,7 +6431,7 @@ __metadata:
languageName: node
linkType: hard
"extract-zip@npm:2.0.1":
"extract-zip@npm:^2.0.1":
version: 2.0.1
resolution: "extract-zip@npm:2.0.1"
dependencies:
@@ -7147,7 +7189,7 @@ __metadata:
languageName: node
linkType: hard
"http-proxy-agent@npm:^7.0.0":
"http-proxy-agent@npm:^7.0.0, http-proxy-agent@npm:^7.0.1":
version: 7.0.2
resolution: "http-proxy-agent@npm:7.0.2"
dependencies:
@@ -7157,16 +7199,6 @@ __metadata:
languageName: node
linkType: hard
"https-proxy-agent@npm:5.0.1":
version: 5.0.1
resolution: "https-proxy-agent@npm:5.0.1"
dependencies:
agent-base: 6
debug: 4
checksum: 571fccdf38184f05943e12d37d6ce38197becdd69e58d03f43637f7fa1269cf303a7d228aa27e5b27bbd3af8f09fd938e1c91dcfefff2df7ba77c20ed8dfc765
languageName: node
linkType: hard
"https-proxy-agent@npm:^7.0.0, https-proxy-agent@npm:^7.0.1, https-proxy-agent@npm:^7.0.2":
version: 7.0.4
resolution: "https-proxy-agent@npm:7.0.4"
@@ -7177,6 +7209,16 @@ __metadata:
languageName: node
linkType: hard
"https-proxy-agent@npm:^7.0.3":
version: 7.0.5
resolution: "https-proxy-agent@npm:7.0.5"
dependencies:
agent-base: ^7.0.2
debug: 4
checksum: 2e1a28960f13b041a50702ee74f240add8e75146a5c37fc98f1960f0496710f6918b3a9fe1e5aba41e50f58e6df48d107edd9c405c5f0d73ac260dabf2210857
languageName: node
linkType: hard
"human-signals@npm:^1.1.1":
version: 1.1.1
resolution: "human-signals@npm:1.1.1"
@@ -9027,7 +9069,7 @@ __metadata:
languageName: node
linkType: hard
"ms@npm:2.1.3, ms@npm:^2.0.0, ms@npm:^2.1.1":
"ms@npm:2.1.3, ms@npm:^2.0.0, ms@npm:^2.1.1, ms@npm:^2.1.3":
version: 2.1.3
resolution: "ms@npm:2.1.3"
checksum: aa92de608021b242401676e35cfa5aa42dd70cbdc082b916da7fb925c542173e36bce97ea3e804923fe92c0ad991434e4a38327e15a1b5b5f945d66df615ae6d
@@ -9164,7 +9206,7 @@ __metadata:
languageName: node
linkType: hard
"node-fetch@npm:^2.6.1, node-fetch@npm:^2.6.12, node-fetch@npm:^2.6.9":
"node-fetch@npm:^2.6.1, node-fetch@npm:^2.6.9":
version: 2.7.0
resolution: "node-fetch@npm:2.7.0"
dependencies:
@@ -9178,6 +9220,17 @@ __metadata:
languageName: node
linkType: hard
"node-gyp-build@npm:^4.3.0":
version: 4.8.2
resolution: "node-gyp-build@npm:4.8.2"
bin:
node-gyp-build: bin.js
node-gyp-build-optional: optional.js
node-gyp-build-test: build-test.js
checksum: 1a57bba8c4c193f808bd8ad1484d4ebdd8106dd9f04a3e82554dc716e3a2d87d7e369e9503c145e0e6a7e2c663fec0d8aaf52bd8156342ec7fc388195f37824e
languageName: node
linkType: hard
"node-gyp@npm:latest":
version: 10.1.0
resolution: "node-gyp@npm:10.1.0"
@@ -9956,7 +10009,7 @@ __metadata:
languageName: node
linkType: hard
"progress@npm:2.0.3, progress@npm:^2.0.0":
"progress@npm:^2.0.0, progress@npm:^2.0.3":
version: 2.0.3
resolution: "progress@npm:2.0.3"
checksum: f67403fe7b34912148d9252cb7481266a354bd99ce82c835f79070643bb3c6583d10dbcfda4d41e04bbc1d8437e9af0fb1e1f2135727878f5308682a579429b7
@@ -10044,23 +10097,23 @@ __metadata:
languageName: node
linkType: hard
"proxy-agent@npm:6.3.1":
version: 6.3.1
resolution: "proxy-agent@npm:6.3.1"
"proxy-agent@npm:^6.4.0":
version: 6.4.0
resolution: "proxy-agent@npm:6.4.0"
dependencies:
agent-base: ^7.0.2
debug: ^4.3.4
http-proxy-agent: ^7.0.0
https-proxy-agent: ^7.0.2
http-proxy-agent: ^7.0.1
https-proxy-agent: ^7.0.3
lru-cache: ^7.14.1
pac-proxy-agent: ^7.0.1
proxy-from-env: ^1.1.0
socks-proxy-agent: ^8.0.2
checksum: 31030da419da31809340ac2521090c9a5bf4fe47a944843f829b3502883208c8586a468955e64b694140a41d70af6f45cf4793f5efd4a6f3ed94e5ac8023e36d
checksum: 4d3794ad5e07486298902f0a7f250d0f869fa0e92d790767ca3f793a81374ce0ab6c605f8ab8e791c4d754da96656b48d1c24cb7094bfd310a15867e4a0841d7
languageName: node
linkType: hard
"proxy-from-env@npm:1.1.0, proxy-from-env@npm:^1.1.0":
"proxy-from-env@npm:^1.1.0":
version: 1.1.0
resolution: "proxy-from-env@npm:1.1.0"
checksum: ed7fcc2ba0a33404958e34d95d18638249a68c430e30fcb6c478497d72739ba64ce9810a24f53a7d921d0c065e5b78e3822759800698167256b04659366ca4d4
@@ -10098,60 +10151,33 @@ __metadata:
languageName: node
linkType: hard
"puppeteer-core@npm:18.2.1":
version: 18.2.1
resolution: "puppeteer-core@npm:18.2.1"
"puppeteer-core@npm:23.6.0":
version: 23.6.0
resolution: "puppeteer-core@npm:23.6.0"
dependencies:
cross-fetch: 3.1.5
debug: 4.3.4
devtools-protocol: 0.0.1045489
extract-zip: 2.0.1
https-proxy-agent: 5.0.1
proxy-from-env: 1.1.0
rimraf: 3.0.2
tar-fs: 2.1.1
unbzip2-stream: 1.4.3
ws: 8.9.0
checksum: 27ed65a7335ee3f4356425ebe4bbb5202f7b39493398643554a12b981922b2243dc331d9a478215706b79696bcf391637dd5e5405d60aff6ba8f94f0c3fea742
"@puppeteer/browsers": 2.4.0
chromium-bidi: 0.8.0
debug: ^4.3.7
devtools-protocol: 0.0.1354347
typed-query-selector: ^2.12.0
ws: ^8.18.0
checksum: cb2d659a94d4cb8f466f6e56ac5aff1374506326c2cd2a964dcd556e872f00db8c4079f1e7149ffb8200b30da9d31d4cd299ad28ac38a4c445ac0b66cf90fafc
languageName: node
linkType: hard
"puppeteer-core@npm:21.11.0":
version: 21.11.0
resolution: "puppeteer-core@npm:21.11.0"
"puppeteer@npm:^23.6.0":
version: 23.6.0
resolution: "puppeteer@npm:23.6.0"
dependencies:
"@puppeteer/browsers": 1.9.1
chromium-bidi: 0.5.8
cross-fetch: 4.0.0
debug: 4.3.4
devtools-protocol: 0.0.1232444
ws: 8.16.0
checksum: 953018ca679542d747e5974fd732349d73f4c685c0b49dd6027d493ad95d64a23b2d6e3e72f0a0a12c7335d3e08a0b3e6bc9ac3adfb3fb0316fb1ca62f46f129
languageName: node
linkType: hard
"puppeteer@npm:^18.2.1":
version: 18.2.1
resolution: "puppeteer@npm:18.2.1"
dependencies:
https-proxy-agent: 5.0.1
progress: 2.0.3
proxy-from-env: 1.1.0
puppeteer-core: 18.2.1
checksum: 58ba8c7abd82071dc12e280622fa352a8ac253375a514c7a3527b468672a4ccdfe20b37aa09498774da2f0666bcdd9a47838c6661de483a3d6e10b47ef666113
languageName: node
linkType: hard
"puppeteer@npm:^21.0.0":
version: 21.11.0
resolution: "puppeteer@npm:21.11.0"
dependencies:
"@puppeteer/browsers": 1.9.1
cosmiconfig: 9.0.0
puppeteer-core: 21.11.0
"@puppeteer/browsers": 2.4.0
chromium-bidi: 0.8.0
cosmiconfig: ^9.0.0
devtools-protocol: 0.0.1354347
puppeteer-core: 23.6.0
typed-query-selector: ^2.12.0
bin:
puppeteer: lib/esm/puppeteer/node/cli.js
checksum: 0f2e6f6d9269e511022ec8e8478f42bf5f3dbddbdac7b50d31c90504993eaef436e8729a5b3f105576ec2031519de723d4a9d0ad797821d0f16540073fabc37a
puppeteer: lib/cjs/puppeteer/node/cli.js
checksum: c0cf82393418154d392dcccca8614aaf894f489fa0a5de664556816d70eff773e8a0bb60331a727b5d954a30c0cab74bff968ff11e37defa1a75ae524655ac2c
languageName: node
linkType: hard
@@ -10555,17 +10581,6 @@ __metadata:
languageName: node
linkType: hard
"rimraf@npm:3.0.2, rimraf@npm:^3.0.2":
version: 3.0.2
resolution: "rimraf@npm:3.0.2"
dependencies:
glob: ^7.1.3
bin:
rimraf: bin.js
checksum: 87f4164e396f0171b0a3386cc1877a817f572148ee13a7e113b238e48e8a9f2f31d009a92ec38a591ff1567d9662c6b67fd8818a2dbbaed74bc26a87a2a4a9a0
languageName: node
linkType: hard
"rimraf@npm:4.4.1":
version: 4.4.1
resolution: "rimraf@npm:4.4.1"
@@ -10577,6 +10592,17 @@ __metadata:
languageName: node
linkType: hard
"rimraf@npm:^3.0.2":
version: 3.0.2
resolution: "rimraf@npm:3.0.2"
dependencies:
glob: ^7.1.3
bin:
rimraf: bin.js
checksum: 87f4164e396f0171b0a3386cc1877a817f572148ee13a7e113b238e48e8a9f2f31d009a92ec38a591ff1567d9662c6b67fd8818a2dbbaed74bc26a87a2a4a9a0
languageName: node
linkType: hard
"run-async@npm:^2.4.0":
version: 2.4.1
resolution: "run-async@npm:2.4.1"
@@ -10593,7 +10619,7 @@ __metadata:
languageName: node
linkType: hard
"rxjs@npm:7.8.1, rxjs@npm:^7.1.0, rxjs@npm:^7.5.5":
"rxjs@npm:7.8.1, rxjs@npm:^7.5.5, rxjs@npm:^7.8.1":
version: 7.8.1
resolution: "rxjs@npm:7.8.1"
dependencies:
@@ -10689,6 +10715,15 @@ __metadata:
languageName: node
linkType: hard
"semver@npm:^7.6.3":
version: 7.6.3
resolution: "semver@npm:7.6.3"
bin:
semver: bin/semver.js
checksum: 4110ec5d015c9438f322257b1c51fe30276e5f766a3f64c09edd1d7ea7118ecbc3f379f3b69032bacf13116dc7abc4ad8ce0d7e2bd642e26b0d271b56b61a7d8
languageName: node
linkType: hard
"send@npm:0.18.0":
version: 0.18.0
resolution: "send@npm:0.18.0"
@@ -11125,6 +11160,21 @@ __metadata:
languageName: node
linkType: hard
"streamx@npm:^2.20.0":
version: 2.20.1
resolution: "streamx@npm:2.20.1"
dependencies:
bare-events: ^2.2.0
fast-fifo: ^1.3.2
queue-tick: ^1.0.1
text-decoder: ^1.1.0
dependenciesMeta:
bare-events:
optional: true
checksum: 48605ddd3abdd86d2e3ee945ec7c9317f36abb5303347a8fff6e4c7926a72c33ec7ac86b50734ccd1cf65602b6a38e247966e8199b24e5a7485d9cec8f5327bd
languageName: node
linkType: hard
"string-length@npm:^4.0.1":
version: 4.0.2
resolution: "string-length@npm:4.0.2"
@@ -11419,7 +11469,7 @@ __metadata:
languageName: node
linkType: hard
"tar-fs@npm:2.1.1, tar-fs@npm:^2.0.0":
"tar-fs@npm:^2.0.0":
version: 2.1.1
resolution: "tar-fs@npm:2.1.1"
dependencies:
@@ -11431,14 +11481,20 @@ __metadata:
languageName: node
linkType: hard
"tar-fs@npm:3.0.4":
version: 3.0.4
resolution: "tar-fs@npm:3.0.4"
"tar-fs@npm:^3.0.6":
version: 3.0.6
resolution: "tar-fs@npm:3.0.6"
dependencies:
mkdirp-classic: ^0.5.2
bare-fs: ^2.1.1
bare-path: ^2.1.0
pump: ^3.0.0
tar-stream: ^3.1.5
checksum: dcf4054f9e92ca0efe61c2b3f612914fb259a47900aa908a63106513a6d006c899b426ada53eb88d9dbbf089b5724c8e90b96a2c4ca6171845fa14203d734e30
dependenciesMeta:
bare-fs:
optional: true
bare-path:
optional: true
checksum: b4fa09c70f75caf05bf5cf87369cd2862f1ac5fb75c4ddf9d25d55999f7736a94b58ad679d384196cba837c5f5ff14086e060fafccef5474a16e2d3058ffa488
languageName: node
linkType: hard
@@ -11640,13 +11696,6 @@ __metadata:
languageName: node
linkType: hard
"tr46@npm:~0.0.3":
version: 0.0.3
resolution: "tr46@npm:0.0.3"
checksum: 726321c5eaf41b5002e17ffbd1fb7245999a073e8979085dacd47c4b4e8068ff5777142fc6726d6ca1fd2ff16921b48788b87225cbc57c72636f6efa8efbffe3
languageName: node
linkType: hard
"traverse@npm:>=0.3.0 <0.4":
version: 0.3.9
resolution: "traverse@npm:0.3.9"
@@ -11926,6 +11975,13 @@ __metadata:
languageName: node
linkType: hard
"typed-query-selector@npm:^2.12.0":
version: 2.12.0
resolution: "typed-query-selector@npm:2.12.0"
checksum: c4652f2eec16112d69e0da30c2effab3f03d1710f9559da1e1209bbfc9a20990d5de4ba97890c11f9d17d85c8ae3310953a86c198166599d4c36abc63664f169
languageName: node
linkType: hard
"typedarray@npm:^0.0.6":
version: 0.0.6
resolution: "typedarray@npm:0.0.6"
@@ -11962,6 +12018,15 @@ __metadata:
languageName: node
linkType: hard
"ulid@npm:^2.3.0":
version: 2.3.0
resolution: "ulid@npm:2.3.0"
bin:
ulid: ./bin/cli.js
checksum: d6dbf253fdc189f60fe2829d934ee5447b3dab62d05449a2e0fe89670d77087dd6eba4f844a69f9ffdb01384ec6fd97bdd9be638fc67d593569a45e8969f1e69
languageName: node
linkType: hard
"unbox-primitive@npm:^1.0.2":
version: 1.0.2
resolution: "unbox-primitive@npm:1.0.2"
@@ -11974,7 +12039,7 @@ __metadata:
languageName: node
linkType: hard
"unbzip2-stream@npm:1.4.3":
"unbzip2-stream@npm:^1.4.3":
version: 1.4.3
resolution: "unbzip2-stream@npm:1.4.3"
dependencies:
@@ -12189,6 +12254,7 @@ __metadata:
axios: ^1.7.7
axios-retry: ^4.5.0
better-sqlite3: 11.3.0
bufferutil: ^4.0.8
check-disk-space: ^3.4.0
chokidar: ^3.6.0
class-transformer: ^0.5.1
@@ -12218,12 +12284,12 @@ __metadata:
prettier: ^1.19.1
pretty-bytes: 5.6.0
promise-retry: ^2.0.1
puppeteer: ^21.0.0
puppeteer: ^23.6.0
qrcode: ^1.5.1
qrcode-terminal: ^0.12.0
reflect-metadata: ^0.1.13
rimraf: ^3.0.2
rxjs: ^7.1.0
rxjs: ^7.8.1
sharp: ^0.33.4
supertest: ^4.0.2
swagger-ui-express: ^4.1.4
@@ -12232,7 +12298,11 @@ __metadata:
ts-node: ^10.9.1
tsconfig-paths: ^4.1.0
typescript: ^4.8.4
ulid: ^2.3.0
whatsapp-web.js: "github:devlikeapro/whatsapp-web.js#fork-main-channels"
dependenciesMeta:
bufferutil:
optional: true
languageName: unknown
linkType: soft
@@ -12264,13 +12334,6 @@ __metadata:
languageName: node
linkType: hard
"webidl-conversions@npm:^3.0.0":
version: 3.0.1
resolution: "webidl-conversions@npm:3.0.1"
checksum: c92a0a6ab95314bde9c32e1d0a6dfac83b578f8fa5f21e675bc2706ed6981bc26b7eb7e6a1fab158e5ce4adf9caa4a0aee49a52505d4d13c7be545f15021b17c
languageName: node
linkType: hard
"webidl-conversions@npm:^7.0.0":
version: 7.0.0
resolution: "webidl-conversions@npm:7.0.0"
@@ -12331,7 +12394,7 @@ __metadata:
"whatsapp-web.js@github:devlikeapro/whatsapp-web.js#fork-main-channels":
version: 1.26.0
resolution: "whatsapp-web.js@https://github.com/devlikeapro/whatsapp-web.js.git#commit=6a333d0088591269238726e9effa3cbb14c8e7c7"
resolution: "whatsapp-web.js@https://github.com/devlikeapro/whatsapp-web.js.git#commit=aa90f7378659a237f30cda6d368359a1f81904dc"
dependencies:
"@pedroslopez/moduleraid": ^5.0.2
archiver: ^5.3.1
@@ -12349,11 +12412,11 @@ __metadata:
optional: true
unzipper:
optional: true
checksum: 51e9dce51270cf6fb20acd0bd7368f36763dcc3d28c6c3725c78b46edd5da21fea2c9c1f78ab6699464e4e1a6d9c361b6ab76a88225ebdeeb3f9abdd860d082c
checksum: 7c914e9cdd4a25c967e057d0cb5019973f0556f6220320e4bcbc0666669801b92abf97c2cc783762ef462d0006123953e233136f457689ebac395260fe69a5fc
languageName: node
linkType: hard
"whatwg-url@npm:^13.0.0":
"whatwg-url@npm:13.0.0":
version: 13.0.0
resolution: "whatwg-url@npm:13.0.0"
dependencies:
@@ -12363,16 +12426,6 @@ __metadata:
languageName: node
linkType: hard
"whatwg-url@npm:^5.0.0":
version: 5.0.0
resolution: "whatwg-url@npm:5.0.0"
dependencies:
tr46: ~0.0.3
webidl-conversions: ^3.0.0
checksum: b8daed4ad3356cc4899048a15b2c143a9aed0dfae1f611ebd55073310c7b910f522ad75d727346ad64203d7e6c79ef25eafd465f4d12775ca44b90fa82ed9e2c
languageName: node
linkType: hard
"which-boxed-primitive@npm:^1.0.2":
version: 1.0.2
resolution: "which-boxed-primitive@npm:1.0.2"
@@ -12523,9 +12576,9 @@ __metadata:
languageName: node
linkType: hard
"ws@npm:8.13.0":
version: 8.13.0
resolution: "ws@npm:8.13.0"
"ws@npm:^8.18.0":
version: 8.18.0
resolution: "ws@npm:8.18.0"
peerDependencies:
bufferutil: ^4.0.1
utf-8-validate: ">=5.0.2"
@@ -12534,52 +12587,7 @@ __metadata:
optional: true
utf-8-validate:
optional: true
checksum: 53e991bbf928faf5dc6efac9b8eb9ab6497c69feeb94f963d648b7a3530a720b19ec2e0ec037344257e05a4f35bd9ad04d9de6f289615ffb133282031b18c61c
languageName: node
linkType: hard
"ws@npm:8.16.0":
version: 8.16.0
resolution: "ws@npm:8.16.0"
peerDependencies:
bufferutil: ^4.0.1
utf-8-validate: ">=5.0.2"
peerDependenciesMeta:
bufferutil:
optional: true
utf-8-validate:
optional: true
checksum: feb3eecd2bae82fa8a8beef800290ce437d8b8063bdc69712725f21aef77c49cb2ff45c6e5e7fce622248f9c7abaee506bae0a9064067ffd6935460c7357321b
languageName: node
linkType: hard
"ws@npm:8.9.0":
version: 8.9.0
resolution: "ws@npm:8.9.0"
peerDependencies:
bufferutil: ^4.0.1
utf-8-validate: ^5.0.2
peerDependenciesMeta:
bufferutil:
optional: true
utf-8-validate:
optional: true
checksum: 23aa0f021b2eb65c108ec4c3e08c0d81ba01f82b500432dfe327fd6be36079c1d81fdb0eac6464d2a0eb49904d34a9ab8c59619d673fa07b8346f83aeb0cbf12
languageName: node
linkType: hard
"ws@npm:^8.13.0":
version: 8.17.1
resolution: "ws@npm:8.17.1"
peerDependencies:
bufferutil: ^4.0.1
utf-8-validate: ">=5.0.2"
peerDependenciesMeta:
bufferutil:
optional: true
utf-8-validate:
optional: true
checksum: 442badcce1f1178ec87a0b5372ae2e9771e07c4929a3180321901f226127f252441e8689d765aa5cfba5f50ac60dd830954afc5aeae81609aefa11d3ddf5cecf
checksum: 91d4d35bc99ff6df483bdf029b9ea4bfd7af1f16fc91231a96777a63d263e1eabf486e13a2353970efc534f9faa43bdbf9ee76525af22f4752cbc5ebda333975
languageName: node
linkType: hard
@@ -12642,21 +12650,6 @@ __metadata:
languageName: node
linkType: hard
"yargs@npm:17.7.2, yargs@npm:^17.3.1":
version: 17.7.2
resolution: "yargs@npm:17.7.2"
dependencies:
cliui: ^8.0.1
escalade: ^3.1.1
get-caller-file: ^2.0.5
require-directory: ^2.1.1
string-width: ^4.2.3
y18n: ^5.0.5
yargs-parser: ^21.1.1
checksum: 73b572e863aa4a8cbef323dd911d79d193b772defd5a51aab0aca2d446655216f5002c42c5306033968193bdbf892a7a4c110b0d77954a7fdf563e653967b56a
languageName: node
linkType: hard
"yargs@npm:^15.3.1":
version: 15.4.1
resolution: "yargs@npm:15.4.1"
@@ -12676,6 +12669,21 @@ __metadata:
languageName: node
linkType: hard
"yargs@npm:^17.3.1, yargs@npm:^17.7.2":
version: 17.7.2
resolution: "yargs@npm:17.7.2"
dependencies:
cliui: ^8.0.1
escalade: ^3.1.1
get-caller-file: ^2.0.5
require-directory: ^2.1.1
string-width: ^4.2.3
y18n: ^5.0.5
yargs-parser: ^21.1.1
checksum: 73b572e863aa4a8cbef323dd911d79d193b772defd5a51aab0aca2d446655216f5002c42c5306033968193bdbf892a7a4c110b0d77954a7fdf563e653967b56a
languageName: node
linkType: hard
"yauzl@npm:^2.10.0":
version: 2.10.0
resolution: "yauzl@npm:2.10.0"
@@ -12710,3 +12718,10 @@ __metadata:
checksum: 33bd5ee7017656c2ad728b5d4ba510e15bd65ce1ec180c5bbdc7a5f063256353ec482e6a2bc74de7515219d8494147924b9aae16e63fdaaf37cdf7d1ee8df125
languageName: node
linkType: hard
"zod@npm:3.23.8":
version: 3.23.8
resolution: "zod@npm:3.23.8"
checksum: 15949ff82118f59c893dacd9d3c766d02b6fa2e71cf474d5aa888570c469dbf5446ac5ad562bb035bf7ac9650da94f290655c194f4a6de3e766f43febd432c5c
languageName: node
linkType: hard