Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6346d4143d | ||
|
|
fd8dc88760 | ||
|
|
b2810f390e | ||
|
|
23d3cd0f4b | ||
|
|
4a3c76773d | ||
|
|
d23e7604f0 | ||
|
|
0b3f6c6eff | ||
|
|
f2869c892b | ||
|
|
3755a9529b | ||
|
|
890f0593ee | ||
|
|
e6d666b5b0 | ||
|
|
f440acf145 | ||
|
|
1ad0e0c6dd | ||
|
|
bb8691ddc0 | ||
|
|
cf3041d07b | ||
|
|
313b3d5d6b | ||
|
|
122e7336be | ||
|
|
e954453f6b | ||
|
|
55ef3d09f3 | ||
|
|
88b9cc5ac9 | ||
|
|
f07cb7470d | ||
|
|
bd874a0232 | ||
|
|
7cc4cccb9c | ||
|
|
e85a3bacb7 | ||
|
|
530dfa758f | ||
|
|
8bc3489060 | ||
|
|
6914cb171a | ||
|
|
176c2bbd4c | ||
|
|
7771954455 | ||
|
|
a66743bb20 | ||
|
|
851c7b75f9 | ||
|
|
ed0b406027 | ||
|
|
71985972a2 | ||
|
|
2dbeef9290 | ||
|
|
a79453270d | ||
|
|
ae71808c61 | ||
|
|
5c195ba37b | ||
|
|
607ee17a86 | ||
|
|
a15aacc5b3 | ||
|
|
78fb4b1a65 | ||
|
|
8e566cba72 | ||
|
|
36184288de | ||
|
|
6fc5bf38a7 | ||
|
|
e146ecde70 | ||
|
|
72b796019a | ||
|
|
0f66d15ba8 | ||
|
|
4695e933b8 | ||
|
|
5e1da6cd6f | ||
|
|
0c3bbc0980 | ||
|
|
d2bb5bea63 | ||
|
|
c76e191d4d | ||
|
|
171594d0ae | ||
|
|
4e5f499b48 | ||
|
|
bb75ad8b8e | ||
|
|
482cc7028b | ||
|
|
735bdb3854 | ||
|
|
1ee1bd78af | ||
|
|
00463c729d | ||
|
|
dac636b3bb | ||
|
|
ad87121506 | ||
|
|
55d802f005 | ||
|
|
f17164a3d9 | ||
|
|
0b9f9b2335 | ||
|
|
71d3e24241 | ||
|
|
ccd4512e8f | ||
|
|
fe6b77d67d | ||
|
|
58d12f4e70 | ||
|
|
17303039b8 | ||
|
|
f70d5203a3 | ||
|
|
efd6b1db65 | ||
|
|
dcca0fa921 | ||
|
|
e5bc447807 | ||
|
|
cd6b4b5ab1 | ||
|
|
fb79ae6138 | ||
|
|
17801b0b13 | ||
|
|
9010388de0 | ||
|
|
2da901f994 | ||
|
|
7832cc7a5d | ||
|
|
13ab2e8d0e | ||
|
|
868d23a278 | ||
|
|
eaf2bca44e | ||
|
|
af8f537f58 | ||
|
|
bc7ff472be | ||
|
|
9b9a2b5203 | ||
|
|
347259394b | ||
|
|
ead61b5e37 | ||
|
|
c9f7814f5d | ||
|
|
3ee3355e81 | ||
|
|
ce8f8b0ce9 | ||
|
|
cae35cf3b9 | ||
|
|
900e3c23bc | ||
|
|
aaeffeb983 | ||
|
|
15600324be | ||
|
|
da4913edb5 | ||
|
|
e8cc1e4906 | ||
|
|
801fc3f9fd | ||
|
|
703b0b4cde | ||
|
|
d55895389a | ||
|
|
56009e482b | ||
|
|
767d1bb3e3 | ||
|
|
05132ac0f5 | ||
|
|
fb372f8c28 | ||
|
|
a2dc863b7a | ||
|
|
15671c6781 | ||
|
|
6296115adb | ||
|
|
0a099a91d0 | ||
|
|
5f060fe022 | ||
|
|
a4bbe1cf7b | ||
|
|
dce74ae7d5 | ||
|
|
871c509493 | ||
|
|
9333470e3b | ||
|
|
0c108f5232 | ||
|
|
39d298c867 | ||
|
|
376e804a28 | ||
|
|
79e5c0589d | ||
|
|
f6679b7368 | ||
|
|
a3c0b86b67 | ||
|
|
c8f48dd62b | ||
|
|
aed157697a | ||
|
|
913b8606d9 | ||
|
|
7cadbad36e | ||
|
|
abb5bdffcc | ||
|
|
6a44c15dd3 | ||
|
|
e669dfe533 | ||
|
|
be888f195d | ||
|
|
d87cdef311 | ||
|
|
ecf07cb520 | ||
|
|
10829d53b9 | ||
|
|
71093f2f31 | ||
|
|
e88192002a | ||
|
|
3e14282603 | ||
|
|
5f16f4ca77 | ||
|
|
7b4fa4331f | ||
|
|
3ad0a463b5 | ||
|
|
d28d640f77 | ||
|
|
594c0a6122 | ||
|
|
843a826437 | ||
|
|
c4660bc824 | ||
|
|
6053d38e4d | ||
|
|
3865254a1c | ||
|
|
65b61c09cb | ||
|
|
cce1618117 | ||
|
|
8b4b49a34b | ||
|
|
048c5dc2ec | ||
|
|
eb218da8fb | ||
|
|
65942a4ed4 | ||
|
|
1444155c09 | ||
|
|
84ac1d8cfc | ||
|
|
440e1e879a | ||
|
|
bcecf793d4 | ||
|
|
16c0d827e1 | ||
|
|
94f16a9583 | ||
|
|
4bdac3bbc2 | ||
|
|
2bc4a59aaf | ||
|
|
cf886de5ce | ||
|
|
9969966ac6 | ||
|
|
fb809338e3 | ||
|
|
c6284eb994 | ||
|
|
454bda4c82 | ||
|
|
26e21441a0 | ||
|
|
2edef0166e | ||
|
|
05cb89779b | ||
|
|
c0b2524952 | ||
|
|
f1bb445593 | ||
|
|
7f777f338f | ||
|
|
4449dfdbd6 | ||
|
|
6226e65e97 | ||
|
|
be1783c029 | ||
|
|
83a002ae5b | ||
|
|
f0fcb2b329 | ||
|
|
5ff695a5b3 | ||
|
|
43fe1f076e | ||
|
|
1afd7e042c | ||
|
|
ba6474f97e | ||
|
|
d8eed114aa | ||
|
|
e61ad6f79c | ||
|
|
9d27616003 | ||
|
|
0f2641909f | ||
|
|
9ca96acda2 | ||
|
|
77db6c5e98 | ||
|
|
c4aa9804d2 | ||
|
|
eb7bfe3a62 | ||
|
|
01fd7a8d56 | ||
|
|
81fb93e5d6 | ||
|
|
64716ec6e6 | ||
|
|
e0f787be32 | ||
|
|
138da79599 | ||
|
|
b8b290c2a5 | ||
|
|
f35dcc0ec6 | ||
|
|
62704e261a | ||
|
|
f16ace6169 | ||
|
|
35628aa93b | ||
|
|
64bf42dc1c | ||
|
|
55fc6a4282 |
No files matched your search
@@ -1,5 +1,7 @@
|
||||
Dockerfile
|
||||
src/core/engines/gows/proto
|
||||
.*sessions
|
||||
sessions
|
||||
files
|
||||
venv
|
||||
.wwebjs_auth
|
||||
|
||||
+33
-18
@@ -6,10 +6,12 @@
|
||||
# If you know the right URL, you can use it here.
|
||||
# Use any port in WHATSAPP_API_PORT and domain in WHATSAPP_API_HOSTNAME
|
||||
# https://waha.devlike.pro/docs/how-to/config/#common
|
||||
WAHA_BASE_URL=http://localhost:3000
|
||||
|
||||
#WHATSAPP_API_SCHEMA=http
|
||||
#WHATSAPP_API_PORT=3000
|
||||
#WHATSAPP_API_HOSTNAME=localhost
|
||||
#WAHA_BASE_URL=http://localhost:3000
|
||||
|
||||
#
|
||||
# Set your timezone to see the right time on screenshots
|
||||
# Find your timezone name in the list
|
||||
@@ -90,6 +92,17 @@ WHATSAPP_SWAGGER_PASSWORD=admin
|
||||
#WHATSAPP_SWAGGER_TITLE=WAHA API
|
||||
#
|
||||
|
||||
#
|
||||
# PostgresSQL Configuration
|
||||
# https://waha.devlike.pro/docs/how-to/storages/#sessions---postgresql
|
||||
#WHATSAPP_SESSIONS_POSTGRESQL_URL=postgres://postgres:postgres@postgres:5432/postgres?sslmode=disable
|
||||
#
|
||||
# Media - PostgresSQL Storage
|
||||
# https://waha.devlike.pro/docs/how-to/storages/#media---postgresql
|
||||
#WAHA_MEDIA_STORAGE=POSTGRESQL
|
||||
#WAHA_MEDIA_POSTGRESQL_URL=postgres://postgres:postgres@postgres:5432/postgres?sslmode=disable
|
||||
#
|
||||
|
||||
#
|
||||
# Global Proxy Settings
|
||||
# https://waha.devlike.pro/docs/how-to/config/#global-proxy-configuration
|
||||
@@ -101,22 +114,6 @@ WHATSAPP_SWAGGER_PASSWORD=admin
|
||||
#WHATSAPP_PROXY_SERVER_PASSWORD=pass
|
||||
#
|
||||
|
||||
#
|
||||
# HTTPS Configuration
|
||||
# https://waha.devlike.pro/docs/how-to/config/#https
|
||||
#
|
||||
# Consider using certbot for HTTPS
|
||||
# https://waha.devlike.pro/blog/setting-up-https-for-waha/#lets-encrypt-certbot--waha
|
||||
#
|
||||
#WAHA_HTTPS_ENABLED=true
|
||||
#WAHA_HTTPS_PATH_KEY=/etc/letsencrypt/live/waha.example.pro/privkey.pem
|
||||
#WAHA_HTTPS_PATH_CERT=/etc/letsencrypt/live/waha.example.pro/cert.pem \
|
||||
#WAHA_HTTPS_PATH_CA=/etc/letsencrypt/live/waha.example.pro/chain.pem
|
||||
#WHATSAPP_API_SCHEMA=https
|
||||
#WHATSAPP_API_PORT=3000
|
||||
#WHATSAPP_API_HOSTNAME=waha.example.pro
|
||||
#
|
||||
|
||||
#
|
||||
# Media Configuration
|
||||
#
|
||||
@@ -169,8 +166,26 @@ WHATSAPP_FILES_FOLDER=/app/.media
|
||||
# volumes:
|
||||
# - './.sessions:/app/sessions'
|
||||
|
||||
|
||||
#
|
||||
# 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
|
||||
#
|
||||
|
||||
#
|
||||
# HTTPS Configuration
|
||||
# https://waha.devlike.pro/docs/how-to/config/#https
|
||||
#
|
||||
# Consider using certbot for HTTPS
|
||||
# https://waha.devlike.pro/blog/setting-up-https-for-waha/#lets-encrypt-certbot--waha
|
||||
#
|
||||
#WAHA_HTTPS_ENABLED=true
|
||||
#WAHA_HTTPS_PATH_KEY=/etc/letsencrypt/live/waha.example.pro/privkey.pem
|
||||
#WAHA_HTTPS_PATH_CERT=/etc/letsencrypt/live/waha.example.pro/cert.pem \
|
||||
#WAHA_HTTPS_PATH_CA=/etc/letsencrypt/live/waha.example.pro/chain.pem
|
||||
#WHATSAPP_API_SCHEMA=https
|
||||
#WHATSAPP_API_PORT=3000
|
||||
#WHATSAPP_API_HOSTNAME=waha.example.pro
|
||||
#
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
src/core/engines/gows/grpc/*
|
||||
src/core/engines/gows/proto/*
|
||||
@@ -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 '...'
|
||||
|
||||
@@ -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.
|
||||
@@ -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!
|
||||
@@ -8,7 +8,9 @@ on:
|
||||
jobs:
|
||||
docker-build:
|
||||
runs-on: ${{ matrix.runner }}
|
||||
name: ${{ matrix.platform }} - ${{ matrix.browser }} - ${{ matrix.tag }}
|
||||
name:
|
||||
${{ matrix.engine }} - ${{ matrix.browser }} - ${{ matrix.platform }} -
|
||||
${{ matrix.tag }}
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
@@ -17,6 +19,7 @@ jobs:
|
||||
tag: 'latest'
|
||||
platform: 'amd64'
|
||||
browser: 'chromium'
|
||||
engine: 'WEBJS'
|
||||
goss: 'goss-linux-amd64'
|
||||
|
||||
# Chromium - ARM
|
||||
@@ -24,6 +27,7 @@ jobs:
|
||||
tag: 'arm'
|
||||
platform: 'linux/arm64'
|
||||
browser: 'chromium'
|
||||
engine: 'WEBJS'
|
||||
goss: 'goss-linux-arm'
|
||||
|
||||
# Chrome - x86
|
||||
@@ -31,6 +35,7 @@ jobs:
|
||||
tag: 'chrome'
|
||||
platform: 'amd64'
|
||||
browser: 'chrome'
|
||||
engine: 'WEBJS'
|
||||
goss: 'goss-linux-amd64'
|
||||
|
||||
# Chrome - ARM (Chrome is not available for ARM)
|
||||
@@ -40,18 +45,36 @@ jobs:
|
||||
# browser: "chrome"
|
||||
# goss: 'goss-linux-arm'
|
||||
|
||||
# No browser - x86
|
||||
# NOWEB - x86
|
||||
- runner: 'buildjet-4vcpu-ubuntu-2204'
|
||||
tag: 'noweb'
|
||||
platform: 'amd64'
|
||||
browser: 'none'
|
||||
engine: 'NOWEB'
|
||||
goss: 'goss-linux-amd64'
|
||||
|
||||
# No browser - ARM
|
||||
# NOWEB - ARM
|
||||
- runner: 'buildjet-4vcpu-ubuntu-2204-arm'
|
||||
tag: 'noweb-arm'
|
||||
platform: 'linux/arm64'
|
||||
browser: 'none'
|
||||
engine: 'NOWEB'
|
||||
goss: 'goss-linux-arm'
|
||||
|
||||
# GOWS - x86
|
||||
- runner: 'buildjet-4vcpu-ubuntu-2204'
|
||||
tag: 'gows'
|
||||
platform: 'amd64'
|
||||
browser: 'none'
|
||||
engine: 'GOWS'
|
||||
goss: 'goss-linux-amd64'
|
||||
|
||||
# No browser - ARM
|
||||
- runner: 'buildjet-4vcpu-ubuntu-2204-arm'
|
||||
tag: 'gows-arm'
|
||||
platform: 'linux/arm64'
|
||||
browser: 'none'
|
||||
engine: 'GOWS'
|
||||
goss: 'goss-linux-arm'
|
||||
|
||||
steps:
|
||||
@@ -85,6 +108,7 @@ jobs:
|
||||
platforms: ${{ matrix.platform }}
|
||||
build-args: |
|
||||
USE_BROWSER=${{ matrix.browser }}
|
||||
WHATSAPP_DEFAULT_ENGINE=${{ matrix.engine }}
|
||||
push: false
|
||||
load: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
@@ -116,6 +140,7 @@ jobs:
|
||||
platforms: ${{ matrix.platform }}
|
||||
build-args: |
|
||||
USE_BROWSER=${{ matrix.browser }}
|
||||
WHATSAPP_DEFAULT_ENGINE=${{ matrix.engine }}
|
||||
push: ${{ github.ref_type == 'tag' }}
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
@@ -1,8 +1,10 @@
|
||||
src/core/engines/gows/proto
|
||||
.env
|
||||
*.heapsnapshot
|
||||
.yarn
|
||||
example.ts
|
||||
.*sessions
|
||||
sessions
|
||||
.secrets
|
||||
.minio
|
||||
.media
|
||||
|
||||
@@ -2,6 +2,8 @@ exclude: |
|
||||
(?x)^(
|
||||
^src/dashboard/.*|
|
||||
^src/core/engines/webjs/.*html|
|
||||
^src/core/engines/gows/grpc/.*|
|
||||
^src/core/engines/gows/proto/.*|
|
||||
^src/plus/engines/webjs/.*html|
|
||||
^.yarn/.*
|
||||
)$
|
||||
@@ -72,4 +74,7 @@ repos:
|
||||
entrypoint.sh|
|
||||
.pre-commit-config.yaml|
|
||||
^src/core/engines/webjs/session.webjs.core.ts|
|
||||
^src/core/engines/webjs/_lodash.js|
|
||||
^tests/perf/.*|
|
||||
^scripts/.*|
|
||||
)$
|
||||
+83
-12
@@ -6,7 +6,7 @@ FROM node:${NODE_VERSION} AS build
|
||||
ENV PUPPETEER_SKIP_DOWNLOAD=True
|
||||
|
||||
# npm packages
|
||||
WORKDIR /src
|
||||
WORKDIR /git
|
||||
COPY package.json .
|
||||
COPY yarn.lock .
|
||||
ENV YARN_CHECKSUM_BEHAVIOR=update
|
||||
@@ -15,8 +15,8 @@ RUN yarn set version 3.6.3
|
||||
RUN yarn install
|
||||
|
||||
# App
|
||||
WORKDIR /src
|
||||
ADD . /src
|
||||
WORKDIR /git
|
||||
ADD . /git
|
||||
RUN yarn install
|
||||
RUN yarn build && find ./dist -name "*.d.ts" -delete
|
||||
|
||||
@@ -25,16 +25,51 @@ RUN yarn build && find ./dist -name "*.d.ts" -delete
|
||||
#
|
||||
FROM node:${NODE_VERSION} AS dashboard
|
||||
|
||||
# Download WAHA Dashboard
|
||||
ENV WAHA_DASHBOARD_SHA 5199c68522e81e911b440834fa7adc15ff4a1cf9
|
||||
# jq to parse json
|
||||
RUN apt-get update && apt-get install -y jq && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
COPY waha.config.json /tmp/waha.config.json
|
||||
RUN \
|
||||
wget https://github.com/devlikeapro/dashboard/archive/${WAHA_DASHBOARD_SHA}.zip \
|
||||
WAHA_DASHBOARD_GITHUB_REPO=$(jq -r '.waha.dashboard.repo' /tmp/waha.config.json) && \
|
||||
WAHA_DASHBOARD_SHA=$(jq -r '.waha.dashboard.ref' /tmp/waha.config.json) && \
|
||||
wget https://github.com/${WAHA_DASHBOARD_GITHUB_REPO}/archive/${WAHA_DASHBOARD_SHA}.zip \
|
||||
&& unzip ${WAHA_DASHBOARD_SHA}.zip -d /tmp/dashboard \
|
||||
&& mkdir -p /dashboard \
|
||||
&& mv /tmp/dashboard/dashboard-${WAHA_DASHBOARD_SHA}/* /dashboard/ \
|
||||
&& rm -rf ${WAHA_DASHBOARD_SHA}.zip \
|
||||
&& rm -rf /tmp/dashboard/dashboard-${WAHA_DASHBOARD_SHA}
|
||||
|
||||
#
|
||||
# GOWS
|
||||
#
|
||||
FROM golang:1.23-bullseye AS gows
|
||||
|
||||
# jq to parse json
|
||||
RUN apt-get update && apt-get install -y jq && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# install protoc
|
||||
RUN apt-get update && \
|
||||
apt-get install protobuf-compiler -y
|
||||
|
||||
# Image processing for thumbnails
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y libvips-dev \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
COPY waha.config.json /tmp/waha.config.json
|
||||
WORKDIR /go/gows
|
||||
RUN \
|
||||
GOWS_GITHUB_REPO=$(jq -r '.waha.gows.repo' /tmp/waha.config.json) && \
|
||||
GOWS_SHA=$(jq -r '.waha.gows.ref' /tmp/waha.config.json) && \
|
||||
ARCH=$(uname -m) && \
|
||||
if [ "$ARCH" = "x86_64" ]; then ARCH="amd64"; \
|
||||
elif [ "$ARCH" = "aarch64" ]; then ARCH="arm64"; \
|
||||
else echo "Unsupported architecture: $ARCH" && exit 1; fi && \
|
||||
mkdir -p /go/gows/bin && \
|
||||
wget -O /go/gows/bin/gows https://github.com/${GOWS_GITHUB_REPO}/releases/download/v.${GOWS_SHA}/gows-${ARCH} && \
|
||||
chmod +x /go/gows/bin/gows
|
||||
|
||||
|
||||
#
|
||||
# Final
|
||||
#
|
||||
@@ -44,19 +79,42 @@ ENV PUPPETEER_SKIP_DOWNLOAD=True
|
||||
# https://github.com/devlikeapro/waha/issues/347
|
||||
ENV NODE_OPTIONS="--max-old-space-size=16384"
|
||||
ARG USE_BROWSER=chromium
|
||||
ARG WHATSAPP_DEFAULT_ENGINE
|
||||
|
||||
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/*
|
||||
# Image processing for thumbnails
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y libvips \
|
||||
&& 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 +131,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 \
|
||||
@@ -82,12 +140,25 @@ RUN if [ "$USE_BROWSER" = "chrome" ]; then \
|
||||
&& rm -rf /var/lib/apt/lists/*; \
|
||||
fi
|
||||
|
||||
# GOWS requirements
|
||||
# libc6
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y libc6 \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Set the ENV for docker image
|
||||
ENV WHATSAPP_DEFAULT_ENGINE=$WHATSAPP_DEFAULT_ENGINE
|
||||
|
||||
# Attach sources, install packages
|
||||
WORKDIR /app
|
||||
COPY package.json ./
|
||||
COPY --from=build /src/node_modules ./node_modules
|
||||
COPY --from=build /src/dist ./dist
|
||||
COPY --from=build /git/node_modules ./node_modules
|
||||
COPY --from=build /git/dist ./dist
|
||||
COPY --from=dashboard /dashboard ./dist/dashboard
|
||||
COPY --from=gows /go/gows/bin/gows /app/gows
|
||||
ENV WAHA_GOWS_PATH /app/gows
|
||||
ENV WAHA_GOWS_SOCKET /tmp/gows.sock
|
||||
|
||||
COPY entrypoint.sh /entrypoint.sh
|
||||
|
||||
# Chokidar options to monitor file changes
|
||||
|
||||
@@ -5,7 +5,10 @@ build-chrome:
|
||||
docker build . -t devlikeapro/waha-plus:chrome --build-arg USE_BROWSER=chrome
|
||||
|
||||
build-noweb:
|
||||
docker build . -t devlikeapro/waha:noweb --build-arg USE_BROWSER=none
|
||||
docker build . -t devlikeapro/waha:noweb --build-arg USE_BROWSER=none --build-arg WHATSAPP_DEFAULT_ENGINE=NOWEB
|
||||
|
||||
build-plus-gows:
|
||||
docker build . -t devlikeapro/waha-plus:gows --build-arg USE_BROWSER=none --build-arg WHATSAPP_DEFAULT_ENGINE=GOWS
|
||||
|
||||
build-all: build build-chrome build-noweb
|
||||
|
||||
@@ -32,10 +35,26 @@ for-swagger:
|
||||
WHATSAPP_SWAGGER_CONFIG_ADVANCED=true . ${NVM_DIR}/nvm.sh && nvm exec yarn start
|
||||
|
||||
up-noweb:
|
||||
. ${NVM_DIR}/nvm.sh && nvm exec yarn up @adiwajshing/baileys@github:devlikeapro/Baileys#fork-master-channels-678
|
||||
. ${NVM_DIR}/nvm.sh && nvm exec yarn up @adiwajshing/baileys@github:devlikeapro/Baileys#fork-master-channels-6712
|
||||
|
||||
up-noweb-libsignal:
|
||||
. ${NVM_DIR}/nvm.sh && nvm exec yarn up libsignal@github:devlikeapro/libsignal-node#fork-master
|
||||
|
||||
up-webjs:
|
||||
. ${NVM_DIR}/nvm.sh && nvm exec yarn up whatsapp-web.js@github:devlikeapro/whatsapp-web.js#fork-main-channels
|
||||
|
||||
start-proxy:
|
||||
docker run --rm -d --name squid-container -e TZ=UTC -p 3128:3128 ubuntu/squid:5.2-22.04_beta
|
||||
|
||||
proto-gows:
|
||||
node scripts/gows-proto.js build --dir ../gows/proto
|
||||
|
||||
gows:
|
||||
cd ../gows && \
|
||||
export PATH=${HOME}/go/bin:${PATH} && \
|
||||
make all
|
||||
|
||||
copy-dashboard:
|
||||
cd ../waha-hub/ui && \
|
||||
make copy-waha
|
||||
|
||||
@@ -6,22 +6,16 @@
|
||||
|
||||
**WAHA** - **W**hats**A**pp **H**TTP **A**PI (REST API) that you can install on your own server and run in less than 5 minutes!
|
||||
|
||||
[](https://hub.docker.com/r/devlikeapro/waha)
|
||||
[](https://hub.docker.com/r/devlikeapro/waha)
|
||||
|
||||
- Documentation: [https://waha.devlike.pro/](https://waha.devlike.pro/)
|
||||
- Dashboard Example: [https://waha.devlike.pro/dashboard](https://waha.devlike.pro/dashboard)
|
||||
- Swagger Example: [https://waha.devlike.pro/swagger](https://waha.devlike.pro/swagger)
|
||||
|
||||
# Tables of Contents
|
||||
|
||||
<!-- toc -->
|
||||
|
||||
- [Features](#features)
|
||||
* [Messages](#messages)
|
||||
* [Sessions](#sessions)
|
||||
* [Security](#security)
|
||||
* [Updates](#updates)
|
||||
* [Groups](#groups)
|
||||
* [Contacts](#contacts)
|
||||
- [Quick start](#quick-start)
|
||||
* [Requirements](#requirements)
|
||||
* [Send your first message](#send-your-first-message)
|
||||
@@ -32,52 +26,11 @@
|
||||
+ [5. Get the screenshot](#5-get-the-screenshot)
|
||||
+ [6. Send a text message](#6-send-a-text-message)
|
||||
* [What is next?](#what-is-next)
|
||||
- [Development](#development)
|
||||
* [Start the project](#start-the-project)
|
||||
|
||||
<!-- tocstop -->
|
||||
|
||||
# Features
|
||||
|
||||
## Messages
|
||||
- Send messages
|
||||
- Receive messages
|
||||
- Message replies
|
||||
- Send location
|
||||
- Receive location
|
||||
- React to messages
|
||||
- Send media (images/documents/files)
|
||||
- Send voice messages
|
||||
- Receive media (images/audio/video/documents)
|
||||
- Receive messages - webhook retries
|
||||
|
||||
## Sessions
|
||||
- Multi Device
|
||||
- Get the screenshot
|
||||
- Session saving (don't have to scan QR on every restart)
|
||||
- Single WhatsApp account running inside one container
|
||||
- Multiple WhatsApp account running inside one container
|
||||
|
||||
## Security
|
||||
- Swagger panel authentication
|
||||
- API authentication
|
||||
|
||||
## Updates
|
||||
- Bug fixes and updates come as soon as they are implemented.
|
||||
|
||||
## Groups
|
||||
- Create a group
|
||||
- Get invite for group
|
||||
- Modify group info (subject, description)
|
||||
- Add group participants
|
||||
- Kick group participants
|
||||
- Promote/demote group participants
|
||||
|
||||
## Contacts
|
||||
- Mute/unmute chats
|
||||
- Block/unblock contacts
|
||||
- Get contact info
|
||||
- Get profile pictures
|
||||
- Get presence (online\offline\typing) status
|
||||
|
||||
# Quick start
|
||||
|
||||
## Requirements
|
||||
@@ -87,13 +40,6 @@ instruction <a href="https://docs.docker.com/get-docker/" target="_blank" rel="n
|
||||
|
||||
When you are ready - come back and follows the below steps to send the first text message to WhatsApp via HTTP API!
|
||||
|
||||
{{< details "Why Docker?" >}}
|
||||
Docker makes it easy to ship all-in-one solution with the runtime and dependencies. You don't have to worry about
|
||||
language-specific libraries or chrome installation.
|
||||
|
||||
Also Docker makes installation and update processes so simple, just one command!
|
||||
{{< /details >}}
|
||||
|
||||
## Send your first message
|
||||
|
||||
Let's go over steps that allow you to send your first text message via WhatsApp HTTP API!
|
||||
@@ -114,7 +60,7 @@ docker pull devlikeapro/waha-plus
|
||||
docker logout
|
||||
```
|
||||
|
||||
Read more about how to get `PASSWORD` for [Plus Version →]({{< relref "plus-version" >}})
|
||||
Read more about how to get `PASSWORD` for [**➕ WAHA Plus**](https://waha.devlike.pro/docs/how-to/waha-plus/)
|
||||
|
||||
### 2. Run WhatsApp HTTP API
|
||||
|
||||
@@ -195,3 +141,19 @@ curl -d "{\"chatId\": \"${PHONE}@c.us\", \"text\": \"Hello from WhatsApp HTTP AP
|
||||
|
||||
## What is next?
|
||||
[Go and read the full documentation!](https://waha.devlike.pro/docs/overview/introduction/)
|
||||
|
||||
# Development
|
||||
|
||||
## Start the project
|
||||
1. Clone the repository
|
||||
2. Make sure you're using node>=22
|
||||
3. Run the following commands:
|
||||
```bash
|
||||
# Install dependencies
|
||||
yarn install
|
||||
# Fetch and compile proto files
|
||||
yarn gows:proto
|
||||
# Run
|
||||
yarn start
|
||||
# open http://localhost:3000
|
||||
```
|
||||
+28
-8
@@ -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
|
||||
@@ -22,9 +22,27 @@ services:
|
||||
env_file:
|
||||
- .env
|
||||
|
||||
# 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
|
||||
# NOTE: Only if you're using PostgreSQL to save sessions
|
||||
# https://waha.devlike.pro/docs/how-to/storages/#media---postgresql
|
||||
# postgres:
|
||||
# image: postgres:17
|
||||
# restart: always
|
||||
# environment:
|
||||
# POSTGRES_USER: postgres
|
||||
# POSTGRES_PASSWORD: postgres
|
||||
# POSTGRES_DB: postgres
|
||||
# ports:
|
||||
# - "5432:5432"
|
||||
# volumes:
|
||||
# - pg_data:/var/lib/postgresql/data
|
||||
# command:
|
||||
# - postgres
|
||||
# - "-c"
|
||||
# - "max_connections=3000"
|
||||
|
||||
# NOTE: Only if you're using MongoDB
|
||||
# https://waha.devlike.pro/docs/how-to/storages/#sessions---mongodb
|
||||
# Uncomment this block if you're using MongoDB
|
||||
# mongodb:
|
||||
# image: mongo
|
||||
# container_name: mongodb
|
||||
@@ -32,11 +50,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
|
||||
@@ -53,6 +73,6 @@ services:
|
||||
# command: server /data --console-address ":9001"
|
||||
|
||||
volumes:
|
||||
# NOTE: Only if you're using MongoDB
|
||||
mongodb_data: {}
|
||||
minio_data: {}
|
||||
pg_data: {}
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
|
||||
# Calculate UV_THREADPOOL_SIZE based on number of CPUs
|
||||
cpus=$(node -e "const os = require('os'); console.log(os.cpus().length);")
|
||||
uv_threadpool_size=$(($cpus * 2))
|
||||
uv_threadpool_size=$(($cpus * 1))
|
||||
|
||||
# Set UV_THREADPOOL_SIZE as an environment variable
|
||||
export UV_THREADPOOL_SIZE="${UV_THREADPOOL_SIZE:-$uv_threadpool_size}"
|
||||
|
||||
+29
-6
@@ -20,10 +20,13 @@
|
||||
"test:watch": "jest --watch",
|
||||
"test:cov": "jest --coverage",
|
||||
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
|
||||
"test:e2e": "jest --config ./test/jest-e2e.json"
|
||||
"test:e2e": "jest --config ./test/jest-e2e.json",
|
||||
"gows:proto:fetch": "node scripts/gows-proto.js fetch",
|
||||
"gows:proto:build": "node scripts/gows-proto.js build",
|
||||
"gows:proto": "yarn gows:proto:fetch && yarn gows:proto:build"
|
||||
},
|
||||
"dependencies": {
|
||||
"@adiwajshing/baileys": "github:devlikeapro/Baileys#fork-master-channels-678",
|
||||
"@adiwajshing/baileys": "github:devlikeapro/Baileys#fork-master-channels-6712",
|
||||
"@adiwajshing/keyed-db": "^0.2.4",
|
||||
"@aws-sdk/client-s3": "^3.633.0",
|
||||
"@aws-sdk/s3-request-presigner": "^3.633.0",
|
||||
@@ -55,10 +58,12 @@
|
||||
"express-basic-auth": "^1.2.1",
|
||||
"file-type": "16.5.4",
|
||||
"fs-extra": "^11.2.0",
|
||||
"google-protobuf": "^3.21.4",
|
||||
"https-proxy-agent": "^7.0.0",
|
||||
"joi": "^17.13.3",
|
||||
"knex": "^3.1.0",
|
||||
"libphonenumber-js": "^1.10.36",
|
||||
"libsignal": "github:devlikeapro/libsignal-node#fork-master",
|
||||
"link-preview-js": "^3.0.4",
|
||||
"lodash": "^4.17.21",
|
||||
"mime-types": "^2.1.27",
|
||||
@@ -66,21 +71,36 @@
|
||||
"nestjs-pino": "^4.1.0",
|
||||
"passport": "^0.6.0",
|
||||
"passport-headerapikey": "^1.2.2",
|
||||
"pg": "^8.13.1",
|
||||
"pino-http": "^10.2.0",
|
||||
"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",
|
||||
"whatsapp-web.js": "github:devlikeapro/whatsapp-web.js#fork-main-channels"
|
||||
"ulid": "^2.3.0",
|
||||
"whatsapp-web.js": "github:devlikeapro/whatsapp-web.js#fork-main-channels",
|
||||
"write-file-atomic": "^6.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"bufferutil": "^4.0.8"
|
||||
},
|
||||
"resolutions": {
|
||||
"bufferutil": "^4.0.8",
|
||||
"ws": "^8.18.0",
|
||||
"puppeteer": "^23.6.0",
|
||||
"whatwg-url": "13.0.0",
|
||||
"libsignal": "github:devlikeapro/libsignal-node#fork-master"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@grpc/grpc-js": "^1.12.4",
|
||||
"@grpc/proto-loader": "^0.7.13",
|
||||
"@nestjs/cli": "^9.0.0",
|
||||
"@nestjs/schematics": "^9.0.1",
|
||||
"@nestjs/testing": "^9.0.9",
|
||||
@@ -94,14 +114,17 @@
|
||||
"eslint-config-prettier": "^6.10.0",
|
||||
"eslint-plugin-import": "^2.20.1",
|
||||
"eslint-plugin-simple-import-sort": "^10.0.0",
|
||||
"grpc-tools": "^1.12.4",
|
||||
"jest": "^29.7.0",
|
||||
"prettier": "^1.19.1",
|
||||
"protoc-gen-ts": "^0.8.7",
|
||||
"supertest": "^4.0.2",
|
||||
"ts-jest": "^29.1.3",
|
||||
"ts-loader": "^6.2.1",
|
||||
"ts-node": "^10.9.1",
|
||||
"tsconfig-paths": "^4.1.0",
|
||||
"typescript": "^4.8.4"
|
||||
"typescript": "^4.8.4",
|
||||
"yargs": "^17.7.2"
|
||||
},
|
||||
"jest": {
|
||||
"moduleFileExtensions": [
|
||||
|
||||
@@ -0,0 +1,144 @@
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const fs = require('fs');
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const path = require('path');
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const axios = require('axios');
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const { execSync } = require('child_process');
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const yargs = require('yargs');
|
||||
|
||||
// Defaults
|
||||
const CONFIG_FILE = 'waha.config.json';
|
||||
// Load defaults from package.json
|
||||
gows = (() => {
|
||||
try {
|
||||
const config = JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8'));
|
||||
return config.waha.gows || {};
|
||||
} catch (error) {
|
||||
return {};
|
||||
}
|
||||
})();
|
||||
|
||||
const DEFAULT_REPO = gows.repo;
|
||||
if (!DEFAULT_REPO) {
|
||||
throw new Error(`Missing default repo in ${CONFIG_FILE}`);
|
||||
}
|
||||
const DEFAULT_REF = gows.ref;
|
||||
if (!DEFAULT_REF) {
|
||||
throw new Error('Missing default ref in ${CONFIG_FILE}');
|
||||
}
|
||||
const DEFAULT_DIR = './src/core/engines/gows/proto';
|
||||
|
||||
const PROTO_FILES = ['gows.proto'];
|
||||
const PROTO_OUTPUT = './src/core/engines/gows/grpc';
|
||||
|
||||
// Helper function to clean directory
|
||||
function cleanDirectory(directory, suffix) {
|
||||
if (!fs.existsSync(directory)) {
|
||||
fs.mkdirSync(directory, { recursive: true });
|
||||
return;
|
||||
}
|
||||
|
||||
const files = fs.readdirSync(directory);
|
||||
for (const file of files) {
|
||||
const filePath = path.join(directory, file);
|
||||
if (file.endsWith(suffix)) {
|
||||
fs.unlinkSync(filePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to download files
|
||||
async function downloadFiles(repo, ref, directory) {
|
||||
for (const file of PROTO_FILES) {
|
||||
const url = `https://github.com/${repo}/releases/download/v.${ref}/${file}`;
|
||||
const filePath = path.join(directory, file);
|
||||
try {
|
||||
const response = await axios.get(url, { responseType: 'arraybuffer' });
|
||||
fs.writeFileSync(filePath, response.data);
|
||||
console.log(`Downloaded: ${file}`);
|
||||
} catch (error) {
|
||||
console.error(`Failed to download ${file}: ${error.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handler for fetch command
|
||||
async function handleFetch(repo, ref, dir) {
|
||||
console.log(`Fetching .proto files from ${repo}@${ref} to ${dir}...`);
|
||||
cleanDirectory(dir, '.proto');
|
||||
await downloadFiles(repo, ref, dir);
|
||||
}
|
||||
|
||||
// Handler for build command
|
||||
function handleBuild(dir) {
|
||||
console.log('Building gRPC files...');
|
||||
cleanDirectory(PROTO_OUTPUT);
|
||||
|
||||
const command = `grpc_tools_node_protoc \
|
||||
--plugin=protoc-gen-ts=node_modules/.bin/protoc-gen-ts \
|
||||
--plugin=protoc-gen-grpc=node_modules/.bin/grpc_tools_node_protoc_plugin \
|
||||
--js_out=import_style=commonjs,binary:${PROTO_OUTPUT} \
|
||||
--grpc_out=grpc_js:${PROTO_OUTPUT} \
|
||||
--ts_out=grpc_js:${PROTO_OUTPUT} \
|
||||
-I ${dir} ${dir}/gows.proto`;
|
||||
|
||||
try {
|
||||
execSync(command, { stdio: 'inherit' });
|
||||
console.log('gRPC files built successfully.');
|
||||
} catch (error) {
|
||||
console.error(`Failed to build gRPC files: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Commands
|
||||
//
|
||||
|
||||
// fetch
|
||||
yargs.command(
|
||||
'fetch',
|
||||
'Fetch .proto files from GitHub',
|
||||
(yargs) => {
|
||||
yargs
|
||||
.option('repo', {
|
||||
describe: 'GitHub repository (owner/repo)',
|
||||
type: 'string',
|
||||
default: DEFAULT_REPO,
|
||||
})
|
||||
.option('ref', {
|
||||
describe: 'Git reference (branch or commit SHA)',
|
||||
type: 'string',
|
||||
default: DEFAULT_REF,
|
||||
})
|
||||
.option('dir', {
|
||||
describe: 'Directory to output .proto files',
|
||||
type: 'string',
|
||||
default: DEFAULT_DIR,
|
||||
});
|
||||
},
|
||||
async (argv) => {
|
||||
await handleFetch(argv.repo, argv.ref, argv.dir);
|
||||
},
|
||||
);
|
||||
|
||||
// build
|
||||
yargs.command(
|
||||
'build',
|
||||
'Build gRPC files from .proto files',
|
||||
(yargs) => {
|
||||
yargs.option('dir', {
|
||||
describe: 'Directory containing .proto files',
|
||||
type: 'string',
|
||||
default: DEFAULT_DIR,
|
||||
});
|
||||
},
|
||||
(argv) => {
|
||||
handleBuild(argv.dir);
|
||||
},
|
||||
);
|
||||
|
||||
// Parse arguments
|
||||
yargs.parse();
|
||||
@@ -1,22 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
echo "Starting WAHA..."
|
||||
|
||||
docker run -d \
|
||||
--restart=always \
|
||||
--name waha \
|
||||
-p 3000:3000/tcp \
|
||||
-v ./.sessions:/app/.sessions \
|
||||
-v ./.media:/app/.media \
|
||||
--env WHATSAPP_API_KEY=321 \
|
||||
--env WAHA_DASHBOARD_USERNAME=waha \
|
||||
--env WAHA_DASHBOARD_PASSWORD=waha \
|
||||
--env WAHA_LOG_FORMAT=JSON \
|
||||
--env WAHA_LOG_LEVEL=info \
|
||||
--env WHATSAPP_DEFAULT_ENGINE=WEBJS \
|
||||
--env WAHA_PRINT_QR=False \
|
||||
--env WHATSAPP_FILES_LIFETIME=0 \
|
||||
--env WHATSAPP_FILES_FOLDER=/app/.media \
|
||||
devlikeapro/waha-plus:latest
|
||||
|
||||
echo "WAHA started."
|
||||
@@ -1,21 +0,0 @@
|
||||
set -e
|
||||
echo "Updating WAHA..."
|
||||
echo "Logging in to Docker..."
|
||||
docker login -u devlikeapro # -p {KEY} - you can hard code key here https://portal.devlike.pro/
|
||||
|
||||
echo "Pulling the latest image..."
|
||||
docker pull devlikeapro/waha-plus
|
||||
|
||||
echo "Logging out of Docker..."
|
||||
docker logout
|
||||
|
||||
# Just pull if you're using WAHA Core
|
||||
# docker pull devlikeapro/waha
|
||||
|
||||
echo "Stopping the current container..."
|
||||
docker stop waha || true
|
||||
echo "Removing the current container..."
|
||||
docker rm waha || true
|
||||
|
||||
echo "Running WAHA..."
|
||||
sh ./waha-run.sh
|
||||
@@ -6,7 +6,6 @@ import {
|
||||
Query,
|
||||
UseInterceptors,
|
||||
} from '@nestjs/common';
|
||||
import { UnprocessableEntityException } from '@nestjs/common/exceptions/unprocessable-entity.exception';
|
||||
import { ApiOperation, ApiSecurity, ApiTags } from '@nestjs/swagger';
|
||||
import { ApiFileAcceptHeader } from '@waha/nestjs/ApiFileAcceptHeader';
|
||||
import {
|
||||
@@ -19,8 +18,6 @@ import { SessionManager } from '../core/abc/manager.abc';
|
||||
import { WhatsappSession } from '../core/abc/session.abc';
|
||||
import { BufferResponseInterceptor } from '../nestjs/BufferResponseInterceptor';
|
||||
import {
|
||||
CaptchaBody,
|
||||
OTPRequest,
|
||||
QRCodeFormat,
|
||||
QRCodeQuery,
|
||||
QRCodeValue,
|
||||
@@ -63,42 +60,6 @@ class AuthController {
|
||||
) {
|
||||
return session.requestCode(request.phoneNumber, request.method, request);
|
||||
}
|
||||
|
||||
@Post('authorize-code')
|
||||
@SessionApiParam
|
||||
@ApiOperation({
|
||||
summary: 'Send OTP authentication code.',
|
||||
})
|
||||
authorizeCode(
|
||||
@SessionParam session: WhatsappSession,
|
||||
@Body() request: OTPRequest,
|
||||
) {
|
||||
return session.authorizeCode(request.code);
|
||||
}
|
||||
|
||||
@Get('captcha')
|
||||
@SessionApiParam
|
||||
@ApiFileAcceptHeader()
|
||||
@UseInterceptors(new BufferResponseInterceptor())
|
||||
@ApiOperation({
|
||||
summary: 'Get captcha image.',
|
||||
})
|
||||
async getCaptcha(@SessionParam session: WhatsappSession): Promise<Buffer> {
|
||||
const image = await session.getCaptcha();
|
||||
return image.get();
|
||||
}
|
||||
|
||||
@Post('captcha')
|
||||
@SessionApiParam
|
||||
@ApiOperation({
|
||||
summary: 'Enter captcha code.',
|
||||
})
|
||||
saveCaptcha(
|
||||
@SessionParam session: WhatsappSession,
|
||||
@Body() body: CaptchaBody,
|
||||
) {
|
||||
return session.saveCaptcha(body.code);
|
||||
}
|
||||
}
|
||||
|
||||
export { AuthController };
|
||||
@@ -3,31 +3,129 @@ import {
|
||||
Controller,
|
||||
Delete,
|
||||
Get,
|
||||
HttpCode,
|
||||
HttpStatus,
|
||||
Param,
|
||||
Post,
|
||||
Query,
|
||||
UsePipes,
|
||||
} from '@nestjs/common';
|
||||
import { ApiOperation, ApiSecurity, ApiTags } from '@nestjs/swagger';
|
||||
import { ApiOperation, ApiParam, ApiSecurity, ApiTags } from '@nestjs/swagger';
|
||||
import { ChannelsInfoServiceCore } from '@waha/core/services/ChannelsInfoServiceCore';
|
||||
import {
|
||||
SessionApiParam,
|
||||
WorkingSessionParam,
|
||||
} from '@waha/nestjs/params/SessionApiParam';
|
||||
import { WAHAValidationPipe } from '@waha/nestjs/pipes/WAHAValidationPipe';
|
||||
import {
|
||||
Channel,
|
||||
ChannelCategory,
|
||||
ChannelCountry,
|
||||
ChannelListResult,
|
||||
ChannelMessage,
|
||||
ChannelSearchByText,
|
||||
ChannelSearchByView,
|
||||
ChannelView,
|
||||
CreateChannelRequest,
|
||||
ListChannelsQuery,
|
||||
NewsletterIdApiParam,
|
||||
NewsletterIdOrInviteCodeApiParam,
|
||||
PreviewChannelMessages,
|
||||
} from '@waha/structures/channels.dto';
|
||||
|
||||
import { SessionManager } from '../core/abc/manager.abc';
|
||||
import { isNewsletter, WhatsappSession } from '../core/abc/session.abc';
|
||||
import {
|
||||
isNewsletter,
|
||||
parseChannelInviteLink,
|
||||
WhatsappSession,
|
||||
} from '../core/abc/session.abc';
|
||||
|
||||
@ApiSecurity('api_key')
|
||||
@Controller('api/:session/channels')
|
||||
@ApiTags('📢 Channels')
|
||||
export class ChannelsController {
|
||||
constructor(private manager: SessionManager) {}
|
||||
constructor(
|
||||
private manager: SessionManager,
|
||||
private channelsInfoService: ChannelsInfoServiceCore,
|
||||
) {}
|
||||
|
||||
@Post('/search/by-view')
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@SessionApiParam
|
||||
@UsePipes(new WAHAValidationPipe())
|
||||
@ApiOperation({ summary: 'Search for channels (by view)' })
|
||||
async searchByView(
|
||||
@WorkingSessionParam session: WhatsappSession,
|
||||
@Body() request: ChannelSearchByView,
|
||||
): Promise<ChannelListResult> {
|
||||
return session.searchChannelsByView(request);
|
||||
}
|
||||
|
||||
@Post('/search/by-text')
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@SessionApiParam
|
||||
@UsePipes(new WAHAValidationPipe())
|
||||
@ApiOperation({ summary: 'Search for channels (by text)' })
|
||||
async searchByText(
|
||||
@WorkingSessionParam session: WhatsappSession,
|
||||
@Body() request: ChannelSearchByText,
|
||||
): Promise<ChannelListResult> {
|
||||
return session.searchChannelsByText(request);
|
||||
}
|
||||
|
||||
@Get('/search/views')
|
||||
@SessionApiParam
|
||||
@ApiOperation({ summary: 'Get list of views for channel search' })
|
||||
getSearchViews(): Promise<ChannelView[]> {
|
||||
return this.channelsInfoService.getViews();
|
||||
}
|
||||
|
||||
@Get('/search/countries')
|
||||
@SessionApiParam
|
||||
@ApiOperation({ summary: 'Get list of countries for channel search' })
|
||||
getSearchCountries(): Promise<ChannelCountry[]> {
|
||||
return this.channelsInfoService.getCountries();
|
||||
}
|
||||
|
||||
@Get('/search/categories')
|
||||
@SessionApiParam
|
||||
@ApiOperation({ summary: 'Get list of categories for channel search' })
|
||||
getSearchCategories(): Promise<ChannelCategory[]> {
|
||||
return this.channelsInfoService.getCategories();
|
||||
}
|
||||
|
||||
@Get(':id/messages/preview')
|
||||
@SessionApiParam
|
||||
@UsePipes(new WAHAValidationPipe())
|
||||
@ApiParam({
|
||||
name: 'id',
|
||||
description: 'Channel id or invite code',
|
||||
required: true,
|
||||
type: 'string',
|
||||
schema: {
|
||||
default: '0029Va4K0PZ5a245NkngBA2M',
|
||||
},
|
||||
})
|
||||
@ApiOperation({
|
||||
summary: 'Preview channel messages',
|
||||
description:
|
||||
'You can use either ' +
|
||||
'invite code (https://www.whatsapp.com/channel/123) or (123)' +
|
||||
'OR' +
|
||||
'Channel ID (123@newsletter).',
|
||||
})
|
||||
async previewChannelMessages(
|
||||
@WorkingSessionParam session: WhatsappSession,
|
||||
@Param('id') code: string,
|
||||
@Query() query: PreviewChannelMessages,
|
||||
): Promise<ChannelMessage[]> {
|
||||
if (isNewsletter(code)) {
|
||||
const channel = await session.channelsGetChannel(code);
|
||||
code = parseChannelInviteLink(channel.invite);
|
||||
}
|
||||
const inviteCode = parseChannelInviteLink(code);
|
||||
return session.previewChannelMessages(inviteCode, query);
|
||||
}
|
||||
|
||||
@Get('')
|
||||
@SessionApiParam
|
||||
@@ -76,13 +174,12 @@ export class ChannelsController {
|
||||
if (isNewsletter(id)) {
|
||||
return session.channelsGetChannel(id);
|
||||
} else {
|
||||
// https://www.whatsapp.com/channel/123 => 123
|
||||
const inviteCode = id.split('/').pop();
|
||||
const inviteCode = parseChannelInviteLink(id);
|
||||
return session.channelsGetChannelByInviteCode(inviteCode);
|
||||
}
|
||||
}
|
||||
|
||||
@Post('follow')
|
||||
@Post(':id/follow')
|
||||
@SessionApiParam
|
||||
@NewsletterIdApiParam
|
||||
@ApiOperation({ summary: 'Follow the channel.' })
|
||||
@@ -93,7 +190,7 @@ export class ChannelsController {
|
||||
return session.channelsFollowChannel(id);
|
||||
}
|
||||
|
||||
@Post('unfollow')
|
||||
@Post(':id/unfollow')
|
||||
@SessionApiParam
|
||||
@NewsletterIdApiParam
|
||||
@ApiOperation({ summary: 'Unfollow the channel.' })
|
||||
@@ -104,7 +201,7 @@ export class ChannelsController {
|
||||
return session.channelsUnfollowChannel(id);
|
||||
}
|
||||
|
||||
@Post('mute')
|
||||
@Post(':id/mute')
|
||||
@SessionApiParam
|
||||
@NewsletterIdApiParam
|
||||
@ApiOperation({ summary: 'Mute the channel.' })
|
||||
@@ -115,7 +212,7 @@ export class ChannelsController {
|
||||
return session.channelsMuteChannel(id);
|
||||
}
|
||||
|
||||
@Post('unmute')
|
||||
@Post(':id/unmute')
|
||||
@SessionApiParam
|
||||
@NewsletterIdApiParam
|
||||
@ApiOperation({ summary: 'Unmute the channel.' })
|
||||
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
Controller,
|
||||
Delete,
|
||||
Get,
|
||||
NotFoundException,
|
||||
Param,
|
||||
Post,
|
||||
Put,
|
||||
@@ -20,8 +21,17 @@ 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 {
|
||||
ChatPictureQuery,
|
||||
ChatPictureResponse,
|
||||
ChatsPaginationParams,
|
||||
ChatSummary,
|
||||
GetChatMessageQuery,
|
||||
GetChatMessagesFilter,
|
||||
GetChatMessagesQuery,
|
||||
OverviewPaginationParams,
|
||||
PinMessageRequest,
|
||||
} from '../structures/chats.dto';
|
||||
import { EditMessageRequest } from '../structures/chatting.dto';
|
||||
|
||||
@ApiSecurity('api_key')
|
||||
@@ -36,9 +46,23 @@ class ChatsController {
|
||||
@ApiOperation({ summary: 'Get chats' })
|
||||
getChats(
|
||||
@WorkingSessionParam session: WhatsappSession,
|
||||
@Query() query: GetChatsQuery,
|
||||
@Query() pagination: ChatsPaginationParams,
|
||||
) {
|
||||
return session.getChats(query);
|
||||
return session.getChats(pagination);
|
||||
}
|
||||
|
||||
@Get('overview')
|
||||
@SessionApiParam
|
||||
@ApiOperation({
|
||||
summary:
|
||||
'Get chats overview. Includes all necessary things to build UI "your chats overview" page - chat id, name, picture, last message. Sorting by last message timestamp',
|
||||
})
|
||||
@UsePipes(new ValidationPipe({ transform: true, whitelist: true }))
|
||||
getChatsOverview(
|
||||
@WorkingSessionParam session: WhatsappSession,
|
||||
@Query() pagination: OverviewPaginationParams,
|
||||
): Promise<ChatSummary[]> {
|
||||
return session.getChatsOverview(pagination);
|
||||
}
|
||||
|
||||
@Delete(':chatId')
|
||||
@@ -52,17 +76,75 @@ class ChatsController {
|
||||
return session.deleteChat(chatId);
|
||||
}
|
||||
|
||||
@Get(':chatId/picture')
|
||||
@SessionApiParam
|
||||
@ApiOperation({ summary: 'Gets chat picture' })
|
||||
@UsePipes(new ValidationPipe({ transform: true, whitelist: true }))
|
||||
async getChatPicture(
|
||||
@WorkingSessionParam session: WhatsappSession,
|
||||
@Param('chatId') chatId: string,
|
||||
@Query() query: ChatPictureQuery,
|
||||
): Promise<ChatPictureResponse> {
|
||||
const url = await session.getContactProfilePicture(chatId, query.refresh);
|
||||
return { url: url };
|
||||
}
|
||||
|
||||
@Get(':chatId/messages')
|
||||
@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 +206,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 };
|
||||
@@ -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';
|
||||
@@ -169,11 +171,10 @@ export class ChattingController {
|
||||
return whatsapp.sendLinkPreview(request);
|
||||
}
|
||||
|
||||
@Get('/messages')
|
||||
@ApiOperation({ summary: 'Get messages in a chat' })
|
||||
async getMessages(@Query() query: GetMessageQuery) {
|
||||
const whatsapp = await this.manager.getWorkingSession(query.session);
|
||||
return whatsapp.getMessages(query);
|
||||
@Post('/sendContactVcard')
|
||||
async sendContactVcard(@Body() request: MessageContactVcardRequest) {
|
||||
const whatsapp = await this.manager.getWorkingSession(request.session);
|
||||
return whatsapp.sendContactVCard(request);
|
||||
}
|
||||
|
||||
@Get('/sendText')
|
||||
@@ -186,10 +187,19 @@ export class ChattingController {
|
||||
return whatsapp.sendText(msg);
|
||||
}
|
||||
|
||||
@Post('/sendContactVcard')
|
||||
async sendContactVcard(@Body() request: MessageContactVcardRequest) {
|
||||
const whatsapp = await this.manager.getWorkingSession(request.session);
|
||||
return whatsapp.sendContactVCard(request);
|
||||
@Get('/messages')
|
||||
@ApiOperation({
|
||||
summary: 'Get messages in a chat',
|
||||
description: 'DEPRECATED. Use "GET /api/chats/{id}/messages" instead',
|
||||
deprecated: true,
|
||||
})
|
||||
@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.getChatMessages(query.chatId, query, filter);
|
||||
}
|
||||
|
||||
@Get('/checkNumberStatus')
|
||||
|
||||
@@ -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,12 @@ import {
|
||||
CheckNumberStatusQuery,
|
||||
WANumberExistResult,
|
||||
} from '../structures/chatting.dto';
|
||||
import { ContactQuery, ContactRequest } from '../structures/contacts.dto';
|
||||
import {
|
||||
ContactProfilePictureQuery,
|
||||
ContactQuery,
|
||||
ContactRequest,
|
||||
ContactsPaginationParams,
|
||||
} from '../structures/contacts.dto';
|
||||
|
||||
@ApiSecurity('api_key')
|
||||
@Controller('api/contacts')
|
||||
@@ -15,6 +28,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 +50,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(
|
||||
@@ -59,9 +76,14 @@ export class ContactsController {
|
||||
description:
|
||||
'If privacy settings do not allow to get the picture, the method will return null.',
|
||||
})
|
||||
async getProfilePicture(@Query() query: ContactQuery) {
|
||||
@UsePipes(new ValidationPipe({ transform: true, whitelist: true }))
|
||||
async getProfilePicture(@Query() query: ContactProfilePictureQuery) {
|
||||
const whatsapp = await this.manager.getWorkingSession(query.session);
|
||||
return whatsapp.getContactProfilePicture(query);
|
||||
const url = await whatsapp.getContactProfilePicture(
|
||||
query.contactId,
|
||||
query.refresh,
|
||||
);
|
||||
return { profilePictureURL: url };
|
||||
}
|
||||
|
||||
@Post('/block')
|
||||
|
||||
@@ -6,6 +6,9 @@ import {
|
||||
Param,
|
||||
Post,
|
||||
Put,
|
||||
Query,
|
||||
UsePipes,
|
||||
ValidationPipe,
|
||||
} from '@nestjs/common';
|
||||
import { ApiOperation, ApiSecurity, ApiTags } from '@nestjs/swagger';
|
||||
import { GroupIdApiParam } from '@waha/nestjs/params/ChatIdApiParam';
|
||||
@@ -19,6 +22,9 @@ import { WhatsappSession } from '../core/abc/session.abc';
|
||||
import {
|
||||
CreateGroupRequest,
|
||||
DescriptionRequest,
|
||||
GroupsPaginationParams,
|
||||
JoinGroupRequest,
|
||||
JoinGroupResponse,
|
||||
ParticipantsRequest,
|
||||
SettingsSecurityChangeInfo,
|
||||
SubjectRequest,
|
||||
@@ -40,11 +46,48 @@ export class GroupsController {
|
||||
return session.createGroup(request);
|
||||
}
|
||||
|
||||
@Get('join-info')
|
||||
@SessionApiParam
|
||||
@ApiOperation({ summary: 'Get info about the group before joining.' })
|
||||
async joinInfoGroup(
|
||||
@WorkingSessionParam session: WhatsappSession,
|
||||
@Query() query: JoinGroupRequest,
|
||||
): Promise<any> {
|
||||
// https://chat.whatsapp.com/123 => 123
|
||||
const code = query.code.split('/').pop();
|
||||
return session.joinInfoGroup(code);
|
||||
}
|
||||
|
||||
@Post('join')
|
||||
@SessionApiParam
|
||||
@ApiOperation({ summary: 'Join group via code' })
|
||||
async joinGroup(
|
||||
@WorkingSessionParam session: WhatsappSession,
|
||||
@Body() request: JoinGroupRequest,
|
||||
): Promise<JoinGroupResponse> {
|
||||
// https://chat.whatsapp.com/123 => 123
|
||||
const code = request.code.split('/').pop();
|
||||
const id = await session.joinGroup(code);
|
||||
return { id: id };
|
||||
}
|
||||
|
||||
@Get('')
|
||||
@SessionApiParam
|
||||
@ApiOperation({ summary: 'Get all groups.' })
|
||||
getGroups(@WorkingSessionParam session: WhatsappSession) {
|
||||
return session.getGroups();
|
||||
@UsePipes(new ValidationPipe({ transform: true, whitelist: true }))
|
||||
getGroups(
|
||||
@WorkingSessionParam session: WhatsappSession,
|
||||
@Query() pagination: GroupsPaginationParams,
|
||||
) {
|
||||
return session.getGroups(pagination);
|
||||
}
|
||||
|
||||
@Post('refresh')
|
||||
@SessionApiParam
|
||||
@ApiOperation({ summary: 'Refresh groups from the server.' })
|
||||
@UsePipes(new ValidationPipe({ transform: true, whitelist: true }))
|
||||
async refreshGroups(@WorkingSessionParam session: WhatsappSession) {
|
||||
return { success: await session.refreshGroups() };
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -134,6 +134,13 @@ export class WhatsappConfigService {
|
||||
return this.configService.get('WHATSAPP_SESSIONS_MONGO_URL', undefined);
|
||||
}
|
||||
|
||||
getSessionPostgresUrl(): string | undefined {
|
||||
return this.configService.get(
|
||||
'WHATSAPP_SESSIONS_POSTGRESQL_URL',
|
||||
undefined,
|
||||
);
|
||||
}
|
||||
|
||||
get(name: string, defaultValue: any = undefined): any {
|
||||
return this.configService.get(name, defaultValue);
|
||||
}
|
||||
|
||||
+8
-5
@@ -1,13 +1,16 @@
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const QRCode = require('qrcode');
|
||||
|
||||
export class QR {
|
||||
private base64: string;
|
||||
public raw?: string;
|
||||
|
||||
save(base64: string, raw?: string) {
|
||||
this.base64 = base64.replace(/^data:image\/png;base64,/, '');
|
||||
save(raw?: string) {
|
||||
this.raw = raw;
|
||||
}
|
||||
|
||||
get(): Buffer {
|
||||
return Buffer.from(this.base64, 'base64');
|
||||
async get(): Promise<Buffer> {
|
||||
const url = await QRCode.toDataURL(this.raw);
|
||||
const base64 = url.replace(/^data:image\/png;base64,/, '');
|
||||
return Buffer.from(base64, 'base64');
|
||||
}
|
||||
}
|
||||
@@ -60,10 +60,7 @@ export class SwaggerConfiguratorCore {
|
||||
.addTag('🖼️ Screenshot', 'Get screenshot of WhatsApp and show QR code')
|
||||
.addTag('📤 Chatting', 'Chatting methods')
|
||||
.addTag('📢 Channels', 'Channels (newsletters) methods')
|
||||
.addTag(
|
||||
'🟢 Status',
|
||||
'Status (aka stories) methods. <b>NOWEB</b> engine only!',
|
||||
)
|
||||
.addTag('🟢 Status', 'Status (aka stories) methods')
|
||||
.addTag('💬 Chats', `Chats methods`)
|
||||
.addTag(
|
||||
'👤 Contacts',
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
export interface EngineBootstrap {
|
||||
bootstrap(): Promise<void>;
|
||||
|
||||
shutdown(): Promise<void>;
|
||||
}
|
||||
|
||||
export class NoopEngineBootstrap implements EngineBootstrap {
|
||||
async bootstrap(): Promise<void> {
|
||||
return;
|
||||
}
|
||||
|
||||
async shutdown(): Promise<void> {
|
||||
return;
|
||||
}
|
||||
}
|
||||
+69
-25
@@ -1,15 +1,22 @@
|
||||
import {
|
||||
BeforeApplicationShutdown,
|
||||
OnApplicationBootstrap,
|
||||
UnprocessableEntityException,
|
||||
} from '@nestjs/common';
|
||||
import { WhatsappConfigService } from '@waha/config.service';
|
||||
import {
|
||||
EngineBootstrap,
|
||||
NoopEngineBootstrap,
|
||||
} from '@waha/core/abc/EngineBootstrap';
|
||||
import { GowsEngineConfigService } from '@waha/core/config/GowsEngineConfigService';
|
||||
import { GowsBootstrap } from '@waha/core/engines/gows/GowsBootstrap';
|
||||
import { ISessionMeRepository } from '@waha/core/storage/ISessionMeRepository';
|
||||
import { ISessionWorkerRepository } from '@waha/core/storage/ISessionWorkerRepository';
|
||||
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,28 +32,33 @@ 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');
|
||||
|
||||
export abstract class SessionManager implements BeforeApplicationShutdown {
|
||||
export abstract class SessionManager
|
||||
implements BeforeApplicationShutdown, OnApplicationBootstrap
|
||||
{
|
||||
public store: any;
|
||||
public sessionAuthRepository: ISessionAuthRepository;
|
||||
public sessionConfigRepository: ISessionConfigRepository;
|
||||
protected sessionMeRepository: ISessionMeRepository;
|
||||
protected sessionWorkerRepository: ISessionWorkerRepository;
|
||||
public events: EventEmitter;
|
||||
private lock: any;
|
||||
|
||||
WAIT_STATUS_INTERVAL = 500;
|
||||
WAIT_STATUS_TIMEOUT = 5_000;
|
||||
LOCK_TIMEOUT = 10_000;
|
||||
|
||||
protected constructor(
|
||||
protected config: WhatsappConfigService,
|
||||
protected log: PinoLogger,
|
||||
protected config: WhatsappConfigService,
|
||||
protected gowsConfigService: GowsEngineConfigService,
|
||||
) {
|
||||
this.lock = new AsyncLock({ maxPending: Infinity });
|
||||
this.lock = new AsyncLock({
|
||||
maxPending: Infinity,
|
||||
timeout: this.LOCK_TIMEOUT,
|
||||
});
|
||||
this.log.setContext(SessionManager.name);
|
||||
}
|
||||
|
||||
@@ -72,7 +84,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 +117,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 +137,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 +167,42 @@ export abstract class SessionManager implements BeforeApplicationShutdown {
|
||||
return session;
|
||||
}
|
||||
|
||||
async beforeApplicationShutdown(signal?: string) {
|
||||
this.events.removeAllListeners();
|
||||
beforeApplicationShutdown(signal?: string) {
|
||||
return;
|
||||
}
|
||||
|
||||
onApplicationBootstrap(): any {
|
||||
return;
|
||||
}
|
||||
|
||||
protected getEngineBootstrap(engine: WAHAEngine): EngineBootstrap {
|
||||
const logger = this.log.logger.child({ engine: engine.toLowerCase() });
|
||||
if (engine === WAHAEngine.GOWS) {
|
||||
const config = this.gowsConfigService.getBootstrapConfig();
|
||||
return new GowsBootstrap(logger, config);
|
||||
}
|
||||
return new NoopEngineBootstrap();
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
};
|
||||
};
|
||||
}
|
||||
+251
-60
@@ -1,23 +1,51 @@
|
||||
import {
|
||||
Channel,
|
||||
ChannelListResult,
|
||||
ChannelMessage,
|
||||
ChannelSearchByText,
|
||||
ChannelSearchByView,
|
||||
CreateChannelRequest,
|
||||
ListChannelsQuery,
|
||||
PreviewChannelMessages,
|
||||
} from '@waha/structures/channels.dto';
|
||||
import { GetChatsQuery } from '@waha/structures/chats.dto';
|
||||
import {
|
||||
ChatSummary,
|
||||
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 { PinoLogger } from 'nestjs-pino';
|
||||
import * as NodeCache from 'node-cache';
|
||||
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,
|
||||
@@ -31,6 +59,7 @@ import {
|
||||
MessageTextRequest,
|
||||
MessageVideoRequest,
|
||||
MessageVoiceRequest,
|
||||
SendSeenRequest,
|
||||
} from '../../structures/chatting.dto';
|
||||
import { ContactQuery, ContactRequest } from '../../structures/contacts.dto';
|
||||
import {
|
||||
@@ -41,6 +70,7 @@ import {
|
||||
} from '../../structures/enums.dto';
|
||||
import {
|
||||
CreateGroupRequest,
|
||||
GroupsPaginationParams,
|
||||
ParticipantsRequest,
|
||||
SettingsSecurityChangeInfo,
|
||||
} from '../../structures/groups.dto';
|
||||
@@ -62,7 +92,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 +113,6 @@ export function ensureSuffix(phone) {
|
||||
return phone + suffix;
|
||||
}
|
||||
|
||||
export enum WAHAInternalEvent {
|
||||
ENGINE_START = 'engine.start',
|
||||
}
|
||||
|
||||
export interface SessionParams {
|
||||
name: string;
|
||||
printQR: boolean;
|
||||
@@ -100,7 +125,6 @@ export interface SessionParams {
|
||||
}
|
||||
|
||||
export abstract class WhatsappSession {
|
||||
public events: EventEmitter;
|
||||
public engine: WAHAEngine;
|
||||
|
||||
public name: string;
|
||||
@@ -111,9 +135,15 @@ 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>;
|
||||
protected profilePictures: NodeCache = new NodeCache({
|
||||
stdTTL: 24 * 60 * 60, // 1 day
|
||||
});
|
||||
|
||||
public constructor({
|
||||
name,
|
||||
@@ -125,11 +155,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 +220,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() {
|
||||
@@ -156,24 +247,29 @@ export abstract class WhatsappSession {
|
||||
// References:
|
||||
// https://github.com/pedroslopez/whatsapp-web.js/issues/1420
|
||||
// https://github.com/wppconnect-team/wppconnect/issues/1326
|
||||
// https://superuser.com/questions/654565/how-to-run-google-chrome-in-a-single-process
|
||||
// https://www.bannerbear.com/blog/ways-to-speed-up-puppeteer-screenshots/
|
||||
return [
|
||||
'--aggressive-cache-discard',
|
||||
'--disable-accelerated-2d-canvas',
|
||||
'--disable-application-cache',
|
||||
'--disable-background-networking',
|
||||
// '--disable-background-networking', // COMMENTED to test WEBJS stability
|
||||
// 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-gpu',
|
||||
// '--disable-features=site-per-process', // COMMENTED to test WEBJS stability
|
||||
//'--disable-gpu', // COMMENTED to test WEBJS stability
|
||||
'--disable-offer-store-unmasked-wallet-cards',
|
||||
'--disable-offline-load-stale-cache',
|
||||
'--disable-popup-blocking',
|
||||
'--disable-setuid-sandbox',
|
||||
'--disable-site-isolation-trials', // https://superuser.com/questions/654565/how-to-run-google-chrome-in-a-single-process
|
||||
'--disable-site-isolation-trials',
|
||||
'--disable-speech-api',
|
||||
'--disable-sync',
|
||||
'--disable-translate',
|
||||
@@ -182,7 +278,8 @@ export abstract class WhatsappSession {
|
||||
'--hide-scrollbars',
|
||||
'--ignore-certificate-errors',
|
||||
'--ignore-ssl-errors',
|
||||
'--in-process-gpu', // https://superuser.com/questions/654565/how-to-run-google-chrome-in-a-single-process
|
||||
// https://github.com/devlikeapro/waha/issues/725
|
||||
// '--in-process-gpu', // COMMENTED to test WEBJS stability
|
||||
'--metrics-recording-only',
|
||||
'--mute-audio',
|
||||
'--no-default-browser-check',
|
||||
@@ -191,10 +288,11 @@ export abstract class WhatsappSession {
|
||||
'--no-sandbox',
|
||||
'--no-zygote',
|
||||
'--password-store=basic',
|
||||
'--renderer-process-limit=2', // https://superuser.com/questions/654565/how-to-run-google-chrome-in-a-single-process
|
||||
// '--renderer-process-limit=2', // COMMENTED to test WEBJS stability
|
||||
'--safebrowsing-disable-auto-update',
|
||||
'--single-process',
|
||||
// '--single-process',
|
||||
'--use-mock-keychain',
|
||||
'--window-size=1280,720',
|
||||
];
|
||||
}
|
||||
|
||||
@@ -208,24 +306,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
|
||||
@@ -243,24 +331,12 @@ export abstract class WhatsappSession {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public authorizeCode(code: string) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
abstract getScreenshot(): Promise<Buffer>;
|
||||
|
||||
public getSessionMeInfo(): MeInfo | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
public getCaptcha(): Promise<QR> {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public saveCaptcha(code: string) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
/**
|
||||
* Other methods
|
||||
*/
|
||||
@@ -300,16 +376,12 @@ export abstract class WhatsappSession {
|
||||
|
||||
abstract reply(request: MessageReplyRequest);
|
||||
|
||||
abstract sendSeen(chat: ChatRequest);
|
||||
abstract sendSeen(chat: SendSeenRequest);
|
||||
|
||||
abstract startTyping(chat: ChatRequest);
|
||||
|
||||
abstract stopTyping(chat: ChatRequest);
|
||||
|
||||
getMessages(query: GetMessageQuery) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
abstract setReaction(request: MessageReactionRequest);
|
||||
|
||||
setStar(request: MessageStarRequest): Promise<void> {
|
||||
@@ -319,7 +391,13 @@ export abstract class WhatsappSession {
|
||||
/**
|
||||
* Chats methods
|
||||
*/
|
||||
public getChats(query: GetChatsQuery) {
|
||||
public getChats(pagination: PaginationParams) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public getChatsOverview(
|
||||
pagination: PaginationParams,
|
||||
): Promise<ChatSummary[]> {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
@@ -329,12 +407,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 +457,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 +504,7 @@ export abstract class WhatsappSession {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public getContacts() {
|
||||
public getContacts(pagination: PaginationParams) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
@@ -394,8 +512,40 @@ export abstract class WhatsappSession {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public getContactProfilePicture(query: ContactQuery) {
|
||||
throw new NotImplementedByEngineError();
|
||||
/**
|
||||
* Fetch the latest profile picture of the contact (group, newsletter, etc.)
|
||||
* @param id
|
||||
*/
|
||||
abstract fetchContactProfilePicture(id: string): Promise<string | null>;
|
||||
|
||||
public async getContactProfilePicture(
|
||||
id: string,
|
||||
refresh: boolean,
|
||||
): Promise<string | null> {
|
||||
const has: boolean = this.profilePictures.has(id);
|
||||
if (!has || refresh) {
|
||||
await this.refreshProfilePicture(id);
|
||||
}
|
||||
return this.profilePictures.get(id) || null;
|
||||
}
|
||||
|
||||
protected async refreshProfilePicture(id: string) {
|
||||
let fn: Promise<string>;
|
||||
if (isNewsletter(id)) {
|
||||
fn = this.channelsGetChannel(id).then(
|
||||
(channel: Channel) => channel.picture || channel.preview,
|
||||
);
|
||||
} else {
|
||||
fn = this.fetchContactProfilePicture(id);
|
||||
}
|
||||
this.profilePictures.del(id);
|
||||
const url = await fn.catch((err) => {
|
||||
this.logger.warn('Error fetching profile picture');
|
||||
this.logger.warn(err, err.stack);
|
||||
return null;
|
||||
});
|
||||
this.profilePictures.set(id, url);
|
||||
return url;
|
||||
}
|
||||
|
||||
public blockContact(request: ContactRequest) {
|
||||
@@ -413,7 +563,19 @@ export abstract class WhatsappSession {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public getGroups() {
|
||||
public joinGroup(code: string): Promise<string> {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public joinInfoGroup(code: string): Promise<any> {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public getGroups(pagination: PaginationParams) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public refreshGroups(): Promise<boolean> {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
@@ -493,13 +655,32 @@ export abstract class WhatsappSession {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public subscribePresence(id: string): Promise<void> {
|
||||
public subscribePresence(id: string): Promise<any> {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
/**
|
||||
* Channels methods
|
||||
*/
|
||||
public searchChannelsByView(
|
||||
query: ChannelSearchByView,
|
||||
): Promise<ChannelListResult> {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public searchChannelsByText(
|
||||
query: ChannelSearchByText,
|
||||
): Promise<ChannelListResult> {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public async previewChannelMessages(
|
||||
inviteCode: string,
|
||||
query: PreviewChannelMessages,
|
||||
): Promise<ChannelMessage[]> {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public channelsList(query: ListChannelsQuery): Promise<Channel[]> {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
@@ -611,3 +792,13 @@ export function isNewsletter(jid: string) {
|
||||
export function getChannelInviteLink(code: string) {
|
||||
return `https://whatsapp.com/channel/${code}`;
|
||||
}
|
||||
|
||||
export function parseChannelInviteLink(link: string): string {
|
||||
// https://www.whatsapp.com/channel/123 => 123
|
||||
const code = link.split('/').pop();
|
||||
return code;
|
||||
}
|
||||
|
||||
export function getPublicUrlFromDirectPath(directPath: string) {
|
||||
return `https://pps.whatsapp.net${directPath}`;
|
||||
}
|
||||
@@ -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[]);
|
||||
}
|
||||
@@ -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';
|
||||
|
||||
@@ -32,16 +32,14 @@ export class WebsocketGatewayCore
|
||||
OnGatewayDisconnect,
|
||||
BeforeApplicationShutdown
|
||||
{
|
||||
HEARTBEAT_INTERVAL = 10_000;
|
||||
HEARTBEAT_INTERVAL = 60_000;
|
||||
|
||||
@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,86 +54,74 @@ 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), (err) => {
|
||||
if (!err) {
|
||||
return;
|
||||
}
|
||||
this.logger.error(`Error sending data to client: ${err}`);
|
||||
});
|
||||
});
|
||||
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) {
|
||||
this.logger.log('Shutting down websocket server');
|
||||
this.heartbeat?.stop();
|
||||
|
||||
// Allow pending messages to be sent, it can be even 1ms, just to release the event loop
|
||||
await sleep(100);
|
||||
this.server?.clients.forEach((options, client) => {
|
||||
// @ts-ignore
|
||||
this.logger.debug(`Closing client connection - ${client.id}...`);
|
||||
client.close(1001, 'Server is shutting down');
|
||||
});
|
||||
// Do not turn off heartbeat service here,
|
||||
// it's responsible for terminating the connection that is not alive
|
||||
// Close clients and server
|
||||
await this.close(this.server);
|
||||
this.logger.log('Websocket server is down');
|
||||
this.heartbeat?.stop();
|
||||
}
|
||||
|
||||
// Cherry-pick from nestjs new version
|
||||
// https://github.com/nestjs/nest/pull/13531/files
|
||||
private async close(server: any) {
|
||||
const closeEventSignal = new Promise((resolve, reject) =>
|
||||
server.close((err) => (err ? reject(err) : resolve(undefined))),
|
||||
);
|
||||
for (const ws of server.clients) {
|
||||
ws.terminate();
|
||||
}
|
||||
await closeEventSignal;
|
||||
}
|
||||
|
||||
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));
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -10,8 +10,11 @@ import {
|
||||
ServerDebugController,
|
||||
} from '@waha/api/server.controller';
|
||||
import { WebsocketGatewayCore } from '@waha/core/api/websocket.gateway.core';
|
||||
import { GowsEngineConfigService } from '@waha/core/config/GowsEngineConfigService';
|
||||
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 { ChannelsInfoServiceCore } from '@waha/core/services/ChannelsInfoServiceCore';
|
||||
import { BufferJsonReplacerInterceptor } from '@waha/nestjs/BufferJsonReplacerInterceptor';
|
||||
import {
|
||||
getPinoHttpUseLevel,
|
||||
@@ -105,7 +108,9 @@ export const IMPORTS_CORE = [
|
||||
const IMPORTS_MEDIA = [
|
||||
ConfigModule.forRoot({
|
||||
validationSchema: Joi.object({
|
||||
WAHA_MEDIA_STORAGE: Joi.string().valid('LOCAL', 'S3').default('LOCAL'),
|
||||
WAHA_MEDIA_STORAGE: Joi.string()
|
||||
.valid('LOCAL', 'S3', 'POSTGRESQL')
|
||||
.default('LOCAL'),
|
||||
}),
|
||||
}),
|
||||
MediaLocalStorageModule,
|
||||
@@ -144,8 +149,11 @@ const PROVIDERS = [
|
||||
provide: APP_INTERCEPTOR,
|
||||
useClass: BufferJsonReplacerInterceptor,
|
||||
},
|
||||
ChannelsInfoServiceCore,
|
||||
DashboardConfigServiceCore,
|
||||
SwaggerConfigServiceCore,
|
||||
WebJSEngineConfigService,
|
||||
GowsEngineConfigService,
|
||||
WhatsappConfigService,
|
||||
EngineConfigService,
|
||||
WebsocketGatewayCore,
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { BootstrapConfig } from '@waha/core/engines/gows/GowsBootstrap';
|
||||
import { GowsConfig } from '@waha/core/engines/gows/session.gows.core';
|
||||
|
||||
@Injectable()
|
||||
export class GowsEngineConfigService {
|
||||
constructor(protected configService: ConfigService) {}
|
||||
|
||||
getBootstrapConfig(): BootstrapConfig {
|
||||
return {
|
||||
path: this.configService.get('WAHA_GOWS_PATH'),
|
||||
socket: this.getSocket(),
|
||||
};
|
||||
}
|
||||
|
||||
getSocket() {
|
||||
return this.configService.get('WAHA_GOWS_SOCKET', '/tmp/gows.sock');
|
||||
}
|
||||
|
||||
getConfig(): GowsConfig {
|
||||
return {
|
||||
connection: 'unix:' + this.getSocket(),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export enum Jid {
|
||||
BROADCAST = 'status@broadcast',
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import { EventEmitter } from 'events';
|
||||
import { Observable, Subscription } from 'rxjs';
|
||||
|
||||
interface EventValue<T> {
|
||||
event: string;
|
||||
data: T;
|
||||
}
|
||||
|
||||
export class EventsFromObservable<Events extends string> extends EventEmitter {
|
||||
private subscription: Subscription;
|
||||
|
||||
constructor(private observable: Observable<EventValue<any>>) {
|
||||
super();
|
||||
}
|
||||
|
||||
on(event: Events, listener: (...args: any[]) => void): this {
|
||||
return super.on(event as string, listener);
|
||||
}
|
||||
|
||||
start() {
|
||||
this.subscription = this.observable.subscribe({
|
||||
next: (data) => this.emit(data.event, data.data),
|
||||
complete: () => this.removeAllListeners(),
|
||||
error: (err) => null,
|
||||
});
|
||||
}
|
||||
|
||||
stop() {
|
||||
this.subscription.unsubscribe();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
import { EngineBootstrap } from '@waha/core/abc/EngineBootstrap';
|
||||
import { GowsSubprocess } from '@waha/core/engines/gows/GowsSubprocess';
|
||||
import { promises as fs } from 'fs';
|
||||
import { Logger } from 'pino';
|
||||
|
||||
export async function isUnixSocket(socketPath: string): Promise<boolean> {
|
||||
try {
|
||||
// Get the file stats
|
||||
const stats = await fs.lstat(socketPath);
|
||||
// Check if the path is a socket
|
||||
return stats.isSocket();
|
||||
} catch (error) {
|
||||
// Handle errors (e.g., file does not exist)
|
||||
if (error.code === 'ENOENT') {
|
||||
return false; // Path does not exist
|
||||
}
|
||||
// Re-throw other errors for debugging purposes
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export interface BootstrapConfig {
|
||||
path: string;
|
||||
socket: string;
|
||||
}
|
||||
|
||||
export class GowsBootstrap implements EngineBootstrap {
|
||||
private gows: GowsSubprocess;
|
||||
|
||||
constructor(
|
||||
private logger: Logger,
|
||||
private config: BootstrapConfig,
|
||||
) {}
|
||||
|
||||
async bootstrap(): Promise<void> {
|
||||
if (!this.config.path) {
|
||||
this.logger.warn('GOWS path is not set, skipping GOWS initialization.');
|
||||
this.logger.warn('Make sure to run GOWS manually.');
|
||||
this.checkSocket(this.config.socket);
|
||||
return;
|
||||
}
|
||||
|
||||
this.gows = new GowsSubprocess(
|
||||
this.logger,
|
||||
this.config.path,
|
||||
this.config.socket,
|
||||
);
|
||||
this.gows.start(() => {
|
||||
this.logger.info(`GOWS stopped, exiting...`);
|
||||
process.kill(process.pid, 'SIGTERM');
|
||||
});
|
||||
await this.gows.waitWhenReady(10_000);
|
||||
await this.checkSocket(this.config.socket);
|
||||
return;
|
||||
}
|
||||
|
||||
async shutdown(): Promise<void> {
|
||||
if (this.gows) {
|
||||
await this.gows.stop();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the provided path exists and is a socket
|
||||
* @param path
|
||||
*/
|
||||
async checkSocket(path: string) {
|
||||
if (!(await isUnixSocket(path))) {
|
||||
throw new Error(`Invalid socket path: ${path}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
import * as grpc from '@grpc/grpc-js';
|
||||
import { messages } from '@waha/core/engines/gows/grpc/gows';
|
||||
import { EnginePayload } from '@waha/structures/webhooks.dto';
|
||||
import { sleep } from '@waha/utils/promiseTimeout';
|
||||
import { Logger } from 'pino';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
/**
|
||||
* Observable that listens to a gRPC stream and emits EnginePayload objects.
|
||||
* Pass a factory function that returns a client and a stream.
|
||||
*/
|
||||
export class GowsEventStreamObservable extends Observable<EnginePayload> {
|
||||
_client: grpc.Client;
|
||||
CLIENT_CLOSE_TIMEOUT = 1_000;
|
||||
|
||||
constructor(
|
||||
private logger: Logger,
|
||||
factory: () => {
|
||||
client: grpc.Client;
|
||||
stream: grpc.ClientReadableStream<messages.EventJson>;
|
||||
},
|
||||
) {
|
||||
super((subscriber) => {
|
||||
this.logger.debug('Creating grpc client and stream...');
|
||||
const { client, stream } = factory();
|
||||
this._client = client;
|
||||
|
||||
stream.on('data', (raw) => {
|
||||
const obj = raw.toObject();
|
||||
obj.data = JSON.parse(obj.data);
|
||||
subscriber?.next(obj);
|
||||
});
|
||||
|
||||
stream.on('end', (...args) => {
|
||||
this.logger.debug('Stream ended', args);
|
||||
subscriber?.complete();
|
||||
subscriber = null;
|
||||
});
|
||||
|
||||
stream.on('error', async (err: any) => {
|
||||
const CLIENT_CANCELLED_CODE = grpc.status.CANCELLED;
|
||||
if (err.code === CLIENT_CANCELLED_CODE) {
|
||||
this.logger.debug('Stream cancelled by client');
|
||||
return;
|
||||
}
|
||||
this.logger.error(err, 'Stream error');
|
||||
// Give some time to node event loop to process the error
|
||||
await sleep(100);
|
||||
subscriber?.error(err);
|
||||
subscriber = null;
|
||||
});
|
||||
|
||||
return async () => {
|
||||
this.logger.debug('Closing stream client...');
|
||||
client.close();
|
||||
await sleep(this.CLIENT_CLOSE_TIMEOUT);
|
||||
this.logger.debug('Stream client closed');
|
||||
|
||||
this.logger.debug('Cancelling stream...');
|
||||
stream.cancel();
|
||||
this.logger.debug('Stream cancelled');
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
get client(): Omit<grpc.Client, 'close'> {
|
||||
return this._client;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
import { sleep, waitUntil } from '@waha/utils/promiseTimeout';
|
||||
import { spawn } from 'child_process';
|
||||
import { Logger } from 'pino';
|
||||
|
||||
export class GowsSubprocess {
|
||||
private checkIntervalMs: number = 100;
|
||||
private readyDelayMs: number = 1_000;
|
||||
private readyText = 'gRPC server started!';
|
||||
|
||||
private child: any;
|
||||
private ready: boolean = false;
|
||||
|
||||
constructor(
|
||||
private logger: Logger,
|
||||
readonly path: string,
|
||||
readonly socket: string,
|
||||
) {}
|
||||
|
||||
start(onExit: (code: number) => void) {
|
||||
this.logger.info('Starting GOWS subprocess...');
|
||||
this.logger.debug(`GOWS path '${this.path}', socket: '${this.socket}'...`);
|
||||
|
||||
this.child = spawn(this.path, ['--socket', this.socket], {
|
||||
detached: true,
|
||||
});
|
||||
this.logger.debug(`GOWS started with PID: ${this.child.pid}`);
|
||||
this.child.on('close', async (code, singal) => {
|
||||
const msg = code
|
||||
? `GOWS subprocess closed with code ${code}`
|
||||
: `GOWS subprocess closed by signal ${singal}`;
|
||||
this.logger.debug(msg);
|
||||
onExit(code);
|
||||
});
|
||||
this.child.on('error', (err) => {
|
||||
this.logger.error(`GOWS subprocess error: ${err}`);
|
||||
});
|
||||
|
||||
this.child.stderr.setEncoding('utf8');
|
||||
this.child.stderr.on('data', (data) => {
|
||||
this.logger.error(data);
|
||||
});
|
||||
|
||||
this.child.stdout.setEncoding('utf8');
|
||||
this.child.stdout.on('data', async (data) => {
|
||||
// remove empty line at the end, split by \n
|
||||
const lines = data.trim().split('\n');
|
||||
lines.forEach((line) => this.log(line));
|
||||
});
|
||||
this.listenReady();
|
||||
}
|
||||
|
||||
listenReady() {
|
||||
this.child.stdout.on('data', async (data) => {
|
||||
if (this.ready) {
|
||||
return;
|
||||
}
|
||||
if (!data.includes(this.readyText)) {
|
||||
return;
|
||||
}
|
||||
await sleep(this.readyDelayMs);
|
||||
this.ready = true;
|
||||
this.logger.info('GOWS is ready');
|
||||
});
|
||||
}
|
||||
|
||||
async waitWhenReady(timeout: number) {
|
||||
const started = await waitUntil(
|
||||
async () => this.ready,
|
||||
this.checkIntervalMs,
|
||||
timeout,
|
||||
);
|
||||
if (!started) {
|
||||
const msg = 'GOWS did not start after 10 seconds';
|
||||
this.logger.error(msg);
|
||||
throw new Error(msg);
|
||||
}
|
||||
}
|
||||
|
||||
async stop() {
|
||||
this.logger.info('Stopping GOWS subprocess...');
|
||||
this.child?.kill('SIGTERM');
|
||||
this.logger.info('GOWS subprocess stopped');
|
||||
}
|
||||
|
||||
private log(msg) {
|
||||
if (msg.startsWith('ERROR | ')) {
|
||||
this.logger.error(msg.slice(8));
|
||||
} else if (msg.startsWith('WARN | ')) {
|
||||
this.logger.warn(msg.slice(7));
|
||||
} else if (msg.startsWith('INFO | ')) {
|
||||
this.logger.info(msg.slice(7));
|
||||
} else if (msg.startsWith('DEBUG | ')) {
|
||||
this.logger.debug(msg.slice(8));
|
||||
} else if (msg.startsWith('TRACE | ')) {
|
||||
this.logger.trace(msg.slice(8));
|
||||
} else {
|
||||
this.logger.info(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,748 @@
|
||||
// GENERATED CODE -- DO NOT EDIT!
|
||||
|
||||
'use strict';
|
||||
var grpc = require('@grpc/grpc-js');
|
||||
var gows_pb = require('./gows_pb.js');
|
||||
|
||||
function serialize_messages_ChatPresenceRequest(arg) {
|
||||
if (!(arg instanceof gows_pb.ChatPresenceRequest)) {
|
||||
throw new Error('Expected argument of type messages.ChatPresenceRequest');
|
||||
}
|
||||
return Buffer.from(arg.serializeBinary());
|
||||
}
|
||||
|
||||
function deserialize_messages_ChatPresenceRequest(buffer_arg) {
|
||||
return gows_pb.ChatPresenceRequest.deserializeBinary(new Uint8Array(buffer_arg));
|
||||
}
|
||||
|
||||
function serialize_messages_CheckPhonesRequest(arg) {
|
||||
if (!(arg instanceof gows_pb.CheckPhonesRequest)) {
|
||||
throw new Error('Expected argument of type messages.CheckPhonesRequest');
|
||||
}
|
||||
return Buffer.from(arg.serializeBinary());
|
||||
}
|
||||
|
||||
function deserialize_messages_CheckPhonesRequest(buffer_arg) {
|
||||
return gows_pb.CheckPhonesRequest.deserializeBinary(new Uint8Array(buffer_arg));
|
||||
}
|
||||
|
||||
function serialize_messages_CheckPhonesResponse(arg) {
|
||||
if (!(arg instanceof gows_pb.CheckPhonesResponse)) {
|
||||
throw new Error('Expected argument of type messages.CheckPhonesResponse');
|
||||
}
|
||||
return Buffer.from(arg.serializeBinary());
|
||||
}
|
||||
|
||||
function deserialize_messages_CheckPhonesResponse(buffer_arg) {
|
||||
return gows_pb.CheckPhonesResponse.deserializeBinary(new Uint8Array(buffer_arg));
|
||||
}
|
||||
|
||||
function serialize_messages_CreateNewsletterRequest(arg) {
|
||||
if (!(arg instanceof gows_pb.CreateNewsletterRequest)) {
|
||||
throw new Error('Expected argument of type messages.CreateNewsletterRequest');
|
||||
}
|
||||
return Buffer.from(arg.serializeBinary());
|
||||
}
|
||||
|
||||
function deserialize_messages_CreateNewsletterRequest(buffer_arg) {
|
||||
return gows_pb.CreateNewsletterRequest.deserializeBinary(new Uint8Array(buffer_arg));
|
||||
}
|
||||
|
||||
function serialize_messages_DownloadMediaRequest(arg) {
|
||||
if (!(arg instanceof gows_pb.DownloadMediaRequest)) {
|
||||
throw new Error('Expected argument of type messages.DownloadMediaRequest');
|
||||
}
|
||||
return Buffer.from(arg.serializeBinary());
|
||||
}
|
||||
|
||||
function deserialize_messages_DownloadMediaRequest(buffer_arg) {
|
||||
return gows_pb.DownloadMediaRequest.deserializeBinary(new Uint8Array(buffer_arg));
|
||||
}
|
||||
|
||||
function serialize_messages_DownloadMediaResponse(arg) {
|
||||
if (!(arg instanceof gows_pb.DownloadMediaResponse)) {
|
||||
throw new Error('Expected argument of type messages.DownloadMediaResponse');
|
||||
}
|
||||
return Buffer.from(arg.serializeBinary());
|
||||
}
|
||||
|
||||
function deserialize_messages_DownloadMediaResponse(buffer_arg) {
|
||||
return gows_pb.DownloadMediaResponse.deserializeBinary(new Uint8Array(buffer_arg));
|
||||
}
|
||||
|
||||
function serialize_messages_Empty(arg) {
|
||||
if (!(arg instanceof gows_pb.Empty)) {
|
||||
throw new Error('Expected argument of type messages.Empty');
|
||||
}
|
||||
return Buffer.from(arg.serializeBinary());
|
||||
}
|
||||
|
||||
function deserialize_messages_Empty(buffer_arg) {
|
||||
return gows_pb.Empty.deserializeBinary(new Uint8Array(buffer_arg));
|
||||
}
|
||||
|
||||
function serialize_messages_EntityByIdRequest(arg) {
|
||||
if (!(arg instanceof gows_pb.EntityByIdRequest)) {
|
||||
throw new Error('Expected argument of type messages.EntityByIdRequest');
|
||||
}
|
||||
return Buffer.from(arg.serializeBinary());
|
||||
}
|
||||
|
||||
function deserialize_messages_EntityByIdRequest(buffer_arg) {
|
||||
return gows_pb.EntityByIdRequest.deserializeBinary(new Uint8Array(buffer_arg));
|
||||
}
|
||||
|
||||
function serialize_messages_EventJson(arg) {
|
||||
if (!(arg instanceof gows_pb.EventJson)) {
|
||||
throw new Error('Expected argument of type messages.EventJson');
|
||||
}
|
||||
return Buffer.from(arg.serializeBinary());
|
||||
}
|
||||
|
||||
function deserialize_messages_EventJson(buffer_arg) {
|
||||
return gows_pb.EventJson.deserializeBinary(new Uint8Array(buffer_arg));
|
||||
}
|
||||
|
||||
function serialize_messages_GetChatsRequest(arg) {
|
||||
if (!(arg instanceof gows_pb.GetChatsRequest)) {
|
||||
throw new Error('Expected argument of type messages.GetChatsRequest');
|
||||
}
|
||||
return Buffer.from(arg.serializeBinary());
|
||||
}
|
||||
|
||||
function deserialize_messages_GetChatsRequest(buffer_arg) {
|
||||
return gows_pb.GetChatsRequest.deserializeBinary(new Uint8Array(buffer_arg));
|
||||
}
|
||||
|
||||
function serialize_messages_GetContactsRequest(arg) {
|
||||
if (!(arg instanceof gows_pb.GetContactsRequest)) {
|
||||
throw new Error('Expected argument of type messages.GetContactsRequest');
|
||||
}
|
||||
return Buffer.from(arg.serializeBinary());
|
||||
}
|
||||
|
||||
function deserialize_messages_GetContactsRequest(buffer_arg) {
|
||||
return gows_pb.GetContactsRequest.deserializeBinary(new Uint8Array(buffer_arg));
|
||||
}
|
||||
|
||||
function serialize_messages_GetMessagesRequest(arg) {
|
||||
if (!(arg instanceof gows_pb.GetMessagesRequest)) {
|
||||
throw new Error('Expected argument of type messages.GetMessagesRequest');
|
||||
}
|
||||
return Buffer.from(arg.serializeBinary());
|
||||
}
|
||||
|
||||
function deserialize_messages_GetMessagesRequest(buffer_arg) {
|
||||
return gows_pb.GetMessagesRequest.deserializeBinary(new Uint8Array(buffer_arg));
|
||||
}
|
||||
|
||||
function serialize_messages_GetNewsletterMessagesByInviteRequest(arg) {
|
||||
if (!(arg instanceof gows_pb.GetNewsletterMessagesByInviteRequest)) {
|
||||
throw new Error('Expected argument of type messages.GetNewsletterMessagesByInviteRequest');
|
||||
}
|
||||
return Buffer.from(arg.serializeBinary());
|
||||
}
|
||||
|
||||
function deserialize_messages_GetNewsletterMessagesByInviteRequest(buffer_arg) {
|
||||
return gows_pb.GetNewsletterMessagesByInviteRequest.deserializeBinary(new Uint8Array(buffer_arg));
|
||||
}
|
||||
|
||||
function serialize_messages_Json(arg) {
|
||||
if (!(arg instanceof gows_pb.Json)) {
|
||||
throw new Error('Expected argument of type messages.Json');
|
||||
}
|
||||
return Buffer.from(arg.serializeBinary());
|
||||
}
|
||||
|
||||
function deserialize_messages_Json(buffer_arg) {
|
||||
return gows_pb.Json.deserializeBinary(new Uint8Array(buffer_arg));
|
||||
}
|
||||
|
||||
function serialize_messages_JsonList(arg) {
|
||||
if (!(arg instanceof gows_pb.JsonList)) {
|
||||
throw new Error('Expected argument of type messages.JsonList');
|
||||
}
|
||||
return Buffer.from(arg.serializeBinary());
|
||||
}
|
||||
|
||||
function deserialize_messages_JsonList(buffer_arg) {
|
||||
return gows_pb.JsonList.deserializeBinary(new Uint8Array(buffer_arg));
|
||||
}
|
||||
|
||||
function serialize_messages_MarkReadRequest(arg) {
|
||||
if (!(arg instanceof gows_pb.MarkReadRequest)) {
|
||||
throw new Error('Expected argument of type messages.MarkReadRequest');
|
||||
}
|
||||
return Buffer.from(arg.serializeBinary());
|
||||
}
|
||||
|
||||
function deserialize_messages_MarkReadRequest(buffer_arg) {
|
||||
return gows_pb.MarkReadRequest.deserializeBinary(new Uint8Array(buffer_arg));
|
||||
}
|
||||
|
||||
function serialize_messages_MessageReaction(arg) {
|
||||
if (!(arg instanceof gows_pb.MessageReaction)) {
|
||||
throw new Error('Expected argument of type messages.MessageReaction');
|
||||
}
|
||||
return Buffer.from(arg.serializeBinary());
|
||||
}
|
||||
|
||||
function deserialize_messages_MessageReaction(buffer_arg) {
|
||||
return gows_pb.MessageReaction.deserializeBinary(new Uint8Array(buffer_arg));
|
||||
}
|
||||
|
||||
function serialize_messages_MessageRequest(arg) {
|
||||
if (!(arg instanceof gows_pb.MessageRequest)) {
|
||||
throw new Error('Expected argument of type messages.MessageRequest');
|
||||
}
|
||||
return Buffer.from(arg.serializeBinary());
|
||||
}
|
||||
|
||||
function deserialize_messages_MessageRequest(buffer_arg) {
|
||||
return gows_pb.MessageRequest.deserializeBinary(new Uint8Array(buffer_arg));
|
||||
}
|
||||
|
||||
function serialize_messages_MessageResponse(arg) {
|
||||
if (!(arg instanceof gows_pb.MessageResponse)) {
|
||||
throw new Error('Expected argument of type messages.MessageResponse');
|
||||
}
|
||||
return Buffer.from(arg.serializeBinary());
|
||||
}
|
||||
|
||||
function deserialize_messages_MessageResponse(buffer_arg) {
|
||||
return gows_pb.MessageResponse.deserializeBinary(new Uint8Array(buffer_arg));
|
||||
}
|
||||
|
||||
function serialize_messages_Newsletter(arg) {
|
||||
if (!(arg instanceof gows_pb.Newsletter)) {
|
||||
throw new Error('Expected argument of type messages.Newsletter');
|
||||
}
|
||||
return Buffer.from(arg.serializeBinary());
|
||||
}
|
||||
|
||||
function deserialize_messages_Newsletter(buffer_arg) {
|
||||
return gows_pb.Newsletter.deserializeBinary(new Uint8Array(buffer_arg));
|
||||
}
|
||||
|
||||
function serialize_messages_NewsletterInfoRequest(arg) {
|
||||
if (!(arg instanceof gows_pb.NewsletterInfoRequest)) {
|
||||
throw new Error('Expected argument of type messages.NewsletterInfoRequest');
|
||||
}
|
||||
return Buffer.from(arg.serializeBinary());
|
||||
}
|
||||
|
||||
function deserialize_messages_NewsletterInfoRequest(buffer_arg) {
|
||||
return gows_pb.NewsletterInfoRequest.deserializeBinary(new Uint8Array(buffer_arg));
|
||||
}
|
||||
|
||||
function serialize_messages_NewsletterList(arg) {
|
||||
if (!(arg instanceof gows_pb.NewsletterList)) {
|
||||
throw new Error('Expected argument of type messages.NewsletterList');
|
||||
}
|
||||
return Buffer.from(arg.serializeBinary());
|
||||
}
|
||||
|
||||
function deserialize_messages_NewsletterList(buffer_arg) {
|
||||
return gows_pb.NewsletterList.deserializeBinary(new Uint8Array(buffer_arg));
|
||||
}
|
||||
|
||||
function serialize_messages_NewsletterListRequest(arg) {
|
||||
if (!(arg instanceof gows_pb.NewsletterListRequest)) {
|
||||
throw new Error('Expected argument of type messages.NewsletterListRequest');
|
||||
}
|
||||
return Buffer.from(arg.serializeBinary());
|
||||
}
|
||||
|
||||
function deserialize_messages_NewsletterListRequest(buffer_arg) {
|
||||
return gows_pb.NewsletterListRequest.deserializeBinary(new Uint8Array(buffer_arg));
|
||||
}
|
||||
|
||||
function serialize_messages_NewsletterSearchPageResult(arg) {
|
||||
if (!(arg instanceof gows_pb.NewsletterSearchPageResult)) {
|
||||
throw new Error('Expected argument of type messages.NewsletterSearchPageResult');
|
||||
}
|
||||
return Buffer.from(arg.serializeBinary());
|
||||
}
|
||||
|
||||
function deserialize_messages_NewsletterSearchPageResult(buffer_arg) {
|
||||
return gows_pb.NewsletterSearchPageResult.deserializeBinary(new Uint8Array(buffer_arg));
|
||||
}
|
||||
|
||||
function serialize_messages_NewsletterToggleFollowRequest(arg) {
|
||||
if (!(arg instanceof gows_pb.NewsletterToggleFollowRequest)) {
|
||||
throw new Error('Expected argument of type messages.NewsletterToggleFollowRequest');
|
||||
}
|
||||
return Buffer.from(arg.serializeBinary());
|
||||
}
|
||||
|
||||
function deserialize_messages_NewsletterToggleFollowRequest(buffer_arg) {
|
||||
return gows_pb.NewsletterToggleFollowRequest.deserializeBinary(new Uint8Array(buffer_arg));
|
||||
}
|
||||
|
||||
function serialize_messages_NewsletterToggleMuteRequest(arg) {
|
||||
if (!(arg instanceof gows_pb.NewsletterToggleMuteRequest)) {
|
||||
throw new Error('Expected argument of type messages.NewsletterToggleMuteRequest');
|
||||
}
|
||||
return Buffer.from(arg.serializeBinary());
|
||||
}
|
||||
|
||||
function deserialize_messages_NewsletterToggleMuteRequest(buffer_arg) {
|
||||
return gows_pb.NewsletterToggleMuteRequest.deserializeBinary(new Uint8Array(buffer_arg));
|
||||
}
|
||||
|
||||
function serialize_messages_PairCodeRequest(arg) {
|
||||
if (!(arg instanceof gows_pb.PairCodeRequest)) {
|
||||
throw new Error('Expected argument of type messages.PairCodeRequest');
|
||||
}
|
||||
return Buffer.from(arg.serializeBinary());
|
||||
}
|
||||
|
||||
function deserialize_messages_PairCodeRequest(buffer_arg) {
|
||||
return gows_pb.PairCodeRequest.deserializeBinary(new Uint8Array(buffer_arg));
|
||||
}
|
||||
|
||||
function serialize_messages_PairCodeResponse(arg) {
|
||||
if (!(arg instanceof gows_pb.PairCodeResponse)) {
|
||||
throw new Error('Expected argument of type messages.PairCodeResponse');
|
||||
}
|
||||
return Buffer.from(arg.serializeBinary());
|
||||
}
|
||||
|
||||
function deserialize_messages_PairCodeResponse(buffer_arg) {
|
||||
return gows_pb.PairCodeResponse.deserializeBinary(new Uint8Array(buffer_arg));
|
||||
}
|
||||
|
||||
function serialize_messages_PresenceRequest(arg) {
|
||||
if (!(arg instanceof gows_pb.PresenceRequest)) {
|
||||
throw new Error('Expected argument of type messages.PresenceRequest');
|
||||
}
|
||||
return Buffer.from(arg.serializeBinary());
|
||||
}
|
||||
|
||||
function deserialize_messages_PresenceRequest(buffer_arg) {
|
||||
return gows_pb.PresenceRequest.deserializeBinary(new Uint8Array(buffer_arg));
|
||||
}
|
||||
|
||||
function serialize_messages_ProfilePictureRequest(arg) {
|
||||
if (!(arg instanceof gows_pb.ProfilePictureRequest)) {
|
||||
throw new Error('Expected argument of type messages.ProfilePictureRequest');
|
||||
}
|
||||
return Buffer.from(arg.serializeBinary());
|
||||
}
|
||||
|
||||
function deserialize_messages_ProfilePictureRequest(buffer_arg) {
|
||||
return gows_pb.ProfilePictureRequest.deserializeBinary(new Uint8Array(buffer_arg));
|
||||
}
|
||||
|
||||
function serialize_messages_ProfilePictureResponse(arg) {
|
||||
if (!(arg instanceof gows_pb.ProfilePictureResponse)) {
|
||||
throw new Error('Expected argument of type messages.ProfilePictureResponse');
|
||||
}
|
||||
return Buffer.from(arg.serializeBinary());
|
||||
}
|
||||
|
||||
function deserialize_messages_ProfilePictureResponse(buffer_arg) {
|
||||
return gows_pb.ProfilePictureResponse.deserializeBinary(new Uint8Array(buffer_arg));
|
||||
}
|
||||
|
||||
function serialize_messages_SearchNewslettersByTextRequest(arg) {
|
||||
if (!(arg instanceof gows_pb.SearchNewslettersByTextRequest)) {
|
||||
throw new Error('Expected argument of type messages.SearchNewslettersByTextRequest');
|
||||
}
|
||||
return Buffer.from(arg.serializeBinary());
|
||||
}
|
||||
|
||||
function deserialize_messages_SearchNewslettersByTextRequest(buffer_arg) {
|
||||
return gows_pb.SearchNewslettersByTextRequest.deserializeBinary(new Uint8Array(buffer_arg));
|
||||
}
|
||||
|
||||
function serialize_messages_SearchNewslettersByViewRequest(arg) {
|
||||
if (!(arg instanceof gows_pb.SearchNewslettersByViewRequest)) {
|
||||
throw new Error('Expected argument of type messages.SearchNewslettersByViewRequest');
|
||||
}
|
||||
return Buffer.from(arg.serializeBinary());
|
||||
}
|
||||
|
||||
function deserialize_messages_SearchNewslettersByViewRequest(buffer_arg) {
|
||||
return gows_pb.SearchNewslettersByViewRequest.deserializeBinary(new Uint8Array(buffer_arg));
|
||||
}
|
||||
|
||||
function serialize_messages_Session(arg) {
|
||||
if (!(arg instanceof gows_pb.Session)) {
|
||||
throw new Error('Expected argument of type messages.Session');
|
||||
}
|
||||
return Buffer.from(arg.serializeBinary());
|
||||
}
|
||||
|
||||
function deserialize_messages_Session(buffer_arg) {
|
||||
return gows_pb.Session.deserializeBinary(new Uint8Array(buffer_arg));
|
||||
}
|
||||
|
||||
function serialize_messages_SessionStateResponse(arg) {
|
||||
if (!(arg instanceof gows_pb.SessionStateResponse)) {
|
||||
throw new Error('Expected argument of type messages.SessionStateResponse');
|
||||
}
|
||||
return Buffer.from(arg.serializeBinary());
|
||||
}
|
||||
|
||||
function deserialize_messages_SessionStateResponse(buffer_arg) {
|
||||
return gows_pb.SessionStateResponse.deserializeBinary(new Uint8Array(buffer_arg));
|
||||
}
|
||||
|
||||
function serialize_messages_StartSessionRequest(arg) {
|
||||
if (!(arg instanceof gows_pb.StartSessionRequest)) {
|
||||
throw new Error('Expected argument of type messages.StartSessionRequest');
|
||||
}
|
||||
return Buffer.from(arg.serializeBinary());
|
||||
}
|
||||
|
||||
function deserialize_messages_StartSessionRequest(buffer_arg) {
|
||||
return gows_pb.StartSessionRequest.deserializeBinary(new Uint8Array(buffer_arg));
|
||||
}
|
||||
|
||||
function serialize_messages_SubscribePresenceRequest(arg) {
|
||||
if (!(arg instanceof gows_pb.SubscribePresenceRequest)) {
|
||||
throw new Error('Expected argument of type messages.SubscribePresenceRequest');
|
||||
}
|
||||
return Buffer.from(arg.serializeBinary());
|
||||
}
|
||||
|
||||
function deserialize_messages_SubscribePresenceRequest(buffer_arg) {
|
||||
return gows_pb.SubscribePresenceRequest.deserializeBinary(new Uint8Array(buffer_arg));
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Events
|
||||
//
|
||||
var EventStreamService = exports.EventStreamService = {
|
||||
streamEvents: {
|
||||
path: '/messages.EventStream/StreamEvents',
|
||||
requestStream: false,
|
||||
responseStream: true,
|
||||
requestType: gows_pb.Session,
|
||||
responseType: gows_pb.EventJson,
|
||||
requestSerialize: serialize_messages_Session,
|
||||
requestDeserialize: deserialize_messages_Session,
|
||||
responseSerialize: serialize_messages_EventJson,
|
||||
responseDeserialize: deserialize_messages_EventJson,
|
||||
},
|
||||
};
|
||||
|
||||
exports.EventStreamClient = grpc.makeGenericClientConstructor(EventStreamService);
|
||||
var MessageServiceService = exports.MessageServiceService = {
|
||||
//
|
||||
// Session management
|
||||
//
|
||||
startSession: {
|
||||
path: '/messages.MessageService/StartSession',
|
||||
requestStream: false,
|
||||
responseStream: false,
|
||||
requestType: gows_pb.StartSessionRequest,
|
||||
responseType: gows_pb.Empty,
|
||||
requestSerialize: serialize_messages_StartSessionRequest,
|
||||
requestDeserialize: deserialize_messages_StartSessionRequest,
|
||||
responseSerialize: serialize_messages_Empty,
|
||||
responseDeserialize: deserialize_messages_Empty,
|
||||
},
|
||||
stopSession: {
|
||||
path: '/messages.MessageService/StopSession',
|
||||
requestStream: false,
|
||||
responseStream: false,
|
||||
requestType: gows_pb.Session,
|
||||
responseType: gows_pb.Empty,
|
||||
requestSerialize: serialize_messages_Session,
|
||||
requestDeserialize: deserialize_messages_Session,
|
||||
responseSerialize: serialize_messages_Empty,
|
||||
responseDeserialize: deserialize_messages_Empty,
|
||||
},
|
||||
getSessionState: {
|
||||
path: '/messages.MessageService/GetSessionState',
|
||||
requestStream: false,
|
||||
responseStream: false,
|
||||
requestType: gows_pb.Session,
|
||||
responseType: gows_pb.SessionStateResponse,
|
||||
requestSerialize: serialize_messages_Session,
|
||||
requestDeserialize: deserialize_messages_Session,
|
||||
responseSerialize: serialize_messages_SessionStateResponse,
|
||||
responseDeserialize: deserialize_messages_SessionStateResponse,
|
||||
},
|
||||
requestCode: {
|
||||
path: '/messages.MessageService/RequestCode',
|
||||
requestStream: false,
|
||||
responseStream: false,
|
||||
requestType: gows_pb.PairCodeRequest,
|
||||
responseType: gows_pb.PairCodeResponse,
|
||||
requestSerialize: serialize_messages_PairCodeRequest,
|
||||
requestDeserialize: deserialize_messages_PairCodeRequest,
|
||||
responseSerialize: serialize_messages_PairCodeResponse,
|
||||
responseDeserialize: deserialize_messages_PairCodeResponse,
|
||||
},
|
||||
logout: {
|
||||
path: '/messages.MessageService/Logout',
|
||||
requestStream: false,
|
||||
responseStream: false,
|
||||
requestType: gows_pb.Session,
|
||||
responseType: gows_pb.Empty,
|
||||
requestSerialize: serialize_messages_Session,
|
||||
requestDeserialize: deserialize_messages_Session,
|
||||
responseSerialize: serialize_messages_Empty,
|
||||
responseDeserialize: deserialize_messages_Empty,
|
||||
},
|
||||
//
|
||||
// Actions
|
||||
//
|
||||
sendMessage: {
|
||||
path: '/messages.MessageService/SendMessage',
|
||||
requestStream: false,
|
||||
responseStream: false,
|
||||
requestType: gows_pb.MessageRequest,
|
||||
responseType: gows_pb.MessageResponse,
|
||||
requestSerialize: serialize_messages_MessageRequest,
|
||||
requestDeserialize: deserialize_messages_MessageRequest,
|
||||
responseSerialize: serialize_messages_MessageResponse,
|
||||
responseDeserialize: deserialize_messages_MessageResponse,
|
||||
},
|
||||
sendReaction: {
|
||||
path: '/messages.MessageService/SendReaction',
|
||||
requestStream: false,
|
||||
responseStream: false,
|
||||
requestType: gows_pb.MessageReaction,
|
||||
responseType: gows_pb.MessageResponse,
|
||||
requestSerialize: serialize_messages_MessageReaction,
|
||||
requestDeserialize: deserialize_messages_MessageReaction,
|
||||
responseSerialize: serialize_messages_MessageResponse,
|
||||
responseDeserialize: deserialize_messages_MessageResponse,
|
||||
},
|
||||
getProfilePicture: {
|
||||
path: '/messages.MessageService/GetProfilePicture',
|
||||
requestStream: false,
|
||||
responseStream: false,
|
||||
requestType: gows_pb.ProfilePictureRequest,
|
||||
responseType: gows_pb.ProfilePictureResponse,
|
||||
requestSerialize: serialize_messages_ProfilePictureRequest,
|
||||
requestDeserialize: deserialize_messages_ProfilePictureRequest,
|
||||
responseSerialize: serialize_messages_ProfilePictureResponse,
|
||||
responseDeserialize: deserialize_messages_ProfilePictureResponse,
|
||||
},
|
||||
sendPresence: {
|
||||
path: '/messages.MessageService/SendPresence',
|
||||
requestStream: false,
|
||||
responseStream: false,
|
||||
requestType: gows_pb.PresenceRequest,
|
||||
responseType: gows_pb.Empty,
|
||||
requestSerialize: serialize_messages_PresenceRequest,
|
||||
requestDeserialize: deserialize_messages_PresenceRequest,
|
||||
responseSerialize: serialize_messages_Empty,
|
||||
responseDeserialize: deserialize_messages_Empty,
|
||||
},
|
||||
sendChatPresence: {
|
||||
path: '/messages.MessageService/SendChatPresence',
|
||||
requestStream: false,
|
||||
responseStream: false,
|
||||
requestType: gows_pb.ChatPresenceRequest,
|
||||
responseType: gows_pb.Empty,
|
||||
requestSerialize: serialize_messages_ChatPresenceRequest,
|
||||
requestDeserialize: deserialize_messages_ChatPresenceRequest,
|
||||
responseSerialize: serialize_messages_Empty,
|
||||
responseDeserialize: deserialize_messages_Empty,
|
||||
},
|
||||
subscribePresence: {
|
||||
path: '/messages.MessageService/SubscribePresence',
|
||||
requestStream: false,
|
||||
responseStream: false,
|
||||
requestType: gows_pb.SubscribePresenceRequest,
|
||||
responseType: gows_pb.Empty,
|
||||
requestSerialize: serialize_messages_SubscribePresenceRequest,
|
||||
requestDeserialize: deserialize_messages_SubscribePresenceRequest,
|
||||
responseSerialize: serialize_messages_Empty,
|
||||
responseDeserialize: deserialize_messages_Empty,
|
||||
},
|
||||
markRead: {
|
||||
path: '/messages.MessageService/MarkRead',
|
||||
requestStream: false,
|
||||
responseStream: false,
|
||||
requestType: gows_pb.MarkReadRequest,
|
||||
responseType: gows_pb.Empty,
|
||||
requestSerialize: serialize_messages_MarkReadRequest,
|
||||
requestDeserialize: deserialize_messages_MarkReadRequest,
|
||||
responseSerialize: serialize_messages_Empty,
|
||||
responseDeserialize: deserialize_messages_Empty,
|
||||
},
|
||||
checkPhones: {
|
||||
path: '/messages.MessageService/CheckPhones',
|
||||
requestStream: false,
|
||||
responseStream: false,
|
||||
requestType: gows_pb.CheckPhonesRequest,
|
||||
responseType: gows_pb.CheckPhonesResponse,
|
||||
requestSerialize: serialize_messages_CheckPhonesRequest,
|
||||
requestDeserialize: deserialize_messages_CheckPhonesRequest,
|
||||
responseSerialize: serialize_messages_CheckPhonesResponse,
|
||||
responseDeserialize: deserialize_messages_CheckPhonesResponse,
|
||||
},
|
||||
//
|
||||
// Newsletters
|
||||
//
|
||||
getSubscribedNewsletters: {
|
||||
path: '/messages.MessageService/GetSubscribedNewsletters',
|
||||
requestStream: false,
|
||||
responseStream: false,
|
||||
requestType: gows_pb.NewsletterListRequest,
|
||||
responseType: gows_pb.NewsletterList,
|
||||
requestSerialize: serialize_messages_NewsletterListRequest,
|
||||
requestDeserialize: deserialize_messages_NewsletterListRequest,
|
||||
responseSerialize: serialize_messages_NewsletterList,
|
||||
responseDeserialize: deserialize_messages_NewsletterList,
|
||||
},
|
||||
getNewsletterInfo: {
|
||||
path: '/messages.MessageService/GetNewsletterInfo',
|
||||
requestStream: false,
|
||||
responseStream: false,
|
||||
requestType: gows_pb.NewsletterInfoRequest,
|
||||
responseType: gows_pb.Newsletter,
|
||||
requestSerialize: serialize_messages_NewsletterInfoRequest,
|
||||
requestDeserialize: deserialize_messages_NewsletterInfoRequest,
|
||||
responseSerialize: serialize_messages_Newsletter,
|
||||
responseDeserialize: deserialize_messages_Newsletter,
|
||||
},
|
||||
getNewsletterMessagesByInvite: {
|
||||
path: '/messages.MessageService/GetNewsletterMessagesByInvite',
|
||||
requestStream: false,
|
||||
responseStream: false,
|
||||
requestType: gows_pb.GetNewsletterMessagesByInviteRequest,
|
||||
responseType: gows_pb.Json,
|
||||
requestSerialize: serialize_messages_GetNewsletterMessagesByInviteRequest,
|
||||
requestDeserialize: deserialize_messages_GetNewsletterMessagesByInviteRequest,
|
||||
responseSerialize: serialize_messages_Json,
|
||||
responseDeserialize: deserialize_messages_Json,
|
||||
},
|
||||
searchNewslettersByView: {
|
||||
path: '/messages.MessageService/SearchNewslettersByView',
|
||||
requestStream: false,
|
||||
responseStream: false,
|
||||
requestType: gows_pb.SearchNewslettersByViewRequest,
|
||||
responseType: gows_pb.NewsletterSearchPageResult,
|
||||
requestSerialize: serialize_messages_SearchNewslettersByViewRequest,
|
||||
requestDeserialize: deserialize_messages_SearchNewslettersByViewRequest,
|
||||
responseSerialize: serialize_messages_NewsletterSearchPageResult,
|
||||
responseDeserialize: deserialize_messages_NewsletterSearchPageResult,
|
||||
},
|
||||
searchNewslettersByText: {
|
||||
path: '/messages.MessageService/SearchNewslettersByText',
|
||||
requestStream: false,
|
||||
responseStream: false,
|
||||
requestType: gows_pb.SearchNewslettersByTextRequest,
|
||||
responseType: gows_pb.NewsletterSearchPageResult,
|
||||
requestSerialize: serialize_messages_SearchNewslettersByTextRequest,
|
||||
requestDeserialize: deserialize_messages_SearchNewslettersByTextRequest,
|
||||
responseSerialize: serialize_messages_NewsletterSearchPageResult,
|
||||
responseDeserialize: deserialize_messages_NewsletterSearchPageResult,
|
||||
},
|
||||
createNewsletter: {
|
||||
path: '/messages.MessageService/CreateNewsletter',
|
||||
requestStream: false,
|
||||
responseStream: false,
|
||||
requestType: gows_pb.CreateNewsletterRequest,
|
||||
responseType: gows_pb.Newsletter,
|
||||
requestSerialize: serialize_messages_CreateNewsletterRequest,
|
||||
requestDeserialize: deserialize_messages_CreateNewsletterRequest,
|
||||
responseSerialize: serialize_messages_Newsletter,
|
||||
responseDeserialize: deserialize_messages_Newsletter,
|
||||
},
|
||||
newsletterToggleMute: {
|
||||
path: '/messages.MessageService/NewsletterToggleMute',
|
||||
requestStream: false,
|
||||
responseStream: false,
|
||||
requestType: gows_pb.NewsletterToggleMuteRequest,
|
||||
responseType: gows_pb.Empty,
|
||||
requestSerialize: serialize_messages_NewsletterToggleMuteRequest,
|
||||
requestDeserialize: deserialize_messages_NewsletterToggleMuteRequest,
|
||||
responseSerialize: serialize_messages_Empty,
|
||||
responseDeserialize: deserialize_messages_Empty,
|
||||
},
|
||||
newsletterToggleFollow: {
|
||||
path: '/messages.MessageService/NewsletterToggleFollow',
|
||||
requestStream: false,
|
||||
responseStream: false,
|
||||
requestType: gows_pb.NewsletterToggleFollowRequest,
|
||||
responseType: gows_pb.Empty,
|
||||
requestSerialize: serialize_messages_NewsletterToggleFollowRequest,
|
||||
requestDeserialize: deserialize_messages_NewsletterToggleFollowRequest,
|
||||
responseSerialize: serialize_messages_Empty,
|
||||
responseDeserialize: deserialize_messages_Empty,
|
||||
},
|
||||
//
|
||||
// Media
|
||||
//
|
||||
downloadMedia: {
|
||||
path: '/messages.MessageService/DownloadMedia',
|
||||
requestStream: false,
|
||||
responseStream: false,
|
||||
requestType: gows_pb.DownloadMediaRequest,
|
||||
responseType: gows_pb.DownloadMediaResponse,
|
||||
requestSerialize: serialize_messages_DownloadMediaRequest,
|
||||
requestDeserialize: deserialize_messages_DownloadMediaRequest,
|
||||
responseSerialize: serialize_messages_DownloadMediaResponse,
|
||||
responseDeserialize: deserialize_messages_DownloadMediaResponse,
|
||||
},
|
||||
//
|
||||
// Storage
|
||||
//
|
||||
getMessageById: {
|
||||
path: '/messages.MessageService/GetMessageById',
|
||||
requestStream: false,
|
||||
responseStream: false,
|
||||
requestType: gows_pb.EntityByIdRequest,
|
||||
responseType: gows_pb.Json,
|
||||
requestSerialize: serialize_messages_EntityByIdRequest,
|
||||
requestDeserialize: deserialize_messages_EntityByIdRequest,
|
||||
responseSerialize: serialize_messages_Json,
|
||||
responseDeserialize: deserialize_messages_Json,
|
||||
},
|
||||
getMessages: {
|
||||
path: '/messages.MessageService/GetMessages',
|
||||
requestStream: false,
|
||||
responseStream: false,
|
||||
requestType: gows_pb.GetMessagesRequest,
|
||||
responseType: gows_pb.JsonList,
|
||||
requestSerialize: serialize_messages_GetMessagesRequest,
|
||||
requestDeserialize: deserialize_messages_GetMessagesRequest,
|
||||
responseSerialize: serialize_messages_JsonList,
|
||||
responseDeserialize: deserialize_messages_JsonList,
|
||||
},
|
||||
getContacts: {
|
||||
path: '/messages.MessageService/GetContacts',
|
||||
requestStream: false,
|
||||
responseStream: false,
|
||||
requestType: gows_pb.GetContactsRequest,
|
||||
responseType: gows_pb.JsonList,
|
||||
requestSerialize: serialize_messages_GetContactsRequest,
|
||||
requestDeserialize: deserialize_messages_GetContactsRequest,
|
||||
responseSerialize: serialize_messages_JsonList,
|
||||
responseDeserialize: deserialize_messages_JsonList,
|
||||
},
|
||||
getContactById: {
|
||||
path: '/messages.MessageService/GetContactById',
|
||||
requestStream: false,
|
||||
responseStream: false,
|
||||
requestType: gows_pb.EntityByIdRequest,
|
||||
responseType: gows_pb.Json,
|
||||
requestSerialize: serialize_messages_EntityByIdRequest,
|
||||
requestDeserialize: deserialize_messages_EntityByIdRequest,
|
||||
responseSerialize: serialize_messages_Json,
|
||||
responseDeserialize: deserialize_messages_Json,
|
||||
},
|
||||
getChats: {
|
||||
path: '/messages.MessageService/GetChats',
|
||||
requestStream: false,
|
||||
responseStream: false,
|
||||
requestType: gows_pb.GetChatsRequest,
|
||||
responseType: gows_pb.JsonList,
|
||||
requestSerialize: serialize_messages_GetChatsRequest,
|
||||
requestDeserialize: deserialize_messages_GetChatsRequest,
|
||||
responseSerialize: serialize_messages_JsonList,
|
||||
responseDeserialize: deserialize_messages_JsonList,
|
||||
},
|
||||
};
|
||||
|
||||
exports.MessageServiceClient = grpc.makeGenericClientConstructor(MessageServiceService);
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,42 @@
|
||||
import { messages } from '@waha/core/engines/gows/grpc/gows';
|
||||
import { MessageStatus } from '@waha/core/engines/gows/types';
|
||||
import { WAMessageAck } from '@waha/structures/enums.dto';
|
||||
|
||||
export function optional(value: any, type) {
|
||||
if (value === null || value === undefined) {
|
||||
return null;
|
||||
}
|
||||
return new type({ value: value });
|
||||
}
|
||||
|
||||
export function parseJson(value: messages.Json | { data?: string }) {
|
||||
if (value instanceof messages.Json) {
|
||||
value = value.toObject();
|
||||
}
|
||||
if (value.data === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
return JSON.parse(value.data);
|
||||
}
|
||||
|
||||
export function parseJsonList(value: messages.JsonList) {
|
||||
return value.toObject().elements.map((value) => parseJson(value));
|
||||
}
|
||||
|
||||
export function statusToAck(status: MessageStatus): WAMessageAck | null {
|
||||
switch (status) {
|
||||
case MessageStatus.Error:
|
||||
return WAMessageAck.ERROR;
|
||||
case MessageStatus.Pending:
|
||||
return WAMessageAck.PENDING;
|
||||
case MessageStatus.ServerAck:
|
||||
return WAMessageAck.SERVER;
|
||||
case MessageStatus.DeliveryAck:
|
||||
return WAMessageAck.DEVICE;
|
||||
case MessageStatus.Read:
|
||||
return WAMessageAck.READ;
|
||||
case MessageStatus.Played:
|
||||
return WAMessageAck.PLAYED;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,4 @@
|
||||
export interface GowsAuth {
|
||||
address(): string;
|
||||
dialect(): string;
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import * as path from 'node:path';
|
||||
|
||||
import { DataStore } from '@waha/core/abc/DataStore';
|
||||
import { GowsAuthSimple } from '@waha/core/engines/gows/store/GowsAuthSimple';
|
||||
import { LocalStore } from '@waha/core/storage/LocalStore';
|
||||
|
||||
import { GowsAuth } from './GowsAuth';
|
||||
|
||||
export class GowsAuthFactoryCore {
|
||||
buildAuth(store: DataStore, name: string): Promise<GowsAuth> {
|
||||
if (store instanceof LocalStore) return this.buildSqlite3(store, name);
|
||||
throw new Error(`Unsupported store type '${store.constructor.name}'`);
|
||||
}
|
||||
|
||||
protected async buildSqlite3(
|
||||
store: LocalStore,
|
||||
name: string,
|
||||
): Promise<GowsAuth> {
|
||||
await store.init(name);
|
||||
const authFolder = store.getSessionDirectory(name);
|
||||
// resolve path
|
||||
const authFolderFullPath = path.resolve(authFolder);
|
||||
const connection = `file:${authFolderFullPath}/gows.db`;
|
||||
return new GowsAuthSimple(connection, 'sqlite3');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import { GowsAuth } from '@waha/core/engines/gows/store/GowsAuth';
|
||||
|
||||
export class GowsAuthSimple implements GowsAuth {
|
||||
private _address: string;
|
||||
private _dialect: string;
|
||||
|
||||
constructor(address: string, dialect: string) {
|
||||
this._address = address;
|
||||
this._dialect = dialect;
|
||||
}
|
||||
|
||||
address() {
|
||||
return this._address;
|
||||
}
|
||||
|
||||
dialect(): string {
|
||||
return this._dialect;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
export interface Presence {
|
||||
From: string;
|
||||
Unavailable: boolean;
|
||||
LastSeen: string;
|
||||
}
|
||||
|
||||
export enum ChatPresenceState {
|
||||
COMPOSING = 'composing',
|
||||
PAUSED = 'paused',
|
||||
}
|
||||
|
||||
export enum ChatPresenceMedia {
|
||||
TEXT = '',
|
||||
AUDIO = 'audio',
|
||||
}
|
||||
|
||||
export interface ChatPresence {
|
||||
Chat: string;
|
||||
Sender: string;
|
||||
IsFromMe: boolean;
|
||||
IsGroup: boolean;
|
||||
State: ChatPresenceState;
|
||||
Media: ChatPresenceMedia;
|
||||
}
|
||||
|
||||
/**
|
||||
* StatusError Status = 0
|
||||
* StatusPending Status = 1
|
||||
* StatusServerAck Status = 2
|
||||
* StatusDeliveryAck Status = 3
|
||||
* StatusRead Status = 4
|
||||
* StatusPlayed Status = 5
|
||||
*/
|
||||
export enum MessageStatus {
|
||||
Error = 0,
|
||||
Pending = 1,
|
||||
ServerAck = 2,
|
||||
DeliveryAck = 3,
|
||||
Read = 4,
|
||||
Played = 5,
|
||||
}
|
||||
@@ -11,7 +11,7 @@ export class NowebAuthFactoryCore {
|
||||
protected async buildLocalAuth(store: LocalStore, name: string) {
|
||||
await store.init(name);
|
||||
const authFolder = store.getSessionDirectory(name);
|
||||
const { state, saveCreds } = await useMultiFileAuthState(authFolder);
|
||||
return { state, saveCreds };
|
||||
const { state, saveCreds, close } = await useMultiFileAuthState(authFolder);
|
||||
return { state, saveCreds, close };
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,11 +1,15 @@
|
||||
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,
|
||||
broadcast: boolean,
|
||||
): 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>;
|
||||
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
import { GroupMetadata } from '@adiwajshing/baileys';
|
||||
import { PaginationParams } from '@waha/structures/pagination.dto';
|
||||
|
||||
export interface IGroupRepository {
|
||||
getAll(pagination?: PaginationParams): Promise<GroupMetadata[]>;
|
||||
|
||||
getById(id: string): Promise<GroupMetadata | null>;
|
||||
|
||||
deleteAll(): Promise<void>;
|
||||
|
||||
deleteById(id: string): Promise<void>;
|
||||
|
||||
save(group: GroupMetadata): Promise<void>;
|
||||
}
|
||||
@@ -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>;
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { WAMessage } from '@adiwajshing/baileys';
|
||||
import { LabelAssociation } from '@adiwajshing/baileys/lib/Types/LabelAssociation';
|
||||
import { IGroupRepository } from '@waha/core/engines/noweb/store/IGroupRepository';
|
||||
import { ILabelAssociationRepository } from '@waha/core/engines/noweb/store/ILabelAssociationsRepository';
|
||||
import { ILabelsRepository } from '@waha/core/engines/noweb/store/ILabelsRepository';
|
||||
|
||||
@@ -16,25 +15,11 @@ export abstract class INowebStorage {
|
||||
|
||||
abstract getChatRepository(): IChatRepository;
|
||||
|
||||
abstract getGroupRepository(): IGroupRepository;
|
||||
|
||||
abstract getMessagesRepository(): IMessagesRepository;
|
||||
|
||||
abstract getLabelsRepository(): ILabelsRepository;
|
||||
|
||||
abstract getLabelAssociationRepository(): ILabelAssociationRepository;
|
||||
|
||||
protected getMessagesMetadata(): Map<string, any> {
|
||||
return new Map()
|
||||
.set('jid', (msg: WAMessage) => msg.key.remoteJid)
|
||||
.set('id', (msg: WAMessage) => msg.key.id)
|
||||
.set('messageTimestamp', (msg: WAMessage) => msg.messageTimestamp);
|
||||
}
|
||||
|
||||
protected getLabelAssociationMetadata() {
|
||||
return new Map().set(
|
||||
'id',
|
||||
(a: LabelAssociation) =>
|
||||
// @ts-ignore
|
||||
`${a.type}_${a.labelId}_${a.chatId}_${a.messageId}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,10 @@ import {
|
||||
Contact,
|
||||
proto,
|
||||
} from '@adiwajshing/baileys';
|
||||
import { GroupMetadata } from '@adiwajshing/baileys/lib/Types/GroupMetadata';
|
||||
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 +20,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, broadcast: boolean): Promise<Chat[]>;
|
||||
|
||||
getContacts(pagination: PaginationParams): Promise<Contact[]>;
|
||||
|
||||
getContactById(jid: string): Promise<Contact>;
|
||||
|
||||
@@ -32,4 +41,8 @@ export interface INowebStore {
|
||||
getChatsByLabelId(labelId: string): Promise<Chat[]>;
|
||||
|
||||
getChatLabels(chatId: string): Promise<Label[]>;
|
||||
|
||||
getGroups(pagination: PaginationParams): Promise<GroupMetadata[]>;
|
||||
|
||||
resetGroupsCache(): void;
|
||||
}
|
||||
@@ -1,6 +1,15 @@
|
||||
import { Chat, Contact, makeInMemoryStore, proto } from '@adiwajshing/baileys';
|
||||
import makeWASocket, {
|
||||
Chat,
|
||||
Contact,
|
||||
GroupMetadata,
|
||||
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 { PaginatorInMemory } from '@waha/utils/Paginator';
|
||||
|
||||
import { INowebStore } from './INowebStore';
|
||||
|
||||
@@ -8,6 +17,8 @@ import { INowebStore } from './INowebStore';
|
||||
const logger = require('pino')();
|
||||
|
||||
export class NowebInMemoryStore implements INowebStore {
|
||||
private socket: ReturnType<typeof makeWASocket>;
|
||||
|
||||
private store: ReturnType<typeof makeInMemoryStore>;
|
||||
errorMessage =
|
||||
'Enable NOWEB store "config.noweb.store.enabled=True" and "config.noweb.store.full_sync=True" when starting a new session. ' +
|
||||
@@ -31,21 +42,30 @@ export class NowebInMemoryStore implements INowebStore {
|
||||
|
||||
bind(ev: any, socket: any) {
|
||||
this.store.bind(ev);
|
||||
this.socket = socket;
|
||||
}
|
||||
|
||||
loadMessage(jid: string, id: string): Promise<proto.IWebMessageInfo> {
|
||||
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, broadcast: boolean): Promise<Chat[]> {
|
||||
throw new BadRequestException(this.errorMessage);
|
||||
}
|
||||
|
||||
getContacts(pagination: PaginationParams): Promise<Contact[]> {
|
||||
throw new BadRequestException(this.errorMessage);
|
||||
}
|
||||
|
||||
@@ -68,4 +88,15 @@ export class NowebInMemoryStore implements INowebStore {
|
||||
getChatLabels(chatId: string): Promise<Label[]> {
|
||||
throw new BadRequestException(this.errorMessage);
|
||||
}
|
||||
|
||||
async getGroups(pagination: PaginationParams): Promise<GroupMetadata[]> {
|
||||
const response = await this.socket?.groupFetchAllParticipating();
|
||||
const groups = Object.values(response);
|
||||
const paginator = new PaginatorInMemory(pagination);
|
||||
return paginator.apply(groups);
|
||||
}
|
||||
|
||||
resetGroupsCache() {
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -1,25 +1,34 @@
|
||||
import {
|
||||
import makeWASocket, {
|
||||
areJidsSameUser,
|
||||
BaileysEventEmitter,
|
||||
Chat,
|
||||
ChatUpdate,
|
||||
Contact,
|
||||
GroupParticipant,
|
||||
isRealMessage,
|
||||
jidNormalizedUser,
|
||||
ParticipantAction,
|
||||
proto,
|
||||
updateMessageWithReaction,
|
||||
updateMessageWithReceipt,
|
||||
} from '@adiwajshing/baileys';
|
||||
import { GroupMetadata } from '@adiwajshing/baileys/lib/Types/GroupMetadata';
|
||||
import { Label } from '@adiwajshing/baileys/lib/Types/Label';
|
||||
import {
|
||||
LabelAssociation,
|
||||
LabelAssociationType,
|
||||
} from '@adiwajshing/baileys/lib/Types/LabelAssociation';
|
||||
import { IGroupRepository } from '@waha/core/engines/noweb/store/IGroupRepository';
|
||||
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 { DefaultMap } from '@waha/utils/DefaultMap';
|
||||
import { sleep, waitUntil } from '@waha/utils/promiseTimeout';
|
||||
import * as lodash from 'lodash';
|
||||
import { toNumber } from 'lodash';
|
||||
import { Logger } from 'pino';
|
||||
|
||||
import { toJID } from '../session.noweb.core';
|
||||
import { IChatRepository } from './IChatRepository';
|
||||
import { IContactRepository } from './IContactRepository';
|
||||
import { IMessagesRepository } from './IMessagesRepository';
|
||||
@@ -29,15 +38,27 @@ import { INowebStore } from './INowebStore';
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const AsyncLock = require('async-lock');
|
||||
|
||||
type ms = number;
|
||||
const HOUR: ms = 60 * 60 * 1000;
|
||||
|
||||
export class NowebPersistentStore implements INowebStore {
|
||||
private socket: any;
|
||||
private socket: ReturnType<typeof makeWASocket>;
|
||||
private chatRepo: IChatRepository;
|
||||
private groupRepo: IGroupRepository;
|
||||
private contactRepo: IContactRepository;
|
||||
private messagesRepo: IMessagesRepository;
|
||||
private labelsRepo: ILabelsRepository;
|
||||
private labelAssociationsRepo: ILabelAssociationRepository;
|
||||
public presences: any;
|
||||
private lock: any;
|
||||
private groupsFetchLock: any = new AsyncLock({
|
||||
maxPending: Infinity,
|
||||
maxExecutionTime: 60_000,
|
||||
});
|
||||
|
||||
private lastTimeGroupUpdate: Date = new Date(0);
|
||||
private lastTimeGroupFetch: Date = new Date(0);
|
||||
private GROUP_METADATA_CACHE_TIME = 24 * HOUR;
|
||||
|
||||
constructor(
|
||||
private logger: Logger,
|
||||
@@ -45,6 +66,7 @@ export class NowebPersistentStore implements INowebStore {
|
||||
) {
|
||||
this.socket = null;
|
||||
this.chatRepo = storage.getChatRepository();
|
||||
this.groupRepo = storage.getGroupRepository();
|
||||
this.contactRepo = storage.getContactsRepository();
|
||||
this.messagesRepo = storage.getMessagesRepository();
|
||||
this.labelsRepo = storage.getLabelsRepository();
|
||||
@@ -86,6 +108,19 @@ export class NowebPersistentStore implements INowebStore {
|
||||
ev.on('chats.delete', (data) =>
|
||||
this.withLock('chats', () => this.onChatDelete(data)),
|
||||
);
|
||||
// Groups
|
||||
ev.on('groups.upsert', (data) =>
|
||||
this.withLock('groups', () => this.onGroupUpsert(data)),
|
||||
);
|
||||
ev.on('groups.update', (data) =>
|
||||
this.withLock('groups', () => this.onGroupUpdate(data)),
|
||||
);
|
||||
ev.on('group-participants.update', (data) =>
|
||||
this.withLock(`group-${data.id}`, () =>
|
||||
this.onGroupParticipantsUpdate(data),
|
||||
),
|
||||
);
|
||||
|
||||
// Contacts
|
||||
ev.on('contacts.upsert', (data) =>
|
||||
this.withLock('contacts', () => this.onContactsUpsert(data)),
|
||||
@@ -190,17 +225,94 @@ export class NowebPersistentStore implements INowebStore {
|
||||
private async onChatUpsert(chats: Chat[]) {
|
||||
for (const chat of chats) {
|
||||
delete chat['messages'];
|
||||
chat.conversationTimestamp = toNumber(chat.conversationTimestamp);
|
||||
chat.conversationTimestamp = toNumber(chat.conversationTimestamp) || null;
|
||||
await this.chatRepo.save(chat);
|
||||
}
|
||||
this.logger.info(`history sync - '${chats.length}' synced chats`);
|
||||
this.logger.info(`store sync - '${chats.length}' synced chats`);
|
||||
}
|
||||
|
||||
private async onGroupUpsert(groups: GroupMetadata[]) {
|
||||
for (const group of groups) {
|
||||
await this.groupRepo.save(group);
|
||||
}
|
||||
this.logger.info(`store sync - '${groups.length}' synced groups`);
|
||||
}
|
||||
|
||||
private async onGroupUpdate(groups: Partial<GroupMetadata>[]) {
|
||||
for (const update of groups) {
|
||||
let group = await this.groupRepo.getById(update.id);
|
||||
group = Object.assign(group || {}, update) as GroupMetadata;
|
||||
await this.groupRepo.save(group);
|
||||
}
|
||||
this.logger.info(`store sync - '${groups.length}' updated groups`);
|
||||
this.lastTimeGroupUpdate = new Date();
|
||||
}
|
||||
|
||||
private async onGroupParticipantsUpdate(data) {
|
||||
const id: string = data.id;
|
||||
const participants: string[] = data.participants;
|
||||
const action: ParticipantAction = data.action;
|
||||
|
||||
if (action == 'remove') {
|
||||
// Remove the group if the current user is removed
|
||||
const myJid = this.socket?.authState?.creds?.me?.id;
|
||||
const participantsIncludesMe = lodash.find(participants, (p) =>
|
||||
areJidsSameUser(p, myJid),
|
||||
);
|
||||
if (participantsIncludesMe) {
|
||||
await this.groupRepo.deleteById(id);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let group = await this.groupRepo.getById(id);
|
||||
if (!group) {
|
||||
group = { id: id, participants: [] } as GroupMetadata;
|
||||
}
|
||||
|
||||
const participantsById = new DefaultMap<string, GroupParticipant>((key) => {
|
||||
return { id: key, admin: null } as GroupParticipant;
|
||||
});
|
||||
for (const participant of group.participants) {
|
||||
participantsById.set(participant.id, participant);
|
||||
}
|
||||
|
||||
for (const participant of participants) {
|
||||
this.participantUpdate(participantsById, participant, action);
|
||||
}
|
||||
group.participants = Array.from(participantsById.values());
|
||||
await this.groupRepo.save(group);
|
||||
}
|
||||
|
||||
private participantUpdate(
|
||||
participantsById: DefaultMap<string, GroupParticipant>,
|
||||
participant: string,
|
||||
action: ParticipantAction,
|
||||
) {
|
||||
switch (action) {
|
||||
case 'add':
|
||||
// if there's no participant - add it (by id)
|
||||
participantsById.get(participant);
|
||||
break;
|
||||
case 'remove':
|
||||
// remove the participant (by id)
|
||||
participantsById.delete(participant);
|
||||
break;
|
||||
case 'promote':
|
||||
// set admin: admin
|
||||
participantsById.get(participant).admin = 'admin';
|
||||
break;
|
||||
case 'demote':
|
||||
participantsById.get(participant).admin = null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private async onChatUpdate(updates: ChatUpdate[]) {
|
||||
for (const update of updates) {
|
||||
const chat = (await this.chatRepo.getById(update.id)) || ({} as Chat);
|
||||
Object.assign(chat, update);
|
||||
chat.conversationTimestamp = toNumber(chat.conversationTimestamp);
|
||||
chat.conversationTimestamp = toNumber(chat.conversationTimestamp) || null;
|
||||
delete chat['messages'];
|
||||
await this.chatRepo.save(chat);
|
||||
}
|
||||
@@ -231,7 +343,7 @@ export class NowebPersistentStore implements INowebStore {
|
||||
|
||||
private async onContactUpdate(updates: Partial<Contact>[]) {
|
||||
for (const update of updates) {
|
||||
const contact = await this.contactRepo.getById(update.id);
|
||||
let contact = await this.contactRepo.getById(update.id);
|
||||
|
||||
if (!contact) {
|
||||
this.logger.warn(
|
||||
@@ -239,7 +351,7 @@ export class NowebPersistentStore implements INowebStore {
|
||||
update,
|
||||
)}'`,
|
||||
);
|
||||
continue;
|
||||
contact = {} as Contact;
|
||||
// TODO: Find contact by hash if not found
|
||||
// find contact by attrs.hash, when user is not saved as a contact
|
||||
// check the in-memory for that
|
||||
@@ -322,20 +434,67 @@ 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, broadcast: boolean): Promise<Chat[]> {
|
||||
pagination.sortBy ||= 'conversationTimestamp';
|
||||
pagination.sortOrder ||= SortOrder.DESC;
|
||||
return this.chatRepo.getAllWithMessages(pagination, broadcast);
|
||||
}
|
||||
|
||||
private shouldFetchGroup(): boolean {
|
||||
const timePassed = new Date().getTime() - this.lastTimeGroupFetch.getTime();
|
||||
return timePassed > this.GROUP_METADATA_CACHE_TIME;
|
||||
}
|
||||
|
||||
private async fetchGroups() {
|
||||
await this.groupsFetchLock.acquire('groups-fetch', async () => {
|
||||
if (!this.shouldFetchGroup()) {
|
||||
// Update has been done by another request
|
||||
return;
|
||||
}
|
||||
const lastTimeGroupUpdate = this.lastTimeGroupUpdate;
|
||||
await this.groupRepo.deleteAll();
|
||||
await this.socket?.groupFetchAllParticipating();
|
||||
// Wait until the groups update is done
|
||||
await waitUntil(
|
||||
async () => this.lastTimeGroupUpdate > lastTimeGroupUpdate,
|
||||
100,
|
||||
5_000,
|
||||
);
|
||||
this.lastTimeGroupFetch = new Date();
|
||||
});
|
||||
}
|
||||
|
||||
resetGroupsCache() {
|
||||
this.lastTimeGroupFetch = new Date(0);
|
||||
}
|
||||
|
||||
async getGroups(pagination: PaginationParams): Promise<GroupMetadata[]> {
|
||||
if (this.shouldFetchGroup()) {
|
||||
await this.fetchGroups();
|
||||
}
|
||||
return this.groupRepo.getAll(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,9 +1,19 @@
|
||||
import { LocalStore } from '@waha/core/storage/LocalStore';
|
||||
|
||||
import { DataStore } from '../../../abc/DataStore';
|
||||
import { INowebStorage } from './INowebStorage';
|
||||
import { Sqlite3Storage } from './sqlite3/Sqlite3Storage';
|
||||
|
||||
export class NowebStorageFactoryCore {
|
||||
createStorage(store: DataStore, name: string): INowebStorage {
|
||||
return new Sqlite3Storage(':memory:');
|
||||
if (store instanceof LocalStore) {
|
||||
return this.buildStorageSqlite3(store, name);
|
||||
}
|
||||
throw new Error(`Unsupported store type '${store.constructor.name}'`);
|
||||
}
|
||||
|
||||
private buildStorageSqlite3(store: LocalStore, name: string) {
|
||||
const filePath = store.getFilePath(name, 'store.sqlite3');
|
||||
return new Sqlite3Storage(filePath);
|
||||
}
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
import { Field, Index, Schema } from '@waha/core/storage/sqlite3/Schema';
|
||||
|
||||
export const NOWEB_STORE_SCHEMA = [
|
||||
new Schema(
|
||||
'contacts',
|
||||
[new Field('id', 'TEXT'), new Field('data', 'TEXT')],
|
||||
[new Index('contacts_id_index', ['id'])],
|
||||
),
|
||||
new Schema(
|
||||
'chats',
|
||||
[
|
||||
new Field('id', 'TEXT'),
|
||||
new Field('conversationTimestamp', 'INTEGER'),
|
||||
new Field('data', 'TEXT'),
|
||||
],
|
||||
[
|
||||
new Index('chats_id_index', ['id']),
|
||||
new Index('chats_conversationTimestamp_index', ['conversationTimestamp']),
|
||||
],
|
||||
),
|
||||
new Schema(
|
||||
'messages',
|
||||
[
|
||||
new Field('jid', 'TEXT'),
|
||||
new Field('id', 'TEXT'),
|
||||
new Field('messageTimestamp', 'INTEGER'),
|
||||
new Field('data', 'TEXT'),
|
||||
],
|
||||
[
|
||||
new Index('messages_id_index', ['id']),
|
||||
new Index('messages_jid_id_index', ['jid', 'id']),
|
||||
new Index('messages_jid_timestamp_index', ['jid', 'messageTimestamp']),
|
||||
new Index('timestamp_index', ['messageTimestamp']),
|
||||
],
|
||||
),
|
||||
new Schema(
|
||||
'labels',
|
||||
[new Field('id', 'TEXT'), new Field('data', 'TEXT')],
|
||||
[new Index('labels_id_index', ['id'])],
|
||||
),
|
||||
new Schema(
|
||||
'labelAssociations',
|
||||
[
|
||||
new Field('id', 'TEXT'),
|
||||
new Field('type', 'TEXT'),
|
||||
new Field('labelId', 'TEXT'),
|
||||
new Field('chatId', 'TEXT'),
|
||||
new Field('messageId', 'TEXT'),
|
||||
new Field('data', 'TEXT'),
|
||||
],
|
||||
[
|
||||
new Index('label_assoc_id_index', ['id']),
|
||||
new Index('label_assoc_type_label_index', ['type', 'labelId']),
|
||||
new Index('label_assoc_type_chat_index', ['type', 'chatId']),
|
||||
new Index('label_assoc_type_message_index', ['type', 'messageId']),
|
||||
],
|
||||
),
|
||||
];
|
||||
@@ -0,0 +1,14 @@
|
||||
import { WAMessage } from '@adiwajshing/baileys';
|
||||
import { LabelAssociation } from '@adiwajshing/baileys/lib/Types/LabelAssociation';
|
||||
|
||||
export const NowebMessagesMetadata = new Map()
|
||||
.set('jid', (msg: WAMessage) => msg.key.remoteJid)
|
||||
.set('id', (msg: WAMessage) => msg.key.id)
|
||||
.set('messageTimestamp', (msg: WAMessage) => msg.messageTimestamp);
|
||||
|
||||
export const NowebLabelAssociationsMetadata = new Map().set(
|
||||
'id',
|
||||
(a: LabelAssociation) =>
|
||||
// @ts-ignore
|
||||
`${a.type}_${a.labelId}_${a.chatId}_${a.messageId}`,
|
||||
);
|
||||
@@ -0,0 +1,103 @@
|
||||
import { Field, Index, Schema } from '@waha/core/storage/Schema';
|
||||
|
||||
export const NowebContactSchema = new Schema(
|
||||
'contacts',
|
||||
[new Field('id', 'TEXT'), new Field('data', 'TEXT')],
|
||||
[new Index('contacts_id_index', ['id'])],
|
||||
);
|
||||
|
||||
export const NowebChatSchema = new Schema(
|
||||
'chats',
|
||||
[
|
||||
new Field('id', 'TEXT'),
|
||||
new Field('conversationTimestamp', 'INTEGER'),
|
||||
new Field('data', 'TEXT'),
|
||||
],
|
||||
[
|
||||
new Index('chats_id_index', ['id']),
|
||||
new Index('chats_conversationTimestamp_index', ['conversationTimestamp']),
|
||||
],
|
||||
);
|
||||
|
||||
export const NowebGroupsSchema = new Schema(
|
||||
'groups',
|
||||
[new Field('id', 'TEXT'), new Field('data', 'TEXT')],
|
||||
[new Index('groups_id_index', ['id'])],
|
||||
);
|
||||
|
||||
export const NowebMessagesSchema = new Schema(
|
||||
'messages',
|
||||
[
|
||||
new Field('jid', 'TEXT'),
|
||||
new Field('id', 'TEXT'),
|
||||
new Field('messageTimestamp', 'INTEGER'),
|
||||
new Field('data', 'TEXT'),
|
||||
],
|
||||
[
|
||||
new Index('messages_id_index', ['id']),
|
||||
new Index('messages_jid_id_index', ['jid', 'id']),
|
||||
new Index('messages_jid_timestamp_index', ['jid', 'messageTimestamp']),
|
||||
new Index('timestamp_index', ['messageTimestamp']),
|
||||
],
|
||||
);
|
||||
|
||||
export const NowebLabelsSchema = new Schema(
|
||||
'labels',
|
||||
[new Field('id', 'TEXT'), new Field('data', 'TEXT')],
|
||||
[new Index('labels_id_index', ['id'])],
|
||||
);
|
||||
|
||||
export const NowebLabelAssociationsSchema = new Schema(
|
||||
'labelAssociations',
|
||||
[
|
||||
new Field('id', 'TEXT'),
|
||||
new Field('type', 'TEXT'),
|
||||
new Field('labelId', 'TEXT'),
|
||||
new Field('chatId', 'TEXT'),
|
||||
new Field('messageId', 'TEXT'),
|
||||
new Field('data', 'TEXT'),
|
||||
],
|
||||
[
|
||||
new Index('label_assoc_id_index', ['id']),
|
||||
new Index('label_assoc_type_label_index', ['type', 'labelId']),
|
||||
new Index('label_assoc_type_chat_index', ['type', 'chatId']),
|
||||
new Index('label_assoc_type_message_index', ['type', 'messageId']),
|
||||
],
|
||||
);
|
||||
|
||||
export const NOWEB_STORE_SCHEMA = [
|
||||
NowebContactSchema,
|
||||
NowebChatSchema,
|
||||
NowebGroupsSchema,
|
||||
NowebMessagesSchema,
|
||||
NowebLabelsSchema,
|
||||
NowebLabelAssociationsSchema,
|
||||
];
|
||||
|
||||
export const Migrations: string[] = [
|
||||
// Contacts
|
||||
'CREATE TABLE IF NOT EXISTS contacts (id TEXT PRIMARY KEY, data TEXT)',
|
||||
'CREATE UNIQUE INDEX IF NOT EXISTS contacts_id_index ON contacts (id)',
|
||||
// Chats
|
||||
'CREATE TABLE IF NOT EXISTS chats (id TEXT PRIMARY KEY, "conversationTimestamp" INTEGER, data TEXT)',
|
||||
'CREATE UNIQUE INDEX IF NOT EXISTS chats_id_index ON chats (id)',
|
||||
'CREATE INDEX IF NOT EXISTS "chats_conversationTimestamp_index" ON chats ("conversationTimestamp")',
|
||||
// Groups
|
||||
'CREATE TABLE IF NOT EXISTS groups (id TEXT PRIMARY KEY, data TEXT)',
|
||||
'CREATE UNIQUE INDEX IF NOT EXISTS groups_id_index ON groups (id)',
|
||||
// Messages
|
||||
'CREATE TABLE IF NOT EXISTS messages (jid TEXT, id TEXT, "messageTimestamp" INTEGER, data TEXT)',
|
||||
'CREATE UNIQUE INDEX IF NOT EXISTS messages_id_index ON messages (id)',
|
||||
'CREATE INDEX IF NOT EXISTS messages_jid_id_index ON messages (jid, id)',
|
||||
'CREATE INDEX IF NOT EXISTS messages_jid_timestamp_index ON messages (jid, "messageTimestamp")',
|
||||
'CREATE INDEX IF NOT EXISTS timestamp_index ON messages ("messageTimestamp")',
|
||||
// Labels
|
||||
'CREATE TABLE IF NOT EXISTS labels (id TEXT PRIMARY KEY, data TEXT)',
|
||||
'CREATE UNIQUE INDEX IF NOT EXISTS labels_id_index ON labels (id)',
|
||||
// Label associations
|
||||
'CREATE TABLE IF NOT EXISTS "labelAssociations" (id TEXT PRIMARY KEY, type TEXT, "labelId" TEXT, "chatId" TEXT, "messageId" TEXT, data TEXT)',
|
||||
'CREATE UNIQUE INDEX IF NOT EXISTS label_assoc_id_index ON "labelAssociations" (id)',
|
||||
'CREATE INDEX IF NOT EXISTS label_assoc_type_label_index ON "labelAssociations" (type, "labelId")',
|
||||
'CREATE INDEX IF NOT EXISTS label_assoc_type_chat_index ON "labelAssociations" (type, "chatId")',
|
||||
'CREATE INDEX IF NOT EXISTS label_assoc_type_message_index ON "labelAssociations" (type, "messageId")',
|
||||
];
|
||||
@@ -0,0 +1,23 @@
|
||||
import { Chat } from '@adiwajshing/baileys';
|
||||
import { SqlKVRepository } from '@waha/core/storage/sql/SqlKVRepository';
|
||||
import { PaginationParams } from '@waha/structures/pagination.dto';
|
||||
|
||||
export class SqlChatMethods {
|
||||
constructor(private repository: SqlKVRepository<any>) {}
|
||||
|
||||
async getAllWithMessages(
|
||||
pagination: PaginationParams,
|
||||
broadcast: boolean,
|
||||
): Promise<Chat[]> {
|
||||
// Get chats with conversationTimestamp is not Null
|
||||
let query = this.repository.select().whereNotNull('conversationTimestamp');
|
||||
if (!broadcast) {
|
||||
// filter out chat by id if it ends at @newsletter or @broadcast
|
||||
query = query
|
||||
.andWhereNot('id', 'like', '%@broadcast')
|
||||
.andWhereNot('id', 'like', '%@newsletter');
|
||||
}
|
||||
query = this.repository.pagination(query, pagination);
|
||||
return await this.repository.all(query);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import {
|
||||
LabelAssociation,
|
||||
LabelAssociationType,
|
||||
} from '@adiwajshing/baileys/lib/Types/LabelAssociation';
|
||||
import { SqlKVRepository } from '@waha/core/storage/sql/SqlKVRepository';
|
||||
|
||||
export class SqlLabelAssociationsMethods {
|
||||
constructor(private repository: SqlKVRepository<any>) {}
|
||||
|
||||
async deleteOne(association: LabelAssociation): Promise<void> {
|
||||
await this.repository.deleteBy({
|
||||
type: association.type,
|
||||
chatId: association.chatId,
|
||||
labelId: association.labelId,
|
||||
// @ts-ignore: messageId doesn't existing in ChatLabelAssociation
|
||||
messageId: association.messageId || null,
|
||||
});
|
||||
}
|
||||
|
||||
async deleteByLabelId(labelId: string): Promise<void> {
|
||||
await this.repository.deleteBy({ labelId: labelId });
|
||||
}
|
||||
|
||||
getAssociationsByLabelId(
|
||||
labelId: string,
|
||||
type: LabelAssociationType,
|
||||
): Promise<LabelAssociation[]> {
|
||||
return this.repository.getAllBy({
|
||||
type: type,
|
||||
labelId: labelId,
|
||||
});
|
||||
}
|
||||
|
||||
getAssociationsByChatId(chatId: string): Promise<LabelAssociation[]> {
|
||||
return this.repository.getAllBy({
|
||||
chatId: chatId,
|
||||
type: LabelAssociationType.Chat,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
import { ALL_JID } from '@waha/core/engines/noweb/session.noweb.core';
|
||||
import { SqlKVRepository } from '@waha/core/storage/sql/SqlKVRepository';
|
||||
import { GetChatMessagesFilter } from '@waha/structures/chats.dto';
|
||||
import { PaginationParams } from '@waha/structures/pagination.dto';
|
||||
|
||||
export class SqlMessagesMethods {
|
||||
constructor(private repository: SqlKVRepository<any>) {}
|
||||
|
||||
upsert(messages: any[]): Promise<void> {
|
||||
return this.repository.upsertMany(messages);
|
||||
}
|
||||
|
||||
async getAllByJid(
|
||||
jid: string,
|
||||
filter: GetChatMessagesFilter,
|
||||
pagination: PaginationParams,
|
||||
): Promise<any[]> {
|
||||
let query = this.repository.select();
|
||||
if (jid !== ALL_JID) {
|
||||
query = this.repository.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 json inside
|
||||
const sql = this.repository.filterJson('data', 'key.fromMe');
|
||||
query = query.whereRaw(sql, [filter['filter.fromMe'] ? 'true' : 'false']);
|
||||
}
|
||||
query = this.repository.pagination(query, pagination);
|
||||
return this.repository.all(query);
|
||||
}
|
||||
|
||||
async getByJidById(jid: string, id: string): Promise<any> {
|
||||
return this.repository.getBy({ jid: jid, id: id });
|
||||
}
|
||||
|
||||
async updateByJidAndId(
|
||||
jid: string,
|
||||
id: string,
|
||||
update: any,
|
||||
): Promise<boolean> {
|
||||
const entity = await this.getByJidById(jid, id);
|
||||
if (!entity) {
|
||||
return false;
|
||||
}
|
||||
Object.assign(entity, update);
|
||||
await this.repository.upsertOne(entity);
|
||||
}
|
||||
|
||||
async deleteByJidByIds(jid: string, ids: string[]): Promise<void> {
|
||||
const query = `DELETE
|
||||
FROM "${this.repository.table}"
|
||||
WHERE jid = ?
|
||||
AND id IN (${ids.map(() => '?').join(', ')})`;
|
||||
await this.repository.raw(query, [jid, ...ids]);
|
||||
}
|
||||
|
||||
deleteAllByJid(jid: string): Promise<void> {
|
||||
return this.repository.deleteBy({ jid: jid });
|
||||
}
|
||||
}
|
||||
@@ -1,23 +1,34 @@
|
||||
import { Chat } from '@adiwajshing/baileys';
|
||||
import { NowebChatSchema } from '@waha/core/engines/noweb/store/schemas';
|
||||
import { SqlChatMethods } from '@waha/core/engines/noweb/store/sql/SqlChatMethods';
|
||||
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[]> {
|
||||
// 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);
|
||||
}
|
||||
return await this.all(query);
|
||||
protected Paginator = ChatPaginator;
|
||||
|
||||
get schema() {
|
||||
return NowebChatSchema;
|
||||
}
|
||||
|
||||
get methods() {
|
||||
return new SqlChatMethods(this);
|
||||
}
|
||||
|
||||
getAllWithMessages(
|
||||
pagination: PaginationParams,
|
||||
broadcast: boolean,
|
||||
): Promise<Chat[]> {
|
||||
return this.methods.getAllWithMessages(pagination, broadcast);
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,21 @@
|
||||
import { Contact } from '@adiwajshing/baileys';
|
||||
import { NowebContactSchema } from '@waha/core/engines/noweb/store/schemas';
|
||||
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;
|
||||
|
||||
get schema() {
|
||||
return NowebContactSchema;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import { GroupMetadata } from '@adiwajshing/baileys/lib/Types/GroupMetadata';
|
||||
import { IGroupRepository } from '@waha/core/engines/noweb/store/IGroupRepository';
|
||||
import { NowebGroupsSchema } from '@waha/core/engines/noweb/store/schemas';
|
||||
import { KnexPaginator } from '@waha/utils/Paginator';
|
||||
|
||||
import { NOWEBSqlite3KVRepository } from './NOWEBSqlite3KVRepository';
|
||||
|
||||
class Paginator extends KnexPaginator {
|
||||
indexes = ['id'];
|
||||
}
|
||||
|
||||
export class Sqlite3GroupRepository
|
||||
extends NOWEBSqlite3KVRepository<GroupMetadata>
|
||||
implements IGroupRepository
|
||||
{
|
||||
protected Paginator = Paginator;
|
||||
|
||||
get schema() {
|
||||
return NowebGroupsSchema;
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,9 @@ import {
|
||||
LabelAssociationType,
|
||||
} from '@adiwajshing/baileys/lib/Types/LabelAssociation';
|
||||
import { ILabelAssociationRepository } from '@waha/core/engines/noweb/store/ILabelAssociationsRepository';
|
||||
import { NowebLabelAssociationsMetadata } from '@waha/core/engines/noweb/store/metadata';
|
||||
import { NowebLabelAssociationsSchema } from '@waha/core/engines/noweb/store/schemas';
|
||||
import { SqlLabelAssociationsMethods } from '@waha/core/engines/noweb/store/sql/SqlLabelAssociationsMethods';
|
||||
|
||||
import { NOWEBSqlite3KVRepository } from './NOWEBSqlite3KVRepository';
|
||||
|
||||
@@ -10,31 +13,34 @@ export class Sqlite3LabelAssociationsRepository
|
||||
extends NOWEBSqlite3KVRepository<LabelAssociation>
|
||||
implements ILabelAssociationRepository
|
||||
{
|
||||
get schema() {
|
||||
return NowebLabelAssociationsSchema;
|
||||
}
|
||||
|
||||
get metadata() {
|
||||
return NowebLabelAssociationsMetadata;
|
||||
}
|
||||
|
||||
get methods() {
|
||||
return new SqlLabelAssociationsMethods(this);
|
||||
}
|
||||
|
||||
async deleteOne(association: LabelAssociation): Promise<void> {
|
||||
await this.deleteBy({
|
||||
type: association.type,
|
||||
chatId: association.chatId,
|
||||
labelId: association.labelId,
|
||||
// @ts-ignore: messageId doesn't existing in ChatLabelAssociation
|
||||
messageId: association.messageId || null,
|
||||
});
|
||||
return this.methods.deleteOne(association);
|
||||
}
|
||||
|
||||
async deleteByLabelId(labelId: string): Promise<void> {
|
||||
await this.deleteBy({ labelId: labelId });
|
||||
return this.methods.deleteByLabelId(labelId);
|
||||
}
|
||||
|
||||
getAssociationsByLabelId(
|
||||
labelId: string,
|
||||
type: LabelAssociationType,
|
||||
): Promise<LabelAssociation[]> {
|
||||
return this.getAllBy({
|
||||
type: type,
|
||||
labelId: labelId,
|
||||
});
|
||||
return this.methods.getAssociationsByLabelId(labelId, type);
|
||||
}
|
||||
|
||||
getAssociationsByChatId(chatId: string): Promise<LabelAssociation[]> {
|
||||
return this.getAllBy({ chatId: chatId, type: LabelAssociationType.Chat });
|
||||
return this.methods.getAssociationsByChatId(chatId);
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,14 @@
|
||||
import { Label } from '@adiwajshing/baileys/lib/Types/Label';
|
||||
import { ILabelsRepository } from '@waha/core/engines/noweb/store/ILabelsRepository';
|
||||
import { NowebLabelsSchema } from '@waha/core/engines/noweb/store/schemas';
|
||||
|
||||
import { NOWEBSqlite3KVRepository } from './NOWEBSqlite3KVRepository';
|
||||
|
||||
export class Sqlite3LabelsRepository
|
||||
extends NOWEBSqlite3KVRepository<Label>
|
||||
implements ILabelsRepository {}
|
||||
implements ILabelsRepository
|
||||
{
|
||||
get schema() {
|
||||
return NowebLabelsSchema;
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,9 @@
|
||||
import { NowebMessagesMetadata } from '@waha/core/engines/noweb/store/metadata';
|
||||
import { NowebMessagesSchema } from '@waha/core/engines/noweb/store/schemas';
|
||||
import { SqlMessagesMethods } from '@waha/core/engines/noweb/store/sql/SqlMessagesMethods';
|
||||
import { GetChatMessagesFilter } from '@waha/structures/chats.dto';
|
||||
import { PaginationParams } from '@waha/structures/pagination.dto';
|
||||
|
||||
import { IMessagesRepository } from '../IMessagesRepository';
|
||||
import { NOWEBSqlite3KVRepository } from './NOWEBSqlite3KVRepository';
|
||||
|
||||
@@ -5,20 +11,32 @@ export class Sqlite3MessagesRepository
|
||||
extends NOWEBSqlite3KVRepository<any>
|
||||
implements IMessagesRepository
|
||||
{
|
||||
upsert(messages: any[]): Promise<void> {
|
||||
return this.upsertMany(messages);
|
||||
get schema() {
|
||||
return NowebMessagesSchema;
|
||||
}
|
||||
|
||||
getAllByJid(jid: string, limit: number): Promise<any[]> {
|
||||
const query = this.select()
|
||||
.where({ jid: jid })
|
||||
.limit(limit)
|
||||
.orderBy('messageTimestamp', 'DESC');
|
||||
return this.all(query);
|
||||
get methods() {
|
||||
return new SqlMessagesMethods(this);
|
||||
}
|
||||
|
||||
get metadata() {
|
||||
return NowebMessagesMetadata;
|
||||
}
|
||||
|
||||
upsert(messages: any[]): Promise<void> {
|
||||
return this.methods.upsert(messages);
|
||||
}
|
||||
|
||||
async getAllByJid(
|
||||
jid: string,
|
||||
filter: GetChatMessagesFilter,
|
||||
pagination: PaginationParams,
|
||||
): Promise<any[]> {
|
||||
return this.methods.getAllByJid(jid, filter, pagination);
|
||||
}
|
||||
|
||||
async getByJidById(jid: string, id: string): Promise<any> {
|
||||
return this.getBy({ jid: jid, id: id });
|
||||
return this.methods.getByJidById(jid, id);
|
||||
}
|
||||
|
||||
async updateByJidAndId(
|
||||
@@ -26,23 +44,14 @@ export class Sqlite3MessagesRepository
|
||||
id: string,
|
||||
update: any,
|
||||
): Promise<boolean> {
|
||||
const entity = await this.getByJidById(jid, id);
|
||||
if (!entity) {
|
||||
return false;
|
||||
}
|
||||
Object.assign(entity, update);
|
||||
await this.upsertOne(entity);
|
||||
return this.methods.updateByJidAndId(jid, id, update);
|
||||
}
|
||||
|
||||
async deleteByJidByIds(jid: string, ids: string[]): Promise<void> {
|
||||
const query = `DELETE
|
||||
FROM ${this.table}
|
||||
WHERE jid = ?
|
||||
AND id IN (${ids.map(() => '?').join(', ')})`;
|
||||
this.db.prepare(query).run([jid, ...ids]);
|
||||
return this.methods.deleteByJidByIds(jid, ids);
|
||||
}
|
||||
|
||||
deleteAllByJid(jid: string): Promise<void> {
|
||||
return this.deleteBy({ jid: jid });
|
||||
return this.methods.deleteAllByJid(jid);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Schema } from '@waha/core/storage/sqlite3/Schema';
|
||||
import { Schema } from '@waha/core/storage/Schema';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const Database = require('better-sqlite3');
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
import { WAMessage } from '@adiwajshing/baileys';
|
||||
import { LabelAssociation } 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 { Sqlite3GroupRepository } from '@waha/core/engines/noweb/store/sqlite3/Sqlite3GroupRepository';
|
||||
import { Sqlite3LabelAssociationsRepository } from '@waha/core/engines/noweb/store/sqlite3/Sqlite3LabelAssociationsRepository';
|
||||
import { Sqlite3LabelsRepository } from '@waha/core/engines/noweb/store/sqlite3/Sqlite3LabelsRepository';
|
||||
import { Field, Index, Schema } from '@waha/core/storage/sqlite3/Schema';
|
||||
import { Schema } from '@waha/core/storage/Schema';
|
||||
|
||||
import { INowebStorage } from '../INowebStorage';
|
||||
import { NOWEB_STORE_SCHEMA } from '../Schema';
|
||||
import { Migrations, NOWEB_STORE_SCHEMA } from '../schemas';
|
||||
import { Sqlite3ChatRepository } from './Sqlite3ChatRepository';
|
||||
import { Sqlite3ContactRepository } from './Sqlite3ContactRepository';
|
||||
import { Sqlite3MessagesRepository } from './Sqlite3MessagesRepository';
|
||||
@@ -43,109 +42,36 @@ export class Sqlite3Storage extends INowebStorage {
|
||||
}
|
||||
|
||||
private migration0001init() {
|
||||
// Contacts
|
||||
this.db.exec(
|
||||
'CREATE TABLE IF NOT EXISTS contacts (id TEXT PRIMARY KEY, data TEXT)',
|
||||
);
|
||||
this.db.exec(
|
||||
'CREATE UNIQUE INDEX IF NOT EXISTS contacts_id_index ON contacts (id)',
|
||||
);
|
||||
|
||||
// Chats
|
||||
this.db.exec(
|
||||
'CREATE TABLE IF NOT EXISTS chats (id TEXT PRIMARY KEY, conversationTimestamp INTEGER, data TEXT)',
|
||||
);
|
||||
this.db.exec(
|
||||
'CREATE UNIQUE INDEX IF NOT EXISTS chats_id_index ON chats (id)',
|
||||
);
|
||||
this.db.exec(
|
||||
'CREATE INDEX IF NOT EXISTS chats_conversationTimestamp_index ON chats (conversationTimestamp)',
|
||||
);
|
||||
|
||||
// Messages
|
||||
this.db.exec(
|
||||
'CREATE TABLE IF NOT EXISTS messages (jid TEXT, id TEXT, messageTimestamp INTEGER, data TEXT)',
|
||||
);
|
||||
this.db.exec(
|
||||
'CREATE UNIQUE INDEX IF NOT EXISTS messages_id_index ON messages (id)',
|
||||
);
|
||||
this.db.exec(
|
||||
'CREATE INDEX IF NOT EXISTS messages_jid_id_index ON messages (jid, id)',
|
||||
);
|
||||
this.db.exec(
|
||||
'CREATE INDEX IF NOT EXISTS messages_jid_timestamp_index ON messages (jid, messageTimestamp)',
|
||||
);
|
||||
this.db.exec(
|
||||
'CREATE INDEX IF NOT EXISTS timestamp_index ON messages (messageTimestamp)',
|
||||
);
|
||||
|
||||
//
|
||||
// Labels
|
||||
//
|
||||
this.db.exec(
|
||||
'CREATE TABLE IF NOT EXISTS labels (id TEXT PRIMARY KEY, data TEXT)',
|
||||
);
|
||||
this.db.exec(
|
||||
'CREATE UNIQUE INDEX IF NOT EXISTS labels_id_index ON labels (id)',
|
||||
);
|
||||
|
||||
// Label associations
|
||||
this.db.exec(
|
||||
'CREATE TABLE IF NOT EXISTS labelAssociations (id TEXT PRIMARY KEY, type TEXT, labelId TEXT, chatId TEXT, messageId TEXT, data TEXT)',
|
||||
);
|
||||
this.db.exec(
|
||||
'CREATE UNIQUE INDEX IF NOT EXISTS label_assoc_id_index ON labelAssociations (id)',
|
||||
);
|
||||
this.db.exec(
|
||||
'CREATE INDEX IF NOT EXISTS label_assoc_type_label_index ON labelAssociations (type, labelId)',
|
||||
);
|
||||
this.db.exec(
|
||||
'CREATE INDEX IF NOT EXISTS label_assoc_type_chat_index ON labelAssociations (type, chatId)',
|
||||
);
|
||||
this.db.exec(
|
||||
'CREATE INDEX IF NOT EXISTS label_assoc_type_message_index ON labelAssociations (type, messageId)',
|
||||
);
|
||||
for (const migration of Migrations) {
|
||||
this.db.exec(migration);
|
||||
}
|
||||
}
|
||||
|
||||
async close() {
|
||||
this.db.close();
|
||||
return this.db.close();
|
||||
}
|
||||
|
||||
getContactsRepository() {
|
||||
return new Sqlite3ContactRepository(this.db, this.getSchema('contacts'));
|
||||
return new Sqlite3ContactRepository(this.db);
|
||||
}
|
||||
|
||||
getChatRepository() {
|
||||
return new Sqlite3ChatRepository(this.db, this.getSchema('chats'));
|
||||
return new Sqlite3ChatRepository(this.db);
|
||||
}
|
||||
|
||||
getGroupRepository() {
|
||||
return new Sqlite3GroupRepository(this.db);
|
||||
}
|
||||
|
||||
getLabelsRepository(): ILabelsRepository {
|
||||
return new Sqlite3LabelsRepository(this.db, this.getSchema('labels'));
|
||||
return new Sqlite3LabelsRepository(this.db);
|
||||
}
|
||||
|
||||
getLabelAssociationRepository(): ILabelAssociationRepository {
|
||||
const metadata = this.getLabelAssociationMetadata();
|
||||
return new Sqlite3LabelAssociationsRepository(
|
||||
this.db,
|
||||
this.getSchema('labelAssociations'),
|
||||
metadata,
|
||||
);
|
||||
return new Sqlite3LabelAssociationsRepository(this.db);
|
||||
}
|
||||
|
||||
getMessagesRepository() {
|
||||
const metadata = this.getMessagesMetadata();
|
||||
return new Sqlite3MessagesRepository(
|
||||
this.db,
|
||||
this.getSchema('messages'),
|
||||
metadata,
|
||||
);
|
||||
}
|
||||
|
||||
getSchema(name: string) {
|
||||
const schema = this.tables.find((table) => table.name === name);
|
||||
if (!schema) {
|
||||
throw new Error(`Schema not found: ${name}`);
|
||||
}
|
||||
return schema;
|
||||
return new Sqlite3MessagesRepository(this.db);
|
||||
}
|
||||
}
|
||||
@@ -4,9 +4,12 @@ import {
|
||||
proto,
|
||||
} from '@adiwajshing/baileys';
|
||||
import { BufferJSON, initAuthCreds } from '@adiwajshing/baileys/lib/Utils';
|
||||
import { mkdir, readFile, stat, unlink, writeFile } from 'fs/promises';
|
||||
import { mkdir, readFile, stat, unlink } from 'fs/promises';
|
||||
import { join } from 'path';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const writeFileAtomic = require('write-file-atomic');
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const AsyncLock = require('async-lock');
|
||||
|
||||
@@ -29,11 +32,15 @@ export const useMultiFileAuthState = async (
|
||||
): Promise<{
|
||||
state: AuthenticationState;
|
||||
saveCreds: () => Promise<void>;
|
||||
close: () => Promise<void>;
|
||||
}> => {
|
||||
const writeData = (data: any, file: string) => {
|
||||
const filePath = join(folder, fixFileName(file));
|
||||
return fileLock.acquire(filePath, () =>
|
||||
writeFile(join(filePath), JSON.stringify(data, BufferJSON.replacer)),
|
||||
writeFileAtomic(
|
||||
join(filePath),
|
||||
JSON.stringify(data, BufferJSON.replacer),
|
||||
),
|
||||
);
|
||||
};
|
||||
|
||||
@@ -111,5 +118,8 @@ export const useMultiFileAuthState = async (
|
||||
saveCreds: () => {
|
||||
return writeData(creds, 'creds.json');
|
||||
},
|
||||
close: async () => {
|
||||
return;
|
||||
},
|
||||
};
|
||||
};
|
||||
@@ -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(.*)('|')s\u0001(?:'|')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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
};
|
||||
};
|
||||
@@ -1,38 +0,0 @@
|
||||
import { Client } from 'whatsapp-web.js';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const { LoadUtils } = require('./WahaInjected');
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
async getChats(limit?: number, offset?: number) {
|
||||
if (limit == null && offset == null) {
|
||||
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,
|
||||
);
|
||||
|
||||
return chats.map((chat) => ChatFactory.create(this, chat));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,204 @@
|
||||
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 { 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 WebjsClientCore extends Client {
|
||||
constructor(options) {
|
||||
super(options);
|
||||
// Wait until it's READY and inject more utils
|
||||
this.on(Events.READY, async () => {
|
||||
await this.injectWaha();
|
||||
});
|
||||
}
|
||||
|
||||
async injectWaha() {
|
||||
await this.pupPage.evaluate(LoadLodash);
|
||||
await this.pupPage.evaluate(LoadPaginator);
|
||||
await this.pupPage.evaluate(LoadWAHA);
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
// 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));
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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
@@ -1,30 +1,40 @@
|
||||
import { UnprocessableEntityException } from '@nestjs/common';
|
||||
import {
|
||||
getChannelInviteLink,
|
||||
WAHAInternalEvent,
|
||||
WhatsappSession,
|
||||
} from '@waha/core/abc/session.abc';
|
||||
import { WebjsClient } from '@waha/core/engines/webjs/WebjsClient';
|
||||
import { LocalAuth } from '@waha/core/engines/webjs/LocalAuth';
|
||||
import { WebjsClientCore } from '@waha/core/engines/webjs/WebjsClientCore';
|
||||
import {
|
||||
AvailableInPlusVersion,
|
||||
NotImplementedByEngineError,
|
||||
} 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 {
|
||||
Channel,
|
||||
ChannelListResult,
|
||||
ChannelMessage,
|
||||
ChannelRole,
|
||||
ChannelSearchByText,
|
||||
ChannelSearchByView,
|
||||
CreateChannelRequest,
|
||||
ListChannelsQuery,
|
||||
PreviewChannelMessages,
|
||||
} from '@waha/structures/channels.dto';
|
||||
import { ChatArchiveEvent, GetChatsQuery } from '@waha/structures/chats.dto';
|
||||
import {
|
||||
ChatSortField,
|
||||
ChatSummary,
|
||||
GetChatMessageQuery,
|
||||
GetChatMessagesFilter,
|
||||
GetChatMessagesQuery,
|
||||
} from '@waha/structures/chats.dto';
|
||||
import {
|
||||
ChatRequest,
|
||||
CheckNumberStatusQuery,
|
||||
EditMessageRequest,
|
||||
GetMessageQuery,
|
||||
MessageFileRequest,
|
||||
MessageForwardRequest,
|
||||
MessageImageRequest,
|
||||
@@ -49,17 +59,27 @@ import {
|
||||
} from '@waha/structures/enums.dto';
|
||||
import {
|
||||
CreateGroupRequest,
|
||||
GroupSortField,
|
||||
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, SortOrder } 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 { SingleDelayedJobRunner } from '@waha/utils/SingleDelayedJobRunner';
|
||||
import { 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 * as lodash from 'lodash';
|
||||
import { fromEvent, merge, mergeMap, Observable } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
import WAWebJS, {
|
||||
Call,
|
||||
Channel as WEBJSChannel,
|
||||
Chat,
|
||||
@@ -76,11 +96,9 @@ import {
|
||||
} from 'whatsapp-web.js';
|
||||
import { Message as MessageInstance } from 'whatsapp-web.js/src/structures';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const QRCode = require('qrcode');
|
||||
|
||||
export interface WebJSConfig {
|
||||
webVersion?: string;
|
||||
cacheType: 'local' | 'none';
|
||||
}
|
||||
|
||||
export class WhatsappSessionWebJSCore extends WhatsappSession {
|
||||
@@ -93,7 +111,7 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
|
||||
private engineStateCheckDelayedJob: SingleDelayedJobRunner;
|
||||
private shouldRestart: boolean;
|
||||
|
||||
whatsapp: WebjsClient;
|
||||
whatsapp: WebjsClientCore;
|
||||
protected qr: QR;
|
||||
|
||||
public constructor(config) {
|
||||
@@ -124,8 +142,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,17 +157,24 @@ 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();
|
||||
const base = process.env.WAHA_LOCAL_STORE_BASE_DIR || './.sessions';
|
||||
clientOptions.authStrategy = new LocalAuth({
|
||||
clientId: this.name,
|
||||
dataPath: `${base}/webjs/default`,
|
||||
logger: this.logger,
|
||||
rmMaxRetries: undefined,
|
||||
});
|
||||
this.addProxyConfig(clientOptions);
|
||||
return new WebjsClient(clientOptions);
|
||||
return new WebjsClientCore(clientOptions);
|
||||
}
|
||||
|
||||
private restartClient() {
|
||||
@@ -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,26 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
|
||||
async stop() {
|
||||
this.shouldRestart = false;
|
||||
this.status = WAHASessionStatus.STOPPED;
|
||||
this.events.removeAllListeners();
|
||||
this.stopEvents();
|
||||
this.startDelayedJob.cancel();
|
||||
this.mediaManager.close();
|
||||
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 {
|
||||
@@ -299,8 +339,7 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
|
||||
this.whatsapp.on(Events.QR_RECEIVED, async (qr) => {
|
||||
this.logger.debug('QR received');
|
||||
// Convert to image and save
|
||||
const url = await QRCode.toDataURL(qr);
|
||||
this.qr.save(url, qr);
|
||||
this.qr.save(qr);
|
||||
this.printQR(this.qr);
|
||||
this.status = WAHASessionStatus.SCAN_QR_CODE;
|
||||
});
|
||||
@@ -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;
|
||||
}
|
||||
@@ -446,7 +484,11 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
|
||||
|
||||
reply(request: MessageReplyRequest) {
|
||||
const options = this.getMessageOptions(request);
|
||||
return this.whatsapp.sendMessage(request.chatId, request.text, options);
|
||||
return this.whatsapp.sendMessage(
|
||||
this.ensureSuffix(request.chatId),
|
||||
request.text,
|
||||
options,
|
||||
);
|
||||
}
|
||||
|
||||
sendImage(request: MessageImageRequest) {
|
||||
@@ -466,12 +508,16 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
|
||||
name: request.title,
|
||||
});
|
||||
const options = this.getMessageOptions(request);
|
||||
return this.whatsapp.sendMessage(request.chatId, location, options);
|
||||
return this.whatsapp.sendMessage(
|
||||
this.ensureSuffix(request.chatId),
|
||||
location,
|
||||
options,
|
||||
);
|
||||
}
|
||||
|
||||
async forwardMessage(request: MessageForwardRequest): Promise<WAMessage> {
|
||||
const forwardMessage = this.recreateMessage(request.messageId);
|
||||
const msg = await forwardMessage.forward(request.chatId);
|
||||
const msg = await forwardMessage.forward(this.ensureSuffix(request.chatId));
|
||||
// Return "sent: true" for now
|
||||
// need to research how to get the data from WebJS
|
||||
// @ts-ignore
|
||||
@@ -479,24 +525,26 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
|
||||
}
|
||||
|
||||
async sendSeen(request: SendSeenRequest) {
|
||||
const chat: Chat = await this.whatsapp.getChatById(request.chatId);
|
||||
const chat: Chat = await this.whatsapp.getChatById(
|
||||
this.ensureSuffix(request.chatId),
|
||||
);
|
||||
await chat.sendSeen();
|
||||
}
|
||||
|
||||
async startTyping(request: ChatRequest) {
|
||||
const chat: Chat = await this.whatsapp.getChatById(request.chatId);
|
||||
const chat: Chat = await this.whatsapp.getChatById(
|
||||
this.ensureSuffix(request.chatId),
|
||||
);
|
||||
await chat.sendStateTyping();
|
||||
}
|
||||
|
||||
async stopTyping(request: ChatRequest) {
|
||||
const chat: Chat = await this.whatsapp.getChatById(request.chatId);
|
||||
const chat: Chat = await this.whatsapp.getChatById(
|
||||
this.ensureSuffix(request.chatId),
|
||||
);
|
||||
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 +573,73 @@ 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 getChatsOverview(
|
||||
pagination: PaginationParams,
|
||||
): Promise<ChatSummary[]> {
|
||||
pagination = {
|
||||
...pagination,
|
||||
sortBy: ChatSortField.CONVERSATION_TIMESTAMP,
|
||||
sortOrder: SortOrder.DESC,
|
||||
};
|
||||
const chats = await this.getChats(pagination);
|
||||
|
||||
const promises = [];
|
||||
for (const chat of chats) {
|
||||
promises.push(this.fetchChatSummary(chat));
|
||||
}
|
||||
const result = await Promise.all(promises);
|
||||
return result;
|
||||
}
|
||||
|
||||
protected async fetchChatSummary(chat: Chat): Promise<ChatSummary> {
|
||||
const picture = await this.getContactProfilePicture(
|
||||
chat.id._serialized,
|
||||
false,
|
||||
);
|
||||
const lastMessage = !!chat.lastMessage
|
||||
? this.toWAMessage(chat.lastMessage)
|
||||
: null;
|
||||
return {
|
||||
id: chat.id._serialized,
|
||||
name: chat.name || null,
|
||||
picture: picture,
|
||||
lastMessage: lastMessage,
|
||||
_chat: chat,
|
||||
};
|
||||
}
|
||||
|
||||
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,8 +649,35 @@ 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);
|
||||
const chat = await this.whatsapp.getChatById(this.ensureSuffix(chatId));
|
||||
return chat.delete();
|
||||
}
|
||||
|
||||
@@ -566,6 +696,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 +711,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 +764,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) {
|
||||
@@ -624,11 +778,10 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
|
||||
return { about: await contact.getAbout() };
|
||||
}
|
||||
|
||||
public async getContactProfilePicture(query: ContactQuery) {
|
||||
const contact = await this.whatsapp.getContactById(
|
||||
this.ensureSuffix(query.contactId),
|
||||
);
|
||||
return { profilePictureURL: await contact.getProfilePicUrl() };
|
||||
public async fetchContactProfilePicture(id: string) {
|
||||
const contact = await this.whatsapp.getContactById(this.ensureSuffix(id));
|
||||
const url = await contact.getProfilePicUrl();
|
||||
return url;
|
||||
}
|
||||
|
||||
public async blockContact(request: ContactRequest) {
|
||||
@@ -655,6 +808,14 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
|
||||
return this.whatsapp.createGroup(request.name, participantIds);
|
||||
}
|
||||
|
||||
public joinGroup(code: string) {
|
||||
return this.whatsapp.acceptInvite(code);
|
||||
}
|
||||
|
||||
public joinInfoGroup(code: string) {
|
||||
return this.whatsapp.getInviteInfo(code);
|
||||
}
|
||||
|
||||
public async getInfoAdminsOnly(id): Promise<SettingsSecurityChangeInfo> {
|
||||
const groupChat = (await this.whatsapp.getChatById(id)) as GroupChat;
|
||||
return {
|
||||
@@ -684,10 +845,25 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
|
||||
return groupChat.setMessagesAdminsOnly(value);
|
||||
}
|
||||
|
||||
public getGroups() {
|
||||
return this.whatsapp
|
||||
.getChats()
|
||||
.then((chats) => chats.filter((chat) => chat.isGroup));
|
||||
public async getGroups(pagination: PaginationParams) {
|
||||
const chats = await this.whatsapp.getChats();
|
||||
const groups = lodash.filter(chats, (chat) => chat.isGroup);
|
||||
|
||||
switch (pagination.sortBy) {
|
||||
case GroupSortField.ID:
|
||||
pagination.sortBy = 'id._serialized';
|
||||
break;
|
||||
case GroupSortField.SUBJECT:
|
||||
pagination.sortBy = 'groupMetadata.subject';
|
||||
break;
|
||||
}
|
||||
|
||||
const paginator = new PaginatorInMemory(pagination);
|
||||
return paginator.apply(groups);
|
||||
}
|
||||
|
||||
public async refreshGroups(): Promise<boolean> {
|
||||
return true;
|
||||
}
|
||||
|
||||
public getGroup(id) {
|
||||
@@ -765,6 +941,25 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
|
||||
/**
|
||||
* Channels methods
|
||||
*/
|
||||
public searchChannelsByView(
|
||||
query: ChannelSearchByView,
|
||||
): Promise<ChannelListResult> {
|
||||
throw new AvailableInPlusVersion();
|
||||
}
|
||||
|
||||
public searchChannelsByText(
|
||||
query: ChannelSearchByText,
|
||||
): Promise<ChannelListResult> {
|
||||
throw new AvailableInPlusVersion();
|
||||
}
|
||||
|
||||
public async previewChannelMessages(
|
||||
inviteCode: string,
|
||||
query: PreviewChannelMessages,
|
||||
): Promise<ChannelMessage[]> {
|
||||
throw new AvailableInPlusVersion();
|
||||
}
|
||||
|
||||
protected ChatToChannel(chat: WEBJSChannel): Channel {
|
||||
// @ts-ignore
|
||||
const metadata = chat.channelMetadata;
|
||||
@@ -781,6 +976,7 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
|
||||
picture: null,
|
||||
verified: metadata.verified,
|
||||
role: role,
|
||||
subscribersCount: null,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -798,6 +994,7 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
|
||||
picture: metadata.pictureUrl,
|
||||
verified: metadata.isVerified,
|
||||
role: role,
|
||||
subscribersCount: metadata.subscribersCount,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -809,6 +1006,10 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
|
||||
channels = channels.filter((channel) => channel.role === query.role);
|
||||
}
|
||||
|
||||
// Exclude GUEST, browser saves the data
|
||||
// when we search channels or getting messages
|
||||
channels = channels.filter((channel) => channel.role === 'GUEST');
|
||||
|
||||
const promises = channels.map(async (channel) =>
|
||||
this.whatsapp.getProfilePicUrl(channel.id),
|
||||
);
|
||||
@@ -894,90 +1095,170 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
|
||||
}
|
||||
|
||||
/**
|
||||
* END - Methods for API
|
||||
* Status methods
|
||||
*/
|
||||
|
||||
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,
|
||||
};
|
||||
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;
|
||||
protected checkStatusRequest(request: StatusRequest) {
|
||||
if (request.contacts && request.contacts?.length > 0) {
|
||||
const msg =
|
||||
"WEBJS doesn't accept 'contacts'. Remove the field to send status to all contacts.";
|
||||
throw new UnprocessableEntityException(msg);
|
||||
}
|
||||
}
|
||||
|
||||
private async processIncomingMessage(message: Message, downloadMedia = true) {
|
||||
public sendTextStatus(status: TextStatus) {
|
||||
this.checkStatusRequest(status);
|
||||
return this.whatsapp.sendTextStatus(status);
|
||||
}
|
||||
|
||||
/**
|
||||
* END - Methods for API
|
||||
*/
|
||||
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,
|
||||
};
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
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$);
|
||||
}
|
||||
|
||||
protected async processIncomingMessage(
|
||||
message: Message,
|
||||
downloadMedia = true,
|
||||
) {
|
||||
if (downloadMedia) {
|
||||
try {
|
||||
message = await this.downloadMedia(message);
|
||||
@@ -986,7 +1267,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 +1285,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,12 +1309,12 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
|
||||
vCards: message.vCards,
|
||||
replyTo: replyTo,
|
||||
_data: message.rawData,
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
protected extractReplyTo(message: Message): ReplyToMessage | null {
|
||||
// @ts-ignore
|
||||
const quotedMsg = message.rawData.quotedMsg;
|
||||
const quotedMsg = message.rawData?.quotedMsg;
|
||||
if (!quotedMsg) {
|
||||
return;
|
||||
}
|
||||
@@ -1072,6 +1353,7 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
|
||||
return {
|
||||
mentions: mentions,
|
||||
quotedMessageId: quotedMessageId,
|
||||
linkPreview: request.linkPreview,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1087,6 +1369,10 @@ export class WEBJSEngineMediaProcessor
|
||||
return message.type !== 'revoked';
|
||||
}
|
||||
|
||||
getChatId(message: Message): string {
|
||||
return message.id.remote;
|
||||
}
|
||||
|
||||
getMessageId(message: Message): string {
|
||||
return message.id._serialized;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+98
-25
@@ -1,13 +1,23 @@
|
||||
import {
|
||||
Injectable,
|
||||
NotFoundException,
|
||||
OnModuleInit,
|
||||
UnprocessableEntityException,
|
||||
} from '@nestjs/common';
|
||||
import { EngineBootstrap } from '@waha/core/abc/EngineBootstrap';
|
||||
import { GowsEngineConfigService } from '@waha/core/config/GowsEngineConfigService';
|
||||
import { WebJSEngineConfigService } from '@waha/core/config/WebJSEngineConfigService';
|
||||
import { WhatsappSessionGoWSCore } from '@waha/core/engines/gows/session.gows.core';
|
||||
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 +33,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 +43,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() {
|
||||
@@ -49,7 +58,7 @@ enum DefaultSessionStatus {
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class SessionManagerCore extends SessionManager {
|
||||
export class SessionManagerCore extends SessionManager implements OnModuleInit {
|
||||
SESSION_STOP_TIMEOUT = 3000;
|
||||
|
||||
// session - exists and running (or failed or smth)
|
||||
@@ -59,25 +68,34 @@ 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>>;
|
||||
protected readonly engineBootstrap: EngineBootstrap;
|
||||
|
||||
constructor(
|
||||
config: WhatsappConfigService,
|
||||
private engineConfigService: EngineConfigService,
|
||||
private webjsEngineConfigService: WebJSEngineConfigService,
|
||||
gowsConfigService: GowsEngineConfigService,
|
||||
log: PinoLogger,
|
||||
private mediaStorageFactory: MediaStorageFactory,
|
||||
) {
|
||||
super(config, log);
|
||||
this.events = new EventEmitter();
|
||||
super(log, config, gowsConfigService);
|
||||
this.session = DefaultSessionStatus.STOPPED;
|
||||
this.sessionConfig = null;
|
||||
const engineName = this.engineConfigService.getDefaultEngineName();
|
||||
this.EngineClass = this.getEngine(engineName);
|
||||
this.engineBootstrap = this.getEngineBootstrap(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();
|
||||
this.clearStorage().catch((error) => {
|
||||
this.log.error({ error }, 'Error while clearing storage');
|
||||
});
|
||||
@@ -88,6 +106,8 @@ export class SessionManagerCore extends SessionManager {
|
||||
return WhatsappSessionWebJSCore;
|
||||
} else if (engine === WAHAEngine.NOWEB) {
|
||||
return WhatsappSessionNoWebCore;
|
||||
} else if (engine === WAHAEngine.GOWS) {
|
||||
return WhatsappSessionGoWSCore;
|
||||
} else {
|
||||
throw new NotFoundException(`Unknown whatsapp engine '${engine}'.`);
|
||||
}
|
||||
@@ -100,14 +120,21 @@ export class SessionManagerCore extends SessionManager {
|
||||
}
|
||||
|
||||
async beforeApplicationShutdown(signal?: string) {
|
||||
if (!this.session) {
|
||||
return;
|
||||
if (this.session) {
|
||||
await this.stop(this.DEFAULT, true);
|
||||
}
|
||||
await this.stop(this.DEFAULT, true);
|
||||
this.stopEvents();
|
||||
await this.engineBootstrap.shutdown();
|
||||
}
|
||||
|
||||
async onApplicationBootstrap() {
|
||||
await this.engineBootstrap.bootstrap();
|
||||
this.startPredefinedSessions();
|
||||
}
|
||||
|
||||
private async clearStorage() {
|
||||
const storage = this.mediaStorageFactory.build(
|
||||
const storage = await this.mediaStorageFactory.build(
|
||||
'all',
|
||||
this.log.logger.child({ name: 'Storage' }),
|
||||
);
|
||||
await storage.purge();
|
||||
@@ -138,12 +165,13 @@ 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;
|
||||
|
||||
const storage = this.mediaStorageFactory.build(
|
||||
const storage = await this.mediaStorageFactory.build(
|
||||
name,
|
||||
loggerBuilder.child({ name: 'Storage' }),
|
||||
);
|
||||
await storage.init();
|
||||
@@ -153,7 +181,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 +192,21 @@ export class SessionManagerCore extends SessionManager {
|
||||
proxyConfig: proxyConfig,
|
||||
sessionConfig: this.sessionConfig,
|
||||
};
|
||||
if (this.EngineClass === WhatsappSessionWebJSCore) {
|
||||
sessionConfig.engineConfig = this.webjsEngineConfigService.getConfig();
|
||||
} else if (this.EngineClass === WhatsappSessionGoWSCore) {
|
||||
sessionConfig.engineConfig = this.gowsConfigService.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 +217,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 +252,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 +279,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 +387,16 @@ export class SessionManagerCore extends SessionManager {
|
||||
const engine = await this.fetchEngineInfo();
|
||||
return { ...session, engine: engine };
|
||||
}
|
||||
|
||||
protected stopEvents() {
|
||||
complete(this.events2);
|
||||
}
|
||||
|
||||
async onModuleInit() {
|
||||
await this.init();
|
||||
}
|
||||
|
||||
async init() {
|
||||
await this.store.init();
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,8 @@ interface IMediaEngineProcessor<Message> {
|
||||
|
||||
getMessageId(message: Message): string;
|
||||
|
||||
getChatId(message: Message): string;
|
||||
|
||||
getMediaBuffer(message: Message): Promise<Buffer | null>;
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ interface IMediaManager {
|
||||
message: Message,
|
||||
session: string,
|
||||
): Promise<Message>;
|
||||
close(): void;
|
||||
}
|
||||
|
||||
export { IMediaManager };
|
||||
@@ -6,6 +6,9 @@ import { S3MediaData } from '@waha/structures/media.s3.dto';
|
||||
interface Message {
|
||||
// false_111111111@c.us_AAAAAAAAAAAAAA
|
||||
id: string;
|
||||
|
||||
// 1111111@c.us
|
||||
chatId: string;
|
||||
}
|
||||
|
||||
interface File {
|
||||
@@ -37,6 +40,19 @@ abstract class IMediaStorage {
|
||||
abstract getStorageData(data: MediaData): Promise<MediaStorageData>;
|
||||
|
||||
abstract purge(): Promise<void>;
|
||||
|
||||
abstract close(): Promise<void>;
|
||||
}
|
||||
|
||||
export function getMetadata(data: MediaData): any {
|
||||
const now = new Date();
|
||||
return {
|
||||
'waha-session': data.session,
|
||||
'waha-chat-id': data.message.chatId,
|
||||
'waha-message-id': data.message.id,
|
||||
'waha-media-filename': data.file.filename,
|
||||
'waha-created-at': now.getTime(),
|
||||
};
|
||||
}
|
||||
|
||||
export { IMediaStorage };
|
||||
@@ -52,6 +52,7 @@ export class MediaManager implements IMediaManager {
|
||||
session: string,
|
||||
): Promise<WAMedia | null> {
|
||||
const messageId = processor.getMessageId(message);
|
||||
const chatId = processor.getChatId(message);
|
||||
const mimetype = processor.getMimetype(message);
|
||||
const filename = processor.getFilename(message);
|
||||
if (!this.shouldProcessMimetype(mimetype)) {
|
||||
@@ -66,6 +67,7 @@ export class MediaManager implements IMediaManager {
|
||||
session: session,
|
||||
message: {
|
||||
id: messageId,
|
||||
chatId: chatId,
|
||||
},
|
||||
file: {
|
||||
extension: extension,
|
||||
@@ -195,4 +197,10 @@ export class MediaManager implements IMediaManager {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
close() {
|
||||
this.storage.close().catch((err) => {
|
||||
this.log.error(`Failed to close media storage: ${err}`);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -2,5 +2,5 @@ import { IMediaStorage } from '@waha/core/media/IMediaStorage';
|
||||
import { Logger } from 'pino';
|
||||
|
||||
export abstract class MediaStorageFactory {
|
||||
abstract build(logger: Logger): IMediaStorage;
|
||||
abstract build(name: string, logger: Logger): Promise<IMediaStorage>;
|
||||
}
|
||||
@@ -3,12 +3,12 @@ import { SECOND } from '@waha/structures/enums.dto';
|
||||
import * as fsp from 'fs/promises';
|
||||
import * as path from 'path';
|
||||
import { Logger } from 'pino';
|
||||
import { promisify } from 'util';
|
||||
import fs = require('fs');
|
||||
import del = require('del');
|
||||
import { fileExists } from '@waha/utils/files';
|
||||
|
||||
const writeFileAsync = promisify(fs.writeFile);
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const writeFileAtomic = require('write-file-atomic');
|
||||
|
||||
/**
|
||||
* Save files locally using the filesystem
|
||||
@@ -41,7 +41,7 @@ export class MediaLocalStorage implements IMediaStorage {
|
||||
const filepath = this.getFullPath(data);
|
||||
const folder = path.dirname(filepath);
|
||||
await fsp.mkdir(folder, { recursive: true });
|
||||
await writeFileAsync(filepath, buffer);
|
||||
await writeFileAtomic(filepath, buffer);
|
||||
this.postponeRemoval(filepath);
|
||||
return true;
|
||||
}
|
||||
@@ -92,4 +92,8 @@ export class MediaLocalStorage implements IMediaStorage {
|
||||
this.lifetimeMs,
|
||||
);
|
||||
}
|
||||
|
||||
async close() {
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,8 @@ export class MediaLocalStorageFactory extends MediaStorageFactory {
|
||||
super();
|
||||
}
|
||||
|
||||
build(logger: Logger): IMediaStorage {
|
||||
async build(name: string, logger: Logger): Promise<IMediaStorage> {
|
||||
// Local storage uses the same folder for all sessions
|
||||
return new MediaLocalStorage(
|
||||
logger,
|
||||
this.config.filesFolder,
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { AvailableInPlusVersion } from '@waha/core/exceptions';
|
||||
import {
|
||||
ChannelCategory,
|
||||
ChannelCountry,
|
||||
ChannelView,
|
||||
} from '@waha/structures/channels.dto';
|
||||
|
||||
@Injectable()
|
||||
export class ChannelsInfoServiceCore {
|
||||
async getCountries(): Promise<ChannelCountry[]> {
|
||||
throw new AvailableInPlusVersion();
|
||||
}
|
||||
|
||||
async getCategories(): Promise<ChannelCategory[]> {
|
||||
throw new AvailableInPlusVersion();
|
||||
}
|
||||
|
||||
async getViews(): Promise<ChannelView[]> {
|
||||
throw new AvailableInPlusVersion();
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,18 @@
|
||||
import { SessionConfig } from '../../structures/sessions.dto';
|
||||
|
||||
export abstract class ISessionConfigRepository {
|
||||
abstract save(
|
||||
abstract saveConfig(
|
||||
sessionName: string,
|
||||
config: SessionConfig | null,
|
||||
): Promise<void>;
|
||||
|
||||
abstract get(sessionName: string): Promise<SessionConfig | null>;
|
||||
abstract getConfig(sessionName: string): Promise<SessionConfig | null>;
|
||||
|
||||
abstract exists(sessionName: string): Promise<boolean>;
|
||||
|
||||
abstract delete(sessionName: string): Promise<void>;
|
||||
abstract deleteConfig(sessionName: string): Promise<void>;
|
||||
|
||||
abstract getAll(): Promise<string[]>;
|
||||
abstract getAllConfigs(): Promise<string[]>;
|
||||
|
||||
abstract init(): Promise<void>;
|
||||
}
|
||||
Loaded 100 of 145 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user