Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d745279fec | ||
|
|
df34c9f2fd | ||
|
|
37da9aabe3 | ||
|
|
3a2205147c | ||
|
|
5c6acaf10a | ||
|
|
d916f2dea5 | ||
|
|
254a1aedcb | ||
|
|
dce3175388 | ||
|
|
e2b6b45418 | ||
|
|
e210d1e9fc | ||
|
|
df0f449e13 | ||
|
|
895105c900 | ||
|
|
209538709b | ||
|
|
ef7c35286b | ||
|
|
70af0828cb | ||
|
|
ef18e311b1 | ||
|
|
68de84973a | ||
|
|
ef6d4f9998 | ||
|
|
9f5413963b | ||
|
|
ec7c0a5cb7 | ||
|
|
f82bd9eadf | ||
|
|
843c064ebe | ||
|
|
8a52e1d1d0 | ||
|
|
8ec220dc9a | ||
|
|
9c4b9f4433 | ||
|
|
6059a29660 | ||
|
|
e65e6764fa | ||
|
|
1e77cb234a | ||
|
|
4a357087d1 | ||
|
|
6a3823cdff | ||
|
|
c2372ce96e | ||
|
|
0f5e79b163 | ||
|
|
fa4026c83f | ||
|
|
3e4d66ca27 | ||
|
|
2ec94dc13c | ||
|
|
6b11e08f3d | ||
|
|
e884e56b47 | ||
|
|
2d74affed4 | ||
|
|
3572b17049 | ||
|
|
4ee7091cd1 | ||
|
|
ff5e89d97c | ||
|
|
c4e82afc0c | ||
|
|
52c5d85487 | ||
|
|
fd0438f80b | ||
|
|
cb5781ca65 | ||
|
|
4d8aad9dfe | ||
|
|
39429a2e44 | ||
|
|
0a3514d81e | ||
|
|
4e5e0a7c34 | ||
|
|
f6cc6a9bef | ||
|
|
2e545e9fb9 | ||
|
|
d49ef64e7e | ||
|
|
b1e6eaef90 | ||
|
|
3420d0199b | ||
|
|
671db35571 | ||
|
|
d724f87726 | ||
|
|
5ab8565205 | ||
|
|
660c567d1b | ||
|
|
2c68d13c56 | ||
|
|
b9fc155b4f | ||
|
|
8dd5aefc81 | ||
|
|
f9aaf5dcef | ||
|
|
34a6e773df | ||
|
|
f0beb03344 | ||
|
|
1ff9a81ab3 | ||
|
|
9b1771c693 | ||
|
|
823e604737 | ||
|
|
97a31bdf17 | ||
|
|
ee7a198ae6 | ||
|
|
f143a51010 | ||
|
|
e483185b2c | ||
|
|
40e1c75746 | ||
|
|
456bd4693c | ||
|
|
5ac9c0f760 | ||
|
|
f621d84b83 | ||
|
|
0dd001e95a | ||
|
|
69b5235db9 | ||
|
|
a673d63b33 | ||
|
|
36485e10f2 | ||
|
|
8a1a062127 | ||
|
|
5961e0de08 | ||
|
|
60bda521a0 | ||
|
|
49f63c0f0d | ||
|
|
33910aedb8 | ||
|
|
3359a81a14 | ||
|
|
e27f850e68 | ||
|
|
07340b8f53 | ||
|
|
2f9dd7faa8 | ||
|
|
3f81fd4a87 | ||
|
|
4dd961fc66 | ||
|
|
0bd70bfb32 | ||
|
|
3ab845d2d6 | ||
|
|
443f09d5a2 | ||
|
|
3500952bb7 | ||
|
|
e98182c4b5 | ||
|
|
2337827744 | ||
|
|
556dd66af3 | ||
|
|
8bdad3f4f9 | ||
|
|
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 |
No files matched your search
+1
-1
@@ -1,5 +1,5 @@
|
||||
Dockerfile
|
||||
src/core/engines/gows/grpc
|
||||
src/core/engines/gows/proto
|
||||
.*sessions
|
||||
sessions
|
||||
files
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
src/core/engines/gows/grpc/*
|
||||
src/core/engines/gows/proto/*
|
||||
@@ -1,9 +1,10 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: '[BUG][WEBJS] - '
|
||||
title: '[WEBJS] - '
|
||||
labels: bug
|
||||
assignees: ''
|
||||
type: 'BUG'
|
||||
---
|
||||
|
||||
### Describe the bug
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: '[Feature Request]'
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
type: 'Feature'
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.** I'm always
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
src/core/engines/gows/grpc
|
||||
src/core/engines/gows/proto
|
||||
.env
|
||||
*.heapsnapshot
|
||||
.yarn
|
||||
|
||||
@@ -3,6 +3,7 @@ exclude: |
|
||||
^src/dashboard/.*|
|
||||
^src/core/engines/webjs/.*html|
|
||||
^src/core/engines/gows/grpc/.*|
|
||||
^src/core/engines/gows/proto/.*|
|
||||
^src/plus/engines/webjs/.*html|
|
||||
^.yarn/.*
|
||||
)$
|
||||
@@ -74,4 +75,6 @@ repos:
|
||||
.pre-commit-config.yaml|
|
||||
^src/core/engines/webjs/session.webjs.core.ts|
|
||||
^src/core/engines/webjs/_lodash.js|
|
||||
^tests/perf/.*|
|
||||
^scripts/.*|
|
||||
)$
|
||||
+26
-37
@@ -1,9 +1,3 @@
|
||||
# GOWS
|
||||
ARG GOWS_GITHUB_REPO=devlikeapro/gows
|
||||
ARG GOWS_SHA=39ba4dcceeeb6e6fab172ee71ad1c09deea4db16
|
||||
ARG WAHA_DASHBOARD_GITHUB_REPO=devlikeapro/dashboard
|
||||
ARG WAHA_DASHBOARD_SHA=a1c839d8da94d1cade182addffdfc42fc0cf7ed3
|
||||
|
||||
#
|
||||
# Build
|
||||
#
|
||||
@@ -11,12 +5,8 @@ ARG NODE_VERSION=22.8-bullseye
|
||||
FROM node:${NODE_VERSION} AS build
|
||||
ENV PUPPETEER_SKIP_DOWNLOAD=True
|
||||
|
||||
# install protoc
|
||||
RUN apt-get update && \
|
||||
apt-get install protobuf-compiler -y
|
||||
|
||||
# npm packages
|
||||
WORKDIR /src
|
||||
WORKDIR /git
|
||||
COPY package.json .
|
||||
COPY yarn.lock .
|
||||
ENV YARN_CHECKSUM_BEHAVIOR=update
|
||||
@@ -25,19 +15,9 @@ RUN yarn set version 3.6.3
|
||||
RUN yarn install
|
||||
|
||||
# App
|
||||
WORKDIR /src
|
||||
ADD . /src
|
||||
WORKDIR /git
|
||||
ADD . /git
|
||||
RUN yarn install
|
||||
|
||||
# Build node grpc client for GOWS engine
|
||||
WORKDIR /gows/proto
|
||||
ARG GOWS_GITHUB_REPO
|
||||
ARG GOWS_SHA
|
||||
RUN wget https://raw.githubusercontent.com/${GOWS_GITHUB_REPO}/${GOWS_SHA}/proto/gows.proto
|
||||
WORKDIR /src
|
||||
|
||||
RUN make proto-gows
|
||||
|
||||
RUN yarn build && find ./dist -name "*.d.ts" -delete
|
||||
|
||||
#
|
||||
@@ -45,10 +25,13 @@ RUN yarn build && find ./dist -name "*.d.ts" -delete
|
||||
#
|
||||
FROM node:${NODE_VERSION} AS dashboard
|
||||
|
||||
# Download WAHA Dashboard
|
||||
ARG WAHA_DASHBOARD_GITHUB_REPO
|
||||
ARG WAHA_DASHBOARD_SHA
|
||||
# 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 \
|
||||
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 \
|
||||
@@ -60,6 +43,10 @@ RUN \
|
||||
# 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
|
||||
@@ -69,16 +56,18 @@ RUN apt-get update \
|
||||
&& apt-get install -y libvips-dev \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
ARG GOWS_GITHUB_REPO
|
||||
ARG GOWS_SHA
|
||||
RUN git clone https://github.com/${GOWS_GITHUB_REPO} gows && \
|
||||
cd gows && \
|
||||
git checkout ${GOWS_SHA}
|
||||
|
||||
COPY waha.config.json /tmp/waha.config.json
|
||||
WORKDIR /go/gows
|
||||
RUN go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
|
||||
RUN go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
|
||||
RUN make all
|
||||
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/${GOWS_SHA}/gows-${ARCH} && \
|
||||
chmod +x /go/gows/bin/gows
|
||||
|
||||
|
||||
#
|
||||
@@ -163,8 +152,8 @@ 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
|
||||
|
||||
@@ -35,7 +35,7 @@ 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
|
||||
@@ -47,15 +47,7 @@ start-proxy:
|
||||
docker run --rm -d --name squid-container -e TZ=UTC -p 3128:3128 ubuntu/squid:5.2-22.04_beta
|
||||
|
||||
proto-gows:
|
||||
rm -rf src/core/engines/gows/grpc
|
||||
mkdir -p src/core/engines/gows/grpc
|
||||
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:src/core/engines/gows/grpc \
|
||||
--grpc_out=grpc_js:src/core/engines/gows/grpc \
|
||||
--ts_out=grpc_js:src/core/engines/gows/grpc \
|
||||
-I ../gows/proto ../gows/proto/gows.proto
|
||||
node scripts/gows-proto.js build --dir ../gows/proto
|
||||
|
||||
gows:
|
||||
cd ../gows && \
|
||||
|
||||
@@ -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
|
||||
```
|
||||
+1
-1
@@ -38,7 +38,7 @@ services:
|
||||
# command:
|
||||
# - postgres
|
||||
# - "-c"
|
||||
# - "max_connections=1000"
|
||||
# - "max_connections=3000"
|
||||
|
||||
# NOTE: Only if you're using MongoDB
|
||||
# https://waha.devlike.pro/docs/how-to/storages/#sessions---mongodb
|
||||
|
||||
+7
-3
@@ -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",
|
||||
@@ -120,7 +123,8 @@
|
||||
"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/${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,51 @@ 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,
|
||||
) {}
|
||||
|
||||
@Get('')
|
||||
@SessionApiParam
|
||||
@@ -76,12 +96,44 @@ 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);
|
||||
}
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
|
||||
@Post(':id/follow')
|
||||
@SessionApiParam
|
||||
@NewsletterIdApiParam
|
||||
@@ -125,4 +177,49 @@ export class ChannelsController {
|
||||
): Promise<void> {
|
||||
return session.channelsUnmuteChannel(id);
|
||||
}
|
||||
|
||||
@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();
|
||||
}
|
||||
}
|
||||
@@ -18,10 +18,12 @@ import {
|
||||
ChatRequest,
|
||||
CheckNumberStatusQuery,
|
||||
GetMessageQuery,
|
||||
MessageButtonReply,
|
||||
MessageContactVcardRequest,
|
||||
MessageFileRequest,
|
||||
MessageForwardRequest,
|
||||
MessageImageRequest,
|
||||
MessageLinkCustomPreviewRequest,
|
||||
MessageLinkPreviewRequest,
|
||||
MessageLocationRequest,
|
||||
MessagePollRequest,
|
||||
@@ -94,6 +96,25 @@ export class ChattingController {
|
||||
return whatsapp.sendVideo(request);
|
||||
}
|
||||
|
||||
@Post('/send/link-custom-preview')
|
||||
@ApiOperation({
|
||||
summary: 'Send a text message with a CUSTOM link preview.',
|
||||
description:
|
||||
'You can use regular /api/sendText if you wanna send auto-generated link preview.',
|
||||
})
|
||||
@UsePipes(new WAHAValidationPipe())
|
||||
async sendLinkCustomPreview(
|
||||
@Body() request: MessageLinkCustomPreviewRequest,
|
||||
): Promise<any> {
|
||||
const whatsapp = await this.manager.getWorkingSession(request.session);
|
||||
if (!request.text.includes(request.preview.url)) {
|
||||
throw new Error(
|
||||
'"text" must include the URL provided in the "preview.url"',
|
||||
);
|
||||
}
|
||||
return whatsapp.sendLinkCustomPreview(request);
|
||||
}
|
||||
|
||||
@Post('/sendButtons')
|
||||
@ApiOperation({
|
||||
summary: 'Send buttons (interactive message)',
|
||||
@@ -165,18 +186,22 @@ export class ChattingController {
|
||||
return whatsapp.sendLocation(request);
|
||||
}
|
||||
|
||||
@Post('/sendLinkPreview')
|
||||
async sendLinkPreview(@Body() request: MessageLinkPreviewRequest) {
|
||||
const whatsapp = await this.manager.getWorkingSession(request.session);
|
||||
return whatsapp.sendLinkPreview(request);
|
||||
}
|
||||
|
||||
@Post('/sendContactVcard')
|
||||
async sendContactVcard(@Body() request: MessageContactVcardRequest) {
|
||||
const whatsapp = await this.manager.getWorkingSession(request.session);
|
||||
return whatsapp.sendContactVCard(request);
|
||||
}
|
||||
|
||||
@Post('/send/buttons/reply')
|
||||
@ApiOperation({
|
||||
summary: 'Reply on a button message',
|
||||
})
|
||||
@UsePipes(new ValidationPipe({ transform: true, whitelist: true }))
|
||||
async sendButtonsReply(@Body() request: MessageButtonReply) {
|
||||
const whatsapp = await this.manager.getWorkingSession(request.session);
|
||||
return whatsapp.sendButtonsReply(request);
|
||||
}
|
||||
|
||||
@Get('/sendText')
|
||||
@ApiOperation({ summary: 'Send a text message', deprecated: true })
|
||||
async sendTextGet(@Query() query: MessageTextQuery) {
|
||||
@@ -225,4 +250,11 @@ export class ChattingController {
|
||||
const whatsapp = await this.manager.getWorkingSession(request.session);
|
||||
return whatsapp.reply(request);
|
||||
}
|
||||
|
||||
@Post('/sendLinkPreview')
|
||||
@ApiOperation({ deprecated: true })
|
||||
async sendLinkPreview_DEPRECATED(@Body() request: MessageLinkPreviewRequest) {
|
||||
const whatsapp = await this.manager.getWorkingSession(request.session);
|
||||
return whatsapp.sendLinkPreview(request);
|
||||
}
|
||||
}
|
||||
+133
-61
@@ -3,6 +3,8 @@ import {
|
||||
Controller,
|
||||
Delete,
|
||||
Get,
|
||||
HttpCode,
|
||||
HttpStatus,
|
||||
Param,
|
||||
Post,
|
||||
Put,
|
||||
@@ -16,12 +18,20 @@ import {
|
||||
SessionApiParam,
|
||||
WorkingSessionParam,
|
||||
} from '@waha/nestjs/params/SessionApiParam';
|
||||
import { CountResponse, Result } from '@waha/structures/base.dto';
|
||||
import {
|
||||
ChatPictureQuery,
|
||||
ChatPictureResponse,
|
||||
} from '@waha/structures/chats.dto';
|
||||
import { ProfilePictureRequest } from '@waha/structures/profile.dto';
|
||||
|
||||
import { SessionManager } from '../core/abc/manager.abc';
|
||||
import { WhatsappSession } from '../core/abc/session.abc';
|
||||
import { parseGroupInviteLink, WhatsappSession } from '../core/abc/session.abc';
|
||||
import {
|
||||
CreateGroupRequest,
|
||||
DescriptionRequest,
|
||||
GroupField,
|
||||
GroupsListFields,
|
||||
GroupsPaginationParams,
|
||||
JoinGroupRequest,
|
||||
JoinGroupResponse,
|
||||
@@ -53,20 +63,19 @@ export class GroupsController {
|
||||
@WorkingSessionParam session: WhatsappSession,
|
||||
@Query() query: JoinGroupRequest,
|
||||
): Promise<any> {
|
||||
// https://chat.whatsapp.com/123 => 123
|
||||
const code = query.code.split('/').pop();
|
||||
const code = parseGroupInviteLink(query.code);
|
||||
return session.joinInfoGroup(code);
|
||||
}
|
||||
|
||||
@Post('join')
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@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 code = parseGroupInviteLink(request.code);
|
||||
const id = await session.joinGroup(code);
|
||||
return { id: id };
|
||||
}
|
||||
@@ -75,14 +84,32 @@ export class GroupsController {
|
||||
@SessionApiParam
|
||||
@ApiOperation({ summary: 'Get all groups.' })
|
||||
@UsePipes(new ValidationPipe({ transform: true, whitelist: true }))
|
||||
getGroups(
|
||||
async getGroups(
|
||||
@WorkingSessionParam session: WhatsappSession,
|
||||
@Query() pagination: GroupsPaginationParams,
|
||||
@Query() fields: GroupsListFields,
|
||||
) {
|
||||
return session.getGroups(pagination);
|
||||
let groups: any = await session.getGroups(pagination);
|
||||
groups = session.filterGroupsFields(groups, fields);
|
||||
return groups;
|
||||
}
|
||||
|
||||
@Get('/count')
|
||||
@SessionApiParam
|
||||
@ApiOperation({ summary: 'Get the number of groups.' })
|
||||
@UsePipes(new ValidationPipe({ transform: true, whitelist: true }))
|
||||
async getGroupsCount(
|
||||
@WorkingSessionParam session: WhatsappSession,
|
||||
): Promise<CountResponse> {
|
||||
const data: any = await session.getGroups({});
|
||||
const groups: any[] = Array.isArray(data) ? data : Object.values(data);
|
||||
return {
|
||||
count: groups.length,
|
||||
};
|
||||
}
|
||||
|
||||
@Post('refresh')
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@SessionApiParam
|
||||
@ApiOperation({ summary: 'Refresh groups from the server.' })
|
||||
@UsePipes(new ValidationPipe({ transform: true, whitelist: true }))
|
||||
@@ -101,6 +128,100 @@ export class GroupsController {
|
||||
return session.getGroup(id);
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
@SessionApiParam
|
||||
@GroupIdApiParam
|
||||
@ApiOperation({ summary: 'Delete the group.' })
|
||||
deleteGroup(
|
||||
@WorkingSessionParam session: WhatsappSession,
|
||||
@Param('id') id: string,
|
||||
) {
|
||||
return session.deleteGroup(id);
|
||||
}
|
||||
|
||||
@Post(':id/leave')
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@SessionApiParam
|
||||
@GroupIdApiParam
|
||||
@ApiOperation({ summary: 'Leave the group.' })
|
||||
leaveGroup(
|
||||
@WorkingSessionParam session: WhatsappSession,
|
||||
@Param('id') id: string,
|
||||
) {
|
||||
return session.leaveGroup(id);
|
||||
}
|
||||
|
||||
@Get(':id/picture')
|
||||
@SessionApiParam
|
||||
@GroupIdApiParam
|
||||
@ApiOperation({ summary: 'Get group picture' })
|
||||
@UsePipes(new ValidationPipe({ transform: true, whitelist: true }))
|
||||
async getChatPicture(
|
||||
@WorkingSessionParam session: WhatsappSession,
|
||||
@Param('id') id: string,
|
||||
@Query() query: ChatPictureQuery,
|
||||
): Promise<ChatPictureResponse> {
|
||||
const url = await session.getContactProfilePicture(id, query.refresh);
|
||||
return { url: url };
|
||||
}
|
||||
|
||||
@Put(':id/picture')
|
||||
@SessionApiParam
|
||||
@GroupIdApiParam
|
||||
@ApiOperation({ summary: 'Set group picture' })
|
||||
async setPicture(
|
||||
@Param('id') id: string,
|
||||
@WorkingSessionParam session: WhatsappSession,
|
||||
@Body() request: ProfilePictureRequest,
|
||||
): Promise<Result> {
|
||||
const success = await session.updateGroupPicture(id, request.file);
|
||||
return { success: success ?? true };
|
||||
}
|
||||
|
||||
@Delete(':id/picture')
|
||||
@SessionApiParam
|
||||
@GroupIdApiParam
|
||||
@ApiOperation({ summary: 'Delete group picture' })
|
||||
async deletePicture(
|
||||
@Param('id') id: string,
|
||||
@WorkingSessionParam session: WhatsappSession,
|
||||
): Promise<Result> {
|
||||
const success = await session.updateGroupPicture(id, null);
|
||||
return { success: success || true };
|
||||
}
|
||||
|
||||
@Put(':id/description')
|
||||
@ApiOperation({
|
||||
summary: 'Updates the group description.',
|
||||
description:
|
||||
'Returns "true" if the subject was properly updated. This can return "false" if the user does not have the necessary permissions.',
|
||||
})
|
||||
@SessionApiParam
|
||||
@GroupIdApiParam
|
||||
setDescription(
|
||||
@WorkingSessionParam session: WhatsappSession,
|
||||
@Param('id') id: string,
|
||||
@Body() request: DescriptionRequest,
|
||||
) {
|
||||
return session.setDescription(id, request.description);
|
||||
}
|
||||
|
||||
@Put(':id/subject')
|
||||
@SessionApiParam
|
||||
@GroupIdApiParam
|
||||
@ApiOperation({
|
||||
summary: 'Updates the group subject',
|
||||
description:
|
||||
'Returns "true" if the subject was properly updated. This can return "false" if the user does not have the necessary permissions.',
|
||||
})
|
||||
setSubject(
|
||||
@WorkingSessionParam session: WhatsappSession,
|
||||
@Param('id') id: string,
|
||||
@Body() request: SubjectRequest,
|
||||
) {
|
||||
return session.setSubject(id, request.subject);
|
||||
}
|
||||
|
||||
@Put(':id/settings/security/info-admin-only')
|
||||
@SessionApiParam
|
||||
@GroupIdApiParam
|
||||
@@ -162,60 +283,6 @@ export class GroupsController {
|
||||
return session.getMessagesAdminsOnly(id);
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
@SessionApiParam
|
||||
@GroupIdApiParam
|
||||
@ApiOperation({ summary: 'Delete the group.' })
|
||||
deleteGroup(
|
||||
@WorkingSessionParam session: WhatsappSession,
|
||||
@Param('id') id: string,
|
||||
) {
|
||||
return session.deleteGroup(id);
|
||||
}
|
||||
|
||||
@Post(':id/leave')
|
||||
@SessionApiParam
|
||||
@GroupIdApiParam
|
||||
@ApiOperation({ summary: 'Leave the group.' })
|
||||
leaveGroup(
|
||||
@WorkingSessionParam session: WhatsappSession,
|
||||
@Param('id') id: string,
|
||||
) {
|
||||
return session.leaveGroup(id);
|
||||
}
|
||||
|
||||
@Put(':id/description')
|
||||
@ApiOperation({
|
||||
summary: 'Updates the group description.',
|
||||
description:
|
||||
'Returns "true" if the subject was properly updated. This can return "false" if the user does not have the necessary permissions.',
|
||||
})
|
||||
@SessionApiParam
|
||||
@GroupIdApiParam
|
||||
setDescription(
|
||||
@WorkingSessionParam session: WhatsappSession,
|
||||
@Param('id') id: string,
|
||||
@Body() request: DescriptionRequest,
|
||||
) {
|
||||
return session.setDescription(id, request.description);
|
||||
}
|
||||
|
||||
@Put(':id/subject')
|
||||
@SessionApiParam
|
||||
@GroupIdApiParam
|
||||
@ApiOperation({
|
||||
summary: 'Updates the group subject',
|
||||
description:
|
||||
'Returns "true" if the subject was properly updated. This can return "false" if the user does not have the necessary permissions.',
|
||||
})
|
||||
setSubject(
|
||||
@WorkingSessionParam session: WhatsappSession,
|
||||
@Param('id') id: string,
|
||||
@Body() request: SubjectRequest,
|
||||
) {
|
||||
return session.setSubject(id, request.subject);
|
||||
}
|
||||
|
||||
@Get(':id/invite-code')
|
||||
@SessionApiParam
|
||||
@GroupIdApiParam
|
||||
@@ -228,6 +295,7 @@ export class GroupsController {
|
||||
}
|
||||
|
||||
@Post(':id/invite-code/revoke')
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@SessionApiParam
|
||||
@GroupIdApiParam
|
||||
@ApiOperation({
|
||||
@@ -253,6 +321,7 @@ export class GroupsController {
|
||||
}
|
||||
|
||||
@Post(':id/participants/add')
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@SessionApiParam
|
||||
@GroupIdApiParam
|
||||
@ApiOperation({ summary: 'Add participants' })
|
||||
@@ -265,6 +334,7 @@ export class GroupsController {
|
||||
}
|
||||
|
||||
@Post(':id/participants/remove')
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@SessionApiParam
|
||||
@GroupIdApiParam
|
||||
@ApiOperation({
|
||||
@@ -279,6 +349,7 @@ export class GroupsController {
|
||||
}
|
||||
|
||||
@Post(':id/admin/promote')
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@SessionApiParam
|
||||
@GroupIdApiParam
|
||||
@ApiOperation({ summary: 'Promote participants to admin users.' })
|
||||
@@ -291,6 +362,7 @@ export class GroupsController {
|
||||
}
|
||||
|
||||
@Post(':id/admin/demote')
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@SessionApiParam
|
||||
@GroupIdApiParam
|
||||
@ApiOperation({ summary: 'Demotes participants to regular users.' })
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
import {
|
||||
Body,
|
||||
Controller,
|
||||
Delete,
|
||||
Get,
|
||||
Put,
|
||||
UnprocessableEntityException,
|
||||
UsePipes,
|
||||
} from '@nestjs/common';
|
||||
import { ApiOperation, ApiSecurity, ApiTags } from '@nestjs/swagger';
|
||||
import { SessionManager } from '@waha/core/abc/manager.abc';
|
||||
import { WhatsappSession } from '@waha/core/abc/session.abc';
|
||||
import {
|
||||
SessionApiParam,
|
||||
WorkingSessionParam,
|
||||
} from '@waha/nestjs/params/SessionApiParam';
|
||||
import { WAHAValidationPipe } from '@waha/nestjs/pipes/WAHAValidationPipe';
|
||||
import { Result } from '@waha/structures/base.dto';
|
||||
import {
|
||||
MyProfile,
|
||||
ProfileNameRequest,
|
||||
ProfilePictureRequest,
|
||||
ProfileStatusRequest,
|
||||
} from '@waha/structures/profile.dto';
|
||||
|
||||
@ApiSecurity('api_key')
|
||||
@Controller('api/:session/profile')
|
||||
@ApiTags('🆔 Profile')
|
||||
export class ProfileController {
|
||||
constructor(private manager: SessionManager) {}
|
||||
|
||||
@Get('')
|
||||
@SessionApiParam
|
||||
@ApiOperation({ summary: 'Get my profile' })
|
||||
async getMyProfile(
|
||||
@WorkingSessionParam session: WhatsappSession,
|
||||
): Promise<MyProfile> {
|
||||
const me = session.getSessionMeInfo();
|
||||
if (!me) {
|
||||
throw new UnprocessableEntityException('No profile found');
|
||||
}
|
||||
const picture = await session.getContactProfilePicture(me.id, false);
|
||||
return {
|
||||
id: me.id,
|
||||
name: me.pushName,
|
||||
picture: picture || null,
|
||||
};
|
||||
}
|
||||
|
||||
@Put('/name')
|
||||
@SessionApiParam
|
||||
@UsePipes(new WAHAValidationPipe())
|
||||
@ApiOperation({ summary: 'Set my profile name' })
|
||||
async setProfileName(
|
||||
@WorkingSessionParam session: WhatsappSession,
|
||||
@Body() request: ProfileNameRequest,
|
||||
): Promise<Result> {
|
||||
const success = await session.setProfileName(request.name);
|
||||
return { success: success ?? true };
|
||||
}
|
||||
|
||||
@Put('/status')
|
||||
@SessionApiParam
|
||||
@UsePipes(new WAHAValidationPipe())
|
||||
@ApiOperation({ summary: 'Set profile status (About)' })
|
||||
async setProfileStatus(
|
||||
@WorkingSessionParam session: WhatsappSession,
|
||||
@Body() request: ProfileStatusRequest,
|
||||
): Promise<Result> {
|
||||
const success = await session.setProfileStatus(request.status);
|
||||
return { success: success ?? true };
|
||||
}
|
||||
|
||||
@Put('/picture')
|
||||
@SessionApiParam
|
||||
@ApiOperation({ summary: 'Set profile picture' })
|
||||
async setProfilePicture(
|
||||
@WorkingSessionParam session: WhatsappSession,
|
||||
@Body() request: ProfilePictureRequest,
|
||||
): Promise<Result> {
|
||||
const success = await session.updateProfilePicture(request.file);
|
||||
return { success: success ?? true };
|
||||
}
|
||||
|
||||
@Delete('/picture')
|
||||
@SessionApiParam
|
||||
@ApiOperation({ summary: 'Delete profile picture' })
|
||||
async deleteProfilePicture(
|
||||
@WorkingSessionParam session: WhatsappSession,
|
||||
): Promise<Result> {
|
||||
const success = await session.updateProfilePicture(null);
|
||||
return { success: success || true };
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,10 @@
|
||||
import { Body, Controller, Post } from '@nestjs/common';
|
||||
import { Body, Controller, Get, Post } from '@nestjs/common';
|
||||
import { ApiOperation, ApiSecurity, ApiTags } from '@nestjs/swagger';
|
||||
import {
|
||||
SessionApiParam,
|
||||
WorkingSessionParam,
|
||||
} from '@waha/nestjs/params/SessionApiParam';
|
||||
import { NewMessageIDResponse } from '@waha/structures/chatting.dto';
|
||||
|
||||
import { SessionManager } from '../core/abc/manager.abc';
|
||||
import { WhatsappSession } from '../core/abc/session.abc';
|
||||
@@ -70,6 +71,18 @@ class StatusController {
|
||||
) {
|
||||
return session.deleteStatus(status);
|
||||
}
|
||||
|
||||
@Get('new-message-id')
|
||||
@SessionApiParam
|
||||
@ApiOperation({
|
||||
summary: 'Generate message ID you can use to batch contacts',
|
||||
})
|
||||
async getNewMessageId(
|
||||
@WorkingSessionParam session: WhatsappSession,
|
||||
): Promise<NewMessageIDResponse> {
|
||||
const id = await session.generateNewMessageId();
|
||||
return { id: id };
|
||||
}
|
||||
}
|
||||
|
||||
export { StatusController };
|
||||
+18
-19
@@ -1,13 +1,19 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Injectable, Logger, OnApplicationBootstrap } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { WAHAEvents } from '@waha/structures/enums.dto';
|
||||
import { GlobalWebhookConfigConfig } from '@waha/core/config/GlobalWebhookConfig';
|
||||
|
||||
import { parseBool } from './helpers';
|
||||
import { WebhookConfig } from './structures/webhooks.config.dto';
|
||||
|
||||
@Injectable()
|
||||
export class WhatsappConfigService {
|
||||
constructor(private configService: ConfigService) {}
|
||||
export class WhatsappConfigService implements OnApplicationBootstrap {
|
||||
private logger: Logger;
|
||||
private webhookConfig: GlobalWebhookConfigConfig;
|
||||
|
||||
constructor(private configService: ConfigService) {
|
||||
this.logger = new Logger('WhatsappConfigService');
|
||||
this.webhookConfig = new GlobalWebhookConfigConfig(configService);
|
||||
}
|
||||
|
||||
get schema() {
|
||||
return this.configService.get('WHATSAPP_API_SCHEMA', 'http');
|
||||
@@ -113,21 +119,7 @@ export class WhatsappConfigService {
|
||||
}
|
||||
|
||||
getWebhookConfig(): WebhookConfig | undefined {
|
||||
const url = this.getWebhookUrl();
|
||||
const events = this.getWebhookEvents();
|
||||
if (!url || events.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
return { url: url, events: events };
|
||||
}
|
||||
|
||||
private getWebhookUrl(): string | undefined {
|
||||
return this.get('WHATSAPP_HOOK_URL');
|
||||
}
|
||||
|
||||
private getWebhookEvents(): WAHAEvents[] {
|
||||
const value = this.get('WHATSAPP_HOOK_EVENTS', '');
|
||||
return value ? value.split(',') : [];
|
||||
return this.webhookConfig.config;
|
||||
}
|
||||
|
||||
getSessionMongoUrl(): string | undefined {
|
||||
@@ -182,4 +174,11 @@ export class WhatsappConfigService {
|
||||
const value = this.configService.get('WAHA_DEBUG_MODE', 'false');
|
||||
return parseBool(value);
|
||||
}
|
||||
|
||||
onApplicationBootstrap() {
|
||||
const error = this.webhookConfig.validateConfig();
|
||||
if (error) {
|
||||
throw new Error(`Invalid global webhook config:\n${error}\n`);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -57,13 +57,11 @@ export class SwaggerConfiguratorCore {
|
||||
.setVersion(VERSION.version)
|
||||
.addTag('🖥️ Sessions', 'Control WhatsApp sessions (accounts)')
|
||||
.addTag('🔑 Auth', 'Authentication')
|
||||
.addTag('🆔 Profile', 'Your profile information')
|
||||
.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',
|
||||
@@ -140,6 +138,7 @@ export class SwaggerConfiguratorCore {
|
||||
webhooks[event] = {
|
||||
post: {
|
||||
summary: eventMetadata.description,
|
||||
deprecated: eventMetadata.deprecated || false,
|
||||
requestBody: {
|
||||
content: {
|
||||
'application/json': {
|
||||
|
||||
@@ -46,8 +46,10 @@ export abstract class SessionManager
|
||||
protected sessionWorkerRepository: ISessionWorkerRepository;
|
||||
private lock: any;
|
||||
|
||||
WAIT_SESSION_RUNNING_INTERVAL = 500;
|
||||
WAIT_SESSION_RUNNING_TIMEOUT = 5_000;
|
||||
WAIT_STATUS_INTERVAL = 500;
|
||||
WAIT_STATUS_TIMEOUT = 5_000;
|
||||
WAIT_STATUS_TIMEOUT = 10_000;
|
||||
LOCK_TIMEOUT = 10_000;
|
||||
|
||||
protected constructor(
|
||||
@@ -148,6 +150,37 @@ export abstract class SessionManager
|
||||
sessionName: string,
|
||||
expected: WAHASessionStatus[],
|
||||
): Promise<WhatsappSession> {
|
||||
if (!sessionName) {
|
||||
throw new UnprocessableEntityException({
|
||||
error: `Session name is required`,
|
||||
session: sessionName,
|
||||
});
|
||||
}
|
||||
const running = await waitUntil(
|
||||
async () => this.isRunning(sessionName),
|
||||
this.WAIT_SESSION_RUNNING_INTERVAL,
|
||||
this.WAIT_SESSION_RUNNING_TIMEOUT,
|
||||
);
|
||||
|
||||
if (!running) {
|
||||
const exists = await this.exists(sessionName);
|
||||
if (!exists) {
|
||||
throw new UnprocessableEntityException({
|
||||
error: `Session "${sessionName}" does not exist`,
|
||||
session: sessionName,
|
||||
});
|
||||
}
|
||||
|
||||
const msg = {
|
||||
error:
|
||||
'Session status is not as expected. Try again later or restart the session',
|
||||
session: sessionName,
|
||||
status: 'STOPPED',
|
||||
expected: expected,
|
||||
};
|
||||
throw new UnprocessableEntityException(msg);
|
||||
}
|
||||
|
||||
const session = this.getSession(sessionName);
|
||||
const valid = await waitUntil(
|
||||
async () => expected.includes(session.status),
|
||||
@@ -191,11 +224,14 @@ export function populateSessionInfo(
|
||||
) {
|
||||
return (payload: any): WAHAWebhook => {
|
||||
const id = payload._eventId;
|
||||
const timestampMs = payload._timestampMs;
|
||||
const data = { ...payload };
|
||||
delete data._eventId;
|
||||
delete data._timestampMs;
|
||||
const me = session.getSessionMeInfo();
|
||||
return {
|
||||
id: id,
|
||||
timestamp: timestampMs,
|
||||
event: event,
|
||||
session: session.name,
|
||||
metadata: session.sessionConfig?.metadata,
|
||||
|
||||
+210
-12
@@ -1,7 +1,13 @@
|
||||
import { isJidBroadcast } from '@adiwajshing/baileys/lib/WABinary/jid-utils';
|
||||
import {
|
||||
Channel,
|
||||
ChannelListResult,
|
||||
ChannelMessage,
|
||||
ChannelSearchByText,
|
||||
ChannelSearchByView,
|
||||
CreateChannelRequest,
|
||||
ListChannelsQuery,
|
||||
PreviewChannelMessages,
|
||||
} from '@waha/structures/channels.dto';
|
||||
import {
|
||||
ChatSummary,
|
||||
@@ -10,9 +16,10 @@ import {
|
||||
GetChatMessagesQuery,
|
||||
} from '@waha/structures/chats.dto';
|
||||
import { SendButtonsRequest } from '@waha/structures/chatting.buttons.dto';
|
||||
import { BinaryFile, RemoteFile } from '@waha/structures/files.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 { MessageSource, WAMessage } from '@waha/structures/responses.dto';
|
||||
import { DefaultMap } from '@waha/utils/DefaultMap';
|
||||
import { generatePrefixedId } from '@waha/utils/ids';
|
||||
import { LoggerBuilder } from '@waha/utils/logging';
|
||||
@@ -20,7 +27,6 @@ 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 {
|
||||
@@ -41,10 +47,12 @@ import {
|
||||
ChatRequest,
|
||||
CheckNumberStatusQuery,
|
||||
EditMessageRequest,
|
||||
MessageButtonReply,
|
||||
MessageContactVcardRequest,
|
||||
MessageFileRequest,
|
||||
MessageForwardRequest,
|
||||
MessageImageRequest,
|
||||
MessageLinkCustomPreviewRequest,
|
||||
MessageLinkPreviewRequest,
|
||||
MessageLocationRequest,
|
||||
MessagePollRequest,
|
||||
@@ -65,7 +73,8 @@ import {
|
||||
} from '../../structures/enums.dto';
|
||||
import {
|
||||
CreateGroupRequest,
|
||||
GroupsPaginationParams,
|
||||
GroupField,
|
||||
GroupsListFields,
|
||||
ParticipantsRequest,
|
||||
SettingsSecurityChangeInfo,
|
||||
} from '../../structures/groups.dto';
|
||||
@@ -140,6 +149,12 @@ export abstract class WhatsappSession {
|
||||
stdTTL: 24 * 60 * 60, // 1 day
|
||||
});
|
||||
|
||||
// Save sent messages ids in cache so we can determine if a message was sent
|
||||
// via API or APP
|
||||
private sentMessageIds: NodeCache = new NodeCache({
|
||||
stdTTL: 10 * 60, // 10 minutes
|
||||
});
|
||||
|
||||
public constructor({
|
||||
name,
|
||||
printQR,
|
||||
@@ -170,6 +185,7 @@ export abstract class WhatsappSession {
|
||||
filter(Boolean),
|
||||
map((data) => {
|
||||
data._eventId = generatePrefixedId('evt');
|
||||
data._timestampMs = Date.now();
|
||||
return data;
|
||||
}),
|
||||
retry(),
|
||||
@@ -326,27 +342,68 @@ 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> {
|
||||
/**
|
||||
* Profile methods
|
||||
*/
|
||||
public setProfileName(name: string): Promise<boolean> {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public saveCaptcha(code: string) {
|
||||
public setProfileStatus(status: string): Promise<boolean> {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public async updateProfilePicture(
|
||||
file: BinaryFile | RemoteFile | null,
|
||||
): Promise<boolean> {
|
||||
if (file) {
|
||||
await this.setProfilePicture(file);
|
||||
} else {
|
||||
await this.deleteProfilePicture();
|
||||
}
|
||||
|
||||
// Refresh profile picture after update
|
||||
setTimeout(() => {
|
||||
this.logger.debug('Refreshing my profile picture after update...');
|
||||
this.refreshMyProfilePicture()
|
||||
.then(() => {
|
||||
this.logger.debug('Refreshed my profile picture after update');
|
||||
})
|
||||
.catch((err) => {
|
||||
this.logger.error('Error refreshing my profile picture after update');
|
||||
this.logger.error(err, err.stack);
|
||||
});
|
||||
}, 3_000);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected async refreshMyProfilePicture() {
|
||||
const me = this.getSessionMeInfo();
|
||||
await this.getContactProfilePicture(me.id, true);
|
||||
}
|
||||
|
||||
protected setProfilePicture(file: BinaryFile | RemoteFile): Promise<boolean> {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
protected deleteProfilePicture(): Promise<boolean> {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
/**
|
||||
* Other methods
|
||||
*/
|
||||
generateNewMessageId(): Promise<string> {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
abstract checkNumberStatus(request: CheckNumberStatusQuery);
|
||||
|
||||
abstract sendText(request: MessageTextRequest);
|
||||
@@ -365,6 +422,12 @@ export abstract class WhatsappSession {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
sendLinkCustomPreview(
|
||||
request: MessageLinkCustomPreviewRequest,
|
||||
): Promise<any> {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
abstract forwardMessage(request: MessageForwardRequest): Promise<WAMessage>;
|
||||
|
||||
abstract sendImage(request: MessageImageRequest);
|
||||
@@ -381,6 +444,10 @@ export abstract class WhatsappSession {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
sendButtonsReply(request: MessageButtonReply) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
abstract reply(request: MessageReplyRequest);
|
||||
|
||||
abstract sendSeen(chat: SendSeenRequest);
|
||||
@@ -515,7 +582,7 @@ export abstract class WhatsappSession {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public getContactAbout(query: ContactQuery) {
|
||||
public getContactAbout(query: ContactQuery): Promise<{ about: string }> {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
@@ -537,8 +604,18 @@ export abstract class WhatsappSession {
|
||||
}
|
||||
|
||||
protected async refreshProfilePicture(id: string) {
|
||||
let fn: Promise<string>;
|
||||
if (isJidBroadcast(id)) {
|
||||
return null;
|
||||
} else 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 this.fetchContactProfilePicture(id).catch((err) => {
|
||||
const url = await fn.catch((err) => {
|
||||
this.logger.warn('Error fetching profile picture');
|
||||
this.logger.warn(err, err.stack);
|
||||
return null;
|
||||
@@ -570,10 +647,22 @@ export abstract class WhatsappSession {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public getGroups(pagination: PaginationParams) {
|
||||
public getGroups(pagination: PaginationParams): Promise<any> {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public filterGroupsFields(data: any, fields: GroupsListFields) {
|
||||
const groups: any[] = Array.isArray(data) ? data : Object.values(data);
|
||||
if (fields.exclude?.includes(GroupField.PARTICIPANTS)) {
|
||||
groups.forEach((group) => this.removeGroupsFieldParticipant(group));
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
protected removeGroupsFieldParticipant(group: any) {
|
||||
return;
|
||||
}
|
||||
|
||||
public refreshGroups(): Promise<boolean> {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
@@ -610,6 +699,43 @@ export abstract class WhatsappSession {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public async updateGroupPicture(
|
||||
id: string,
|
||||
file: BinaryFile | RemoteFile | null,
|
||||
): Promise<boolean> {
|
||||
if (file) {
|
||||
await this.setGroupPicture(id, file);
|
||||
} else {
|
||||
await this.deleteGroupPicture(id);
|
||||
}
|
||||
|
||||
// Refresh picture after update
|
||||
setTimeout(() => {
|
||||
this.logger.debug('Refreshing group profile picture after update...');
|
||||
this.refreshProfilePicture(id)
|
||||
.then(() => {
|
||||
this.logger.debug('Refreshed group profile picture after update');
|
||||
})
|
||||
.catch((err) => {
|
||||
this.logger.error('Error refreshing my profile picture after update');
|
||||
this.logger.error(err, err.stack);
|
||||
});
|
||||
}, 3_000);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected setGroupPicture(
|
||||
id: string,
|
||||
file: BinaryFile | RemoteFile,
|
||||
): Promise<boolean> {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
protected deleteGroupPicture(id: string): Promise<boolean> {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public setSubject(id, description) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
@@ -661,6 +787,25 @@ export abstract class WhatsappSession {
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
@@ -722,6 +867,9 @@ export abstract class WhatsappSession {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
/**
|
||||
* Engine methods
|
||||
*/
|
||||
public async getEngineInfo(): Promise<any> {
|
||||
return {};
|
||||
}
|
||||
@@ -763,12 +911,62 @@ export abstract class WhatsappSession {
|
||||
);
|
||||
qrcode.generate(qr.raw, { small: true });
|
||||
}
|
||||
|
||||
protected saveSentMessageId(id: string) {
|
||||
this.sentMessageIds.set(id, true);
|
||||
}
|
||||
|
||||
protected getMessageSource(id: string): MessageSource {
|
||||
if (!id) {
|
||||
return MessageSource.APP;
|
||||
}
|
||||
const api = this.sentMessageIds.has(id);
|
||||
return api ? MessageSource.API : MessageSource.APP;
|
||||
}
|
||||
}
|
||||
|
||||
export function isNewsletter(jid: string) {
|
||||
return jid.endsWith('@newsletter');
|
||||
}
|
||||
|
||||
export function getGroupInviteLink(code: string) {
|
||||
if (code.startsWith('https://')) {
|
||||
return code;
|
||||
}
|
||||
return `https://chat.whatsapp.com/${code}`;
|
||||
}
|
||||
|
||||
export function parseGroupInviteLink(link: string) {
|
||||
// https://chat.whatsapp.com/123 => 123
|
||||
return link.split('/').pop();
|
||||
}
|
||||
|
||||
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}`;
|
||||
}
|
||||
|
||||
const deviceRegexp = /^.*:(\d+)@.*$/;
|
||||
|
||||
/**
|
||||
* Extracts the device ID from a JID string.
|
||||
*
|
||||
* @param jid - The JID string (e.g., "123123:12@c.us")
|
||||
* @return The extracted device ID (e.g., "12") or null if the format is invalid.
|
||||
*/
|
||||
export function extractDeviceId(jid: string): string | null {
|
||||
if (!jid) {
|
||||
return null;
|
||||
}
|
||||
const match = jid.match(deviceRegexp);
|
||||
return match ? match[1] : null;
|
||||
}
|
||||
@@ -32,7 +32,7 @@ export class WebsocketGatewayCore
|
||||
OnGatewayDisconnect,
|
||||
BeforeApplicationShutdown
|
||||
{
|
||||
HEARTBEAT_INTERVAL = 10_000;
|
||||
HEARTBEAT_INTERVAL = 60_000;
|
||||
|
||||
@WebSocketServer()
|
||||
server: Server;
|
||||
|
||||
@@ -5,6 +5,7 @@ import { PassportModule } from '@nestjs/passport';
|
||||
import { ServeStaticModule } from '@nestjs/serve-static';
|
||||
import { TerminusModule } from '@nestjs/terminus';
|
||||
import { ChannelsController } from '@waha/api/channels.controller';
|
||||
import { ProfileController } from '@waha/api/profile.controller';
|
||||
import {
|
||||
ServerController,
|
||||
ServerDebugController,
|
||||
@@ -14,6 +15,7 @@ import { GowsEngineConfigService } from '@waha/core/config/GowsEngineConfigServi
|
||||
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,
|
||||
@@ -120,6 +122,7 @@ const IMPORTS = [...IMPORTS_CORE, ...IMPORTS_MEDIA];
|
||||
export const CONTROLLERS = [
|
||||
AuthController,
|
||||
SessionsController,
|
||||
ProfileController,
|
||||
ChattingController,
|
||||
ChatsController,
|
||||
ChannelsController,
|
||||
@@ -148,6 +151,7 @@ const PROVIDERS = [
|
||||
provide: APP_INTERCEPTOR,
|
||||
useClass: BufferJsonReplacerInterceptor,
|
||||
},
|
||||
ChannelsInfoServiceCore,
|
||||
DashboardConfigServiceCore,
|
||||
SwaggerConfigServiceCore,
|
||||
WebJSEngineConfigService,
|
||||
|
||||
@@ -0,0 +1,167 @@
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { WAHAEvents } from '@waha/structures/enums.dto';
|
||||
import {
|
||||
CustomHeader,
|
||||
HmacConfiguration,
|
||||
RetriesConfiguration,
|
||||
WebhookConfig,
|
||||
} from '@waha/structures/webhooks.config.dto';
|
||||
import { plainToInstance } from 'class-transformer';
|
||||
import { validateSync, ValidationError } from 'class-validator';
|
||||
|
||||
enum Env {
|
||||
WHATSAPP_HOOK_URL = 'WHATSAPP_HOOK_URL',
|
||||
WHATSAPP_HOOK_EVENTS = 'WHATSAPP_HOOK_EVENTS',
|
||||
WHATSAPP_HOOK_RETRIES_POLICY = 'WHATSAPP_HOOK_RETRIES_POLICY',
|
||||
WHATSAPP_HOOK_RETRIES_DELAY_SECONDS = 'WHATSAPP_HOOK_RETRIES_DELAY_SECONDS',
|
||||
WHATSAPP_HOOK_RETRIES_ATTEMPTS = 'WHATSAPP_HOOK_RETRIES_ATTEMPTS',
|
||||
WHATSAPP_HOOK_HMAC_KEY = 'WHATSAPP_HOOK_HMAC_KEY',
|
||||
WHATSAPP_HOOK_CUSTOM_HEADERS = 'WHATSAPP_HOOK_CUSTOM_HEADERS',
|
||||
}
|
||||
|
||||
export class GlobalWebhookConfigConfig {
|
||||
protected _config: WebhookConfig | null = null;
|
||||
|
||||
constructor(private configService: ConfigService) {}
|
||||
|
||||
private getUrl(): string | undefined {
|
||||
return this.configService.get(Env.WHATSAPP_HOOK_URL);
|
||||
}
|
||||
|
||||
private getEvents(): WAHAEvents[] {
|
||||
const value = this.configService.get(Env.WHATSAPP_HOOK_EVENTS, '');
|
||||
return value ? value.split(',') : [];
|
||||
}
|
||||
|
||||
private getHmac(): HmacConfiguration | null {
|
||||
const key = this.configService.get(Env.WHATSAPP_HOOK_HMAC_KEY, '');
|
||||
if (!key) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
key: key,
|
||||
};
|
||||
}
|
||||
|
||||
private getRetries(): RetriesConfiguration | null {
|
||||
const policy: any = this.configService.get(
|
||||
Env.WHATSAPP_HOOK_RETRIES_POLICY,
|
||||
null,
|
||||
);
|
||||
const delaySeconds: any = this.configService.get(
|
||||
Env.WHATSAPP_HOOK_RETRIES_DELAY_SECONDS,
|
||||
null,
|
||||
);
|
||||
const attempts: any = this.configService.get(
|
||||
Env.WHATSAPP_HOOK_RETRIES_ATTEMPTS,
|
||||
null,
|
||||
);
|
||||
if (!policy && !delaySeconds && !attempts) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
policy: policy,
|
||||
delaySeconds: delaySeconds,
|
||||
attempts: attempts,
|
||||
};
|
||||
}
|
||||
|
||||
private getCustomHeaders(): CustomHeader[] {
|
||||
// key:value;key2:value
|
||||
const value = this.configService.get(Env.WHATSAPP_HOOK_CUSTOM_HEADERS, '');
|
||||
if (!value) {
|
||||
return [];
|
||||
}
|
||||
const headers = value.split(';');
|
||||
return headers.map((header: string) => {
|
||||
const parts = header.split(':');
|
||||
if (parts.length !== 2) {
|
||||
throw new Error(
|
||||
`${Env.WHATSAPP_HOOK_CUSTOM_HEADERS} - Invalid custom header, no ':' found in '${header}'`,
|
||||
);
|
||||
}
|
||||
return {
|
||||
name: parts[0],
|
||||
value: parts[1],
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
get config() {
|
||||
if (!this._config) {
|
||||
this._config = this.parseWebhookConfig();
|
||||
}
|
||||
return this._config;
|
||||
}
|
||||
|
||||
validateConfig(): string | null {
|
||||
const config = this.parseWebhookConfig();
|
||||
if (!config) {
|
||||
return null;
|
||||
}
|
||||
const errors = validateSync(config);
|
||||
if (errors.length > 0) {
|
||||
return this.formatErrors([], errors, []).join('\n');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
private getEnv(key: string): string | null {
|
||||
const keys = {
|
||||
url: Env.WHATSAPP_HOOK_URL,
|
||||
events: Env.WHATSAPP_HOOK_EVENTS,
|
||||
'retries.policy': Env.WHATSAPP_HOOK_RETRIES_POLICY,
|
||||
'retries.delaySeconds': Env.WHATSAPP_HOOK_RETRIES_DELAY_SECONDS,
|
||||
'retries.attempts': Env.WHATSAPP_HOOK_RETRIES_ATTEMPTS,
|
||||
'hmac.key': Env.WHATSAPP_HOOK_HMAC_KEY,
|
||||
};
|
||||
return keys[key] || null;
|
||||
}
|
||||
|
||||
private formatErrors(
|
||||
lines: string[],
|
||||
errors: ValidationError[],
|
||||
path: string[],
|
||||
) {
|
||||
for (const err of errors) {
|
||||
path = [...path, err.property];
|
||||
for (const key in err.constraints) {
|
||||
if (!err.constraints.hasOwnProperty(key)) {
|
||||
continue;
|
||||
}
|
||||
const property = path.join('.');
|
||||
const env = this.getEnv(property);
|
||||
if (env) {
|
||||
const value = this.configService.get(env, '');
|
||||
const line = `- ${env} (${property}): '${value}' => '${err.value}' - ${err.constraints[key]}`;
|
||||
lines.push(line);
|
||||
} else {
|
||||
const line = `- ${property}: '${err.value}' - ${err.constraints[key]}`;
|
||||
lines.push(line);
|
||||
}
|
||||
}
|
||||
if (err.children) {
|
||||
this.formatErrors(lines, err.children, path);
|
||||
}
|
||||
}
|
||||
return lines;
|
||||
}
|
||||
|
||||
private parseWebhookConfig(): WebhookConfig | null {
|
||||
const url = this.getUrl();
|
||||
const events = this.getEvents();
|
||||
if (!url || events.length === 0) {
|
||||
return null;
|
||||
}
|
||||
const data: WebhookConfig = {
|
||||
url: url,
|
||||
events: events,
|
||||
hmac: this.getHmac(),
|
||||
retries: this.getRetries(),
|
||||
customHeaders: this.getCustomHeaders(),
|
||||
};
|
||||
return plainToInstance(WebhookConfig, data, {
|
||||
enableImplicitConversion: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export enum Jid {
|
||||
BROADCAST = 'status@broadcast',
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
import {
|
||||
GOWSGroupParticipant,
|
||||
GroupInfoEvent,
|
||||
GroupInfoFull,
|
||||
JoinedGroupEvent,
|
||||
} from '@waha/core/engines/gows/types.group';
|
||||
import { toJID } from '@waha/core/engines/noweb/session.noweb.core';
|
||||
import {
|
||||
GroupId,
|
||||
GroupInfo,
|
||||
GroupParticipant,
|
||||
GroupParticipantRole,
|
||||
} from '@waha/structures/groups.dto';
|
||||
import {
|
||||
GroupParticipantType,
|
||||
GroupV2JoinEvent,
|
||||
GroupV2LeaveEvent,
|
||||
GroupV2ParticipantsEvent,
|
||||
GroupV2UpdateEvent,
|
||||
} from '@waha/structures/groups.events.dto';
|
||||
import { MeInfo } from '@waha/structures/sessions.dto';
|
||||
|
||||
export function ToGroupV2JoinEvent(event: JoinedGroupEvent): GroupV2JoinEvent {
|
||||
return {
|
||||
timestamp: Date.now(),
|
||||
group: ToGroupInfo(event),
|
||||
_data: event,
|
||||
};
|
||||
}
|
||||
|
||||
export function ToGroupV2LeaveEvent(
|
||||
me: MeInfo | null,
|
||||
event: GroupInfoEvent,
|
||||
): GroupV2LeaveEvent | null {
|
||||
if (!event.Leave || event.Leave.length === 0) {
|
||||
return null;
|
||||
}
|
||||
if (!me?.id) {
|
||||
return null;
|
||||
}
|
||||
const jid = toJID(me.id);
|
||||
const left = event.Leave;
|
||||
if (!left.includes(jid)) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
timestamp: Date.now(),
|
||||
group: {
|
||||
id: event.JID,
|
||||
},
|
||||
_data: event,
|
||||
};
|
||||
}
|
||||
|
||||
export function ToGroupV2UpdateEvent(
|
||||
event: GroupInfoEvent,
|
||||
): GroupV2UpdateEvent | null {
|
||||
const group = ToGroupInfoPartial(event);
|
||||
if (!group) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
timestamp: Date.now(),
|
||||
group: group,
|
||||
_data: event,
|
||||
};
|
||||
}
|
||||
|
||||
export function ToGroupV2ParticipantsEvents(
|
||||
event: GroupInfoEvent,
|
||||
): GroupV2ParticipantsEvent[] {
|
||||
const id: GroupId = {
|
||||
id: event.JID,
|
||||
};
|
||||
const events: GroupV2ParticipantsEvent[] = [];
|
||||
const maps = [
|
||||
{ participants: event.Leave, type: GroupParticipantType.LEAVE },
|
||||
{ participants: event.Join, type: GroupParticipantType.JOIN },
|
||||
{ participants: event.Promote, type: GroupParticipantType.PROMOTE },
|
||||
{ participants: event.Demote, type: GroupParticipantType.DEMOTE },
|
||||
];
|
||||
for (const map of maps) {
|
||||
const participants = getParticipants(map.participants, map.type);
|
||||
if (participants) {
|
||||
events.push({
|
||||
group: id,
|
||||
type: map.type,
|
||||
timestamp: Date.now(),
|
||||
participants: participants,
|
||||
_data: event,
|
||||
});
|
||||
}
|
||||
}
|
||||
return events;
|
||||
}
|
||||
|
||||
function getParticipants(jids: string[] | null, type: GroupParticipantType) {
|
||||
if (!jids) {
|
||||
return;
|
||||
}
|
||||
let role: GroupParticipantRole;
|
||||
switch (type) {
|
||||
case GroupParticipantType.JOIN:
|
||||
role = GroupParticipantRole.PARTICIPANT;
|
||||
break;
|
||||
case GroupParticipantType.LEAVE:
|
||||
role = GroupParticipantRole.LEFT;
|
||||
break;
|
||||
case GroupParticipantType.PROMOTE:
|
||||
role = GroupParticipantRole.ADMIN;
|
||||
break;
|
||||
case GroupParticipantType.DEMOTE:
|
||||
role = GroupParticipantRole.PARTICIPANT;
|
||||
break;
|
||||
}
|
||||
const participants: GroupParticipant[] = [];
|
||||
for (const jid of jids) {
|
||||
participants.push({
|
||||
id: jid,
|
||||
role: role,
|
||||
});
|
||||
}
|
||||
return participants;
|
||||
}
|
||||
|
||||
function ToGroupInfo(group: GroupInfoFull): GroupInfo {
|
||||
const participants: GroupParticipant[] = extractParticipants(
|
||||
group.Participants,
|
||||
);
|
||||
return {
|
||||
id: group.JID,
|
||||
subject: group.Name,
|
||||
description: group.Topic,
|
||||
invite: undefined,
|
||||
participants: participants,
|
||||
membersCanAddNewMember: group.MemberAddMode === 'all_member_add',
|
||||
membersCanSendMessages: !!group.IsAnnounce,
|
||||
newMembersApprovalRequired: group.IsJoinApprovalRequired,
|
||||
};
|
||||
}
|
||||
|
||||
function ToGroupInfoPartial(group: GroupInfoEvent): GroupInfo | null {
|
||||
const announce: boolean | undefined = group.Announce?.IsAnnounce;
|
||||
let membersCanSendMessages: boolean | undefined;
|
||||
// revert announce or undefined
|
||||
if (announce === undefined) {
|
||||
membersCanSendMessages = undefined;
|
||||
} else {
|
||||
membersCanSendMessages = !announce;
|
||||
}
|
||||
|
||||
const info = {
|
||||
id: group.JID,
|
||||
subject: group.Name?.Name,
|
||||
description: group.Topic?.Topic,
|
||||
invite: group.NewInviteLink || undefined,
|
||||
membersCanAddNewMember: undefined,
|
||||
membersCanSendMessages: membersCanSendMessages,
|
||||
newMembersApprovalRequired:
|
||||
group.MembershipApprovalMode?.IsJoinApprovalRequired,
|
||||
participants: undefined,
|
||||
};
|
||||
// check if all fields are undefined but id - return null
|
||||
for (const key in info) {
|
||||
if (key !== 'id' && info[key] !== undefined) {
|
||||
return info;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function extractParticipants(
|
||||
participants: GOWSGroupParticipant[],
|
||||
): GroupParticipant[] {
|
||||
const result: GroupParticipant[] = [];
|
||||
for (const participant of participants) {
|
||||
let role: GroupParticipantRole;
|
||||
if (participant.IsSuperAdmin) {
|
||||
role = GroupParticipantRole.SUPERADMIN;
|
||||
} else if (participant.IsAdmin) {
|
||||
role = GroupParticipantRole.ADMIN;
|
||||
} else {
|
||||
role = GroupParticipantRole.PARTICIPANT;
|
||||
}
|
||||
result.push({
|
||||
id: participant.JID,
|
||||
role: role,
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
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,108 @@
|
||||
// JID just a string alias, we dump it as String in golang
|
||||
type JID = string;
|
||||
|
||||
interface GroupName {
|
||||
Name: string;
|
||||
NameSetAt: string; // ISO string format for time
|
||||
NameSetBy: JID;
|
||||
}
|
||||
|
||||
interface GroupTopic {
|
||||
Topic: string;
|
||||
TopicID: string;
|
||||
TopicSetAt: string;
|
||||
TopicSetBy: JID;
|
||||
TopicDeleted: boolean;
|
||||
}
|
||||
|
||||
interface GroupLocked {
|
||||
IsLocked: boolean;
|
||||
}
|
||||
|
||||
interface GroupAnnounce {
|
||||
IsAnnounce: boolean;
|
||||
AnnounceVersionID: string;
|
||||
}
|
||||
|
||||
interface GroupEphemeral {
|
||||
IsEphemeral: boolean;
|
||||
DisappearingTimer: number;
|
||||
}
|
||||
|
||||
interface GroupMembershipApprovalMode {
|
||||
IsJoinApprovalRequired: boolean;
|
||||
}
|
||||
|
||||
interface GroupDelete {
|
||||
Deleted: boolean;
|
||||
DeleteReason: string;
|
||||
}
|
||||
|
||||
interface GroupLinkTarget {
|
||||
JID: JID;
|
||||
GroupName: GroupName;
|
||||
GroupIsDefaultSub: GroupIsDefaultSub;
|
||||
}
|
||||
|
||||
interface GroupLinkChange {
|
||||
Type: string; // Enum in Go: GroupLinkChangeType
|
||||
UnlinkReason: string; // Enum in Go: GroupUnlinkReason
|
||||
Group: GroupLinkTarget;
|
||||
}
|
||||
|
||||
interface GroupIsDefaultSub {
|
||||
IsDefaultSubGroup: boolean;
|
||||
}
|
||||
|
||||
export interface GroupInfoEvent {
|
||||
JID: JID;
|
||||
Notify: string;
|
||||
Sender?: JID;
|
||||
Timestamp: string;
|
||||
|
||||
Name?: GroupName;
|
||||
Topic?: GroupTopic;
|
||||
Locked?: GroupLocked;
|
||||
Announce?: GroupAnnounce;
|
||||
Ephemeral?: GroupEphemeral;
|
||||
|
||||
MembershipApprovalMode?: GroupMembershipApprovalMode;
|
||||
Delete?: GroupDelete;
|
||||
|
||||
Link?: GroupLinkChange;
|
||||
Unlink?: GroupLinkChange;
|
||||
NewInviteLink?: string;
|
||||
|
||||
PrevParticipantVersionID: string;
|
||||
ParticipantVersionID: string;
|
||||
|
||||
JoinReason: string;
|
||||
|
||||
Join: JID[];
|
||||
Leave: JID[];
|
||||
Promote: JID[];
|
||||
Demote: JID[];
|
||||
}
|
||||
|
||||
export interface GOWSGroupParticipant {
|
||||
JID: JID;
|
||||
IsAdmin: boolean;
|
||||
IsSuperAdmin: boolean;
|
||||
}
|
||||
|
||||
export interface GroupInfoFull {
|
||||
JID: JID;
|
||||
OwnerJID: JID;
|
||||
Name: string; // Name has additional properties here
|
||||
Topic: string; // Topic has additional properties here
|
||||
IsLocked: boolean; // specifies whether the group info can only be edited by admins.
|
||||
IsAnnounce: boolean; // specifies whether only admins can send messages in the group
|
||||
Participants: GOWSGroupParticipant[];
|
||||
MemberAddMode: string; // all_member_add | admin_add
|
||||
IsJoinApprovalRequired: boolean;
|
||||
}
|
||||
|
||||
export interface JoinedGroupEvent extends GroupInfoFull {
|
||||
Reason: string;
|
||||
Type: string;
|
||||
}
|
||||
@@ -22,3 +22,20 @@ export interface ChatPresence {
|
||||
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,
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
import * as NodeCache from 'node-cache';
|
||||
|
||||
export class PresenceProxy {
|
||||
private cache: NodeCache;
|
||||
|
||||
public proxy: Record<string, any>;
|
||||
|
||||
constructor() {
|
||||
this.cache = new NodeCache({
|
||||
stdTTL: 60, // 1 minute
|
||||
checkperiod: 60, // 1 minute
|
||||
useClones: false,
|
||||
});
|
||||
|
||||
this.proxy = new Proxy(
|
||||
{},
|
||||
{
|
||||
get: (_, prop: string) => {
|
||||
return this.cache.get(prop);
|
||||
},
|
||||
|
||||
set: (_, prop: string, value) => {
|
||||
this.cache.set(prop, value);
|
||||
return true;
|
||||
},
|
||||
|
||||
deleteProperty: (_, prop: string) => {
|
||||
return this.cache.del(prop) > 0;
|
||||
},
|
||||
|
||||
has: (_, prop: string) => {
|
||||
return this.cache.has(prop);
|
||||
},
|
||||
|
||||
ownKeys: () => {
|
||||
return this.cache.keys();
|
||||
},
|
||||
|
||||
getOwnPropertyDescriptor: (_, prop: string) => {
|
||||
if (this.cache.has(prop)) {
|
||||
return {
|
||||
enumerable: true,
|
||||
configurable: true,
|
||||
};
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
import { GroupMetadata, jidNormalizedUser } from '@adiwajshing/baileys';
|
||||
import type { Contact } from '@adiwajshing/baileys/lib/Types/Contact';
|
||||
import {
|
||||
GroupParticipant as NOWEBGroupParticipant,
|
||||
ParticipantAction,
|
||||
} from '@adiwajshing/baileys/lib/Types/GroupMetadata';
|
||||
import { getGroupInviteLink } from '@waha/core/abc/session.abc';
|
||||
import { toCusFormat } from '@waha/core/engines/noweb/session.noweb.core';
|
||||
import {
|
||||
GroupInfo,
|
||||
GroupParticipant,
|
||||
GroupParticipantRole,
|
||||
} from '@waha/structures/groups.dto';
|
||||
import {
|
||||
GroupParticipantType,
|
||||
GroupV2JoinEvent,
|
||||
GroupV2LeaveEvent,
|
||||
GroupV2ParticipantsEvent,
|
||||
GroupV2UpdateEvent,
|
||||
} from '@waha/structures/groups.events.dto';
|
||||
|
||||
export function ToGroupInfo(group: Partial<GroupMetadata>): GroupInfo {
|
||||
let participants: GroupParticipant[] = undefined;
|
||||
if (group.participants && group.participants.length > 0) {
|
||||
participants = group.participants.map(ToGroupParticipant);
|
||||
}
|
||||
return {
|
||||
id: group.id,
|
||||
subject: group.subject,
|
||||
description: group.desc,
|
||||
invite: group.inviteCode ? getGroupInviteLink(group.inviteCode) : undefined,
|
||||
participants: participants,
|
||||
membersCanAddNewMember: group.memberAddMode,
|
||||
membersCanSendMessages: group.announce,
|
||||
newMembersApprovalRequired: group.joinApprovalMode,
|
||||
};
|
||||
}
|
||||
|
||||
export function ToGroupV2JoinEvent(group: GroupMetadata): GroupV2JoinEvent {
|
||||
return {
|
||||
timestamp: Date.now(),
|
||||
group: ToGroupInfo(group),
|
||||
_data: group,
|
||||
};
|
||||
}
|
||||
|
||||
export function ToGroupParticipant(
|
||||
participant: NOWEBGroupParticipant,
|
||||
): GroupParticipant {
|
||||
let role: GroupParticipantRole = GroupParticipantRole.PARTICIPANT;
|
||||
if (participant.admin === 'admin') {
|
||||
role = GroupParticipantRole.ADMIN;
|
||||
} else if (participant.admin === 'superadmin') {
|
||||
role = GroupParticipantRole.SUPERADMIN;
|
||||
}
|
||||
return {
|
||||
id: toCusFormat(participant.id),
|
||||
role: role,
|
||||
};
|
||||
}
|
||||
|
||||
interface GroupParticipantUpdate {
|
||||
id: string;
|
||||
author: string;
|
||||
participants: string[];
|
||||
action: ParticipantAction;
|
||||
}
|
||||
|
||||
export function ToGroupV2Participants(
|
||||
update: GroupParticipantUpdate,
|
||||
): GroupV2ParticipantsEvent {
|
||||
let role: GroupParticipantRole;
|
||||
let type: GroupParticipantType;
|
||||
switch (update.action) {
|
||||
case 'add':
|
||||
role = GroupParticipantRole.PARTICIPANT;
|
||||
type = GroupParticipantType.JOIN;
|
||||
break;
|
||||
case 'remove':
|
||||
role = GroupParticipantRole.LEFT;
|
||||
type = GroupParticipantType.LEAVE;
|
||||
break;
|
||||
case 'promote':
|
||||
role = GroupParticipantRole.ADMIN;
|
||||
type = GroupParticipantType.PROMOTE;
|
||||
break;
|
||||
case 'demote':
|
||||
role = GroupParticipantRole.ADMIN;
|
||||
type = GroupParticipantType.DEMOTE;
|
||||
break;
|
||||
}
|
||||
|
||||
const participants: GroupParticipant[] = update.participants.map((id) => {
|
||||
return {
|
||||
id: toCusFormat(id),
|
||||
role: role,
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
group: {
|
||||
id: toCusFormat(update.id),
|
||||
},
|
||||
type: type,
|
||||
timestamp: Date.now(),
|
||||
participants: participants,
|
||||
_data: update,
|
||||
};
|
||||
}
|
||||
|
||||
export function ToGroupV2UpdateEvent(
|
||||
group: Partial<GroupMetadata>,
|
||||
): GroupV2UpdateEvent {
|
||||
return {
|
||||
timestamp: Date.now(),
|
||||
group: ToGroupInfo(group),
|
||||
_data: group,
|
||||
};
|
||||
}
|
||||
|
||||
export function ToGroupV2LeaveEvent(
|
||||
me: Contact,
|
||||
update: GroupParticipantUpdate,
|
||||
): GroupV2LeaveEvent | null {
|
||||
if (update.action !== 'remove') {
|
||||
return null;
|
||||
}
|
||||
if (!me) {
|
||||
return null;
|
||||
}
|
||||
const meId = jidNormalizedUser(me.id);
|
||||
if (!update.participants.includes(meId)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
timestamp: Date.now(),
|
||||
group: {
|
||||
id: toCusFormat(update.id),
|
||||
},
|
||||
_data: update,
|
||||
};
|
||||
}
|
||||
@@ -5,17 +5,16 @@ import makeWASocket, {
|
||||
DisconnectReason,
|
||||
downloadMediaMessage,
|
||||
extractMessageContent,
|
||||
generateMessageIDV2,
|
||||
getAggregateVotesInPollMessage,
|
||||
getContentType,
|
||||
getKeyAuthor,
|
||||
getUrlFromDirectPath,
|
||||
isJidGroup,
|
||||
isJidNewsletter,
|
||||
isJidStatusBroadcast,
|
||||
isJidUser,
|
||||
isRealMessage,
|
||||
jidNormalizedUser,
|
||||
makeCacheableSignalKeyStore,
|
||||
MiscMessageGenerationOptions,
|
||||
NewsletterMetadata,
|
||||
normalizeMessageContent,
|
||||
PresenceData,
|
||||
@@ -37,20 +36,49 @@ import {
|
||||
LabelAssociationType,
|
||||
} from '@adiwajshing/baileys/lib/Types/LabelAssociation';
|
||||
import { MessageUserReceiptUpdate } from '@adiwajshing/baileys/lib/Types/Message';
|
||||
import { isLidUser } from '@adiwajshing/baileys/lib/WABinary/jid-utils';
|
||||
import {
|
||||
isJidBroadcast,
|
||||
isLidUser,
|
||||
} from '@adiwajshing/baileys/lib/WABinary/jid-utils';
|
||||
import { Logger as BaileysLogger } from '@adiwajshing/baileys/node_modules/pino';
|
||||
import { UnprocessableEntityException } from '@nestjs/common';
|
||||
import {
|
||||
ensureSuffix,
|
||||
getChannelInviteLink,
|
||||
getPublicUrlFromDirectPath,
|
||||
isNewsletter,
|
||||
WhatsappSession,
|
||||
} from '@waha/core/abc/session.abc';
|
||||
import {
|
||||
ToGroupV2JoinEvent,
|
||||
ToGroupV2LeaveEvent,
|
||||
ToGroupV2Participants,
|
||||
ToGroupV2UpdateEvent,
|
||||
} from '@waha/core/engines/noweb/groups.noweb';
|
||||
import { sendButtonMessage } from '@waha/core/engines/noweb/noweb.buttons';
|
||||
import { NowebAuthFactoryCore } from '@waha/core/engines/noweb/NowebAuthFactoryCore';
|
||||
import { NowebInMemoryStore } from '@waha/core/engines/noweb/store/NowebInMemoryStore';
|
||||
import {
|
||||
AvailableInPlusVersion,
|
||||
NotImplementedByEngineError,
|
||||
} from '@waha/core/exceptions';
|
||||
import { toVcard } from '@waha/core/helpers';
|
||||
import { createAgentProxy } from '@waha/core/helpers.proxy';
|
||||
import { IMediaEngineProcessor } from '@waha/core/media/IMediaEngineProcessor';
|
||||
import { QR } from '@waha/core/QR';
|
||||
import { flipObject, 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 {
|
||||
ChatSummary,
|
||||
@@ -60,8 +88,43 @@ import {
|
||||
PinDuration,
|
||||
} from '@waha/structures/chats.dto';
|
||||
import { SendButtonsRequest } from '@waha/structures/chatting.buttons.dto';
|
||||
import {
|
||||
ChatRequest,
|
||||
CheckNumberStatusQuery,
|
||||
EditMessageRequest,
|
||||
MessageContactVcardRequest,
|
||||
MessageDestination,
|
||||
MessageFileRequest,
|
||||
MessageForwardRequest,
|
||||
MessageImageRequest,
|
||||
MessageLinkCustomPreviewRequest,
|
||||
MessageLinkPreviewRequest,
|
||||
MessageLocationRequest,
|
||||
MessagePollRequest,
|
||||
MessageReactionRequest,
|
||||
MessageReplyRequest,
|
||||
MessageStarRequest,
|
||||
MessageTextRequest,
|
||||
MessageVoiceRequest,
|
||||
SendSeenRequest,
|
||||
WANumberExistResult,
|
||||
} from '@waha/structures/chatting.dto';
|
||||
import { ContactQuery, ContactRequest } from '@waha/structures/contacts.dto';
|
||||
import {
|
||||
ACK_UNKNOWN,
|
||||
SECOND,
|
||||
WAHAEngine,
|
||||
WAHAEvents,
|
||||
WAHAPresenceStatus,
|
||||
WAHASessionStatus,
|
||||
WAMessageAck,
|
||||
} from '@waha/structures/enums.dto';
|
||||
import { BinaryFile, RemoteFile } from '@waha/structures/files.dto';
|
||||
import {
|
||||
CreateGroupRequest,
|
||||
ParticipantsRequest,
|
||||
SettingsSecurityChangeInfo,
|
||||
} from '@waha/structures/groups.dto';
|
||||
import {
|
||||
Label,
|
||||
LabelChatAssociation,
|
||||
@@ -70,17 +133,31 @@ import {
|
||||
} from '@waha/structures/labels.dto';
|
||||
import { ReplyToMessage } from '@waha/structures/message.dto';
|
||||
import { PaginationParams } from '@waha/structures/pagination.dto';
|
||||
import {
|
||||
WAHAChatPresences,
|
||||
WAHAPresenceData,
|
||||
} from '@waha/structures/presence.dto';
|
||||
import { WAMessage, WAMessageReaction } from '@waha/structures/responses.dto';
|
||||
import { MeInfo } from '@waha/structures/sessions.dto';
|
||||
import {
|
||||
BROADCAST_ID,
|
||||
DeleteStatusRequest,
|
||||
StatusRequest,
|
||||
TextStatus,
|
||||
} from '@waha/structures/status.dto';
|
||||
import {
|
||||
EnginePayload,
|
||||
PollVote,
|
||||
PollVotePayload,
|
||||
WAMessageAckBody,
|
||||
WAMessageRevokedBody,
|
||||
} from '@waha/structures/webhooks.dto';
|
||||
import { LoggerBuilder } from '@waha/utils/logging';
|
||||
import { sleep, waitUntil } from '@waha/utils/promiseTimeout';
|
||||
import { exclude } from '@waha/utils/reactive/ops/exclude';
|
||||
import { SingleDelayedJobRunner } from '@waha/utils/SingleDelayedJobRunner';
|
||||
import { SinglePeriodicJobRunner } from '@waha/utils/SinglePeriodicJobRunner';
|
||||
import { StatusTracker } from '@waha/utils/StatusTracker';
|
||||
import * as Buffer from 'buffer';
|
||||
import { Agent } from 'https';
|
||||
import * as lodash from 'lodash';
|
||||
@@ -95,71 +172,11 @@ import {
|
||||
Observable,
|
||||
partition,
|
||||
share,
|
||||
Subject,
|
||||
} from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const promiseRetry = require('promise-retry');
|
||||
|
||||
import {
|
||||
ChatRequest,
|
||||
CheckNumberStatusQuery,
|
||||
EditMessageRequest,
|
||||
MessageContactVcardRequest,
|
||||
MessageDestination,
|
||||
MessageFileRequest,
|
||||
MessageForwardRequest,
|
||||
MessageImageRequest,
|
||||
MessageLinkPreviewRequest,
|
||||
MessageLocationRequest,
|
||||
MessagePollRequest,
|
||||
MessageReactionRequest,
|
||||
MessageReplyRequest,
|
||||
MessageStarRequest,
|
||||
MessageTextRequest,
|
||||
MessageVoiceRequest,
|
||||
SendSeenRequest,
|
||||
WANumberExistResult,
|
||||
} from '../../../structures/chatting.dto';
|
||||
import {
|
||||
ACK_UNKNOWN,
|
||||
SECOND,
|
||||
WAHAEngine,
|
||||
WAHAEvents,
|
||||
WAHAPresenceStatus,
|
||||
WAHASessionStatus,
|
||||
WAMessageAck,
|
||||
} from '../../../structures/enums.dto';
|
||||
import {
|
||||
CreateGroupRequest,
|
||||
ParticipantsRequest,
|
||||
} from '../../../structures/groups.dto';
|
||||
import {
|
||||
WAHAChatPresences,
|
||||
WAHAPresenceData,
|
||||
} from '../../../structures/presence.dto';
|
||||
import {
|
||||
WAMessage,
|
||||
WAMessageReaction,
|
||||
} from '../../../structures/responses.dto';
|
||||
import { MeInfo } from '../../../structures/sessions.dto';
|
||||
import {
|
||||
BROADCAST_ID,
|
||||
DeleteStatusRequest,
|
||||
TextStatus,
|
||||
} from '../../../structures/status.dto';
|
||||
import {
|
||||
ensureSuffix,
|
||||
getChannelInviteLink,
|
||||
isNewsletter,
|
||||
WhatsappSession,
|
||||
} from '../../abc/session.abc';
|
||||
import {
|
||||
AvailableInPlusVersion,
|
||||
NotImplementedByEngineError,
|
||||
} from '../../exceptions';
|
||||
import { toVcard } from '../../helpers';
|
||||
import { createAgentProxy } from '../../helpers.proxy';
|
||||
import { QR } from '../../QR';
|
||||
import { NowebAuthFactoryCore } from './NowebAuthFactoryCore';
|
||||
import { INowebStore } from './store/INowebStore';
|
||||
import { NowebPersistentStore } from './store/NowebPersistentStore';
|
||||
import { NowebStorageFactoryCore } from './store/NowebStorageFactoryCore';
|
||||
@@ -205,6 +222,8 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
|
||||
store: INowebStore;
|
||||
private qr: QR;
|
||||
|
||||
private statusTracker = new StatusTracker();
|
||||
|
||||
public constructor(config) {
|
||||
super(config);
|
||||
this.shouldRestart = true;
|
||||
@@ -243,6 +262,15 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
|
||||
this.authNOWEBStore = null;
|
||||
}
|
||||
|
||||
protected set status(value: WAHASessionStatus) {
|
||||
this.statusTracker.track(value);
|
||||
super.status = value;
|
||||
}
|
||||
|
||||
public get status() {
|
||||
return super.status;
|
||||
}
|
||||
|
||||
async start() {
|
||||
this.status = WAHASessionStatus.STARTING;
|
||||
await this.buildClient().catch((err) => {
|
||||
@@ -371,7 +399,7 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
|
||||
this.listenConnectionEvents();
|
||||
this.subscribeEngineEvents2();
|
||||
this.listenContactsUpdatePictureProfile();
|
||||
this.enableAutoRestart();
|
||||
// this.enableAutoRestart();
|
||||
}
|
||||
|
||||
private enableAutoRestart() {
|
||||
@@ -408,6 +436,7 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
this.startDelayedJob.schedule(async () => {
|
||||
if (!this.shouldRestart) {
|
||||
this.logger.warn(
|
||||
@@ -432,12 +461,36 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
|
||||
this.resubscribeToKnownPresences();
|
||||
return;
|
||||
} else if (connection === 'close') {
|
||||
const shouldReconnect =
|
||||
// @ts-ignore: Property output does not exist on type 'Error'
|
||||
lastDisconnect.error?.output?.statusCode !==
|
||||
DisconnectReason.loggedOut;
|
||||
this.qr.save('');
|
||||
// reconnect if not logged out
|
||||
const statusCode = lastDisconnect.error?.output?.statusCode;
|
||||
|
||||
// Restart required from the server
|
||||
const restartRequired = statusCode === DisconnectReason.restartRequired;
|
||||
if (restartRequired) {
|
||||
this.restartClient();
|
||||
return;
|
||||
}
|
||||
|
||||
// Stuck in STARTING status
|
||||
if (this.statusTracker.isStuckInStarting()) {
|
||||
this.logger.error(
|
||||
'Session stuck in STARTING status, force stopping the session.',
|
||||
);
|
||||
await this.failed();
|
||||
return;
|
||||
}
|
||||
|
||||
// Do not reconnect if the QR code has not been scanned yet
|
||||
if (this.status == WAHASessionStatus.SCAN_QR_CODE) {
|
||||
this.logger.warn(
|
||||
'QR code has not been scanned yet, force stopping the session.',
|
||||
);
|
||||
await this.failed();
|
||||
return;
|
||||
}
|
||||
|
||||
// Reconnect if not logged out
|
||||
const shouldReconnect = statusCode !== DisconnectReason.loggedOut;
|
||||
if (shouldReconnect) {
|
||||
if (lastDisconnect.error) {
|
||||
this.logger.info(
|
||||
@@ -445,12 +498,14 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
|
||||
);
|
||||
}
|
||||
this.restartClient();
|
||||
} else {
|
||||
this.logger.error(
|
||||
`Connection closed due to '${lastDisconnect.error}', do not reconnect the session.`,
|
||||
);
|
||||
await this.failed();
|
||||
return;
|
||||
}
|
||||
|
||||
// Unknown error or logged out
|
||||
this.logger.error(
|
||||
`Connection closed due to '${lastDisconnect.error}', do not reconnect the session.`,
|
||||
);
|
||||
await this.failed();
|
||||
}
|
||||
|
||||
// Save QR
|
||||
@@ -642,6 +697,27 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Profile methods
|
||||
*/
|
||||
public async setProfileName(name: string): Promise<boolean> {
|
||||
await this.sock.updateProfileName(name);
|
||||
return true;
|
||||
}
|
||||
|
||||
public async setProfileStatus(status: string): Promise<boolean> {
|
||||
await this.sock.updateProfileStatus(status);
|
||||
return true;
|
||||
}
|
||||
|
||||
protected setProfilePicture(file: BinaryFile | RemoteFile): Promise<boolean> {
|
||||
throw new AvailableInPlusVersion();
|
||||
}
|
||||
|
||||
protected deleteProfilePicture(): Promise<boolean> {
|
||||
throw new AvailableInPlusVersion();
|
||||
}
|
||||
|
||||
/**
|
||||
* Other methods
|
||||
*/
|
||||
@@ -659,6 +735,10 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
|
||||
};
|
||||
}
|
||||
|
||||
async generateNewMessageId(): Promise<string> {
|
||||
return this.generateMessageID();
|
||||
}
|
||||
|
||||
async sendText(request: MessageTextRequest) {
|
||||
const chatId = toJID(this.ensureSuffix(request.chatId));
|
||||
const message = {
|
||||
@@ -666,14 +746,18 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
|
||||
mentions: request.mentions?.map(toJID),
|
||||
linkPreview: this.getLinkPreview(request),
|
||||
};
|
||||
const options = await this.getMessageOptions(request);
|
||||
const options: any = await this.getMessageOptions(request);
|
||||
options.linkPreviewHighQuality = request.linkPreviewHighQuality;
|
||||
return this.sock.sendMessage(chatId, message, options);
|
||||
}
|
||||
|
||||
public deleteMessage(chatId: string, messageId: string) {
|
||||
const jid = toJID(this.ensureSuffix(chatId));
|
||||
const key = parseMessageIdSerialized(messageId);
|
||||
return this.sock.sendMessage(jid, { delete: key });
|
||||
const options = {
|
||||
messageId: this.generateMessageID(),
|
||||
};
|
||||
return this.sock.sendMessage(jid, { delete: key }, options);
|
||||
}
|
||||
|
||||
public editMessage(
|
||||
@@ -687,18 +771,21 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
|
||||
text: request.text,
|
||||
mentions: request.mentions?.map(toJID),
|
||||
edit: key,
|
||||
linkPreview: this.getLinkPreview(request),
|
||||
linkPreviewHighQuality: request.linkPreviewHighQuality,
|
||||
};
|
||||
return this.sock.sendMessage(jid, message);
|
||||
const options = {
|
||||
messageId: this.generateMessageID(),
|
||||
};
|
||||
return this.sock.sendMessage(jid, message, options);
|
||||
}
|
||||
|
||||
async sendContactVCard(request: MessageContactVcardRequest) {
|
||||
const chatId = toJID(this.ensureSuffix(request.chatId));
|
||||
const contacts = request.contacts.map((el) => ({ vcard: toVcard(el) }));
|
||||
await this.sock.sendMessage(chatId, {
|
||||
contacts: {
|
||||
contacts: contacts,
|
||||
},
|
||||
});
|
||||
const options = await this.getMessageOptions(request);
|
||||
const msg = { contacts: { contacts: contacts } };
|
||||
return await this.sock.sendMessage(chatId, msg, options);
|
||||
}
|
||||
|
||||
async sendPoll(request: MessagePollRequest) {
|
||||
@@ -738,6 +825,12 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
|
||||
throw new AvailableInPlusVersion();
|
||||
}
|
||||
|
||||
sendLinkCustomPreview(
|
||||
request: MessageLinkCustomPreviewRequest,
|
||||
): Promise<any> {
|
||||
throw new AvailableInPlusVersion();
|
||||
}
|
||||
|
||||
protected async uploadMedia(
|
||||
file: RemoteFile | BinaryFile,
|
||||
type,
|
||||
@@ -762,13 +855,15 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
|
||||
);
|
||||
}
|
||||
|
||||
sendLocation(request: MessageLocationRequest) {
|
||||
return this.sock.sendMessage(request.chatId, {
|
||||
async sendLocation(request: MessageLocationRequest) {
|
||||
const msg = {
|
||||
location: {
|
||||
degreesLatitude: request.latitude,
|
||||
degreesLongitude: request.longitude,
|
||||
},
|
||||
});
|
||||
};
|
||||
const options = await this.getMessageOptions(request);
|
||||
return await this.sock.sendMessage(request.chatId, msg, options);
|
||||
}
|
||||
|
||||
async forwardMessage(request: MessageForwardRequest): Promise<WAMessage> {
|
||||
@@ -784,14 +879,17 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
|
||||
forward: forwardMessage,
|
||||
force: true,
|
||||
};
|
||||
const result = await this.sock.sendMessage(chatId, message, {});
|
||||
const options = await this.getMessageOptions(request);
|
||||
const result = await this.sock.sendMessage(chatId, message, options);
|
||||
return this.toWAMessage(result);
|
||||
}
|
||||
|
||||
sendLinkPreview(request: MessageLinkPreviewRequest) {
|
||||
async sendLinkPreview(request: MessageLinkPreviewRequest) {
|
||||
const text = `${request.title}\n${request.url}`;
|
||||
const chatId = toJID(this.ensureSuffix(request.chatId));
|
||||
return this.sock.sendMessage(chatId, { text: text });
|
||||
const msg = { text: text };
|
||||
const options = await this.getMessageOptions(request);
|
||||
return this.sock.sendMessage(chatId, msg, options);
|
||||
}
|
||||
|
||||
async sendSeen(request: SendSeenRequest) {
|
||||
@@ -880,13 +978,34 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
|
||||
|
||||
async setReaction(request: MessageReactionRequest) {
|
||||
const key = parseMessageIdSerialized(request.messageId);
|
||||
const reactionMessage = {
|
||||
react: {
|
||||
text: request.reaction,
|
||||
key: key,
|
||||
},
|
||||
};
|
||||
return this.sock.sendMessage(key.remoteJid, reactionMessage);
|
||||
if (isNewsletter(key.remoteJid)) {
|
||||
let serverId = Number(key.id);
|
||||
if (!serverId) {
|
||||
const msg = await this.store.getMessageById(key.remoteJid, key.id);
|
||||
if (msg) {
|
||||
// @ts-ignore
|
||||
serverId = Number(msg.key.server_id);
|
||||
}
|
||||
}
|
||||
if (!serverId) {
|
||||
throw new UnprocessableEntityException(
|
||||
`Unable to get server id for channel message '${key.id}'`,
|
||||
);
|
||||
}
|
||||
return this.sock.newsletterReactMessage(
|
||||
key.remoteJid,
|
||||
serverId.toString(),
|
||||
request.reaction,
|
||||
);
|
||||
} else {
|
||||
const reactionMessage = {
|
||||
react: {
|
||||
text: request.reaction,
|
||||
key: key,
|
||||
},
|
||||
};
|
||||
return this.sock.sendMessage(key.remoteJid, reactionMessage);
|
||||
}
|
||||
}
|
||||
|
||||
async setStar(request: MessageStarRequest) {
|
||||
@@ -1104,18 +1223,8 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
|
||||
return contacts.map(this.toWAContact);
|
||||
}
|
||||
|
||||
public async getContactAbout(query: ContactQuery) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public async fetchContactProfilePicture(id: string) {
|
||||
const contact = this.ensureSuffix(id);
|
||||
if (isJidNewsletter(id)) {
|
||||
return null;
|
||||
}
|
||||
if (isJidStatusBroadcast(id)) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
const url = await this.sock.profilePictureUrl(contact, 'image');
|
||||
return url;
|
||||
@@ -1160,6 +1269,10 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
|
||||
return lodash.keyBy(groups, 'id');
|
||||
}
|
||||
|
||||
protected removeGroupsFieldParticipant(group: any) {
|
||||
delete group.participants;
|
||||
}
|
||||
|
||||
public async refreshGroups(): Promise<boolean> {
|
||||
this.store.resetGroupsCache();
|
||||
await this.store.getGroups({});
|
||||
@@ -1168,13 +1281,37 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
|
||||
|
||||
public async getGroup(id) {
|
||||
const groups = await this.getGroups({});
|
||||
return groups[id];
|
||||
const group = groups[id];
|
||||
if (!group) {
|
||||
throw new Error(`Group with id '${id}' not found`);
|
||||
}
|
||||
return group;
|
||||
}
|
||||
|
||||
public async deleteGroup(id) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public async getInfoAdminsOnly(id): Promise<SettingsSecurityChangeInfo> {
|
||||
const group = await this.getGroup(id);
|
||||
return { adminsOnly: group.restrict };
|
||||
}
|
||||
|
||||
public async setInfoAdminsOnly(id, value) {
|
||||
const setting = value ? 'locked' : 'unlocked';
|
||||
return await this.sock.groupSettingUpdate(id, setting);
|
||||
}
|
||||
|
||||
public async getMessagesAdminsOnly(id): Promise<SettingsSecurityChangeInfo> {
|
||||
const group = await this.getGroup(id);
|
||||
return { adminsOnly: group.announce };
|
||||
}
|
||||
|
||||
public async setMessagesAdminsOnly(id, value) {
|
||||
const setting = value ? 'announcement' : 'not_announcement';
|
||||
return await this.sock.groupSettingUpdate(id, setting);
|
||||
}
|
||||
|
||||
public async leaveGroup(id) {
|
||||
return this.sock.groupLeave(id);
|
||||
}
|
||||
@@ -1246,7 +1383,7 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
|
||||
public async getPresence(chatId: string): Promise<WAHAChatPresences> {
|
||||
const remoteJid = toJID(chatId);
|
||||
if (!(remoteJid in this.store.presences)) {
|
||||
this.store.presences[remoteJid] = [];
|
||||
this.store.presences[remoteJid] = {};
|
||||
await this.sock.presenceSubscribe(remoteJid);
|
||||
await sleep(1000);
|
||||
}
|
||||
@@ -1262,16 +1399,101 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
|
||||
/**
|
||||
* Status methods
|
||||
*/
|
||||
public async sendStatusMessage(
|
||||
message: any,
|
||||
options: any,
|
||||
jids: string[],
|
||||
batchSize?: number,
|
||||
) {
|
||||
if (!batchSize || batchSize == 0) {
|
||||
batchSize = 5_000;
|
||||
}
|
||||
const chunks = lodash.chunk(jids, batchSize);
|
||||
if (chunks.length == 0) {
|
||||
throw new UnprocessableEntityException('No participants to send status');
|
||||
}
|
||||
|
||||
const logger = this.logger.child({
|
||||
'message.id': options.messageId,
|
||||
chunks: chunks.length,
|
||||
size: batchSize,
|
||||
});
|
||||
logger.info(`Sending status message to ${jids.length} participants`);
|
||||
let result = null;
|
||||
for (const [index, participants] of chunks.entries()) {
|
||||
const batchOptions = { ...options };
|
||||
batchOptions.statusJidList = participants;
|
||||
const r = await this.sendStatusMessageOneChunk(
|
||||
message,
|
||||
batchOptions,
|
||||
logger,
|
||||
index,
|
||||
);
|
||||
result = result || r;
|
||||
}
|
||||
logger.info(
|
||||
`Sending status message to ${jids.length} participants - success`,
|
||||
);
|
||||
return result;
|
||||
}
|
||||
|
||||
private async sendStatusMessageOneChunk(
|
||||
message: any,
|
||||
options: any,
|
||||
logger: any,
|
||||
index: number,
|
||||
) {
|
||||
// https://github.com/IndigoUnited/node-promise-retry
|
||||
const retryOptions = {
|
||||
retries: 5,
|
||||
minTimeout: 1000,
|
||||
maxTimeout: 6000,
|
||||
};
|
||||
try {
|
||||
const resp = await promiseRetry((retry, number) => {
|
||||
return this.sock
|
||||
.sendMessage(BROADCAST_ID, message, options)
|
||||
.catch(retry);
|
||||
}, retryOptions);
|
||||
logger.info(`Sending status message (${index + 1} chunk) - success`);
|
||||
return resp;
|
||||
} catch (err) {
|
||||
logger.error(`Sending status message (${index + 1} chunk - failed`);
|
||||
logger.error(err, err.stack);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
public async sendTextStatus(status: TextStatus) {
|
||||
const message = { text: status.text };
|
||||
const message = {
|
||||
text: status.text,
|
||||
linkPreview: this.getLinkPreview(status),
|
||||
};
|
||||
const jids = await this.prepareJidsForStatus(status.contacts);
|
||||
const options = {
|
||||
if (!status.id) {
|
||||
this.upsertMeInJIDs(jids);
|
||||
}
|
||||
const messageId = this.prepareMessageIdForStatus(status);
|
||||
const options: MiscMessageGenerationOptions = {
|
||||
backgroundColor: status.backgroundColor,
|
||||
font: status.font,
|
||||
statusJidList: jids,
|
||||
linkPreviewHighQuality: status.linkPreviewHighQuality,
|
||||
messageId: messageId,
|
||||
};
|
||||
return await this.sendStatusMessage(
|
||||
message,
|
||||
options,
|
||||
jids,
|
||||
status.contacts?.length,
|
||||
);
|
||||
}
|
||||
|
||||
return await this.sock.sendMessage(BROADCAST_ID, message, options);
|
||||
protected prepareMessageIdForStatus(status: StatusRequest) {
|
||||
if (status.id) {
|
||||
this.saveSentMessageId(status.id);
|
||||
return status.id;
|
||||
}
|
||||
return this.generateMessageID();
|
||||
}
|
||||
|
||||
protected async prepareJidsForStatus(contacts: string[]) {
|
||||
@@ -1281,7 +1503,6 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
|
||||
} else {
|
||||
jids = await this.fetchMyContactsJids();
|
||||
}
|
||||
this.upsertMeInJIDs(jids);
|
||||
return jids;
|
||||
}
|
||||
|
||||
@@ -1297,10 +1518,18 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
|
||||
key.fromMe = true;
|
||||
key.remoteJid = BROADCAST_ID;
|
||||
const jids = await this.prepareJidsForStatus(request.contacts);
|
||||
this.upsertMeInJIDs(jids);
|
||||
const newMessageId = this.generateMessageID();
|
||||
const options = {
|
||||
statusJidList: jids,
|
||||
messageId: newMessageId,
|
||||
};
|
||||
return await this.sock.sendMessage(BROADCAST_ID, { delete: key }, options);
|
||||
return await this.sendStatusMessage(
|
||||
{ delete: key },
|
||||
options,
|
||||
jids,
|
||||
request.contacts?.length,
|
||||
);
|
||||
}
|
||||
|
||||
protected upsertMeInJIDs(jids: string[]) {
|
||||
@@ -1309,13 +1538,33 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
|
||||
}
|
||||
const myJID = jidNormalizedUser(this.sock.authState.creds.me.id);
|
||||
if (!jids.includes(myJID)) {
|
||||
jids.push(myJID);
|
||||
// insert my jid first
|
||||
jids.unshift(myJID);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 toChannel(newsletter: NewsletterMetadata): Channel {
|
||||
const role =
|
||||
// @ts-ignore
|
||||
@@ -1323,20 +1572,21 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
|
||||
(newsletter.viewer_metadata?.view_role as ChannelRole) ||
|
||||
ChannelRole.GUEST;
|
||||
const preview = newsletter.preview
|
||||
? getUrlFromDirectPath(newsletter.preview)
|
||||
? getPublicUrlFromDirectPath(newsletter.preview)
|
||||
: null;
|
||||
const picture = newsletter.picture
|
||||
? getUrlFromDirectPath(newsletter.picture)
|
||||
? getPublicUrlFromDirectPath(newsletter.picture)
|
||||
: null;
|
||||
return {
|
||||
id: newsletter.id,
|
||||
name: newsletter.name,
|
||||
description: newsletter.description,
|
||||
invite: getChannelInviteLink(newsletter.invite),
|
||||
preview: preview,
|
||||
picture: picture,
|
||||
preview: preview || picture,
|
||||
picture: picture || preview,
|
||||
verified: newsletter.verification === 'VERIFIED',
|
||||
role: role,
|
||||
subscribersCount: newsletter.subscribers,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1426,6 +1676,24 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
|
||||
this.events2.get(WAHAEvents.MESSAGE).switch(messagesFromOthers$);
|
||||
this.events2.get(WAHAEvents.MESSAGE_ANY).switch(messagesFromAll$);
|
||||
|
||||
const messagesRevoked$ = messagesUpsert$.pipe(
|
||||
// @ts-ignore
|
||||
filter(
|
||||
(message) =>
|
||||
message.message.protocolMessage?.type ===
|
||||
proto.Message.ProtocolMessage.Type.REVOKE,
|
||||
),
|
||||
mergeMap(async (message): Promise<WAMessageRevokedBody> => {
|
||||
const afterMessage = await this.toWAMessage(message);
|
||||
return {
|
||||
after: afterMessage,
|
||||
before: null,
|
||||
_data: message,
|
||||
};
|
||||
}),
|
||||
);
|
||||
this.events2.get(WAHAEvents.MESSAGE_REVOKED).switch(messagesRevoked$);
|
||||
|
||||
//
|
||||
// Message Reactions
|
||||
//
|
||||
@@ -1469,12 +1737,51 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
|
||||
this.events2
|
||||
.get(WAHAEvents.STATE_CHANGE)
|
||||
.switch(fromEvent(this.sock.ev, 'connection.update'));
|
||||
this.events2.get(WAHAEvents.GROUP_JOIN).switch(
|
||||
|
||||
const groupsUpsert$: Observable<GroupMetadata> = fromEvent(
|
||||
this.sock.ev,
|
||||
'groups.upsert',
|
||||
).pipe(
|
||||
// @ts-ignore
|
||||
fromEvent<GroupMetadata[]>(this.sock.ev, 'groups.upsert').pipe(
|
||||
mergeAll(),
|
||||
),
|
||||
mergeAll(),
|
||||
);
|
||||
const groupsUpdate$: Observable<Partial<GroupMetadata>> = fromEvent(
|
||||
this.sock.ev,
|
||||
'groups.update',
|
||||
).pipe(
|
||||
// @ts-ignore
|
||||
mergeAll(),
|
||||
);
|
||||
const groupsParticipantsUpdate$: Observable<any> = fromEvent(
|
||||
this.sock.ev,
|
||||
'group-participants.update',
|
||||
);
|
||||
|
||||
this.events2.get(WAHAEvents.GROUP_JOIN).switch(groupsUpsert$);
|
||||
|
||||
const groupV2Join$ = groupsUpsert$.pipe(
|
||||
map((group) => ToGroupV2JoinEvent(group)),
|
||||
);
|
||||
this.events2.get(WAHAEvents.GROUP_V2_JOIN).switch(groupV2Join$);
|
||||
|
||||
const groupV2Update$ = merge(groupsUpdate$).pipe(map(ToGroupV2UpdateEvent));
|
||||
this.events2.get(WAHAEvents.GROUP_V2_UPDATE).switch(groupV2Update$);
|
||||
|
||||
const groupV2Participants$ = groupsParticipantsUpdate$.pipe(
|
||||
map(ToGroupV2Participants),
|
||||
);
|
||||
this.events2
|
||||
.get(WAHAEvents.GROUP_V2_PARTICIPANTS)
|
||||
.switch(groupV2Participants$);
|
||||
|
||||
const groupV2Leave$ = groupsParticipantsUpdate$.pipe(
|
||||
map((group) =>
|
||||
ToGroupV2LeaveEvent(this.sock?.authState?.creds?.me, group),
|
||||
),
|
||||
filter(Boolean),
|
||||
);
|
||||
this.events2.get(WAHAEvents.GROUP_V2_LEAVE).switch(groupV2Leave$);
|
||||
|
||||
this.events2
|
||||
.get(WAHAEvents.PRESENCE_UPDATE)
|
||||
.switch(
|
||||
@@ -1621,11 +1928,13 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
|
||||
const fromToParticipant = getFromToParticipant(message);
|
||||
const reactionMessage = message.message.reactionMessage;
|
||||
const messageId = buildMessageId(reactionMessage.key);
|
||||
const source = this.getMessageSource(message.key.id);
|
||||
const reaction: WAMessageReaction = {
|
||||
id: id,
|
||||
timestamp: ensureNumber(message.messageTimestamp),
|
||||
from: toCusFormat(fromToParticipant.from),
|
||||
fromMe: message.key.fromMe,
|
||||
source: source,
|
||||
to: toCusFormat(fromToParticipant.to),
|
||||
participant: toCusFormat(fromToParticipant.participant),
|
||||
reaction: {
|
||||
@@ -1636,7 +1945,7 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
|
||||
return reaction;
|
||||
}
|
||||
|
||||
private async processIncomingMessage(message, downloadMedia = true) {
|
||||
protected async processIncomingMessage(message, downloadMedia = true) {
|
||||
// if there is no text or media message
|
||||
if (!message) return;
|
||||
if (!message.message) return;
|
||||
@@ -1644,6 +1953,21 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
|
||||
if (message.message.reactionMessage) return;
|
||||
// Ignore poll votes, we have dedicated handler for that
|
||||
if (message.message.pollUpdateMessage) return;
|
||||
// Ignore calls, we have dedicated handler for that
|
||||
if (message.message.call?.callKey) return;
|
||||
// Ignore revoke, we have a dedicated handler for that
|
||||
if (
|
||||
message.message.protocolMessage?.type ===
|
||||
proto.Message.ProtocolMessage.Type.REVOKE
|
||||
)
|
||||
return;
|
||||
if (
|
||||
message.message.protocolMessage?.type ===
|
||||
proto.Message.ProtocolMessage.Type.EPHEMERAL_SYNC_RESPONSE
|
||||
)
|
||||
return;
|
||||
// Ignore key distribution messages
|
||||
if (message.message.senderKeyDistributionMessage) return;
|
||||
|
||||
if (downloadMedia) {
|
||||
try {
|
||||
@@ -1670,17 +1994,20 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
|
||||
const body = this.extractBody(message.message);
|
||||
const replyTo = this.extractReplyTo(message.message);
|
||||
const ack = message.ack || message.status - 1;
|
||||
const mediaContent = extractMediaContent(message.message);
|
||||
const source = this.getMessageSource(message.key.id);
|
||||
return Promise.resolve({
|
||||
id: id,
|
||||
timestamp: ensureNumber(message.messageTimestamp),
|
||||
from: toCusFormat(fromToParticipant.from),
|
||||
fromMe: message.key.fromMe,
|
||||
body: body,
|
||||
source: source,
|
||||
body: body || null,
|
||||
to: toCusFormat(fromToParticipant.to),
|
||||
participant: toCusFormat(fromToParticipant.participant),
|
||||
// Media
|
||||
hasMedia: Boolean(message.media),
|
||||
media: message.media,
|
||||
hasMedia: Boolean(mediaContent),
|
||||
media: message.media || null,
|
||||
mediaUrl: message.media?.url,
|
||||
// @ts-ignore
|
||||
ack: ack,
|
||||
@@ -1697,21 +2024,28 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
|
||||
if (!message) {
|
||||
return null;
|
||||
}
|
||||
let body = message.conversation;
|
||||
const content = extractMessageContent(message);
|
||||
if (!content) {
|
||||
return null;
|
||||
}
|
||||
let body = content.conversation || null;
|
||||
if (!body) {
|
||||
// Some of the messages have no conversation, but instead have text in extendedTextMessage
|
||||
// https://github.com/devlikeapro/waha/issues/90
|
||||
body = message.extendedTextMessage?.text;
|
||||
body = content.extendedTextMessage?.text;
|
||||
}
|
||||
if (!body) {
|
||||
// Populate from caption
|
||||
const mediaContent = extractMediaContent(message);
|
||||
const mediaContent = extractMediaContent(content);
|
||||
// @ts-ignore - AudioMessage doesn't have caption field
|
||||
body = mediaContent?.caption;
|
||||
}
|
||||
// Response for buttons
|
||||
if (!body) {
|
||||
body = message.templateButtonReplyMessage?.selectedDisplayText;
|
||||
body = content.templateButtonReplyMessage?.selectedDisplayText;
|
||||
}
|
||||
if (!body) {
|
||||
body = content.buttonsResponseMessage?.selectedDisplayText;
|
||||
}
|
||||
return body;
|
||||
}
|
||||
@@ -1731,6 +2065,7 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
|
||||
id: contextInfo.stanzaId,
|
||||
participant: toCusFormat(contextInfo.participant),
|
||||
body: body,
|
||||
_data: quotedMessage,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1900,15 +2235,23 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
|
||||
return this.mediaManager.processMedia(processor, message, this.name);
|
||||
}
|
||||
|
||||
protected async getMessageOptions(request: any): Promise<any> {
|
||||
protected async getMessageOptions(request: {
|
||||
chatId: string;
|
||||
reply_to?: string;
|
||||
}) {
|
||||
const jid = toJID(request.chatId);
|
||||
|
||||
let quoted;
|
||||
if (request.reply_to) {
|
||||
const key = parseMessageIdSerialized(request.reply_to, true);
|
||||
quoted = await this.store.loadMessage(toJID(request.chatId), key.id);
|
||||
quoted = await this.store.loadMessage(jid, key.id);
|
||||
}
|
||||
|
||||
const chat = await this.store.getChat(jid);
|
||||
const messageId = this.generateMessageID();
|
||||
return {
|
||||
quoted: quoted,
|
||||
ephemeralExpiration: chat?.ephemeralExpiration,
|
||||
messageId: messageId,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1927,6 +2270,12 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
|
||||
}
|
||||
return linkPreview;
|
||||
}
|
||||
|
||||
protected generateMessageID() {
|
||||
const id = generateMessageIDV2(this.sock.user?.id);
|
||||
this.saveSentMessageId(id);
|
||||
return id;
|
||||
}
|
||||
}
|
||||
|
||||
function hasPath(url: string) {
|
||||
@@ -2011,7 +2360,7 @@ export function toCusFormat(remoteJid) {
|
||||
if (isJidGroup(remoteJid)) {
|
||||
return remoteJid;
|
||||
}
|
||||
if (isJidStatusBroadcast(remoteJid)) {
|
||||
if (isJidBroadcast(remoteJid)) {
|
||||
return remoteJid;
|
||||
}
|
||||
if (isLidUser(remoteJid)) {
|
||||
@@ -2042,12 +2391,15 @@ export function toJID(chatId) {
|
||||
if (isJidGroup(chatId)) {
|
||||
return chatId;
|
||||
}
|
||||
if (isJidStatusBroadcast(chatId)) {
|
||||
if (isJidBroadcast(chatId)) {
|
||||
return chatId;
|
||||
}
|
||||
if (isNewsletter(chatId)) {
|
||||
return chatId;
|
||||
}
|
||||
if (isLidUser(chatId)) {
|
||||
return chatId;
|
||||
}
|
||||
const number = chatId.split('@')[0];
|
||||
return number + '@s.whatsapp.net';
|
||||
}
|
||||
|
||||
@@ -8,6 +8,8 @@ export interface IMessagesRepository {
|
||||
|
||||
upsertOne(message: any): Promise<void>;
|
||||
|
||||
getById(id: string): Promise<any | null>;
|
||||
|
||||
getAllByJid(
|
||||
jid: string,
|
||||
filter: GetChatMessagesFilter,
|
||||
|
||||
@@ -30,6 +30,8 @@ export interface INowebStore {
|
||||
|
||||
getChats(pagination: PaginationParams, broadcast: boolean): Promise<Chat[]>;
|
||||
|
||||
getChat(jid: string): Promise<Chat | null>;
|
||||
|
||||
getContacts(pagination: PaginationParams): Promise<Contact[]>;
|
||||
|
||||
getContactById(jid: string): Promise<Contact>;
|
||||
|
||||
@@ -26,6 +26,10 @@ export class NowebInMemoryStore implements INowebStore {
|
||||
|
||||
constructor() {
|
||||
this.store = makeInMemoryStore({ logger: logger });
|
||||
const presences = {};
|
||||
this.store.presences = presences;
|
||||
// Adjust inline even handler
|
||||
this.store.setPresences(presences);
|
||||
}
|
||||
|
||||
init(): Promise<void> {
|
||||
@@ -65,6 +69,10 @@ export class NowebInMemoryStore implements INowebStore {
|
||||
throw new BadRequestException(this.errorMessage);
|
||||
}
|
||||
|
||||
getChat(jid: string): Promise<Chat | null> {
|
||||
return null;
|
||||
}
|
||||
|
||||
getContacts(pagination: PaginationParams): Promise<Contact[]> {
|
||||
throw new BadRequestException(this.errorMessage);
|
||||
}
|
||||
@@ -91,7 +99,7 @@ export class NowebInMemoryStore implements INowebStore {
|
||||
|
||||
async getGroups(pagination: PaginationParams): Promise<GroupMetadata[]> {
|
||||
const response = await this.socket?.groupFetchAllParticipating();
|
||||
const groups = Object.values(response);
|
||||
const groups: any[] = Object.values(response);
|
||||
const paginator = new PaginatorInMemory(pagination);
|
||||
return paginator.apply(groups);
|
||||
}
|
||||
|
||||
@@ -269,6 +269,9 @@ export class NowebPersistentStore implements INowebStore {
|
||||
if (!group) {
|
||||
group = { id: id, participants: [] } as GroupMetadata;
|
||||
}
|
||||
if (!group.participants) {
|
||||
group.participants = [];
|
||||
}
|
||||
|
||||
const participantsById = new DefaultMap<string, GroupParticipant>((key) => {
|
||||
return { id: key, admin: null } as GroupParticipant;
|
||||
@@ -360,7 +363,12 @@ export class NowebPersistentStore implements INowebStore {
|
||||
|
||||
if (update.imgUrl === 'changed') {
|
||||
contact.imgUrl = this.socket
|
||||
? await this.socket?.profilePictureUrl(contact.id)
|
||||
? await this.socket?.profilePictureUrl(contact.id).catch((error) => {
|
||||
this.logger.warn(
|
||||
`failed to get profile picture for contact '${contact.id}': ${error}`,
|
||||
);
|
||||
return undefined;
|
||||
})
|
||||
: undefined;
|
||||
} else if (update.imgUrl === 'removed') {
|
||||
delete contact.imgUrl;
|
||||
@@ -427,7 +435,12 @@ export class NowebPersistentStore implements INowebStore {
|
||||
}
|
||||
|
||||
async loadMessage(jid: string, id: string) {
|
||||
const data = await this.messagesRepo.getByJidById(jid, id);
|
||||
let data;
|
||||
if (!jid) {
|
||||
data = await this.messagesRepo.getById(id);
|
||||
} else {
|
||||
data = await this.messagesRepo.getByJidById(jid, id);
|
||||
}
|
||||
if (!data) {
|
||||
return null;
|
||||
}
|
||||
@@ -454,6 +467,10 @@ export class NowebPersistentStore implements INowebStore {
|
||||
return this.chatRepo.getAllWithMessages(pagination, broadcast);
|
||||
}
|
||||
|
||||
async getChat(jid: string): Promise<Chat | null> {
|
||||
return await this.chatRepo.getById(jid);
|
||||
}
|
||||
|
||||
private shouldFetchGroup(): boolean {
|
||||
const timePassed = new Date().getTime() - this.lastTimeGroupFetch.getTime();
|
||||
return timePassed > this.GROUP_METADATA_CACHE_TIME;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { extractMessageContent, proto } from '@adiwajshing/baileys';
|
||||
|
||||
export function extractMediaContent(
|
||||
content: proto.IMessage | null | undefined,
|
||||
content: any | proto.IMessage | null | undefined,
|
||||
) {
|
||||
content = extractMessageContent(content);
|
||||
const mediaContent =
|
||||
|
||||
@@ -2,9 +2,11 @@ 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 { EventEmitter } from 'events';
|
||||
import * as lodash from 'lodash';
|
||||
import { Client, Events } from 'whatsapp-web.js';
|
||||
import { Message } from 'whatsapp-web.js/src/structures';
|
||||
import { exposeFunctionIfAbsent } from 'whatsapp-web.js/src/util/Puppeter';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const { LoadWAHA } = require('./_WAHA.js');
|
||||
@@ -18,17 +20,51 @@ const { LoadPaginator } = require('./_Paginator.js');
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const ChatFactory = require('whatsapp-web.js/src/factories/ChatFactory');
|
||||
|
||||
export class WebjsClient extends Client {
|
||||
export class WebjsClientCore extends Client {
|
||||
public events = new EventEmitter();
|
||||
|
||||
constructor(options) {
|
||||
super(options);
|
||||
// Wait until it's READY and inject more utils
|
||||
this.on(Events.READY, async () => {
|
||||
await this.pupPage.evaluate(LoadLodash);
|
||||
await this.pupPage.evaluate(LoadPaginator);
|
||||
await this.pupPage.evaluate(LoadWAHA);
|
||||
await this.attachCustomEventListeners();
|
||||
await this.injectWaha();
|
||||
});
|
||||
}
|
||||
|
||||
async injectWaha() {
|
||||
await this.pupPage.evaluate(LoadLodash);
|
||||
await this.pupPage.evaluate(LoadPaginator);
|
||||
await this.pupPage.evaluate(LoadWAHA);
|
||||
}
|
||||
|
||||
async attachCustomEventListeners() {
|
||||
await exposeFunctionIfAbsent(
|
||||
this.pupPage,
|
||||
'onNewMessageId',
|
||||
(messageId: string) => {
|
||||
this.events.emit('message.id', { id: messageId });
|
||||
return;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
async destroy() {
|
||||
this.events.removeAllListeners();
|
||||
await super.destroy();
|
||||
}
|
||||
|
||||
async setPushName(name: string) {
|
||||
await this.pupPage.evaluate(async (pushName) => {
|
||||
return await window['WAHA'].WAWebSetPushnameConnAction.setPushname(
|
||||
pushName,
|
||||
);
|
||||
}, name);
|
||||
if (this.info) {
|
||||
this.info.pushname = name;
|
||||
}
|
||||
}
|
||||
|
||||
async unpair() {
|
||||
await this.pupPage.evaluate(async () => {
|
||||
if (
|
||||
@@ -11,4 +11,14 @@ exports.LoadWAHA = () => {
|
||||
const chatPromises = chats.map((chat) => window.WWebJS.getChatModel(chat));
|
||||
return await Promise.all(chatPromises);
|
||||
};
|
||||
|
||||
// Set push name
|
||||
window.WAHA.WAWebSetPushnameConnAction = window.require(
|
||||
'WAWebSetPushnameConnAction',
|
||||
);
|
||||
|
||||
// Get my status
|
||||
window.WAHA.WAWebTextStatusCollection = window.require(
|
||||
'WAWebTextStatusCollection',
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,154 @@
|
||||
import { WebjsClientCore } from '@waha/core/engines/webjs/WebjsClientCore';
|
||||
import {
|
||||
GroupId,
|
||||
GroupInfo,
|
||||
GroupParticipant,
|
||||
GroupParticipantRole,
|
||||
} from '@waha/structures/groups.dto';
|
||||
import {
|
||||
GroupParticipantType,
|
||||
GroupV2JoinEvent,
|
||||
GroupV2LeaveEvent,
|
||||
GroupV2ParticipantsEvent,
|
||||
GroupV2UpdateEvent,
|
||||
} from '@waha/structures/groups.events.dto';
|
||||
import {
|
||||
GroupChat,
|
||||
GroupNotification,
|
||||
GroupNotificationTypes,
|
||||
GroupParticipant as WEBJSGroupParticipant,
|
||||
} from 'whatsapp-web.js';
|
||||
|
||||
function ToGroupInfo(
|
||||
group: GroupChat,
|
||||
invite: string,
|
||||
participants = [],
|
||||
): GroupInfo {
|
||||
// @ts-ignore
|
||||
const groupMetadata = group.groupMetadata;
|
||||
const info: GroupInfo = {
|
||||
// @ts-ignore
|
||||
id: group.id._serialized,
|
||||
subject: group.name,
|
||||
description: group.description,
|
||||
invite: invite,
|
||||
membersCanAddNewMember: groupMetadata.restrict,
|
||||
membersCanSendMessages: groupMetadata.announce,
|
||||
newMembersApprovalRequired: groupMetadata.membershipApprovalMode,
|
||||
participants: participants,
|
||||
};
|
||||
return info;
|
||||
}
|
||||
|
||||
export async function ToGroupV2JoinEvent(
|
||||
client: WebjsClientCore,
|
||||
me: string,
|
||||
notification: GroupNotification,
|
||||
): Promise<GroupV2JoinEvent | null> {
|
||||
if (!notification.recipientIds.includes(me)) {
|
||||
return null;
|
||||
}
|
||||
// @ts-ignore
|
||||
const group: GroupChat = await client.getChatById(notification.id.remote);
|
||||
const invite = await group.getInviteCode();
|
||||
const participants = getParticipants(group.participants);
|
||||
const info: GroupInfo = ToGroupInfo(group, invite, participants);
|
||||
return {
|
||||
timestamp: notification.timestamp,
|
||||
group: info,
|
||||
_data: notification,
|
||||
};
|
||||
}
|
||||
|
||||
function getParticipants(
|
||||
participants: WEBJSGroupParticipant[],
|
||||
): GroupParticipant[] {
|
||||
return participants.map((participant) => {
|
||||
let role: GroupParticipantRole = GroupParticipantRole.PARTICIPANT;
|
||||
if (participant.isSuperAdmin) {
|
||||
role = GroupParticipantRole.SUPERADMIN;
|
||||
} else if (participant.isAdmin) {
|
||||
role = GroupParticipantRole.ADMIN;
|
||||
}
|
||||
|
||||
return {
|
||||
id: participant.id._serialized,
|
||||
role: role,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
export function ToGroupV2LeaveEvent(
|
||||
me: string,
|
||||
notification: GroupNotification,
|
||||
): GroupV2LeaveEvent | null {
|
||||
if (!notification.recipientIds.includes(me)) {
|
||||
return null;
|
||||
}
|
||||
const group: GroupId = {
|
||||
// @ts-ignore
|
||||
id: notification.id.remote,
|
||||
};
|
||||
return {
|
||||
timestamp: notification.timestamp,
|
||||
group: group,
|
||||
_data: notification,
|
||||
};
|
||||
}
|
||||
|
||||
export async function ToGroupV2UpdateEvent(
|
||||
client: WebjsClientCore,
|
||||
notification: GroupNotification,
|
||||
): Promise<GroupV2UpdateEvent> {
|
||||
// @ts-ignore
|
||||
const group: GroupChat = await client.getChatById(notification.id.remote);
|
||||
const invite = await group.getInviteCode();
|
||||
const info: GroupInfo = ToGroupInfo(group, invite, undefined);
|
||||
return {
|
||||
group: info,
|
||||
timestamp: notification.timestamp,
|
||||
_data: notification,
|
||||
};
|
||||
}
|
||||
|
||||
export function ToGroupV2ParticipantsEvent(
|
||||
notification: GroupNotification,
|
||||
): GroupV2ParticipantsEvent | null {
|
||||
let type: GroupParticipantType;
|
||||
let role: GroupParticipantRole;
|
||||
if (['add', 'invite', 'linked_group_join'].includes(notification.type)) {
|
||||
type = GroupParticipantType.JOIN;
|
||||
role = GroupParticipantRole.PARTICIPANT;
|
||||
} else if (['remove', 'leave'].includes(notification.type)) {
|
||||
type = GroupParticipantType.LEAVE;
|
||||
role = GroupParticipantRole.LEFT;
|
||||
} else if (['promote'].includes(notification.type)) {
|
||||
type = GroupParticipantType.PROMOTE;
|
||||
role = GroupParticipantRole.ADMIN;
|
||||
} else if (['demote'].includes(notification.type)) {
|
||||
type = GroupParticipantType.DEMOTE;
|
||||
role = GroupParticipantRole.PARTICIPANT;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
const participants: GroupParticipant[] = notification.recipientIds.map(
|
||||
(id) => {
|
||||
return {
|
||||
id: id,
|
||||
role: role,
|
||||
};
|
||||
},
|
||||
);
|
||||
const group: GroupId = {
|
||||
// @ts-ignore
|
||||
id: notification.id.remote,
|
||||
};
|
||||
return {
|
||||
group: group,
|
||||
type: type,
|
||||
timestamp: notification.timestamp,
|
||||
participants: participants,
|
||||
_data: notification,
|
||||
};
|
||||
}
|
||||
@@ -3,8 +3,14 @@ import {
|
||||
getChannelInviteLink,
|
||||
WhatsappSession,
|
||||
} from '@waha/core/abc/session.abc';
|
||||
import {
|
||||
ToGroupV2JoinEvent,
|
||||
ToGroupV2LeaveEvent,
|
||||
ToGroupV2ParticipantsEvent,
|
||||
ToGroupV2UpdateEvent,
|
||||
} from '@waha/core/engines/webjs/groups.webjs';
|
||||
import { LocalAuth } from '@waha/core/engines/webjs/LocalAuth';
|
||||
import { WebjsClient } from '@waha/core/engines/webjs/WebjsClient';
|
||||
import { WebjsClientCore } from '@waha/core/engines/webjs/WebjsClientCore';
|
||||
import {
|
||||
AvailableInPlusVersion,
|
||||
NotImplementedByEngineError,
|
||||
@@ -15,9 +21,14 @@ import { splitAt } from '@waha/helpers';
|
||||
import { PairingCodeResponse } from '@waha/structures/auth.dto';
|
||||
import {
|
||||
Channel,
|
||||
ChannelListResult,
|
||||
ChannelMessage,
|
||||
ChannelRole,
|
||||
ChannelSearchByText,
|
||||
ChannelSearchByView,
|
||||
CreateChannelRequest,
|
||||
ListChannelsQuery,
|
||||
PreviewChannelMessages,
|
||||
} from '@waha/structures/channels.dto';
|
||||
import {
|
||||
ChatSortField,
|
||||
@@ -30,6 +41,7 @@ import {
|
||||
ChatRequest,
|
||||
CheckNumberStatusQuery,
|
||||
EditMessageRequest,
|
||||
MessageButtonReply,
|
||||
MessageFileRequest,
|
||||
MessageForwardRequest,
|
||||
MessageImageRequest,
|
||||
@@ -52,6 +64,7 @@ import {
|
||||
WAHASessionStatus,
|
||||
WAMessageAck,
|
||||
} from '@waha/structures/enums.dto';
|
||||
import { BinaryFile, RemoteFile } from '@waha/structures/files.dto';
|
||||
import {
|
||||
CreateGroupRequest,
|
||||
GroupSortField,
|
||||
@@ -61,7 +74,11 @@ import {
|
||||
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 {
|
||||
MessageSource,
|
||||
WAMessage,
|
||||
WAMessageReaction,
|
||||
} from '@waha/structures/responses.dto';
|
||||
import { MeInfo } from '@waha/structures/sessions.dto';
|
||||
import { StatusRequest, TextStatus } from '@waha/structures/status.dto';
|
||||
import {
|
||||
@@ -72,9 +89,9 @@ 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 { filter, fromEvent, merge, mergeMap, Observable } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
import WAWebJS, {
|
||||
import {
|
||||
Call,
|
||||
Channel as WEBJSChannel,
|
||||
Chat,
|
||||
@@ -82,6 +99,7 @@ import WAWebJS, {
|
||||
Contact,
|
||||
Events,
|
||||
GroupChat,
|
||||
GroupNotification,
|
||||
Label as WEBJSLabel,
|
||||
Location,
|
||||
Message,
|
||||
@@ -106,7 +124,7 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
|
||||
private engineStateCheckDelayedJob: SingleDelayedJobRunner;
|
||||
private shouldRestart: boolean;
|
||||
|
||||
whatsapp: WebjsClient;
|
||||
whatsapp: WebjsClientCore;
|
||||
protected qr: QR;
|
||||
|
||||
public constructor(config) {
|
||||
@@ -169,7 +187,7 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
|
||||
rmMaxRetries: undefined,
|
||||
});
|
||||
this.addProxyConfig(clientOptions);
|
||||
return new WebjsClient(clientOptions);
|
||||
return new WebjsClientCore(clientOptions);
|
||||
}
|
||||
|
||||
private restartClient() {
|
||||
@@ -450,6 +468,30 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Profile methods
|
||||
*/
|
||||
public async setProfileName(name: string): Promise<boolean> {
|
||||
await this.whatsapp.setPushName(name);
|
||||
return true;
|
||||
}
|
||||
|
||||
public async setProfileStatus(status: string): Promise<boolean> {
|
||||
await this.whatsapp.setStatus(status);
|
||||
return true;
|
||||
}
|
||||
|
||||
protected setProfilePicture(file: BinaryFile | RemoteFile): Promise<boolean> {
|
||||
throw new AvailableInPlusVersion();
|
||||
}
|
||||
|
||||
protected deleteProfilePicture(): Promise<boolean> {
|
||||
throw new AvailableInPlusVersion();
|
||||
}
|
||||
|
||||
/**
|
||||
* Other methods
|
||||
*/
|
||||
sendText(request: MessageTextRequest) {
|
||||
const options = this.getMessageOptions(request);
|
||||
return this.whatsapp.sendMessage(
|
||||
@@ -473,13 +515,18 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
|
||||
const options = {
|
||||
// It's fine to sent just ids instead of Contact object
|
||||
mentions: request.mentions as unknown as string[],
|
||||
linkPreview: request.linkPreview,
|
||||
};
|
||||
return message.edit(request.text, options);
|
||||
}
|
||||
|
||||
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) {
|
||||
@@ -494,17 +541,25 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
|
||||
throw new AvailableInPlusVersion();
|
||||
}
|
||||
|
||||
sendButtonsReply(request: MessageButtonReply) {
|
||||
throw new AvailableInPlusVersion();
|
||||
}
|
||||
|
||||
async sendLocation(request: MessageLocationRequest) {
|
||||
const location = new Location(request.latitude, request.longitude, {
|
||||
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
|
||||
@@ -512,17 +567,23 @@ 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();
|
||||
}
|
||||
|
||||
@@ -658,7 +719,7 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
|
||||
}
|
||||
|
||||
async deleteChat(chatId) {
|
||||
const chat = await this.whatsapp.getChatById(chatId);
|
||||
const chat = await this.whatsapp.getChatById(this.ensureSuffix(chatId));
|
||||
return chat.delete();
|
||||
}
|
||||
|
||||
@@ -843,6 +904,13 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
|
||||
return paginator.apply(groups);
|
||||
}
|
||||
|
||||
protected removeGroupsFieldParticipant(group: any) {
|
||||
delete group.groupMetadata?.participants;
|
||||
delete group.groupMetadata?.pendingParticipants;
|
||||
delete group.groupMetadata?.pastParticipants;
|
||||
delete group.groupMetadata?.membershipApprovalRequests;
|
||||
}
|
||||
|
||||
public async refreshGroups(): Promise<boolean> {
|
||||
return true;
|
||||
}
|
||||
@@ -922,6 +990,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;
|
||||
@@ -938,6 +1025,7 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
|
||||
picture: null,
|
||||
verified: metadata.verified,
|
||||
role: role,
|
||||
subscribersCount: null,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -955,6 +1043,7 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
|
||||
picture: metadata.pictureUrl,
|
||||
verified: metadata.isVerified,
|
||||
role: role,
|
||||
subscribersCount: metadata.subscribersCount,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -966,6 +1055,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),
|
||||
);
|
||||
@@ -1054,7 +1147,7 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
|
||||
* Status methods
|
||||
*/
|
||||
protected checkStatusRequest(request: StatusRequest) {
|
||||
if (request.contacts?.length > 0) {
|
||||
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);
|
||||
@@ -1070,6 +1163,11 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
|
||||
* END - Methods for API
|
||||
*/
|
||||
subscribeEngineEvents2() {
|
||||
// Save sent message in cache
|
||||
this.whatsapp.events.on('message.id', (data) => {
|
||||
this.saveSentMessageId(data.id);
|
||||
});
|
||||
|
||||
//
|
||||
// All
|
||||
//
|
||||
@@ -1164,10 +1262,52 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
|
||||
//
|
||||
// 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$);
|
||||
const groupJoin$ = fromEvent<GroupNotification>(
|
||||
this.whatsapp,
|
||||
Events.GROUP_JOIN,
|
||||
);
|
||||
this.events2.get(WAHAEvents.GROUP_JOIN).switch(groupJoin$); // v1
|
||||
const groupV2Join$ = groupJoin$.pipe(
|
||||
mergeMap((evt) =>
|
||||
ToGroupV2JoinEvent(this.whatsapp, this.getSessionMeInfo().id, evt),
|
||||
),
|
||||
filter(Boolean),
|
||||
);
|
||||
this.events2.get(WAHAEvents.GROUP_V2_JOIN).switch(groupV2Join$);
|
||||
|
||||
const groupLeave$ = fromEvent<GroupNotification>(
|
||||
this.whatsapp,
|
||||
Events.GROUP_LEAVE,
|
||||
);
|
||||
this.events2.get(WAHAEvents.GROUP_LEAVE).switch(groupLeave$); // v1
|
||||
const groupV2Leave$ = groupLeave$.pipe(
|
||||
map((evt) => ToGroupV2LeaveEvent(this.getSessionMeInfo().id, evt)),
|
||||
filter(Boolean),
|
||||
);
|
||||
this.events2.get(WAHAEvents.GROUP_V2_LEAVE).switch(groupV2Leave$);
|
||||
|
||||
const groupAdminChanged$ = fromEvent(
|
||||
this.whatsapp,
|
||||
Events.GROUP_ADMIN_CHANGED,
|
||||
);
|
||||
const groupV2Participants = merge(
|
||||
groupJoin$,
|
||||
groupLeave$,
|
||||
groupAdminChanged$,
|
||||
).pipe(map(ToGroupV2ParticipantsEvent), filter(Boolean));
|
||||
this.events2
|
||||
.get(WAHAEvents.GROUP_V2_PARTICIPANTS)
|
||||
.switch(groupV2Participants);
|
||||
|
||||
const groupUpdate$ = fromEvent<GroupNotification>(
|
||||
this.whatsapp,
|
||||
Events.GROUP_UPDATE,
|
||||
);
|
||||
const groupV2Update$ = groupUpdate$.pipe(
|
||||
mergeMap((evt) => ToGroupV2UpdateEvent(this.whatsapp, evt)),
|
||||
filter(Boolean),
|
||||
);
|
||||
this.events2.get(WAHAEvents.GROUP_V2_UPDATE).switch(groupV2Update$);
|
||||
|
||||
//
|
||||
// Chats
|
||||
@@ -1211,7 +1351,10 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
|
||||
this.events2.get(WAHAEvents.CALL_RECEIVED).switch(calls$);
|
||||
}
|
||||
|
||||
private async processIncomingMessage(message: Message, downloadMedia = true) {
|
||||
protected async processIncomingMessage(
|
||||
message: Message,
|
||||
downloadMedia = true,
|
||||
) {
|
||||
if (downloadMedia) {
|
||||
try {
|
||||
message = await this.downloadMedia(message);
|
||||
@@ -1224,10 +1367,12 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
|
||||
}
|
||||
|
||||
private processMessageReaction(reaction: Reaction): WAMessageReaction {
|
||||
const source = this.getMessageSource(reaction.id.id);
|
||||
return {
|
||||
id: reaction.id._serialized,
|
||||
from: reaction.senderId,
|
||||
fromMe: reaction.id.fromMe,
|
||||
source: source,
|
||||
participant: reaction.senderId,
|
||||
to: reaction.id.remote,
|
||||
timestamp: reaction.timestamp,
|
||||
@@ -1240,19 +1385,21 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
|
||||
|
||||
protected toWAMessage(message: Message): WAMessage {
|
||||
const replyTo = this.extractReplyTo(message);
|
||||
const source = this.getMessageSource(message.id.id);
|
||||
// @ts-ignore
|
||||
return {
|
||||
id: message.id._serialized,
|
||||
timestamp: message.timestamp,
|
||||
from: message.from,
|
||||
fromMe: message.fromMe,
|
||||
source: source,
|
||||
to: message.to,
|
||||
body: message.body,
|
||||
// Media
|
||||
// @ts-ignore
|
||||
hasMedia: Boolean(message.media),
|
||||
hasMedia: Boolean(message.hasMedia),
|
||||
// @ts-ignore
|
||||
media: message.media,
|
||||
media: message.media || null,
|
||||
// @ts-ignore
|
||||
mediaUrl: message.media?.url,
|
||||
// @ts-ignore
|
||||
@@ -1267,7 +1414,7 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
|
||||
|
||||
protected extractReplyTo(message: Message): ReplyToMessage | null {
|
||||
// @ts-ignore
|
||||
const quotedMsg = message.rawData.quotedMsg;
|
||||
const quotedMsg = message.rawData?.quotedMsg;
|
||||
if (!quotedMsg) {
|
||||
return;
|
||||
}
|
||||
@@ -1275,6 +1422,7 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
|
||||
id: quotedMsg.id?.id,
|
||||
participant: quotedMsg.author || quotedMsg.from,
|
||||
body: quotedMsg.caption || quotedMsg.body,
|
||||
_data: quotedMsg,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1301,7 +1449,7 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
|
||||
let mentions = request.mentions;
|
||||
mentions = mentions ? mentions.map(this.ensureSuffix) : undefined;
|
||||
|
||||
const quotedMessageId = request.reply_to;
|
||||
const quotedMessageId = request.reply_to || request.replyTo;
|
||||
|
||||
return {
|
||||
mentions: mentions,
|
||||
|
||||
@@ -66,7 +66,7 @@ export class WebhookSender {
|
||||
const headers = {
|
||||
'content-type': 'application/json',
|
||||
};
|
||||
Object.assign(headers, this.getWebhookHeader());
|
||||
Object.assign(headers, this.getWebhookHeader(json));
|
||||
Object.assign(headers, this.getHMACHeaders(body));
|
||||
const ctx = {
|
||||
id: headers['X-Webhook-Request-Id'],
|
||||
@@ -153,12 +153,13 @@ export class WebhookSender {
|
||||
};
|
||||
}
|
||||
|
||||
protected getWebhookHeader() {
|
||||
protected getWebhookHeader(json: any) {
|
||||
const timestamp = json.timestamp?.toString() || Date.now().toString();
|
||||
return {
|
||||
// UUID, no '-' in it
|
||||
'X-Webhook-Request-Id': ulid(),
|
||||
// unix timestamp with ms
|
||||
'X-Webhook-Timestamp': Date.now().toString(),
|
||||
'X-Webhook-Timestamp': timestamp,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -45,9 +45,11 @@ import { LocalSessionAuthRepository } from './storage/LocalSessionAuthRepository
|
||||
import { LocalStoreCore } from './storage/LocalStoreCore';
|
||||
|
||||
export class OnlyDefaultSessionIsAllowed extends UnprocessableEntityException {
|
||||
constructor() {
|
||||
constructor(name: string) {
|
||||
const encoded = Buffer.from(name, 'utf-8').toString('base64');
|
||||
super(
|
||||
`WAHA Core support only 'default' session. If you want to run more then one WhatsApp account - please get WAHA PLUS version. Check this out: ${DOCS_URL}`,
|
||||
`WAHA Core support only 'default' session. You tried to access '${name}' session (base64: ${encoded}). ` +
|
||||
`If you want to run more then one WhatsApp account - please get WAHA PLUS version. Check this out: ${DOCS_URL}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -115,7 +117,7 @@ export class SessionManagerCore extends SessionManager implements OnModuleInit {
|
||||
|
||||
private onlyDefault(name: string) {
|
||||
if (name !== this.DEFAULT) {
|
||||
throw new OnlyDefaultSessionIsAllowed();
|
||||
throw new OnlyDefaultSessionIsAllowed(name);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -362,9 +364,9 @@ export class SessionManagerCore extends SessionManager implements OnModuleInit {
|
||||
try {
|
||||
engineInfo = await promiseTimeout(1000, session.getEngineInfo());
|
||||
} catch (error) {
|
||||
this.log.warn(
|
||||
this.log.debug(
|
||||
{ session: session.name, error: `${error}` },
|
||||
'Error while getting engine info',
|
||||
'Can not get engine info',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -30,4 +30,14 @@ function splitAt(str: string, index) {
|
||||
return [fst.join(''), snd.join('')];
|
||||
}
|
||||
|
||||
export function sortObjectByValues(obj: any) {
|
||||
if (!obj) {
|
||||
return obj;
|
||||
}
|
||||
return Object.fromEntries(
|
||||
// @ts-ignore
|
||||
Object.entries(obj).sort(([, v1], [, v2]) => v2 - v1),
|
||||
);
|
||||
}
|
||||
|
||||
export { flipObject, parseBool, splitAt };
|
||||
+16
-6
@@ -1,5 +1,6 @@
|
||||
import { NestFactory } from '@nestjs/core';
|
||||
import { WsAdapter } from '@nestjs/platform-ws';
|
||||
import { WAHA_WEBHOOKS } from '@waha/structures/webhooks';
|
||||
import {
|
||||
getNestJSLogLevels,
|
||||
getPinoLogLevel,
|
||||
@@ -15,7 +16,6 @@ import { WhatsappConfigService } from './config.service';
|
||||
import { AppModuleCore } from './core/app.module.core';
|
||||
import { SwaggerConfiguratorCore } from './core/SwaggerConfiguratorCore';
|
||||
import { AllExceptionsFilter } from './nestjs/AllExceptionsFilter';
|
||||
import { WAHA_WEBHOOKS } from './structures/webhooks.dto';
|
||||
import { getWAHAVersion, VERSION, WAHAVersion } from './version';
|
||||
|
||||
const logger: Logger = pino({
|
||||
@@ -23,12 +23,22 @@ const logger: Logger = pino({
|
||||
transport: getPinoTransport(),
|
||||
}).child({ name: 'Bootstrap' });
|
||||
|
||||
logger.info('NODE - Catching unhandled rejection enabled');
|
||||
process.on('unhandledRejection', (reason, promise) => {
|
||||
logger.error('Unhandled Rejection at:', promise, 'reason:', reason);
|
||||
// @ts-ignore
|
||||
logger.error(reason.stack);
|
||||
process.on('uncaughtException', (err) => {
|
||||
logger.error('Uncaught Exception:', err);
|
||||
if (err instanceof Error) {
|
||||
logger.error(err.stack);
|
||||
}
|
||||
});
|
||||
process.on('unhandledRejection', (reason, promise) => {
|
||||
logger.error('Unhandled Rejection at:', promise);
|
||||
if (reason instanceof Error) {
|
||||
logger.error(reason.stack);
|
||||
} else {
|
||||
logger.error('Unhandled rejection reason:', reason);
|
||||
}
|
||||
});
|
||||
logger.info('NODE - Catching unhandled rejections and exceptions enabled');
|
||||
|
||||
process.on('SIGINT', () => {
|
||||
logger.info('SIGINT received');
|
||||
});
|
||||
|
||||
@@ -4,6 +4,7 @@ export const MessageIdApiParam = ApiParam({
|
||||
name: 'messageId',
|
||||
required: true,
|
||||
type: 'string',
|
||||
description: 'Message ID',
|
||||
description:
|
||||
'Message ID in format <code>{fromMe}_{chat}_{message_id}[_{participant}]</code>',
|
||||
example: 'true_123456789@c.us_BAE6A33293978B16',
|
||||
});
|
||||
@@ -0,0 +1,28 @@
|
||||
import {
|
||||
registerDecorator,
|
||||
ValidationArguments,
|
||||
ValidationOptions,
|
||||
} from 'class-validator';
|
||||
|
||||
export function IsFileType(validationOptions?: ValidationOptions) {
|
||||
return function (object: any, propertyName: string) {
|
||||
registerDecorator({
|
||||
name: 'isFileType',
|
||||
target: object.constructor,
|
||||
propertyName: propertyName,
|
||||
options: validationOptions,
|
||||
validator: {
|
||||
validate(value: any, _args: ValidationArguments) {
|
||||
return (
|
||||
value &&
|
||||
typeof value === 'object' &&
|
||||
(typeof value.data === 'string' || typeof value.url === 'string')
|
||||
);
|
||||
},
|
||||
defaultMessage() {
|
||||
return 'must contain either "data" or "url".';
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
}
|
||||
@@ -22,7 +22,7 @@ export class WebsocketHeartbeatJob {
|
||||
this.logger.debug(
|
||||
`Terminating client connection due to heartbeat timeout, ${client.id}`,
|
||||
);
|
||||
return client.terminate();
|
||||
client.terminate();
|
||||
}
|
||||
|
||||
client.isAlive = false;
|
||||
|
||||
@@ -35,14 +35,6 @@ export class RequestCodeRequest {
|
||||
localeCountry: string;
|
||||
}
|
||||
|
||||
export class OTPRequest {
|
||||
code: string;
|
||||
}
|
||||
|
||||
export class CaptchaBody {
|
||||
code: string;
|
||||
}
|
||||
|
||||
export class PairingCodeResponse {
|
||||
code: string;
|
||||
}
|
||||
@@ -16,5 +16,17 @@ export class SessionQuery {
|
||||
*/
|
||||
|
||||
export class SessionBaseRequest {
|
||||
@IsString()
|
||||
session: string = WHATSAPP_DEFAULT_SESSION_NAME;
|
||||
}
|
||||
|
||||
export class Result {
|
||||
success: boolean = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Responses
|
||||
*/
|
||||
export class CountResponse {
|
||||
count: number = 0;
|
||||
}
|
||||
@@ -1,5 +1,17 @@
|
||||
import { ApiParam, ApiProperty, getSchemaPath } from '@nestjs/swagger';
|
||||
import { BooleanString } from '@waha/nestjs/validation/BooleanString';
|
||||
import { BinaryFile, RemoteFile } from '@waha/structures/files.dto';
|
||||
import { WAMessage } from '@waha/structures/responses.dto';
|
||||
import { Transform, Type } from 'class-transformer';
|
||||
import {
|
||||
ArrayMinSize,
|
||||
IsArray,
|
||||
IsBoolean,
|
||||
IsNotEmpty,
|
||||
IsNumber,
|
||||
IsOptional,
|
||||
IsString,
|
||||
} from 'class-validator';
|
||||
|
||||
export class CreateChannelRequest {
|
||||
@ApiProperty({
|
||||
@@ -38,7 +50,7 @@ export class ListChannelsQuery {
|
||||
role?: ChannelRoleFilter;
|
||||
}
|
||||
|
||||
export class Channel {
|
||||
class ChannelBase {
|
||||
@ApiProperty({
|
||||
description: 'Newsletter id',
|
||||
example: '123123123123@newsletter',
|
||||
@@ -73,9 +85,15 @@ export class Channel {
|
||||
|
||||
verified: boolean;
|
||||
|
||||
subscribersCount: number;
|
||||
}
|
||||
|
||||
export class Channel extends ChannelBase {
|
||||
role: ChannelRole;
|
||||
}
|
||||
|
||||
export class ChannelPublicInfo extends ChannelBase {}
|
||||
|
||||
export const NewsletterIdApiParam = ApiParam({
|
||||
name: 'id',
|
||||
required: true,
|
||||
@@ -96,3 +114,86 @@ export const NewsletterIdOrInviteCodeApiParam = ApiParam({
|
||||
description:
|
||||
'WhatsApp Channel ID or invite code from invite link https://www.whatsapp.com/channel/11111',
|
||||
});
|
||||
|
||||
export class ChannelCountry {
|
||||
code: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export class ChannelCategory {
|
||||
value: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export class ChannelView {
|
||||
value: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export class ChannelSearch {
|
||||
@IsNumber()
|
||||
@IsOptional()
|
||||
limit: number = 50;
|
||||
|
||||
@IsString()
|
||||
startCursor: string = '';
|
||||
}
|
||||
|
||||
export class ChannelSearchByView extends ChannelSearch {
|
||||
@IsString()
|
||||
view: string = 'RECOMMENDED';
|
||||
|
||||
@IsArray()
|
||||
@IsString({ each: true })
|
||||
@ArrayMinSize(1)
|
||||
countries: string[] = ['US'];
|
||||
|
||||
@IsArray()
|
||||
@IsString({ each: true })
|
||||
categories: string[] = [];
|
||||
}
|
||||
|
||||
export class ChannelSearchByText extends ChannelSearch {
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
text: string = 'Donald Trump';
|
||||
|
||||
@IsArray()
|
||||
@IsString({ each: true })
|
||||
categories: string[] = [];
|
||||
}
|
||||
|
||||
export class ChannelPagination {
|
||||
startCursor: string | null;
|
||||
endCursor: string | null;
|
||||
hasNextPage: boolean;
|
||||
hasPreviousPage: boolean;
|
||||
}
|
||||
|
||||
export class ChannelListResult {
|
||||
page: ChannelPagination;
|
||||
channels: ChannelPublicInfo[];
|
||||
}
|
||||
|
||||
export class ChannelMessage {
|
||||
message: WAMessage;
|
||||
|
||||
@ApiProperty({
|
||||
example: { '👍': 10, '❤️': 5 },
|
||||
type: Object,
|
||||
additionalProperties: { type: 'number' },
|
||||
})
|
||||
reactions: { [emoji: string]: number };
|
||||
|
||||
viewCount: number;
|
||||
}
|
||||
|
||||
export class PreviewChannelMessages {
|
||||
@Transform(BooleanString)
|
||||
@IsBoolean()
|
||||
downloadMedia: boolean = false;
|
||||
|
||||
@IsNumber()
|
||||
@Type(() => Number)
|
||||
limit: number = 100;
|
||||
}
|
||||
@@ -4,8 +4,17 @@ import {
|
||||
ApiProperty,
|
||||
getSchemaPath,
|
||||
} from '@nestjs/swagger';
|
||||
import { IsFileType } from '@waha/nestjs/validation/IsFileType';
|
||||
import { GetChatMessagesQuery } from '@waha/structures/chats.dto';
|
||||
import { IsNotEmpty, IsNumber, IsString } from 'class-validator';
|
||||
import { plainToInstance, Transform, Type } from 'class-transformer';
|
||||
import {
|
||||
IsBoolean,
|
||||
IsNotEmpty,
|
||||
IsOptional,
|
||||
IsString,
|
||||
IsUrl,
|
||||
ValidateNested,
|
||||
} from 'class-validator';
|
||||
|
||||
import {
|
||||
SessionBaseRequest,
|
||||
@@ -14,6 +23,9 @@ import {
|
||||
} from './base.dto';
|
||||
import {
|
||||
BinaryFile,
|
||||
FileContent,
|
||||
FileType,
|
||||
FileURL,
|
||||
RemoteFile,
|
||||
VideoBinaryFile,
|
||||
VideoRemoteFile,
|
||||
@@ -64,6 +76,7 @@ export class GetPresenceQuery extends ChatQuery {}
|
||||
*/
|
||||
export class ChatRequest extends SessionBaseRequest {
|
||||
@ChatIdProperty()
|
||||
@IsString()
|
||||
chatId: string;
|
||||
}
|
||||
|
||||
@@ -158,6 +171,74 @@ export class MessageTextRequest extends ChatRequest {
|
||||
reply_to?: string;
|
||||
|
||||
linkPreview?: boolean = true;
|
||||
linkPreviewHighQuality?: boolean = false;
|
||||
}
|
||||
|
||||
@ApiExtraModels(FileURL, FileContent)
|
||||
export class LinkPreviewData {
|
||||
@IsNotEmpty()
|
||||
@IsUrl({
|
||||
protocols: ['http', 'https'],
|
||||
require_protocol: true,
|
||||
require_tld: false,
|
||||
})
|
||||
url: string = 'https://github.com/';
|
||||
|
||||
@IsString()
|
||||
title: string = 'Your Title';
|
||||
|
||||
@IsString()
|
||||
description: string = 'Check this out, amazing!';
|
||||
|
||||
@IsOptional()
|
||||
@ValidateNested()
|
||||
@Transform(
|
||||
({ value }) => {
|
||||
if (value?.url) {
|
||||
return plainToInstance(FileURL, value);
|
||||
}
|
||||
if (value?.data) {
|
||||
return plainToInstance(FileContent, value);
|
||||
}
|
||||
return value;
|
||||
},
|
||||
{ toClassOnly: true },
|
||||
)
|
||||
@IsFileType({ message: 'Image must contain either "data" or "url".' })
|
||||
@ApiProperty({
|
||||
oneOf: [
|
||||
{ $ref: getSchemaPath(FileURL) },
|
||||
{ $ref: getSchemaPath(FileContent) },
|
||||
],
|
||||
example: {
|
||||
url:
|
||||
process.env.WHATSAPP_SWAGGER_JPG_EXAMPLE_URL ||
|
||||
'https://github.com/devlikeapro/waha/raw/core/examples/waha.jpg',
|
||||
},
|
||||
})
|
||||
image?: FileType;
|
||||
}
|
||||
|
||||
export class MessageLinkCustomPreviewRequest extends ChatRequest {
|
||||
@IsString()
|
||||
@ApiProperty({
|
||||
description:
|
||||
'The text to send. MUST include the URL provided in preview.url',
|
||||
})
|
||||
text: string = 'Check this out! https://github.com/';
|
||||
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
linkPreviewHighQuality?: boolean = true;
|
||||
|
||||
@ValidateNested()
|
||||
@Type(() => LinkPreviewData)
|
||||
preview: LinkPreviewData;
|
||||
|
||||
@ReplyToProperty()
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
reply_to?: string;
|
||||
}
|
||||
|
||||
export class EditMessageRequest {
|
||||
@@ -165,6 +246,9 @@ export class EditMessageRequest {
|
||||
|
||||
@ApiHideProperty()
|
||||
mentions?: string[];
|
||||
|
||||
linkPreview?: boolean = true;
|
||||
linkPreviewHighQuality?: boolean = false;
|
||||
}
|
||||
|
||||
export class MessageReplyRequest extends MessageTextRequest {
|
||||
@@ -329,3 +413,27 @@ export class MessageDestination {
|
||||
from: string;
|
||||
fromMe: boolean;
|
||||
}
|
||||
|
||||
export class MessageButtonReply extends ChatRequest {
|
||||
@ReplyToProperty()
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
replyTo?: string;
|
||||
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
selectedDisplayText: string;
|
||||
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
selectedButtonID: string;
|
||||
}
|
||||
|
||||
export class NewMessageIDResponse {
|
||||
@ApiProperty({
|
||||
description: 'Pre-generated message id',
|
||||
example: 'BBBBBBBBBBBBBBBBB',
|
||||
required: true,
|
||||
})
|
||||
id: string;
|
||||
}
|
||||
@@ -11,6 +11,10 @@ export enum WAHAEvents {
|
||||
STATE_CHANGE = 'state.change',
|
||||
GROUP_JOIN = 'group.join',
|
||||
GROUP_LEAVE = 'group.leave',
|
||||
GROUP_V2_JOIN = 'group.v2.join',
|
||||
GROUP_V2_LEAVE = 'group.v2.leave',
|
||||
GROUP_V2_UPDATE = 'group.v2.update',
|
||||
GROUP_V2_PARTICIPANTS = 'group.v2.participants',
|
||||
PRESENCE_UPDATE = 'presence.update',
|
||||
POLL_VOTE = 'poll.vote',
|
||||
POLL_VOTE_FAILED = 'poll.vote.failed',
|
||||
@@ -25,6 +29,9 @@ export enum WAHAEvents {
|
||||
ENGINE_EVENT = 'engine.event',
|
||||
}
|
||||
|
||||
export type AllEventType = WAHAEvents | '*';
|
||||
export const AllEvents = [...Object.values(WAHAEvents), '*'];
|
||||
|
||||
// All but no state.change, it's internal one
|
||||
export const WAHAEventsWild = Object.values(WAHAEvents).filter(
|
||||
(e) => e !== WAHAEvents.STATE_CHANGE && e !== WAHAEvents.ENGINE_EVENT,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { IsBase64, IsNotEmpty, IsString, IsUrl } from 'class-validator';
|
||||
|
||||
export class Base64File {
|
||||
mimetype: string;
|
||||
@@ -117,3 +118,33 @@ export class VideoRemoteFile {
|
||||
})
|
||||
url: string;
|
||||
}
|
||||
|
||||
export class FileURL {
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@IsUrl({
|
||||
protocols: ['http', 'https'],
|
||||
require_protocol: true,
|
||||
require_tld: false,
|
||||
})
|
||||
@ApiProperty({
|
||||
example:
|
||||
process.env.WHATSAPP_SWAGGER_JPG_EXAMPLE_URL ||
|
||||
'https://github.com/devlikeapro/waha/raw/core/examples/waha.jpg',
|
||||
})
|
||||
url?: string;
|
||||
}
|
||||
|
||||
export class FileContent {
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@IsBase64()
|
||||
@ApiProperty({
|
||||
description: 'Base64-encoded data of the file',
|
||||
example:
|
||||
'/9j/4AAQSkZJRgABAQAAAQABAAD//gAfQ29tcHJlc3NlZCBieSBqcGVnLXJlY29tcHJlc3P/2wCEAAQEBAQEBAQEBAQGBgUGBggHBwcHCAwJCQkJCQwTDA4MDA4MExEUEA8QFBEeFxUVFx4iHRsdIiolJSo0MjRERFwBBAQEBAQEBAQEBAYGBQYGCAcHBwcIDAkJCQkJDBMMDgwMDgwTERQQDxAUER4XFRUXHiIdGx0iKiUlKjQyNEREXP/CABEIAgACAAMBIgACEQEDEQH/xAAcAAEAAgIDAQAAAAAAAAAAAAAABgcEBQEDCAL/2gAIAQEAAAAA9/AAABxix7SajXYWN1uzJzdjud1v8vkAAAAAADjRRGL6kAAbaUzDdcgAAAAAOmDQnVAAADazec9oAAAAA6K9gvQAAAA753YXcAAAAD5hNb4oAAAAMuyppyAAAAa2p4+AAAAAJBbmcAAABD6s6AAAAAAd9qy4AAAKzgIAAAAAAn9lgAAOKjiQAAAAAAJdbfIAAcU7GAAAAAAAEnuLkAAVDEwAAAAAABLbdAAKxgYAAAAAc9HR9ZXICfWYABDKoAAAAAOqLRvR4x9bSQSrbgtuXgBrKO6QAAAAPmFQfoAG/sLajuvPYgCk9CAAAABr6u1QAH1PpsN9dgBBquAAAAA1NUYwAAmNii05uB10HigAAAAwagxgPn7+XIE6nhlX13ArevQAAAAcVJpQTi2ZvteuMV1UWvBbG+LDscOig+gAAAAEQrgMr0TZde1zosiW2rm+dK6DZXHy7787xAK0AAAABxTGCO71PtPNUTD7u+6/OFYhZ0oLNnpxRWrAACPxDKnOYAR6qgvyy/JuqAXPeHkbQiQWsbS9eUfpUAANFUxsrj5AV3DRuPYfmiu74sryJ0A9WaDzkPq8fsuvfqygQAAV1DhcO1AVHpRclvePfr2z9+c6tBZnovxb1C3N0T6zOKL1QAAQqvj6uvJA5pLFHpLo86ceytn5QiQNn7N8kxUWdKDa3phUGAAD4q2Pc2VKwHNH9A9RRehVuegvKsLB2e3PLMFFkS4X5H6jAAA1FQba4ADmk8Qekurzkempr5nhFpa6vm49keSYqLMlQt3SV6AABqaf21wACo9KLjuLx0d3o+yYVM/ugqdtL0H4u6xbO9FiaeJgAAamn9rcIAruGja+xfN1bCyroluo8xxT1nEvPAXh3CWa3SgAAamn9tcAAjlWDj19WNIBx39XF4XH5F0w3ltBu8LBAAa7H4NZW2bZ4+8/IHFLYZsfZvlWNbGPh23pcfmavAsmWhnY/SAHEKhOKAA3tgbkhtdlnekYvJ+YLXWk75bZ315xggZ1zfQd3X8gDiro2AAHNnyZxUGpehpLBYItScbfHi9cVPigtKSA+nyAIPAQAAO25cxrqh6AOHH0AmViAfX31AHFJY4AACdTw0dVdQAAlFm8gd2RggGqp4AAA3tsjU1dgAAJvPwDOzNKAaGpwY/fyDr47Q2dxh1QKG/IA21h70AbvYxMA0FUD4ieJ2y7vGnjrf7sbK5AMSIxrTjjLkMrkAAJZt68A+NbF4MNVGje70QnrczkZlq9uwAfGB0fWdlcAALD3NRgx6dwwYEUJFuBD8V3TQCaWEAAAAW5IKDBHqqAaTW5u/5GLoG9zANjcoAAABfmbReqCN1aAAAAZ90cAAAAbW9OazgIRyrAAAADPujgAAACe2a0VJBo6lAAAANpcQAAAC6pA4orVjiuND0YwMHVANrnA2HbPZIAAABtL2ECrIHMeqkGk0ADfbwNjc3AAAABZc/HVQPQCP1SDFh4CXZgbG5QAAADvvvICuK8BoanAjepBtZKDZ3GAAAAWDZIOqhMUMGlwOI9pxuJDyCRWoAAAAyr67gITVYKf1IDpwGd3gLGmAAAAC0ZyAUpoA0FUAAAbK4fsAAADfXYAMCiukIlXHyAANja2eAAAB3XjswAh9Sg10M0eNxxyDjnnYSSY9gAAAC15mABWcBAcgA4AAAAE8s4AAqGJgAAAAAACV2+AAOKdjAAAAAAACT3FyAAHFRxIAAAAAAEst3kAABWcBAAAAAAE8s3kAAAQ6rOkAAAAAHdaMzAAAA19Sx8AAAAASC19mAAAAcQytcMAAAAGZY825AAAAB8V5A8cAAABkTuwO8AAAAAdUIg2qAAAG1m047wAAAAAHGiiMX1IAA20nl2+5AAAAAAA4xY/ptPrMPo6nb35mz2+5kOWAAAD//EABsBAQACAwEBAAAAAAAAAAAAAAAEBQIDBgEH/9oACAECEAAAAAA11NXBi6m2VOtLbYAACJzdLgAM7vo5YADDmeexAAZdD02YAROMggAAndnLAIHE6AAAG/tp4EThNIAAePTd3csMODhAAGuBFwbZc7Kb3mY5XnQACLV4g2223o+pInAYgANFP4trjCigtl1s7+W5CiAAKXT73EShz6Wv5dPsb7rtXzvADGqztBopnUR8ue8drztZne5/RKrjQEenX2RCrHf8D9b+YwUvqeLXmzsayhAKjdYiBXO+4Hs6+lxw7bhV1uvoNeAVm6aIlU7rjdHX9LT8/J5lfZT42kHmKt3S/cvcKPzrqWv1+zuz4DDfct2rwK6D4DKymVsH6Dh740cfoW0p68ESqAPbrZT6fHrwnWR7t0ishPfD14s5uNbDDKwnDdJgGNXFys9EFZe1ixnetUXX7ulZBPn0LCj8e2OiInoBtuwAL6z45rowAMr4ADsrX53hjS4eMto0+Pd9wADP6Jt5CiKuGzsxW61lOAAvuuROAxVkImSkaEWkwAGXfyzledRqgZsD272AA6PqRhwcJDgYA2WUgAE3vMwicJpAAAG7u5YEDiNIAADd288AicXCAABO7OWAGHM89iAAy6HpswAETnKTAAZ3fRywAAa6mrgxdbZKnWlrtAH/xAAbAQEAAgMBAQAAAAAAAAAAAAAABAUCAwYBB//aAAgBAxAAAAAAMIEGJowZ75c6fmAACPTVmIAys7mQAAxpKfwAB7cXeQAaOaigAAldLvAIvMagAAG3p5QEfltQAAePTZ1UgMeVigAGEXRizkSvZXVZCipgADRB8BnYZ3N6R+S8AAaq/wAWFhjVxWdjn1shz1UAAVuv3p49VndRKJKmWvQ4cdiB5AynDVXLzV7UeOmpoWVpl2NfzgDTXrTIjQnWcp9B4eKkXvNLPPo4NUAV+yYIsN1nJ9JErPMem5ZZbLWLEAIWySI8F1POauhuq6p3Ui0yl6NYPPEPOS99xrPOhrImHsnpeSx22Lbr8CHG8B7Mkw4vXYjVzupP3vXgjwQBZ5V+o98EqYe7NQhRnvgE2T5Cjh7LlDbuiHkDTlN1RU32ClyvWGjBs35BLl1LGs8ezNUdLRDOzAAtpvOMKwADK0AA6Sfx2ONdge7Br8e7bAAGXY589VEGOzmiFgmyQALXoUfkvEKMSd7RGJ0gAHvWyCipmmvGTE9sswAXN6MeUjI8Tzwe5TNwAJXVZBH5bUAAANvUyAIvL6wAAG3p5QBH5uKAACV0kgAMaSoxAAZW93kAA0U1XiAMrO5kAAAwgQYujW2b5U6fmAP/xABREAACAQIDAgcJCgwFAwUBAAABAgMEBQAGESExEhMgQVFhcQciMDJAQnKBkRAUFVBSU6GxwdEjMzVDVWJzgpKTorIWJDRU0mN0g0RFZMLhw//aAAgBAQABPwDyiqu1vpNlRVwx6czOAT6sVWdLPESImlm9BNB/Vpioz65BWmt4A6Xf7AMS5zvbghHiiB+Qn/InEuY73N49xk/dAT+0DD3O5P49fUntlbDTzMdWmc9rHBZjvJOAzDcSMLPMh1WZwepiMLc7kniXCpHZK2Isx3uHxLhJ+8A/9wOIs53pAA7xSgfLT/iRilz/ACJsqLeDrvZH+wjFJnSyysOOMsPpp/x1xTXW31ZHvesikPyVcE+zGvxYSBvOK3M1ooeEr1YkceZF35+jYMVuep31WgpFQfLlOp9gxV3y7VuvH10nBPmqeAvsXTyGkvt2oiOIrpQBuVjwx7G1xRZ7nTRa6kVxzvFsPsOKDNForVVI6sRSsfFl7w4BB3H4nq66koo+HUTpFGOdiBri454iTWO2wcYfnJNi+obziuvdzuOoqqtyh8xe9T2Dyehvd0tpHvWrcIPMbvk9h3YtueomKpcoDG3zke1fZvGKOto6xONpp0kj5ypB9XxHW1tLQxGaqnWNF5yfoGLpnd24UVri4I+ekG31LioqqirkMtTM8rnnc6+V09VUUkgmppnicc6nTFrzu6hYbrFwh89GNvay4oq2krIRUUsySod2h5+vy9mVQWYgADUk4vOc4KctBbAJpdxkP4tezpxV11XXymarnaV+s7B2Dm8vo66roJRNSTtG/VuPaOfFlzvFPwYLmBFLzS+Y3b8nCsGAZSCDtBHll1vNFaouMqZdCR3ka7XbsGLxmOuuxZC3FU3NEh3+kef4js+Y6+0MqK3G0+u2Jzu9E82LTeaC7RcZTy7VGrxnYwPWPKr/AJrhoOHS0RWWq3E70j7ek4qKmermeeplaSVjtZviWnqZ6SZJ6aVo5F3MuMv5shuHFUldwY6ncp3I5+/q8nZlRWZiAoGpJxmLNjTF6K1uVj3PMN7dS9XX8U5cza0BSiurloT3qzHevU334RldVZWBUjUEeSSyx08TyzOFRQWZidAAMZizLLc2alpGZKMHsMnWerq+K8uZnltTrS1TF6Jj2mP7x1YhminjSWF1ZHAKlTqCPIp5o4InllcJGgJLHYABjMOYpbtK0EJKUanYu4uRzt8W5dzFNaJVhmYtRsdq86E86/aMQzRzxJNE4aNgGVgdQQfIHdY0Z3YKqjUk7ABjMuYnukrUtMxFGjfzCOc9XR8X5ZzG9pmFNUuTRufXGTzjqwjq6q6MCrDUEbQR4fNmYjUM9son/BKdJnHnkeaOofEmh36Yeppo/wAZUwp6UijBudtG+4U381cLX0D+LXU57JVwrK+1HVvRIPgco5ka
Line truncated
|
||||
})
|
||||
data?: string;
|
||||
}
|
||||
|
||||
export type FileType = FileURL | FileContent;
|
||||
@@ -74,6 +74,23 @@ export class JoinGroupResponse {
|
||||
id: string;
|
||||
}
|
||||
|
||||
export enum GroupField {
|
||||
NONE = '',
|
||||
PARTICIPANTS = 'participants',
|
||||
}
|
||||
|
||||
export class GroupsListFields {
|
||||
@IsOptional()
|
||||
@ApiProperty({
|
||||
description: 'Exclude fields',
|
||||
enum: GroupField,
|
||||
isArray: true,
|
||||
required: false,
|
||||
})
|
||||
@IsEnum(GroupField, { each: true })
|
||||
exclude: string[];
|
||||
}
|
||||
|
||||
export enum GroupSortField {
|
||||
ID = 'id',
|
||||
SUBJECT = 'subject',
|
||||
@@ -88,3 +105,69 @@ export class GroupsPaginationParams extends PaginationParams {
|
||||
@IsEnum(GroupSortField)
|
||||
sortBy?: string;
|
||||
}
|
||||
|
||||
export enum GroupParticipantRole {
|
||||
LEFT = 'left',
|
||||
PARTICIPANT = 'participant',
|
||||
ADMIN = 'admin',
|
||||
SUPERADMIN = 'superadmin',
|
||||
}
|
||||
|
||||
export class GroupParticipant {
|
||||
@ApiProperty({
|
||||
example: '123456789@c.us',
|
||||
})
|
||||
id: string;
|
||||
|
||||
@ApiProperty({
|
||||
example: GroupParticipantRole.PARTICIPANT,
|
||||
})
|
||||
role: GroupParticipantRole;
|
||||
}
|
||||
|
||||
export class GroupId {
|
||||
@ApiProperty({
|
||||
example: '123456789@g.us',
|
||||
})
|
||||
id: string;
|
||||
}
|
||||
|
||||
export class GroupInfo {
|
||||
@ApiProperty({
|
||||
example: '123456789@g.us',
|
||||
})
|
||||
id: string;
|
||||
|
||||
@ApiProperty({
|
||||
example: 'Group Name',
|
||||
})
|
||||
subject: string;
|
||||
|
||||
@ApiProperty({
|
||||
example: 'Group Description',
|
||||
})
|
||||
description: string;
|
||||
|
||||
participants: GroupParticipant[];
|
||||
|
||||
@ApiProperty({
|
||||
description: 'Invite URL',
|
||||
example: 'https://chat.whatsapp.com/1234567890abcdef',
|
||||
})
|
||||
invite?: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'Members can add new members',
|
||||
})
|
||||
membersCanAddNewMember: boolean;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'Members can send messages to the group',
|
||||
})
|
||||
membersCanSendMessages: boolean;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'Admin approval required for new members',
|
||||
})
|
||||
newMembersApprovalRequired: boolean;
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
|
||||
import { GroupId, GroupInfo, GroupParticipant } from './groups.dto';
|
||||
|
||||
/**
|
||||
* Happens when you join or are added to a group.
|
||||
*/
|
||||
export class GroupV2JoinEvent {
|
||||
@ApiProperty({
|
||||
description: 'Unix timestamp',
|
||||
example: 1666943582,
|
||||
})
|
||||
timestamp: number;
|
||||
|
||||
group: GroupInfo;
|
||||
|
||||
_data: any;
|
||||
}
|
||||
|
||||
/**
|
||||
* Happens when you leave or are removed from a group.
|
||||
*/
|
||||
export class GroupV2LeaveEvent {
|
||||
@ApiProperty({
|
||||
description: 'Unix timestamp',
|
||||
example: 1666943582,
|
||||
})
|
||||
timestamp: number;
|
||||
|
||||
group: GroupId;
|
||||
|
||||
_data: any;
|
||||
}
|
||||
|
||||
export class GroupV2UpdateEvent {
|
||||
@ApiProperty({
|
||||
description: 'Unix timestamp',
|
||||
example: 1666943582,
|
||||
})
|
||||
timestamp: number;
|
||||
|
||||
group: Partial<GroupInfo>;
|
||||
|
||||
_data: any;
|
||||
}
|
||||
|
||||
export enum GroupParticipantType {
|
||||
JOIN = 'join',
|
||||
LEAVE = 'leave',
|
||||
PROMOTE = 'promote',
|
||||
DEMOTE = 'demote',
|
||||
}
|
||||
|
||||
export class GroupV2ParticipantsEvent {
|
||||
group: GroupId;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'Type of the event',
|
||||
})
|
||||
type: GroupParticipantType;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'Unix timestamp',
|
||||
example: 1666943582,
|
||||
})
|
||||
timestamp: number;
|
||||
|
||||
participants: GroupParticipant[];
|
||||
|
||||
_data: any;
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { WAHAEvents } from '@waha/structures/enums.dto';
|
||||
import {
|
||||
GroupV2JoinEvent,
|
||||
GroupV2LeaveEvent,
|
||||
GroupV2ParticipantsEvent,
|
||||
GroupV2UpdateEvent,
|
||||
} from '@waha/structures/groups.events.dto';
|
||||
import { WAHAWebhook } from '@waha/structures/webhooks.dto';
|
||||
|
||||
export class WebhookGroupV2Join extends WAHAWebhook {
|
||||
@ApiProperty({
|
||||
description: 'When you joined or were added to a group',
|
||||
})
|
||||
event = WAHAEvents.GROUP_V2_JOIN;
|
||||
|
||||
payload: GroupV2JoinEvent;
|
||||
}
|
||||
|
||||
export class WebhookGroupV2Leave extends WAHAWebhook {
|
||||
@ApiProperty({
|
||||
description: 'When you left or were removed from a group',
|
||||
})
|
||||
event = WAHAEvents.GROUP_V2_LEAVE;
|
||||
|
||||
payload: GroupV2LeaveEvent;
|
||||
}
|
||||
|
||||
export class WebhookGroupV2Update extends WAHAWebhook {
|
||||
@ApiProperty({
|
||||
description: 'When group info is updated',
|
||||
})
|
||||
event = WAHAEvents.GROUP_V2_UPDATE;
|
||||
|
||||
payload: GroupV2UpdateEvent;
|
||||
}
|
||||
|
||||
export class WebhookGroupV2Participants extends WAHAWebhook {
|
||||
@ApiProperty({
|
||||
description: 'When participants changed - join, leave, promote to admin',
|
||||
})
|
||||
event = WAHAEvents.GROUP_V2_PARTICIPANTS;
|
||||
|
||||
payload: GroupV2ParticipantsEvent;
|
||||
}
|
||||
@@ -15,4 +15,9 @@ export class ReplyToMessage {
|
||||
example: 'Hello!',
|
||||
})
|
||||
body?: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: "Raw data from reply's message",
|
||||
})
|
||||
_data?: any;
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
import { ApiExtraModels, ApiProperty, getSchemaPath } from '@nestjs/swagger';
|
||||
import { BinaryFile, RemoteFile } from '@waha/structures/files.dto';
|
||||
import { ChatIdProperty } from '@waha/structures/properties.dto';
|
||||
import { IsNotEmpty, IsString } from 'class-validator';
|
||||
|
||||
export class MyProfile {
|
||||
@ChatIdProperty()
|
||||
id: string;
|
||||
|
||||
name: string;
|
||||
|
||||
@ApiProperty({
|
||||
example: 'https://example.com/picture.jpg',
|
||||
})
|
||||
picture: string | null;
|
||||
}
|
||||
|
||||
export class ProfileNameRequest {
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@ApiProperty({
|
||||
example: 'My New Name',
|
||||
})
|
||||
name: string;
|
||||
}
|
||||
|
||||
export class ProfileStatusRequest {
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@ApiProperty({
|
||||
example: '🎉 Hey there! I am using WhatsApp 🎉',
|
||||
})
|
||||
status: string;
|
||||
}
|
||||
|
||||
@ApiExtraModels(RemoteFile, BinaryFile)
|
||||
export class ProfilePictureRequest {
|
||||
@ApiProperty({
|
||||
oneOf: [
|
||||
{ $ref: getSchemaPath(RemoteFile) },
|
||||
{ $ref: getSchemaPath(BinaryFile) },
|
||||
],
|
||||
})
|
||||
file: BinaryFile | RemoteFile;
|
||||
}
|
||||
@@ -11,6 +11,11 @@ export class WALocation {
|
||||
longitude: string;
|
||||
}
|
||||
|
||||
export enum MessageSource {
|
||||
API = 'api',
|
||||
APP = 'app',
|
||||
}
|
||||
|
||||
class WAMessageBase {
|
||||
@MessageIdProperty()
|
||||
id: string;
|
||||
@@ -33,6 +38,13 @@ class WAMessageBase {
|
||||
})
|
||||
fromMe: boolean;
|
||||
|
||||
@ApiProperty({
|
||||
description:
|
||||
'The device that sent the message - either API or APP. Available in events (webhooks/websockets) only and only "fromMe: true" messages.',
|
||||
example: MessageSource.API,
|
||||
})
|
||||
source: MessageSource;
|
||||
|
||||
@ChatIdProperty({
|
||||
description: `
|
||||
* ID for who this message is for.
|
||||
|
||||
@@ -17,21 +17,30 @@ const ContactsProperty = ApiProperty({
|
||||
required: false,
|
||||
});
|
||||
|
||||
export interface StatusRequest {
|
||||
contacts?: string[];
|
||||
}
|
||||
|
||||
export class TextStatus {
|
||||
text: string = 'Have a look! https://github.com/';
|
||||
backgroundColor: string = '#38b42f';
|
||||
font: number = 1;
|
||||
export class StatusRequest {
|
||||
@ApiProperty({
|
||||
description: 'Pre-generated status message id',
|
||||
example: 'BBBBBBBBBBBBBBBBB',
|
||||
default: null,
|
||||
required: false,
|
||||
})
|
||||
id?: string;
|
||||
|
||||
@ContactsProperty
|
||||
contacts?: string[];
|
||||
}
|
||||
|
||||
export class TextStatus extends StatusRequest {
|
||||
text: string = 'Have a look! https://github.com/';
|
||||
backgroundColor: string = '#38b42f';
|
||||
font: number = 0;
|
||||
|
||||
linkPreview?: boolean = true;
|
||||
linkPreviewHighQuality?: boolean = false;
|
||||
}
|
||||
|
||||
@ApiExtraModels(RemoteFile, BinaryFile)
|
||||
export class ImageStatus {
|
||||
export class ImageStatus extends StatusRequest {
|
||||
@ApiProperty({
|
||||
oneOf: [
|
||||
{ $ref: getSchemaPath(RemoteFile) },
|
||||
@@ -41,13 +50,10 @@ export class ImageStatus {
|
||||
file: RemoteFile | BinaryFile;
|
||||
|
||||
caption?: string;
|
||||
|
||||
@ContactsProperty
|
||||
contacts?: string[];
|
||||
}
|
||||
|
||||
@ApiExtraModels(VoiceRemoteFile, VoiceBinaryFile)
|
||||
export class VoiceStatus {
|
||||
export class VoiceStatus extends StatusRequest {
|
||||
@ApiProperty({
|
||||
oneOf: [
|
||||
{ $ref: getSchemaPath(VoiceRemoteFile) },
|
||||
@@ -57,13 +63,10 @@ export class VoiceStatus {
|
||||
file: VoiceRemoteFile | VoiceBinaryFile;
|
||||
|
||||
backgroundColor: string = '#38b42f';
|
||||
|
||||
@ContactsProperty
|
||||
contacts?: string[];
|
||||
}
|
||||
|
||||
@ApiExtraModels(VideoRemoteFile, VideoBinaryFile)
|
||||
export class VideoStatus {
|
||||
export class VideoStatus extends StatusRequest {
|
||||
@ApiProperty({
|
||||
oneOf: [
|
||||
{ $ref: getSchemaPath(VideoRemoteFile) },
|
||||
@@ -73,14 +76,11 @@ export class VideoStatus {
|
||||
file: VideoRemoteFile | VideoBinaryFile;
|
||||
|
||||
caption?: string;
|
||||
|
||||
@ContactsProperty
|
||||
contacts?: string[];
|
||||
}
|
||||
|
||||
export class DeleteStatusRequest {
|
||||
export class DeleteStatusRequest extends StatusRequest {
|
||||
@ApiProperty({
|
||||
description: 'status message id',
|
||||
description: 'Status message id to delete',
|
||||
example: 'AAAAAAAAAAAAAAAAA',
|
||||
})
|
||||
id: string;
|
||||
|
||||
@@ -1,15 +1,21 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { WAHAEvents } from '@waha/structures/enums.dto';
|
||||
import {
|
||||
AllEvents,
|
||||
AllEventType,
|
||||
WAHAEvents,
|
||||
} from '@waha/structures/enums.dto';
|
||||
import { Type } from 'class-transformer';
|
||||
import {
|
||||
IsArray,
|
||||
IsEnum,
|
||||
IsIn,
|
||||
IsNumber,
|
||||
IsOptional,
|
||||
IsString,
|
||||
IsUrl,
|
||||
ValidateNested,
|
||||
} from 'class-validator';
|
||||
import { each } from 'lodash';
|
||||
|
||||
export enum RetryPolicy {
|
||||
LINEAR = 'linear',
|
||||
@@ -70,16 +76,20 @@ export class WebhookConfig {
|
||||
description:
|
||||
'You can use https://docs.webhook.site/ to test webhooks and see the payload',
|
||||
})
|
||||
@IsUrl({ require_protocol: true, require_tld: false })
|
||||
@IsUrl({
|
||||
protocols: ['http', 'https'],
|
||||
require_protocol: true,
|
||||
require_tld: false,
|
||||
})
|
||||
url: string;
|
||||
|
||||
@ApiProperty({
|
||||
example: ['message', 'session.status'],
|
||||
required: true,
|
||||
})
|
||||
@IsEnum(WAHAEvents, { each: true })
|
||||
@IsIn(AllEvents, { each: true })
|
||||
@IsArray()
|
||||
events: WAHAEvents[];
|
||||
events: AllEventType[];
|
||||
|
||||
@ApiProperty({
|
||||
example: null,
|
||||
|
||||
@@ -91,6 +91,7 @@ export class PollVotePayload {
|
||||
export class WAMessageRevokedBody {
|
||||
after?: WAMessage;
|
||||
before?: WAMessage;
|
||||
_data?: any;
|
||||
}
|
||||
|
||||
export class WASessionStatusBody {
|
||||
@@ -104,10 +105,18 @@ export class WASessionStatusBody {
|
||||
|
||||
export class WAHAWebhook {
|
||||
@ApiProperty({
|
||||
example: 'evt_01jcn4pjwwg47bwy2gsey6q5sx',
|
||||
example: 'evt_01aaaaaaaaaaaaaaaaaaaaaaaa',
|
||||
description:
|
||||
'Unique identifier for the event - lower case ULID format. https://github.com/ulid/spec',
|
||||
})
|
||||
id: string;
|
||||
|
||||
@ApiProperty({
|
||||
example: 1634567890123,
|
||||
description: 'Unix timestamp (ms) for when the event was created.',
|
||||
})
|
||||
timestamp: number;
|
||||
|
||||
@ApiProperty({
|
||||
example: 'default',
|
||||
})
|
||||
@@ -148,14 +157,14 @@ export class WAHAWebhookSessionStatus extends WAHAWebhook {
|
||||
payload: WASessionStatusBody;
|
||||
}
|
||||
|
||||
class WAHAWebhookMessage extends WAHAWebhook {
|
||||
export class WAHAWebhookMessage extends WAHAWebhook {
|
||||
@ApiProperty({ description: 'Incoming message.' })
|
||||
event = WAHAEvents.MESSAGE;
|
||||
|
||||
payload: WAMessage;
|
||||
}
|
||||
|
||||
class WAHAWebhookMessageReaction extends WAHAWebhook {
|
||||
export class WAHAWebhookMessageReaction extends WAHAWebhook {
|
||||
@ApiProperty({
|
||||
description:
|
||||
'The event is triggered when a user reacts or removes a reaction.',
|
||||
@@ -165,7 +174,7 @@ class WAHAWebhookMessageReaction extends WAHAWebhook {
|
||||
payload: WAMessageReaction;
|
||||
}
|
||||
|
||||
class WAHAWebhookMessageAny extends WAHAWebhook {
|
||||
export class WAHAWebhookMessageAny extends WAHAWebhook {
|
||||
@ApiProperty({
|
||||
description: 'Fired on all message creations, including your own.',
|
||||
})
|
||||
@@ -174,7 +183,7 @@ class WAHAWebhookMessageAny extends WAHAWebhook {
|
||||
payload: WAMessage;
|
||||
}
|
||||
|
||||
class WAHAWebhookMessageAck extends WAHAWebhook {
|
||||
export class WAHAWebhookMessageAck extends WAHAWebhook {
|
||||
@ApiProperty({
|
||||
description:
|
||||
'Receive events when server or recipient gets the message, read or played it.',
|
||||
@@ -184,7 +193,7 @@ class WAHAWebhookMessageAck extends WAHAWebhook {
|
||||
payload: WAMessageAckBody;
|
||||
}
|
||||
|
||||
class WAHAWebhookMessageRevoked extends WAHAWebhook {
|
||||
export class WAHAWebhookMessageRevoked extends WAHAWebhook {
|
||||
@ApiProperty({
|
||||
description:
|
||||
'The event is triggered when a user, whether it be you or any other participant, ' +
|
||||
@@ -195,34 +204,37 @@ class WAHAWebhookMessageRevoked extends WAHAWebhook {
|
||||
payload: WAMessageRevokedBody;
|
||||
}
|
||||
|
||||
class WAHAWebhookStateChange extends WAHAWebhook {
|
||||
export class WAHAWebhookStateChange extends WAHAWebhook {
|
||||
@ApiProperty({
|
||||
description: 'It’s an internal engine’s state, not session status.',
|
||||
deprecated: true,
|
||||
})
|
||||
event = WAHAEvents.STATE_CHANGE;
|
||||
|
||||
payload: any;
|
||||
}
|
||||
|
||||
class WAHAWebhookGroupJoin extends WAHAWebhook {
|
||||
export class WAHAWebhookGroupJoin extends WAHAWebhook {
|
||||
@ApiProperty({
|
||||
description: 'Some one join a group.',
|
||||
deprecated: true,
|
||||
})
|
||||
event = WAHAEvents.GROUP_JOIN;
|
||||
|
||||
payload: any;
|
||||
}
|
||||
|
||||
class WAHAWebhookGroupLeave extends WAHAWebhook {
|
||||
export class WAHAWebhookGroupLeave extends WAHAWebhook {
|
||||
@ApiProperty({
|
||||
description: 'Some one left a group.',
|
||||
deprecated: true,
|
||||
})
|
||||
event = WAHAEvents.GROUP_LEAVE;
|
||||
|
||||
payload: any;
|
||||
}
|
||||
|
||||
class WAHAWebhookPresenceUpdate extends WAHAWebhook {
|
||||
export class WAHAWebhookPresenceUpdate extends WAHAWebhook {
|
||||
@ApiProperty({
|
||||
description: 'The most recent presence information for a chat.',
|
||||
})
|
||||
@@ -231,7 +243,7 @@ class WAHAWebhookPresenceUpdate extends WAHAWebhook {
|
||||
payload: WAHAChatPresences;
|
||||
}
|
||||
|
||||
class WAHAWebhookPollVote extends WAHAWebhook {
|
||||
export class WAHAWebhookPollVote extends WAHAWebhook {
|
||||
@ApiProperty({
|
||||
description: 'With this event, you receive new votes for the poll sent.',
|
||||
})
|
||||
@@ -240,7 +252,7 @@ class WAHAWebhookPollVote extends WAHAWebhook {
|
||||
payload: PollVotePayload;
|
||||
}
|
||||
|
||||
class WAHAWebhookPollVoteFailed extends WAHAWebhook {
|
||||
export class WAHAWebhookPollVoteFailed extends WAHAWebhook {
|
||||
@ApiProperty({
|
||||
description:
|
||||
'There may be cases when it fails to decrypt a vote from the user. ' +
|
||||
@@ -251,7 +263,7 @@ class WAHAWebhookPollVoteFailed extends WAHAWebhook {
|
||||
payload: PollVotePayload;
|
||||
}
|
||||
|
||||
class WAHAWebhookChatArchive extends WAHAWebhook {
|
||||
export class WAHAWebhookChatArchive extends WAHAWebhook {
|
||||
@ApiProperty({
|
||||
description:
|
||||
'The event is triggered when the chat is archived or unarchived',
|
||||
@@ -261,7 +273,7 @@ class WAHAWebhookChatArchive extends WAHAWebhook {
|
||||
payload: ChatArchiveEvent;
|
||||
}
|
||||
|
||||
class WAHAWebhookCallReceived extends WAHAWebhook {
|
||||
export class WAHAWebhookCallReceived extends WAHAWebhook {
|
||||
@ApiProperty({
|
||||
description:
|
||||
'The event is triggered when the call is received by the user.',
|
||||
@@ -271,7 +283,7 @@ class WAHAWebhookCallReceived extends WAHAWebhook {
|
||||
payload: CallData;
|
||||
}
|
||||
|
||||
class WAHAWebhookCallAccepted extends WAHAWebhook {
|
||||
export class WAHAWebhookCallAccepted extends WAHAWebhook {
|
||||
@ApiProperty({
|
||||
description:
|
||||
'The event is triggered when the call is accepted by the user.',
|
||||
@@ -281,7 +293,7 @@ class WAHAWebhookCallAccepted extends WAHAWebhook {
|
||||
payload: CallData;
|
||||
}
|
||||
|
||||
class WAHAWebhookCallRejected extends WAHAWebhook {
|
||||
export class WAHAWebhookCallRejected extends WAHAWebhook {
|
||||
@ApiProperty({
|
||||
description:
|
||||
'The event is triggered when the call is rejected by the user.',
|
||||
@@ -291,7 +303,7 @@ class WAHAWebhookCallRejected extends WAHAWebhook {
|
||||
payload: CallData;
|
||||
}
|
||||
|
||||
class WAHAWebhookLabelUpsert extends WAHAWebhook {
|
||||
export class WAHAWebhookLabelUpsert extends WAHAWebhook {
|
||||
@ApiProperty({
|
||||
description: 'The event is triggered when a label is created or updated',
|
||||
})
|
||||
@@ -300,7 +312,7 @@ class WAHAWebhookLabelUpsert extends WAHAWebhook {
|
||||
payload: Label;
|
||||
}
|
||||
|
||||
class WAHAWebhookLabelDeleted extends WAHAWebhook {
|
||||
export class WAHAWebhookLabelDeleted extends WAHAWebhook {
|
||||
@ApiProperty({
|
||||
description: 'The event is triggered when a label is deleted',
|
||||
})
|
||||
@@ -309,7 +321,7 @@ class WAHAWebhookLabelDeleted extends WAHAWebhook {
|
||||
payload: Label;
|
||||
}
|
||||
|
||||
class WAHAWebhookLabelChatAdded extends WAHAWebhook {
|
||||
export class WAHAWebhookLabelChatAdded extends WAHAWebhook {
|
||||
@ApiProperty({
|
||||
description: 'The event is triggered when a label is added to a chat',
|
||||
})
|
||||
@@ -318,7 +330,7 @@ class WAHAWebhookLabelChatAdded extends WAHAWebhook {
|
||||
payload: LabelChatAssociation;
|
||||
}
|
||||
|
||||
class WAHAWebhookLabelChatDeleted extends WAHAWebhook {
|
||||
export class WAHAWebhookLabelChatDeleted extends WAHAWebhook {
|
||||
@ApiProperty({
|
||||
description: 'The event is triggered when a label is deleted from a chat',
|
||||
})
|
||||
@@ -332,7 +344,7 @@ export class EnginePayload {
|
||||
data: any;
|
||||
}
|
||||
|
||||
class WAHAWebhookEngineEvent extends WAHAWebhook {
|
||||
export class WAHAWebhookEngineEvent extends WAHAWebhook {
|
||||
@ApiProperty({
|
||||
description: 'Internal engine event.',
|
||||
})
|
||||
@@ -340,28 +352,3 @@ class WAHAWebhookEngineEvent extends WAHAWebhook {
|
||||
|
||||
payload: EnginePayload;
|
||||
}
|
||||
|
||||
const WAHA_WEBHOOKS = [
|
||||
WAHAWebhookSessionStatus,
|
||||
WAHAWebhookMessage,
|
||||
WAHAWebhookMessageReaction,
|
||||
WAHAWebhookMessageAny,
|
||||
WAHAWebhookMessageAck,
|
||||
WAHAWebhookMessageRevoked,
|
||||
WAHAWebhookStateChange,
|
||||
WAHAWebhookGroupJoin,
|
||||
WAHAWebhookGroupLeave,
|
||||
WAHAWebhookPresenceUpdate,
|
||||
WAHAWebhookPollVote,
|
||||
WAHAWebhookPollVoteFailed,
|
||||
WAHAWebhookChatArchive,
|
||||
WAHAWebhookCallReceived,
|
||||
WAHAWebhookCallAccepted,
|
||||
WAHAWebhookCallRejected,
|
||||
WAHAWebhookLabelUpsert,
|
||||
WAHAWebhookLabelDeleted,
|
||||
WAHAWebhookLabelChatAdded,
|
||||
WAHAWebhookLabelChatDeleted,
|
||||
WAHAWebhookEngineEvent,
|
||||
];
|
||||
export { WAHA_WEBHOOKS };
|
||||
@@ -0,0 +1,58 @@
|
||||
import {
|
||||
WebhookGroupV2Join,
|
||||
WebhookGroupV2Leave,
|
||||
WebhookGroupV2Participants,
|
||||
WebhookGroupV2Update,
|
||||
} from '@waha/structures/groups.webhooks.dto';
|
||||
import {
|
||||
WAHAWebhookCallAccepted,
|
||||
WAHAWebhookCallReceived,
|
||||
WAHAWebhookCallRejected,
|
||||
WAHAWebhookChatArchive,
|
||||
WAHAWebhookEngineEvent,
|
||||
WAHAWebhookGroupJoin,
|
||||
WAHAWebhookGroupLeave,
|
||||
WAHAWebhookLabelChatAdded,
|
||||
WAHAWebhookLabelChatDeleted,
|
||||
WAHAWebhookLabelDeleted,
|
||||
WAHAWebhookLabelUpsert,
|
||||
WAHAWebhookMessage,
|
||||
WAHAWebhookMessageAck,
|
||||
WAHAWebhookMessageAny,
|
||||
WAHAWebhookMessageReaction,
|
||||
WAHAWebhookMessageRevoked,
|
||||
WAHAWebhookPollVote,
|
||||
WAHAWebhookPollVoteFailed,
|
||||
WAHAWebhookPresenceUpdate,
|
||||
WAHAWebhookSessionStatus,
|
||||
WAHAWebhookStateChange,
|
||||
} from '@waha/structures/webhooks.dto';
|
||||
|
||||
const WAHA_WEBHOOKS = [
|
||||
WAHAWebhookSessionStatus,
|
||||
WAHAWebhookMessage,
|
||||
WAHAWebhookMessageReaction,
|
||||
WAHAWebhookMessageAny,
|
||||
WAHAWebhookMessageAck,
|
||||
WAHAWebhookMessageRevoked,
|
||||
WebhookGroupV2Join,
|
||||
WebhookGroupV2Leave,
|
||||
WebhookGroupV2Update,
|
||||
WebhookGroupV2Participants,
|
||||
WAHAWebhookPresenceUpdate,
|
||||
WAHAWebhookPollVote,
|
||||
WAHAWebhookPollVoteFailed,
|
||||
WAHAWebhookChatArchive,
|
||||
WAHAWebhookCallReceived,
|
||||
WAHAWebhookCallAccepted,
|
||||
WAHAWebhookCallRejected,
|
||||
WAHAWebhookLabelUpsert,
|
||||
WAHAWebhookLabelDeleted,
|
||||
WAHAWebhookLabelChatAdded,
|
||||
WAHAWebhookLabelChatDeleted,
|
||||
WAHAWebhookEngineEvent,
|
||||
WAHAWebhookGroupJoin,
|
||||
WAHAWebhookGroupLeave,
|
||||
WAHAWebhookStateChange,
|
||||
];
|
||||
export { WAHA_WEBHOOKS };
|
||||
@@ -0,0 +1,25 @@
|
||||
import { WAHASessionStatus } from '../structures/enums.dto';
|
||||
|
||||
const STUCK_IN_STARTING_THRESHOLD = 20;
|
||||
|
||||
/**
|
||||
* Keeps track of session statuses (with timestamps) for the last `trackPeriodMs` milliseconds.
|
||||
*/
|
||||
export class StatusTracker {
|
||||
private numberOfStarting: number = 0;
|
||||
|
||||
public track(status: WAHASessionStatus): void {
|
||||
if (status == WAHASessionStatus.STARTING) {
|
||||
this.numberOfStarting += 1;
|
||||
} else {
|
||||
this.numberOfStarting = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the session has been continuously 'STARTING'
|
||||
*/
|
||||
public isStuckInStarting(): boolean {
|
||||
return this.numberOfStarting >= STUCK_IN_STARTING_THRESHOLD;
|
||||
}
|
||||
}
|
||||
+3
-2
@@ -33,8 +33,9 @@ export function getEngineName(): string {
|
||||
}
|
||||
|
||||
export const VERSION: WAHAEnvironment = {
|
||||
version: '2025.1.4',
|
||||
version: '2025.4.1',
|
||||
engine: getEngineName(),
|
||||
tier: getWAHAVersion(),
|
||||
browser: getBrowserExecutablePath(),
|
||||
browser:
|
||||
getEngineName() === WAHAEngine.WEBJS ? getBrowserExecutablePath() : null,
|
||||
};
|
||||
@@ -0,0 +1,59 @@
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const axios = require('axios');
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const yargs = require('yargs/yargs');
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const { hideBin } = require('yargs/helpers');
|
||||
|
||||
// Parse CLI arguments
|
||||
const argv = yargs(hideBin(process.argv))
|
||||
.option('number', {
|
||||
alias: 'n',
|
||||
type: 'number',
|
||||
description: 'Number of messages to send',
|
||||
default: 10,
|
||||
})
|
||||
.option('chat-id', {
|
||||
alias: 'to',
|
||||
type: 'number',
|
||||
description: 'Chat ID to send messages to',
|
||||
demandOption: true,
|
||||
})
|
||||
.option('session', {
|
||||
type: 'string',
|
||||
description: 'Session ID to use',
|
||||
demandOption: true,
|
||||
})
|
||||
.option('api-key', {
|
||||
alias: 'k',
|
||||
type: 'string',
|
||||
description: 'API key for authentication',
|
||||
demandOption: true,
|
||||
})
|
||||
.help().argv;
|
||||
|
||||
const sendTextMessage = async (session, numMessages, chatId, apiKey) => {
|
||||
const url = 'http://localhost:3000/api/sendText/';
|
||||
for (let i = 1; i <= numMessages; i++) {
|
||||
console.log(`Sending message ${i}`);
|
||||
const response = await axios.post(
|
||||
url,
|
||||
{
|
||||
chatId: chatId.toString(),
|
||||
session: session,
|
||||
text: `Message - ${i}`,
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
'X-Api-Key': apiKey,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
},
|
||||
);
|
||||
console.log(`Message ${i} sent successfully:`, response.data);
|
||||
}
|
||||
};
|
||||
|
||||
// Run the script
|
||||
sendTextMessage(argv.session, argv.number, argv['chat-id'], argv['api-key']);
|
||||
@@ -0,0 +1,56 @@
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const axios = require('axios');
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const yargs = require('yargs/yargs');
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const { hideBin } = require('yargs/helpers');
|
||||
|
||||
// Parse CLI arguments
|
||||
const argv = yargs(hideBin(process.argv))
|
||||
.option('sessions', {
|
||||
alias: 'n',
|
||||
type: 'number',
|
||||
description: 'Number of sessions to create',
|
||||
default: 10,
|
||||
})
|
||||
.option('api-key', {
|
||||
alias: 'k',
|
||||
type: 'string',
|
||||
description: 'API key for authentication',
|
||||
demandOption: true,
|
||||
})
|
||||
.help().argv;
|
||||
|
||||
const createSessions = async (numSessions, apiKey) => {
|
||||
const url = 'http://localhost:3000/api/sessions/';
|
||||
|
||||
for (let i = 1; i <= numSessions; i++) {
|
||||
try {
|
||||
console.log(`Creating session ${i}`);
|
||||
const response = await axios.post(
|
||||
url,
|
||||
{
|
||||
name: '',
|
||||
config: {
|
||||
metadata: {},
|
||||
webhooks: [],
|
||||
},
|
||||
start: true,
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
'X-Api-Key': apiKey,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
},
|
||||
);
|
||||
console.log(`Session ${i} created successfully:`, response.data);
|
||||
} catch (error) {
|
||||
console.error(`Error creating session ${i}:`, error.message);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Run the script
|
||||
createSessions(argv.sessions, argv['api-key']);
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"exclude": ["node_modules", "test", "dist", "**/*spec.ts"]
|
||||
"exclude": ["node_modules", "test", "dist", "**/*spec.ts", "scripts/*.js"]
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"waha": {
|
||||
"gows": {
|
||||
"repo": "devlikeapro/gows",
|
||||
"ref": "v1.0.3"
|
||||
},
|
||||
"dashboard": {
|
||||
"repo": "devlikeapro/dashboard",
|
||||
"ref": "0e7e2e7edc715b0c30fff89c10ee2d2fc3402d2d"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,12 +5,13 @@ __metadata:
|
||||
version: 6
|
||||
cacheKey: 8
|
||||
|
||||
"@adiwajshing/baileys@github:devlikeapro/Baileys#fork-master-channels-678":
|
||||
version: 6.7.8
|
||||
resolution: "@adiwajshing/baileys@https://github.com/devlikeapro/Baileys.git#commit=04738f528321857efe837e3f93c73059878ecdcb"
|
||||
"@adiwajshing/baileys@github:devlikeapro/Baileys#fork-master-channels-6712":
|
||||
version: 6.7.12
|
||||
resolution: "@adiwajshing/baileys@https://github.com/devlikeapro/Baileys.git#commit=4e51470a1e1f6d56b3e48feabf886b80c0a75bd0"
|
||||
dependencies:
|
||||
"@adiwajshing/keyed-db": ^0.2.4
|
||||
"@hapi/boom": ^9.1.3
|
||||
"@whiskeysockets/eslint-config": "github:whiskeysockets/eslint-config"
|
||||
async-lock: ^1.4.1
|
||||
audio-decode: ^2.1.3
|
||||
axios: ^1.6.0
|
||||
@@ -39,7 +40,7 @@ __metadata:
|
||||
optional: true
|
||||
sharp:
|
||||
optional: true
|
||||
checksum: 0f7b2031eeb25137c65aee3151028f7a8637cfed0763c7426817415ec818ca71f55267b9de88511905bd266f4006301a26206dac9bdb1821d13f1a0cbf075e32
|
||||
checksum: d2d5fe4f4ed2a519fd30c486b079978fac1894b4ad195d337387acc841110f663e4525b89336f8dcccc63e6e7b54f4542e00006e7d42ee196dbce7b7d790b7fc
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -1242,6 +1243,24 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@eslint-community/eslint-utils@npm:^4.4.0":
|
||||
version: 4.4.1
|
||||
resolution: "@eslint-community/eslint-utils@npm:4.4.1"
|
||||
dependencies:
|
||||
eslint-visitor-keys: ^3.4.3
|
||||
peerDependencies:
|
||||
eslint: ^6.0.0 || ^7.0.0 || >=8.0.0
|
||||
checksum: a7ffc838eb6a9ef594cda348458ccf38f34439ac77dc090fa1c120024bcd4eb911dfd74d5ef44d42063e7949fa7c5123ce714a015c4abb917d4124be1bd32bfe
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@eslint-community/regexpp@npm:^4.10.0":
|
||||
version: 4.12.1
|
||||
resolution: "@eslint-community/regexpp@npm:4.12.1"
|
||||
checksum: 0d628680e204bc316d545b4993d3658427ca404ae646ce541fcc65306b8c712c340e5e573e30fb9f85f4855c0c5f6dca9868931f2fcced06417fbe1a0c6cd2d6
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@grpc/grpc-js@npm:^1.12.4":
|
||||
version: 1.12.4
|
||||
resolution: "@grpc/grpc-js@npm:1.12.4"
|
||||
@@ -3453,6 +3472,29 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/eslint-plugin@npm:^7.15.0":
|
||||
version: 7.18.0
|
||||
resolution: "@typescript-eslint/eslint-plugin@npm:7.18.0"
|
||||
dependencies:
|
||||
"@eslint-community/regexpp": ^4.10.0
|
||||
"@typescript-eslint/scope-manager": 7.18.0
|
||||
"@typescript-eslint/type-utils": 7.18.0
|
||||
"@typescript-eslint/utils": 7.18.0
|
||||
"@typescript-eslint/visitor-keys": 7.18.0
|
||||
graphemer: ^1.4.0
|
||||
ignore: ^5.3.1
|
||||
natural-compare: ^1.4.0
|
||||
ts-api-utils: ^1.3.0
|
||||
peerDependencies:
|
||||
"@typescript-eslint/parser": ^7.0.0
|
||||
eslint: ^8.56.0
|
||||
peerDependenciesMeta:
|
||||
typescript:
|
||||
optional: true
|
||||
checksum: dfcf150628ca2d4ccdfc20b46b0eae075c2f16ef5e70d9d2f0d746acf4c69a09f962b93befee01a529f14bbeb3e817b5aba287d7dd0edc23396bc5ed1f448c3d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/experimental-utils@npm:3.9.1":
|
||||
version: 3.9.1
|
||||
resolution: "@typescript-eslint/experimental-utils@npm:3.9.1"
|
||||
@@ -3486,6 +3528,51 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/parser@npm:^7.15.0":
|
||||
version: 7.18.0
|
||||
resolution: "@typescript-eslint/parser@npm:7.18.0"
|
||||
dependencies:
|
||||
"@typescript-eslint/scope-manager": 7.18.0
|
||||
"@typescript-eslint/types": 7.18.0
|
||||
"@typescript-eslint/typescript-estree": 7.18.0
|
||||
"@typescript-eslint/visitor-keys": 7.18.0
|
||||
debug: ^4.3.4
|
||||
peerDependencies:
|
||||
eslint: ^8.56.0
|
||||
peerDependenciesMeta:
|
||||
typescript:
|
||||
optional: true
|
||||
checksum: 132b56ac3b2d90b588d61d005a70f6af322860974225b60201cbf45abf7304d67b7d8a6f0ade1c188ac4e339884e78d6dcd450417f1481998f9ddd155bab0801
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/scope-manager@npm:7.18.0":
|
||||
version: 7.18.0
|
||||
resolution: "@typescript-eslint/scope-manager@npm:7.18.0"
|
||||
dependencies:
|
||||
"@typescript-eslint/types": 7.18.0
|
||||
"@typescript-eslint/visitor-keys": 7.18.0
|
||||
checksum: b982c6ac13d8c86bb3b949c6b4e465f3f60557c2ccf4cc229799827d462df56b9e4d3eaed7711d79b875422fc3d71ec1ebcb5195db72134d07c619e3c5506b57
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/type-utils@npm:7.18.0":
|
||||
version: 7.18.0
|
||||
resolution: "@typescript-eslint/type-utils@npm:7.18.0"
|
||||
dependencies:
|
||||
"@typescript-eslint/typescript-estree": 7.18.0
|
||||
"@typescript-eslint/utils": 7.18.0
|
||||
debug: ^4.3.4
|
||||
ts-api-utils: ^1.3.0
|
||||
peerDependencies:
|
||||
eslint: ^8.56.0
|
||||
peerDependenciesMeta:
|
||||
typescript:
|
||||
optional: true
|
||||
checksum: 68fd5df5146c1a08cde20d59b4b919acab06a1b06194fe4f7ba1b928674880249890785fbbc97394142f2ef5cff5a7fba9b8a940449e7d5605306505348e38bc
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/types@npm:3.9.1":
|
||||
version: 3.9.1
|
||||
resolution: "@typescript-eslint/types@npm:3.9.1"
|
||||
@@ -3493,6 +3580,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/types@npm:7.18.0":
|
||||
version: 7.18.0
|
||||
resolution: "@typescript-eslint/types@npm:7.18.0"
|
||||
checksum: 7df2750cd146a0acd2d843208d69f153b458e024bbe12aab9e441ad2c56f47de3ddfeb329c4d1ea0079e2577fea4b8c1c1ce15315a8d49044586b04fedfe7a4d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/typescript-estree@npm:3.9.1":
|
||||
version: 3.9.1
|
||||
resolution: "@typescript-eslint/typescript-estree@npm:3.9.1"
|
||||
@@ -3512,6 +3606,39 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/typescript-estree@npm:7.18.0":
|
||||
version: 7.18.0
|
||||
resolution: "@typescript-eslint/typescript-estree@npm:7.18.0"
|
||||
dependencies:
|
||||
"@typescript-eslint/types": 7.18.0
|
||||
"@typescript-eslint/visitor-keys": 7.18.0
|
||||
debug: ^4.3.4
|
||||
globby: ^11.1.0
|
||||
is-glob: ^4.0.3
|
||||
minimatch: ^9.0.4
|
||||
semver: ^7.6.0
|
||||
ts-api-utils: ^1.3.0
|
||||
peerDependenciesMeta:
|
||||
typescript:
|
||||
optional: true
|
||||
checksum: c82d22ec9654973944f779eb4eb94c52f4a6eafaccce2f0231ff7757313f3a0d0256c3252f6dfe6d43f57171d09656478acb49a629a9d0c193fb959bc3f36116
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/utils@npm:7.18.0":
|
||||
version: 7.18.0
|
||||
resolution: "@typescript-eslint/utils@npm:7.18.0"
|
||||
dependencies:
|
||||
"@eslint-community/eslint-utils": ^4.4.0
|
||||
"@typescript-eslint/scope-manager": 7.18.0
|
||||
"@typescript-eslint/types": 7.18.0
|
||||
"@typescript-eslint/typescript-estree": 7.18.0
|
||||
peerDependencies:
|
||||
eslint: ^8.56.0
|
||||
checksum: 751dbc816dab8454b7dc6b26a56671dbec08e3f4ef94c2661ce1c0fc48fa2d05a64e03efe24cba2c22d03ba943cd3c5c7a5e1b7b03bbb446728aec1c640bd767
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/visitor-keys@npm:3.9.1":
|
||||
version: 3.9.1
|
||||
resolution: "@typescript-eslint/visitor-keys@npm:3.9.1"
|
||||
@@ -3521,6 +3648,16 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/visitor-keys@npm:7.18.0":
|
||||
version: 7.18.0
|
||||
resolution: "@typescript-eslint/visitor-keys@npm:7.18.0"
|
||||
dependencies:
|
||||
"@typescript-eslint/types": 7.18.0
|
||||
eslint-visitor-keys: ^3.4.3
|
||||
checksum: 6e806a7cdb424c5498ea187a5a11d0fef7e4602a631be413e7d521e5aec1ab46ba00c76cfb18020adaa0a8c9802354a163bfa0deb74baa7d555526c7517bb158
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@wasm-audio-decoders/common@npm:9.0.4":
|
||||
version: 9.0.4
|
||||
resolution: "@wasm-audio-decoders/common@npm:9.0.4"
|
||||
@@ -3711,6 +3848,20 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@whiskeysockets/eslint-config@github:whiskeysockets/eslint-config":
|
||||
version: 1.0.0
|
||||
resolution: "@whiskeysockets/eslint-config@https://github.com/whiskeysockets/eslint-config.git#commit=326b55f2842668f4e11f471451c4e39819a0e1bf"
|
||||
dependencies:
|
||||
"@typescript-eslint/eslint-plugin": ^7.15.0
|
||||
"@typescript-eslint/parser": ^7.15.0
|
||||
eslint-plugin-simple-import-sort: ^12.1.1
|
||||
peerDependencies:
|
||||
eslint: "*"
|
||||
typescript: ">=4"
|
||||
checksum: a1ae2c83b0b635cf4898b51a597cd9cabc0df3f5bd4f439a705a1f35877802e8f07836f3298d02b5543d41e07c266b348bd8c8458f83ed225d975e89618b0483
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@xtuc/ieee754@npm:^1.2.0":
|
||||
version: 1.2.0
|
||||
resolution: "@xtuc/ieee754@npm:1.2.0"
|
||||
@@ -6236,6 +6387,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"eslint-plugin-simple-import-sort@npm:^12.1.1":
|
||||
version: 12.1.1
|
||||
resolution: "eslint-plugin-simple-import-sort@npm:12.1.1"
|
||||
peerDependencies:
|
||||
eslint: ">=5.0.0"
|
||||
checksum: 6d73e43ecf6221c1952e0cc820e6867e3e1fe973575e23d438d1d3de52284ceb2a01d31e20a76de11feb158bbba98f113150a676cc5526a8b3a5844d63ca37f8
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"eslint-scope@npm:5.1.1, eslint-scope@npm:^5.0.0, eslint-scope@npm:^5.1.0":
|
||||
version: 5.1.1
|
||||
resolution: "eslint-scope@npm:5.1.1"
|
||||
@@ -6262,6 +6422,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"eslint-visitor-keys@npm:^3.4.3":
|
||||
version: 3.4.3
|
||||
resolution: "eslint-visitor-keys@npm:3.4.3"
|
||||
checksum: 36e9ef87fca698b6fd7ca5ca35d7b2b6eeaaf106572e2f7fd31c12d3bfdaccdb587bba6d3621067e5aece31c8c3a348b93922ab8f7b2cbc6aaab5e1d89040c60
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"eslint@npm:7.7.0":
|
||||
version: 7.7.0
|
||||
resolution: "eslint@npm:7.7.0"
|
||||
@@ -7174,7 +7341,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"globby@npm:^11.0.1":
|
||||
"globby@npm:^11.0.1, globby@npm:^11.1.0":
|
||||
version: 11.1.0
|
||||
resolution: "globby@npm:11.1.0"
|
||||
dependencies:
|
||||
@@ -7211,6 +7378,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"graphemer@npm:^1.4.0":
|
||||
version: 1.4.0
|
||||
resolution: "graphemer@npm:1.4.0"
|
||||
checksum: bab8f0be9b568857c7bec9fda95a89f87b783546d02951c40c33f84d05bb7da3fd10f863a9beb901463669b6583173a8c8cc6d6b306ea2b9b9d5d3d943c3a673
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"grpc-tools@npm:^1.12.4":
|
||||
version: 1.12.4
|
||||
resolution: "grpc-tools@npm:1.12.4"
|
||||
@@ -7440,6 +7614,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"ignore@npm:^5.3.1":
|
||||
version: 5.3.2
|
||||
resolution: "ignore@npm:5.3.2"
|
||||
checksum: 2acfd32a573260ea522ea0bfeff880af426d68f6831f973129e2ba7363f422923cf53aab62f8369cbf4667c7b25b6f8a3761b34ecdb284ea18e87a5262a865be
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"import-fresh@npm:^3.0.0, import-fresh@npm:^3.2.1, import-fresh@npm:^3.3.0":
|
||||
version: 3.3.0
|
||||
resolution: "import-fresh@npm:3.3.0"
|
||||
@@ -12050,6 +12231,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"ts-api-utils@npm:^1.3.0":
|
||||
version: 1.4.3
|
||||
resolution: "ts-api-utils@npm:1.4.3"
|
||||
peerDependencies:
|
||||
typescript: ">=4.2.0"
|
||||
checksum: ea00dee382d19066b2a3d8929f1089888b05fec797e32e7a7004938eda1dccf2e77274ee2afcd4166f53fab9b8d7ee90ebb225a3183f9ba8817d636f688a148d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"ts-jest@npm:^29.1.3":
|
||||
version: 29.1.5
|
||||
resolution: "ts-jest@npm:29.1.5"
|
||||
@@ -12559,7 +12749,7 @@ __metadata:
|
||||
version: 0.0.0-use.local
|
||||
resolution: "waha@workspace:."
|
||||
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
|
||||
@@ -12646,6 +12836,7 @@ __metadata:
|
||||
ulid: ^2.3.0
|
||||
whatsapp-web.js: "github:devlikeapro/whatsapp-web.js#fork-main-channels"
|
||||
write-file-atomic: ^6.0.0
|
||||
yargs: ^17.7.2
|
||||
dependenciesMeta:
|
||||
bufferutil:
|
||||
optional: true
|
||||
@@ -12740,7 +12931,7 @@ __metadata:
|
||||
|
||||
"whatsapp-web.js@github:devlikeapro/whatsapp-web.js#fork-main-channels":
|
||||
version: 1.26.0
|
||||
resolution: "whatsapp-web.js@https://github.com/devlikeapro/whatsapp-web.js.git#commit=a8683b0d1a5b8025ac1e7deb3476042ceadb4c07"
|
||||
resolution: "whatsapp-web.js@https://github.com/devlikeapro/whatsapp-web.js.git#commit=c73f3cfb4e1290103ee946ca7f56d5638f1a66bd"
|
||||
dependencies:
|
||||
"@pedroslopez/moduleraid": ^5.0.2
|
||||
archiver: ^5.3.1
|
||||
@@ -12758,7 +12949,7 @@ __metadata:
|
||||
optional: true
|
||||
unzipper:
|
||||
optional: true
|
||||
checksum: 637e53dffdaa80813a5cdd72e519f8f68978e00db17b39b471f39c8e2b98bc85643fe74168255a1dfdc655c71803e3d01b4ca6635bc6159c429dd59232ac07d4
|
||||
checksum: 831a4a2819041f63c2a51a98e1ad7dd9ff8474b1005b11314b613c6dadac545f3c780e38081b7d260d32afa9ffd333a25f9c6407b630a0660bd08dadbc8ead89
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
||||
Reference in new issue
Block a user