Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e4f88771a8 | ||
|
|
e885ea8e95 | ||
|
|
f8a9ed7d06 | ||
|
|
d89e9105d0 | ||
|
|
3ba92dec04 | ||
|
|
441ebbd525 | ||
|
|
690c43c245 | ||
|
|
26d8591a6c | ||
|
|
ac63c097ec | ||
|
|
d29e4f8351 | ||
|
|
615ccda4c3 | ||
|
|
d52eb71c76 | ||
|
|
8edd81a765 | ||
|
|
5830cee384 | ||
|
|
20f15b4370 | ||
|
|
8432cd4f8d | ||
|
|
55870b88bf | ||
|
|
6acf4373f4 | ||
|
|
2381f0843f | ||
|
|
b4e6f5e722 | ||
|
|
415bee3e8e | ||
|
|
aad924be79 | ||
|
|
81083e7f82 | ||
|
|
f8e52959a4 | ||
|
|
8eb542f49e | ||
|
|
45ae263ab9 | ||
|
|
f066f982bf | ||
|
|
0888640741 | ||
|
|
bde3722f5c | ||
|
|
0cd3df117a | ||
|
|
4c9525cc9f | ||
|
|
c14fe1e67d | ||
|
|
d1e3b209e7 | ||
|
|
f37bf62a30 | ||
|
|
538bfe0e24 | ||
|
|
93cd5307d9 | ||
|
|
b697f37b1e | ||
|
|
96a9cd8cbb | ||
|
|
a21b9a11a9 | ||
|
|
213b51e598 | ||
|
|
faf3c77508 | ||
|
|
e6fdfb88ca | ||
|
|
99746df626 | ||
|
|
43aeeb55e8 | ||
|
|
4f89f49d44 | ||
|
|
be583e7e40 | ||
|
|
e8c8f89733 | ||
|
|
13b6d3ddf7 | ||
|
|
5e97fb74b1 | ||
|
|
cc47321cad | ||
|
|
607dff8f7a | ||
|
|
0a74f8dbcd | ||
|
|
796befcf87 | ||
|
|
2c5610dd01 | ||
|
|
43d110fa31 | ||
|
|
bf52dd4874 | ||
|
|
f8268180bf | ||
|
|
04cf9bc8c0 | ||
|
|
ff38cf7660 | ||
|
|
4c0b44c4b5 | ||
|
|
e75c29d57f | ||
|
|
f8329c29b1 |
No files matched your search
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Bash(yarn build:*)",
|
||||
"Bash(cat:*)",
|
||||
"Bash(node:*)",
|
||||
"Bash(npm show:*)",
|
||||
"Bash(python3:*)",
|
||||
"Bash(yarn test:unit:*)",
|
||||
"Bash(npx tsc:*)"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,8 @@ WHATSAPP_SWAGGER_ENABLED=True
|
||||
# ==================
|
||||
# WhatsApp engine (WEBJS is default, GOWS or NOWEB for better performance)
|
||||
WHATSAPP_DEFAULT_ENGINE=WEBJS
|
||||
# So you don't have to create sessions/apps when switching between engines
|
||||
WAHA_NAMESPACE=all
|
||||
|
||||
# "Firefox (YourApp)" in Linked Devices
|
||||
# WAHA_CLIENT_DEVICE_NAME=YourApp
|
||||
|
||||
@@ -15,7 +15,7 @@ jobs:
|
||||
matrix:
|
||||
include:
|
||||
# Chromium - x86
|
||||
- runner: 'buildjet-4vcpu-ubuntu-2204'
|
||||
- runner: 'ubuntu-latest'
|
||||
tag: 'latest'
|
||||
platform: 'amd64'
|
||||
browser: 'chromium'
|
||||
@@ -23,7 +23,7 @@ jobs:
|
||||
goss: 'goss-linux-amd64'
|
||||
|
||||
# Chromium - ARM
|
||||
- runner: 'buildjet-4vcpu-ubuntu-2204-arm'
|
||||
- runner: 'ubuntu-24.04-arm'
|
||||
tag: 'arm'
|
||||
platform: 'linux/arm64'
|
||||
browser: 'chromium'
|
||||
@@ -31,7 +31,7 @@ jobs:
|
||||
goss: 'goss-linux-arm'
|
||||
|
||||
# Chrome - x86
|
||||
- runner: 'buildjet-4vcpu-ubuntu-2204'
|
||||
- runner: 'ubuntu-latest'
|
||||
tag: 'chrome'
|
||||
platform: 'amd64'
|
||||
browser: 'chrome'
|
||||
@@ -39,14 +39,14 @@ jobs:
|
||||
goss: 'goss-linux-amd64'
|
||||
|
||||
# Chrome - ARM (Chrome is not available for ARM)
|
||||
#- runner: "buildjet-4vcpu-ubuntu-2204-arm"
|
||||
#- runner: "ubuntu-24.04-arm"
|
||||
# tag: "chrome-arm"
|
||||
# platform: "linux/arm64"
|
||||
# browser: "chrome"
|
||||
# goss: 'goss-linux-arm'
|
||||
|
||||
# NOWEB - x86
|
||||
- runner: 'buildjet-4vcpu-ubuntu-2204'
|
||||
- runner: 'ubuntu-latest'
|
||||
tag: 'noweb'
|
||||
platform: 'amd64'
|
||||
browser: 'none'
|
||||
@@ -54,7 +54,7 @@ jobs:
|
||||
goss: 'goss-linux-amd64'
|
||||
|
||||
# NOWEB - ARM
|
||||
- runner: 'buildjet-4vcpu-ubuntu-2204-arm'
|
||||
- runner: 'ubuntu-24.04-arm'
|
||||
tag: 'noweb-arm'
|
||||
platform: 'linux/arm64'
|
||||
browser: 'none'
|
||||
@@ -62,7 +62,7 @@ jobs:
|
||||
goss: 'goss-linux-arm'
|
||||
|
||||
# GOWS - x86
|
||||
- runner: 'buildjet-4vcpu-ubuntu-2204'
|
||||
- runner: 'ubuntu-latest'
|
||||
tag: 'gows'
|
||||
platform: 'amd64'
|
||||
browser: 'none'
|
||||
@@ -70,7 +70,7 @@ jobs:
|
||||
goss: 'goss-linux-amd64'
|
||||
|
||||
# No browser - ARM
|
||||
- runner: 'buildjet-4vcpu-ubuntu-2204-arm'
|
||||
- runner: 'ubuntu-24.04-arm'
|
||||
tag: 'gows-arm'
|
||||
platform: 'linux/arm64'
|
||||
browser: 'none'
|
||||
|
||||
+31
-31
@@ -6,37 +6,37 @@ on:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
unit-tests:
|
||||
if: github.repository == 'devlikeapro/waha-plus'
|
||||
runs-on: ubuntu-22.04
|
||||
name: Unit tests
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
|
||||
- name: Enable Corepack
|
||||
run: |
|
||||
corepack enable
|
||||
corepack prepare yarn@4.9.2 --activate
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
unset GIT_CONFIG_PARAMETERS
|
||||
yarn install --immutable
|
||||
|
||||
- name: Run unit tests
|
||||
run: yarn test:unit
|
||||
# unit-tests:
|
||||
# if: github.repository == 'devlikeapro/waha-plus'
|
||||
# runs-on: ubuntu-22.04
|
||||
# name: Unit tests
|
||||
# steps:
|
||||
# - name: Checkout
|
||||
# uses: actions/checkout@v4
|
||||
# with:
|
||||
# fetch-depth: 0
|
||||
#
|
||||
# - name: Set up Node
|
||||
# uses: actions/setup-node@v4
|
||||
# with:
|
||||
# node-version: 22
|
||||
#
|
||||
# - name: Enable Corepack
|
||||
# run: |
|
||||
# corepack enable
|
||||
# corepack prepare yarn@4.9.2 --activate
|
||||
#
|
||||
# - name: Install dependencies
|
||||
# run: |
|
||||
# unset GIT_CONFIG_PARAMETERS
|
||||
# yarn install --immutable
|
||||
#
|
||||
# - name: Run unit tests
|
||||
# run: yarn test:unit
|
||||
|
||||
docker-build:
|
||||
if: github.repository == 'devlikeapro/waha-plus'
|
||||
needs: unit-tests
|
||||
# needs: unit-tests
|
||||
runs-on: ${{ matrix.runner }}
|
||||
name:
|
||||
${{ matrix.engine }} - ${{ matrix.browser }} - ${{ matrix.platform }} -
|
||||
@@ -45,7 +45,7 @@ jobs:
|
||||
matrix:
|
||||
include:
|
||||
# Chromium - x86
|
||||
- runner: 'buildjet-4vcpu-ubuntu-2204'
|
||||
- runner: 'ubuntu-latest'
|
||||
tag: 'dev'
|
||||
platform: 'amd64'
|
||||
browser: 'chromium'
|
||||
@@ -53,7 +53,7 @@ jobs:
|
||||
goss: 'goss-linux-amd64'
|
||||
|
||||
# Chrome - x86
|
||||
- runner: 'buildjet-4vcpu-ubuntu-2204'
|
||||
- runner: 'ubuntu-latest'
|
||||
tag: 'dev-chrome'
|
||||
platform: 'amd64'
|
||||
browser: 'chrome'
|
||||
@@ -61,7 +61,7 @@ jobs:
|
||||
goss: 'goss-linux-amd64'
|
||||
|
||||
# Chromium - ARM
|
||||
- runner: 'buildjet-4vcpu-ubuntu-2204-arm'
|
||||
- runner: 'ubuntu-24.04-arm'
|
||||
tag: 'dev-arm'
|
||||
platform: 'linux/arm64'
|
||||
browser: 'chromium'
|
||||
|
||||
@@ -78,6 +78,11 @@ This guide summarizes how to explore, modify, and validate the WhatsApp HTTP API
|
||||
- Prefer named function declarations over `const` arrow functions when possible.
|
||||
- Avoid naming unused variables with a leading underscore; if a parameter is
|
||||
required by a signature, explicitly `void` it instead.
|
||||
- Do not write verbose ternaries like
|
||||
`condition !== undefined ? condition : default`; use idiomatic helpers such as
|
||||
`??` (nullish coalescing) or existing boolean parsers so flags stay readable.
|
||||
- Do not place `await` or other async calls inside ternary expressions (`?:`);
|
||||
use explicit `if/else` blocks instead.
|
||||
- For configs, prefer runtime configurability over constants. Environment keys
|
||||
follow `WAHA_*` for global values and `WAHA_SESSION_CONFIG_*` /
|
||||
`session.config.*` for per-session overrides. If both env and config are
|
||||
@@ -106,10 +111,10 @@ This guide summarizes how to explore, modify, and validate the WhatsApp HTTP API
|
||||
2. Lean on existing services/managers; extend the appropriate session manager
|
||||
rather than branching logic inline.
|
||||
3. After edits run:
|
||||
- `pre-commit run --all-files`
|
||||
- Do **not** run `pre-commit run --all-files` unless the user explicitly
|
||||
asks.
|
||||
- `yarn build`
|
||||
- `yarn test` Use Node 22. Address lint or formatting issues before
|
||||
proceeding.
|
||||
- `yarn test --watchman=false`
|
||||
4. Do **not** start the application yourself; ask the user to run it if runtime
|
||||
validation is required.
|
||||
5. Capture any assumptions or open questions for the user, especially when
|
||||
@@ -126,14 +131,80 @@ This guide summarizes how to explore, modify, and validate the WhatsApp HTTP API
|
||||
- Keep docs and code ASCII unless a file already uses other characters. When
|
||||
updating documentation, mirror the concise, actionable tone used here.
|
||||
|
||||
## Yes No
|
||||
|
||||
Always define key for objects (and in return too)
|
||||
|
||||
```js
|
||||
// NO
|
||||
const variable = 123;
|
||||
const b = { variable };
|
||||
|
||||
// YES
|
||||
const variable = 123;
|
||||
const b = { variable: variable };
|
||||
```
|
||||
|
||||
Use the three-line comment style for section headers inside files:
|
||||
|
||||
```ts
|
||||
// NO
|
||||
// ─── Section name ─────────────────────────────────────────────────────────────
|
||||
|
||||
// YES
|
||||
//
|
||||
// Section name
|
||||
//
|
||||
```
|
||||
|
||||
## Related Sources Code
|
||||
|
||||
You can find related source code in the following paths:
|
||||
You can find and read related source code in the following paths:
|
||||
|
||||
- WEBJS: `../whatsapp-web.js`
|
||||
- NOWEB: `../WhiskeySockets-Baileys`
|
||||
- GOWS: `../gows` + `../whatsmeow`
|
||||
- whatsapp-rust-bridge - `../whatsapp-rust-bridge`
|
||||
- GOWS: `../gows`
|
||||
- whatsmeow - `../whatsmeow`
|
||||
- WPP: `../wa-js`, `../wppconnect`, `../wppconnect-server`
|
||||
- ChatWoot: `../chatwoot`
|
||||
|
||||
Following this playbook keeps contributions aligned with WAHA’s structure,
|
||||
automation hooks, and release process.
|
||||
|
||||
## How to run API
|
||||
|
||||
You can run the project outside of sandbox using the below command, then run
|
||||
queries against `default` session (if not asked to do something different) using
|
||||
`curl` and `X-Api-Key: 666` header.
|
||||
|
||||
```bash
|
||||
export DEBUG=1
|
||||
export WAHA_API_KEY=666
|
||||
export WAHA_DASHBOARD_PASSWORD=666
|
||||
export WAHA_DASHBOARD_USERNAME=admin
|
||||
export WWHATSAPP_SWAGGER_USERNAME=admin
|
||||
export WHATSAPP_SWAGGER_PASSWORD=666
|
||||
export WHATSAPP_DEFAULT_ENGINE={WEBJS|WPP|NOWEB|GOWS}
|
||||
export WAHA_DEBUG_MODE=True
|
||||
export WAHA_HTTP_STRICT_MODE=1
|
||||
export WAHA_MEDIA_STORAGE=LOCAL
|
||||
export WHATSAPP_FILES_FOLDER=./.media
|
||||
|
||||
npm run start
|
||||
```
|
||||
|
||||
- Ask user before running the server.
|
||||
- Before executing some queries make sure the session is in `WORKING` status.
|
||||
- If it's `FAILED` or `SCAN_QR_CODE` ask user to scan QR code or fix failed
|
||||
session.
|
||||
|
||||
## Code
|
||||
|
||||
### @Activity Decorator and Presence Tracking
|
||||
|
||||
Add `@Activity()` (from `src/core/abc/activity.ts`) to every engine method that
|
||||
makes a network call to WhatsApp servers. It triggers `maintainPresenceOnline()`
|
||||
before the method runs, keeping the session ONLINE during API activity and
|
||||
scheduling an OFFLINE transition after an idle period. Skip it on methods that
|
||||
only throw `NotImplementedByEngineError` / `AvailableInPlusVersion`.
|
||||
+3
-1
@@ -8,7 +8,9 @@ FROM node:${NODE_IMAGE_TAG} AS build
|
||||
ENV PUPPETEER_SKIP_DOWNLOAD=True
|
||||
|
||||
# git + build toolchain for git deps
|
||||
RUN apt-get update && apt-get install -y git python3 build-essential && rm -rf /var/lib/apt/lists/*
|
||||
RUN apt-get update && \
|
||||
apt-get install -y --no-install-recommends git python3 build-essential curl ca-certificates unzip && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# npm packages
|
||||
WORKDIR /git
|
||||
|
||||
@@ -35,13 +35,19 @@ for-swagger:
|
||||
export WHATSAPP_SWAGGER_CONFIG_ADVANCED=true && export WHATSAPP_SWAGGER_PASSWORD=666 && yarn start
|
||||
|
||||
up-noweb:
|
||||
yarn up @adiwajshing/baileys@github:devlikeapro/Baileys#fork-master-2026-01-25
|
||||
yarn up @adiwajshing/baileys@github:devlikeapro/Baileys#fork-master-2026-02-11
|
||||
|
||||
up-noweb-libsignal:
|
||||
yarn up libsignal@github:devlikeapro/libsignal-node#fork-master
|
||||
|
||||
up-webjs:
|
||||
yarn up whatsapp-web.js@github:devlikeapro/whatsapp-web.js#fork-main-2026-02-01
|
||||
yarn up whatsapp-web.js@github:devlikeapro/whatsapp-web.js#fork-main-2026-02-18
|
||||
|
||||
up-wpp:
|
||||
yarn up @wppconnect-team/wppconnect
|
||||
|
||||
up-rust-bridge:
|
||||
yarn up -R whatsapp-rust-bridge
|
||||
|
||||
start-proxy:
|
||||
docker run --rm -d --name squid-container -e TZ=UTC -p 3128:3128 ubuntu/squid:5.2-22.04_beta
|
||||
@@ -57,4 +63,3 @@ gows:
|
||||
copy-dashboard:
|
||||
cd ../waha-hub/ui && \
|
||||
make copy-waha
|
||||
|
||||
@@ -147,7 +147,18 @@ curl -d "{\"chatId\": \"${PHONE}@c.us\", \"text\": \"Hello from WhatsApp HTTP AP
|
||||
## Start the project
|
||||
1. Clone the repository
|
||||
2. Make sure you're using node>=22 (check [.nvmrc](/.nvmrc) to get the version)
|
||||
3. Run the following commands:
|
||||
3. Install the **whatsapp-rust-bridge prerequisites**:
|
||||
```bash
|
||||
# Bun runtime used by whatsapp-rust-bridge prepare scripts
|
||||
curl -fsSL https://bun.sh/install | bash -s -- bun-v1.3.9
|
||||
|
||||
# Rust nightly toolchain used for whatsapp-rust-bridge
|
||||
curl -fsSL https://sh.rustup.rs | bash -s -- -y --default-toolchain nightly-2026-01-30
|
||||
rustup target add wasm32-unknown-unknown
|
||||
cargo install wasm-pack --vers 0.14.0 --locked
|
||||
```
|
||||
|
||||
4. Run the following commands:
|
||||
```bash
|
||||
# Install dependencies
|
||||
yarn install
|
||||
|
||||
+7
-3
@@ -29,7 +29,7 @@
|
||||
"gows:proto": "yarn gows:proto:fetch && yarn gows:proto:build"
|
||||
},
|
||||
"dependencies": {
|
||||
"@adiwajshing/baileys": "github:devlikeapro/Baileys#fork-master-2026-01-25",
|
||||
"@adiwajshing/baileys": "github:devlikeapro/Baileys#fork-master-2026-02-11",
|
||||
"@adiwajshing/keyed-db": "^0.2.4",
|
||||
"@aws-sdk/client-s3": "^3.633.0",
|
||||
"@aws-sdk/s3-request-presigner": "^3.633.0",
|
||||
@@ -57,6 +57,7 @@
|
||||
"@types/passport": "^1.0.17",
|
||||
"@types/sqlite3": "^5.1.0",
|
||||
"@types/ws": "^8.5.4",
|
||||
"@wppconnect-team/wppconnect": "^1.40.1",
|
||||
"adm-zip": "0.5.10",
|
||||
"agentkeepalive": "^4.5.0",
|
||||
"async-lock": "^1.4.1",
|
||||
@@ -111,7 +112,8 @@
|
||||
"ulid": "^2.3.0",
|
||||
"undici": "^7.16.0",
|
||||
"uniqid": "^5.4.0",
|
||||
"whatsapp-web.js": "github:devlikeapro/whatsapp-web.js#fork-main-2026-02-01",
|
||||
"user-agents": "^1.1.669",
|
||||
"whatsapp-web.js": "github:devlikeapro/whatsapp-web.js#fork-main-2026-02-18",
|
||||
"write-file-atomic": "^6.0.0",
|
||||
"yaml": "^2.7.1"
|
||||
},
|
||||
@@ -124,7 +126,8 @@
|
||||
"ws": "^8.18.0",
|
||||
"puppeteer": "^24.31.0",
|
||||
"whatwg-url": "13.0.0",
|
||||
"axios": "^1.9.0"
|
||||
"axios": "^1.9.0",
|
||||
"whatsapp-rust-bridge": "npm:@devlikeapro/whatsapp-rust-bridge@0.5.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@grpc/grpc-js": "^1.14.1",
|
||||
@@ -138,6 +141,7 @@
|
||||
"@types/node-fetch": "^2.6.13",
|
||||
"@types/semver": "^7.7.0",
|
||||
"@types/supertest": "^2.0.8",
|
||||
"@types/user-agents": "^1",
|
||||
"dotenv": "^17.2.3",
|
||||
"grpc-tools": "^1.13.0",
|
||||
"jest": "^29.7.0",
|
||||
|
||||
+17
-3
@@ -76,17 +76,31 @@ const updatedContent = exampleContent
|
||||
|
||||
fs.writeFileSync(targetPath, updatedContent, { encoding: 'utf8' });
|
||||
|
||||
const generatedEnvValues = [
|
||||
`WAHA_API_KEY=${apiKey}`,
|
||||
`WAHA_API_KEY_PLAIN=${apiKey}`,
|
||||
'WAHA_DASHBOARD_USERNAME=admin',
|
||||
`WAHA_DASHBOARD_PASSWORD=${adminPassword}`,
|
||||
'WHATSAPP_SWAGGER_USERNAME=admin',
|
||||
`WHATSAPP_SWAGGER_PASSWORD=${adminPassword}`,
|
||||
];
|
||||
|
||||
const lines = [
|
||||
'Credentials generated.',
|
||||
'',
|
||||
'Dashboard and Swagger:',
|
||||
'Generated env values:',
|
||||
...generatedEnvValues.map((line) => ` - ${line}`),
|
||||
'',
|
||||
'Use these credentials to login in Dashboard or Swagger:',
|
||||
' - Username: admin',
|
||||
` - Password: ${adminPassword}`,
|
||||
'',
|
||||
'API key: ',
|
||||
'Use this API key in the x-api-key header:',
|
||||
` - ${apiKey}`,
|
||||
'',
|
||||
'Use it in the Dashboard connection flow: https://waha.devlike.pro/docs/how-to/dashboard/#api-key',
|
||||
'Read more:',
|
||||
' - https://waha.devlike.pro/docs/how-to/dashboard/#api-key',
|
||||
' - https://waha.devlike.pro/docs/how-to/security/',
|
||||
];
|
||||
|
||||
console.log(lines.join('\n'));
|
||||
@@ -131,14 +131,15 @@ export class ChannelsController {
|
||||
})
|
||||
async previewChannelMessages(
|
||||
@WorkingSessionParam session: WhatsappSession,
|
||||
@Param('id') code: string,
|
||||
@Param('id') codeOrId: string,
|
||||
@Query() query: PreviewChannelMessages,
|
||||
): Promise<ChannelMessage[]> {
|
||||
if (isJidNewsletter(code)) {
|
||||
const channel = await session.channelsGetChannel(code);
|
||||
let code = null;
|
||||
if (isJidNewsletter(codeOrId)) {
|
||||
const channel = await session.channelsGetChannel(codeOrId);
|
||||
code = parseChannelInviteLink(channel.invite);
|
||||
}
|
||||
const inviteCode = parseChannelInviteLink(code);
|
||||
const inviteCode = parseChannelInviteLink(code || codeOrId);
|
||||
return session.previewChannelMessages(inviteCode, query);
|
||||
}
|
||||
|
||||
|
||||
@@ -25,15 +25,15 @@ import { WhatsappSession } from '../core/abc/session.abc';
|
||||
import {
|
||||
ChatPictureQuery,
|
||||
ChatPictureResponse,
|
||||
ChatsPaginationParams,
|
||||
ChatSummary,
|
||||
GetChatMessageQuery,
|
||||
GetChatMessagesFilter,
|
||||
GetChatMessagesQuery,
|
||||
GetChatsOverviewParams,
|
||||
GetChatsParams,
|
||||
MessageSortField,
|
||||
OverviewBodyRequest,
|
||||
OverviewFilter,
|
||||
OverviewPaginationParams,
|
||||
PinMessageRequest,
|
||||
ReadChatMessagesQuery,
|
||||
ReadChatMessagesResponse,
|
||||
@@ -61,7 +61,7 @@ class ChatsController {
|
||||
@ApiOperation({ summary: 'Get chats' })
|
||||
getChats(
|
||||
@WorkingSessionParam session: WhatsappSession,
|
||||
@Query() pagination: ChatsPaginationParams,
|
||||
@Query() pagination: GetChatsParams,
|
||||
) {
|
||||
return session.getChats(pagination);
|
||||
}
|
||||
@@ -75,7 +75,7 @@ class ChatsController {
|
||||
@UsePipes(new ValidationPipe({ transform: true, whitelist: true }))
|
||||
getChatsOverview(
|
||||
@WorkingSessionParam session: WhatsappSession,
|
||||
@Query() pagination: OverviewPaginationParams,
|
||||
@Query() pagination: GetChatsOverviewParams,
|
||||
@Query() filter: OverviewFilter,
|
||||
): Promise<ChatSummary[]> {
|
||||
return session.getChatsOverview(pagination, filter);
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import {
|
||||
Body,
|
||||
Controller,
|
||||
Get,
|
||||
Param,
|
||||
Put,
|
||||
UseGuards,
|
||||
UsePipes,
|
||||
} from '@nestjs/common';
|
||||
import { ApiOperation, ApiSecurity, ApiTags } from '@nestjs/swagger';
|
||||
import { ApiOperation, ApiParam, ApiSecurity, ApiTags } from '@nestjs/swagger';
|
||||
import { PoliciesGuard } from '@waha/core/auth/policies.guard';
|
||||
import { CheckPolicies } from '@waha/core/auth/policies.decorator';
|
||||
import { CanSession, FromParam } from '@waha/core/auth/policies';
|
||||
@@ -31,6 +32,27 @@ import { Action } from '@waha/core/auth/casl.types';
|
||||
export class ContactsSessionController {
|
||||
constructor(private manager: SessionManager) {}
|
||||
|
||||
@Get('/:id')
|
||||
@SessionApiParam
|
||||
@ApiParam({
|
||||
name: 'id',
|
||||
required: true,
|
||||
type: 'string',
|
||||
description: 'Contact ID',
|
||||
example: '123456789@c.us',
|
||||
})
|
||||
@ApiOperation({
|
||||
summary: 'Get contact basic info',
|
||||
description:
|
||||
'The method always return result, even if the phone number is not registered in WhatsApp. For that - use /contacts/check-exists endpoint below.',
|
||||
})
|
||||
async get(
|
||||
@WorkingSessionParam session: WhatsappSession,
|
||||
@Param('id') id: string,
|
||||
) {
|
||||
return session.getContact({ session: session.name, contactId: id });
|
||||
}
|
||||
|
||||
@Put('/:chatId')
|
||||
@SessionApiParam
|
||||
@ChatIdApiParam
|
||||
|
||||
@@ -31,7 +31,7 @@ export class ScreenshotController {
|
||||
@Get('/screenshot')
|
||||
@ApiOperation({
|
||||
summary:
|
||||
'Get a screenshot of the current WhatsApp session (**WEBJS** only)',
|
||||
'Get a screenshot of the current WhatsApp session (**WEBJS/WPP** only)',
|
||||
})
|
||||
@UseInterceptors(new BufferResponseInterceptor('image/jpeg'))
|
||||
@ApiFileAcceptHeader('image/jpeg')
|
||||
|
||||
@@ -93,6 +93,7 @@ export class WebsocketGatewayCore
|
||||
|
||||
if (!ability.can(Action.Use, new SessionName(session))) {
|
||||
socket.close(WebSocketCloseCode.POLICY_VIOLATION, 'Forbidden');
|
||||
return;
|
||||
}
|
||||
|
||||
this.logger.debug(`New client connected: ${request.url} - ${socket.id}`);
|
||||
|
||||
@@ -22,7 +22,12 @@ import {
|
||||
import { SessionManager } from '@waha/core/abc/manager.abc';
|
||||
import { CheckPolicies } from '@waha/core/auth/policies.decorator';
|
||||
import { PoliciesGuard } from '@waha/core/auth/policies.guard';
|
||||
import { CanSession, FromBody, FromQuery } from '@waha/core/auth/policies';
|
||||
import {
|
||||
CanServer,
|
||||
CanSession,
|
||||
FromBody,
|
||||
FromQuery,
|
||||
} from '@waha/core/auth/policies';
|
||||
import { Action, session as SessionName } from '@waha/core/auth/casl.types';
|
||||
import { WAHAValidationPipe } from '@waha/nestjs/pipes/WAHAValidationPipe';
|
||||
|
||||
@@ -65,6 +70,7 @@ export class AppsController {
|
||||
|
||||
@Get('/:id')
|
||||
@ApiOperation({ summary: 'Get app by ID' })
|
||||
@CheckPolicies(CanServer(Action.Read))
|
||||
@UsePipes(new WAHAValidationPipe())
|
||||
async get(@Param('id') id: string, @Req() req: any): Promise<App> {
|
||||
const app = await this.appsService.get(this.manager, id);
|
||||
@@ -79,6 +85,7 @@ export class AppsController {
|
||||
|
||||
@Put('/:id')
|
||||
@ApiOperation({ summary: 'Update an existing app' })
|
||||
@CheckPolicies(CanServer(Action.Read))
|
||||
@UsePipes(new WAHAValidationPipe())
|
||||
async update(
|
||||
@Param('id') id: string,
|
||||
@@ -114,6 +121,7 @@ export class AppsController {
|
||||
|
||||
@Delete('/:id')
|
||||
@ApiOperation({ summary: 'Delete an app' })
|
||||
@CheckPolicies(CanServer(Action.Read))
|
||||
@UsePipes(new WAHAValidationPipe())
|
||||
async delete(@Param('id') id: string, @Req() req: any): Promise<void> {
|
||||
const existing = await this.appsService.get(this.manager, id);
|
||||
|
||||
+33
-5
@@ -1,4 +1,10 @@
|
||||
import { parseBool } from '@waha/helpers';
|
||||
import { APPS } from '@waha/apps/app_sdk/apps/definition';
|
||||
|
||||
// Apps that don't require queue (Redis) - derived from AppDefinition
|
||||
const IN_MEMORY_APPS = Object.values(APPS)
|
||||
.filter((app) => !app.queue)
|
||||
.map((app) => app.name);
|
||||
|
||||
function parseCommaSeparatedList(value: string | undefined): string[] {
|
||||
if (!value) {
|
||||
@@ -7,8 +13,30 @@ function parseCommaSeparatedList(value: string | undefined): string[] {
|
||||
return value.split(',').map((item) => item.trim());
|
||||
}
|
||||
|
||||
export const AppEnv = {
|
||||
enabled: parseBool(process.env.WAHA_APPS_ENABLED),
|
||||
on: parseCommaSeparatedList(process.env.WAHA_APPS_ON),
|
||||
off: parseCommaSeparatedList(process.env.WAHA_APPS_OFF),
|
||||
};
|
||||
function isMongoDB(): boolean {
|
||||
return !!process.env.WHATSAPP_SESSIONS_MONGO_URL;
|
||||
}
|
||||
|
||||
function buildAppEnv() {
|
||||
const enabled = process.env.WAHA_APPS_ENABLED;
|
||||
const on = process.env.WAHA_APPS_ON;
|
||||
const off = process.env.WAHA_APPS_OFF;
|
||||
|
||||
// Default if not mongodb
|
||||
const isUserConfigured = enabled !== undefined || on !== undefined;
|
||||
if (!isUserConfigured && !isMongoDB()) {
|
||||
return {
|
||||
enabled: true,
|
||||
on: IN_MEMORY_APPS,
|
||||
off: parseCommaSeparatedList(off),
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
enabled: parseBool(enabled),
|
||||
on: parseCommaSeparatedList(on),
|
||||
off: parseCommaSeparatedList(off),
|
||||
};
|
||||
}
|
||||
|
||||
export const AppEnv = buildAppEnv();
|
||||
@@ -6,7 +6,7 @@ import { Locale } from '@waha/apps/chatwoot/i18n/locale';
|
||||
* Parse human string into milliseconds
|
||||
*/
|
||||
export function ParseMS(value: string) {
|
||||
const duration = ms(value);
|
||||
const duration = ms(value as ms.StringValue);
|
||||
if (duration == null) throw new Error(`Invalid duration: "${value}"`);
|
||||
if (duration < 0) throw new Error(`Duration cannot be negative: "${value}"`);
|
||||
return duration;
|
||||
|
||||
@@ -347,7 +347,7 @@ export abstract class MessageBaseHandler<
|
||||
chat_id: toCusFormat(key.remoteJid),
|
||||
message_id: key.id,
|
||||
from_me: key.fromMe,
|
||||
participant: null,
|
||||
participant: isJidGroup(key.remoteJid) ? key.participant : null,
|
||||
};
|
||||
await this.mappingService.map(chatwoot, whatsapp, 1);
|
||||
return;
|
||||
|
||||
@@ -32,7 +32,10 @@ export class WAHAMessageReactionConsumer extends ChatWootWAHABaseConsumer {
|
||||
}
|
||||
|
||||
GetChatId(event: WAHAWebhookMessageReaction): string {
|
||||
if (event.environment?.engine == WAHAEngine.WEBJS) {
|
||||
if (
|
||||
event.environment?.engine == WAHAEngine.WEBJS ||
|
||||
event.environment?.engine == WAHAEngine.WPP
|
||||
) {
|
||||
// chat in "to" field for WEBJS for message.reaction
|
||||
// probably we need to set it to "from"
|
||||
// but for backward compatability we do this
|
||||
|
||||
@@ -15,6 +15,14 @@ describe('MarkdownToWhatsApp', () => {
|
||||
expect(MarkdownToWhatsApp(input)).toBe(expected);
|
||||
});
|
||||
|
||||
// ChatWoot v4.10.1+ escapes newlines with trailing backslash
|
||||
it('strips trailing backslash before newlines', () => {
|
||||
const input = 'Hi \\\ntext message \\\nwith line \\\nbreak';
|
||||
expect(MarkdownToWhatsApp(input)).toBe(
|
||||
'Hi \ntext message \nwith line \nbreak',
|
||||
);
|
||||
});
|
||||
|
||||
// Tests for URL protection
|
||||
it('preserves URLs with multiple underscores', () => {
|
||||
const input = 'https://example.com/page__name__test';
|
||||
|
||||
@@ -4,6 +4,8 @@ export function MarkdownToWhatsApp(text: string): string {
|
||||
}
|
||||
return (
|
||||
text
|
||||
// Remove trailing backslash before newlines (ChatWoot v4.10.1+ escaping)
|
||||
.replace(/\\\n/g, '\n')
|
||||
// Triple-backtick blocks → WhatsApp monospace blocks (same syntax)
|
||||
.replace(/```([\s\S]*?)```/g, '```$1```')
|
||||
// Italic **first**, but only single‐star or single‐underscore
|
||||
|
||||
@@ -4,6 +4,7 @@ import { WAHAEngine } from '@waha/structures/enums.dto';
|
||||
import { getEngineName } from '@waha/version';
|
||||
import { Message as MessageInstance } from 'whatsapp-web.js/src/structures';
|
||||
import { isLidUser, isPnUser, toCusFormat } from '@waha/core/utils/jids';
|
||||
import { parseMessageIdSerialized } from '@waha/core/utils/ids';
|
||||
import { WAMessage } from '@waha/structures/responses.dto';
|
||||
import { CallData } from '@waha/structures/calls.dto';
|
||||
|
||||
@@ -52,7 +53,7 @@ class NOWEBHelper implements IEngineHelper {
|
||||
}
|
||||
|
||||
FilterChatIdsForMessages(chats: string[]): string[] {
|
||||
return chats;
|
||||
return preferPnChats(chats);
|
||||
}
|
||||
|
||||
ContactIsMy(contact) {
|
||||
@@ -96,7 +97,7 @@ class GOWSHelper implements IEngineHelper {
|
||||
}
|
||||
|
||||
FilterChatIdsForMessages(chats: string[]): string[] {
|
||||
return chats;
|
||||
return preferPnChats(chats);
|
||||
}
|
||||
|
||||
SupportsAllChatForMessage(): boolean {
|
||||
@@ -150,17 +151,63 @@ class WEBJSHelper implements IEngineHelper {
|
||||
}
|
||||
|
||||
FilterChatIdsForMessages(chats: string[]): string[] {
|
||||
if (chats.length == 2) {
|
||||
const lidChat = chats.find(isLidUser);
|
||||
const cusChat = chats.find(isPnUser);
|
||||
if (lidChat && cusChat) {
|
||||
return [lidChat];
|
||||
}
|
||||
// WEBJS engine merges messages for @lid and @c.us
|
||||
// into single chat, so it's fine to pull only from one
|
||||
return preferPnChats(chats);
|
||||
}
|
||||
|
||||
SupportsAllChatForMessage(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
ContactIsMy(contact) {
|
||||
return contact.isMyContact;
|
||||
}
|
||||
}
|
||||
|
||||
class WPPHelper implements IEngineHelper {
|
||||
ChatID(message: WAMessage): string {
|
||||
return toCusFormat(parseMessageIdSerialized(message.id as any).remoteJid);
|
||||
}
|
||||
|
||||
CallChatID(call: CallData): string {
|
||||
return call.from;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse API response and get the data for WPP engine.
|
||||
* WPP returns message.id as a composite string "fromMe_chatId_msgId".
|
||||
*/
|
||||
WhatsAppMessageKeys(message: any): WhatsAppMessage {
|
||||
const parsed = parseMessageIdSerialized(message.id);
|
||||
return {
|
||||
timestamp: new Date(message.timestamp * 1000),
|
||||
from_me: parsed.fromMe,
|
||||
chat_id: toCusFormat(parsed.remoteJid),
|
||||
message_id: parsed.id,
|
||||
participant: message.author || null,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* WPP API lacks server-side sorting hooks, so we buffer and sort by the unix timestamp in memory.
|
||||
*/
|
||||
async *IterateMessages<T extends { timestamp: number }>(
|
||||
messages: AsyncGenerator<T>,
|
||||
): AsyncGenerator<T> {
|
||||
const buffer: T[] = [];
|
||||
|
||||
for await (const message of messages) {
|
||||
buffer.push(message);
|
||||
}
|
||||
// Otherwise - return the original
|
||||
return chats;
|
||||
|
||||
const sorted = lodash.sortBy(buffer, (item) => item.timestamp);
|
||||
|
||||
for (const message of sorted) {
|
||||
yield message;
|
||||
}
|
||||
}
|
||||
|
||||
FilterChatIdsForMessages(chats: string[]): string[] {
|
||||
return preferPnChats(chats);
|
||||
}
|
||||
|
||||
SupportsAllChatForMessage(): boolean {
|
||||
@@ -185,8 +232,22 @@ switch (getEngineName()) {
|
||||
case WAHAEngine.WEBJS:
|
||||
engineHelper = new WEBJSHelper();
|
||||
break;
|
||||
case WAHAEngine.WPP:
|
||||
engineHelper = new WPPHelper();
|
||||
break;
|
||||
default:
|
||||
engineHelper = new WEBJSHelper(); // Default to WEBJS as it's the default engine
|
||||
}
|
||||
|
||||
export const EngineHelper = engineHelper;
|
||||
|
||||
function preferPnChats(chats: string[]): string[] {
|
||||
const unique = lodash.uniq(chats ?? []);
|
||||
const hasPn = unique.some(isPnUser);
|
||||
const hasLid = unique.some(isLidUser);
|
||||
if (hasPn && hasLid) {
|
||||
// Prefer @c.us / phone chats when both formats exist to avoid duplicate fetches.
|
||||
return unique.filter(isPnUser);
|
||||
}
|
||||
return unique;
|
||||
}
|
||||
@@ -5,3 +5,15 @@ export function getEngineName(): string {
|
||||
// If not set - use WEBJS
|
||||
return process.env.WHATSAPP_DEFAULT_ENGINE || WAHAEngine.WEBJS;
|
||||
}
|
||||
|
||||
export function getNamespace(): string {
|
||||
// Controls the prefix used for the main database and folder (non-session data)
|
||||
// If not set - falls back to engine name for backward compatibility
|
||||
return (process.env.WAHA_NAMESPACE || getEngineName()).toLowerCase();
|
||||
}
|
||||
|
||||
export function getSessionNamespace(): string {
|
||||
// Controls the prefix used for session databases and folders
|
||||
// If not set - falls back to engine name for backward compatibility
|
||||
return (process.env.WAHA_SESSION_NAMESPACE || getEngineName()).toLowerCase();
|
||||
}
|
||||
@@ -22,6 +22,7 @@ import { ApiKeyAuthMiddleware } from '@waha/core/auth/api-key-auth.middleware';
|
||||
import { BasicAuthFunction } from '@waha/core/auth/basicAuth';
|
||||
import { WebSocketAuth } from '@waha/core/auth/WebSocketAuth';
|
||||
import { GowsEngineConfigService } from '@waha/core/config/GowsEngineConfigService';
|
||||
import { WPPEngineConfigService } from '@waha/core/config/WPPEngineConfigService';
|
||||
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';
|
||||
@@ -176,6 +177,7 @@ export const PROVIDERS_BASE: Provider[] = [
|
||||
DashboardConfigServiceCore,
|
||||
SwaggerConfigServiceCore,
|
||||
WebJSEngineConfigService,
|
||||
WPPEngineConfigService,
|
||||
GowsEngineConfigService,
|
||||
WhatsappConfigService,
|
||||
EngineConfigService,
|
||||
|
||||
@@ -1,22 +1,27 @@
|
||||
import {
|
||||
Injectable,
|
||||
InternalServerErrorException,
|
||||
NestMiddleware,
|
||||
UnauthorizedException,
|
||||
} from '@nestjs/common';
|
||||
import { IApiKeyAuth, NoAuth } from '@waha/core/auth/auth';
|
||||
import * as passport from 'passport';
|
||||
|
||||
@Injectable()
|
||||
export class ApiKeyAuthMiddleware implements NestMiddleware {
|
||||
constructor() {}
|
||||
constructor(private auth: IApiKeyAuth) {}
|
||||
|
||||
use(req: any, res: any, next: () => void) {
|
||||
if (this.auth instanceof NoAuth) {
|
||||
next();
|
||||
return;
|
||||
}
|
||||
|
||||
passport.authenticate('headerapikey', { session: false }, (err, user?) => {
|
||||
if (err) {
|
||||
const exception =
|
||||
err instanceof UnauthorizedException
|
||||
? err
|
||||
: new InternalServerErrorException();
|
||||
: new UnauthorizedException();
|
||||
res.status(exception.getStatus()).json(exception.getResponse());
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -26,6 +26,10 @@ export class PoliciesGuard implements CanActivate {
|
||||
context.getClass(),
|
||||
]) || [];
|
||||
|
||||
if (handlers.length === 0) {
|
||||
throw new ForbiddenException();
|
||||
}
|
||||
|
||||
const req = context.switchToHttp().getRequest();
|
||||
const user = req.user;
|
||||
const ability = this.caslAbilityFactory.createForUser(user);
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { WPPConfig } from '@waha/core/engines/wpp/WppConfig';
|
||||
|
||||
@Injectable()
|
||||
export class WPPEngineConfigService {
|
||||
constructor(protected configService: ConfigService) {}
|
||||
|
||||
getConfig(): WPPConfig {
|
||||
let webVersion = this.configService.get<string>(
|
||||
'WAHA_WPP_WEB_VERSION',
|
||||
undefined,
|
||||
);
|
||||
if (!webVersion) {
|
||||
webVersion = this.configService.get<string>(
|
||||
'WAHA_WEBJS_WEB_VERSION',
|
||||
undefined,
|
||||
);
|
||||
}
|
||||
return {
|
||||
webVersion: webVersion,
|
||||
puppeteerArgs: this.getPuppeterArgs(),
|
||||
};
|
||||
}
|
||||
|
||||
getPuppeterArgs(): string[] {
|
||||
let args = this.configService.get<string>('WAHA_WPP_PUPPETER_ARGS', '');
|
||||
if (!args) {
|
||||
args = this.configService.get<string>('WAHA_WEBJS_PUPPETER_ARGS', '');
|
||||
}
|
||||
return args
|
||||
.split(' ')
|
||||
.map((arg) => arg.trim())
|
||||
.filter(Boolean);
|
||||
}
|
||||
}
|
||||
@@ -2996,6 +2996,73 @@ export namespace messages {
|
||||
return AudioInfo.deserialize(bytes);
|
||||
}
|
||||
}
|
||||
export class VideoInfo extends pb_1.Message {
|
||||
#one_of_decls: number[][] = [];
|
||||
constructor(data?: any[] | {
|
||||
duration?: number;
|
||||
}) {
|
||||
super();
|
||||
pb_1.Message.initialize(this, Array.isArray(data) ? data : [], 0, -1, [], this.#one_of_decls);
|
||||
if (!Array.isArray(data) && typeof data == "object") {
|
||||
if ("duration" in data && data.duration != undefined) {
|
||||
this.duration = data.duration;
|
||||
}
|
||||
}
|
||||
}
|
||||
get duration() {
|
||||
return pb_1.Message.getFieldWithDefault(this, 1, 0) as number;
|
||||
}
|
||||
set duration(value: number) {
|
||||
pb_1.Message.setField(this, 1, value);
|
||||
}
|
||||
static fromObject(data: {
|
||||
duration?: number;
|
||||
}): VideoInfo {
|
||||
const message = new VideoInfo({});
|
||||
if (data.duration != null) {
|
||||
message.duration = data.duration;
|
||||
}
|
||||
return message;
|
||||
}
|
||||
toObject() {
|
||||
const data: {
|
||||
duration?: number;
|
||||
} = {};
|
||||
if (this.duration != null) {
|
||||
data.duration = this.duration;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
serialize(): Uint8Array;
|
||||
serialize(w: pb_1.BinaryWriter): void;
|
||||
serialize(w?: pb_1.BinaryWriter): Uint8Array | void {
|
||||
const writer = w || new pb_1.BinaryWriter();
|
||||
if (this.duration != 0)
|
||||
writer.writeFloat(1, this.duration);
|
||||
if (!w)
|
||||
return writer.getResultBuffer();
|
||||
}
|
||||
static deserialize(bytes: Uint8Array | pb_1.BinaryReader): VideoInfo {
|
||||
const reader = bytes instanceof pb_1.BinaryReader ? bytes : new pb_1.BinaryReader(bytes), message = new VideoInfo();
|
||||
while (reader.nextField()) {
|
||||
if (reader.isEndGroup())
|
||||
break;
|
||||
switch (reader.getFieldNumber()) {
|
||||
case 1:
|
||||
message.duration = reader.readFloat();
|
||||
break;
|
||||
default: reader.skipField();
|
||||
}
|
||||
}
|
||||
return message;
|
||||
}
|
||||
serializeBinary(): Uint8Array {
|
||||
return this.serialize();
|
||||
}
|
||||
static deserializeBinary(bytes: Uint8Array): VideoInfo {
|
||||
return VideoInfo.deserialize(bytes);
|
||||
}
|
||||
}
|
||||
export class Media extends pb_1.Message {
|
||||
#one_of_decls: number[][] = [];
|
||||
constructor(data?: any[] | {
|
||||
@@ -3005,6 +3072,7 @@ export namespace messages {
|
||||
audio?: AudioInfo;
|
||||
filename?: string;
|
||||
contentPath?: string;
|
||||
video?: VideoInfo;
|
||||
}) {
|
||||
super();
|
||||
pb_1.Message.initialize(this, Array.isArray(data) ? data : [], 0, -1, [], this.#one_of_decls);
|
||||
@@ -3027,6 +3095,9 @@ export namespace messages {
|
||||
if ("contentPath" in data && data.contentPath != undefined) {
|
||||
this.contentPath = data.contentPath;
|
||||
}
|
||||
if ("video" in data && data.video != undefined) {
|
||||
this.video = data.video;
|
||||
}
|
||||
}
|
||||
}
|
||||
get content() {
|
||||
@@ -3068,6 +3139,15 @@ export namespace messages {
|
||||
set contentPath(value: string) {
|
||||
pb_1.Message.setField(this, 6, value);
|
||||
}
|
||||
get video() {
|
||||
return pb_1.Message.getWrapperField(this, VideoInfo, 7) as VideoInfo;
|
||||
}
|
||||
set video(value: VideoInfo) {
|
||||
pb_1.Message.setWrapperField(this, 7, value);
|
||||
}
|
||||
get has_video() {
|
||||
return pb_1.Message.getField(this, 7) != null;
|
||||
}
|
||||
static fromObject(data: {
|
||||
content?: Uint8Array;
|
||||
type?: MediaType;
|
||||
@@ -3075,6 +3155,7 @@ export namespace messages {
|
||||
audio?: ReturnType<typeof AudioInfo.prototype.toObject>;
|
||||
filename?: string;
|
||||
contentPath?: string;
|
||||
video?: ReturnType<typeof VideoInfo.prototype.toObject>;
|
||||
}): Media {
|
||||
const message = new Media({});
|
||||
if (data.content != null) {
|
||||
@@ -3095,6 +3176,9 @@ export namespace messages {
|
||||
if (data.contentPath != null) {
|
||||
message.contentPath = data.contentPath;
|
||||
}
|
||||
if (data.video != null) {
|
||||
message.video = VideoInfo.fromObject(data.video);
|
||||
}
|
||||
return message;
|
||||
}
|
||||
toObject() {
|
||||
@@ -3105,6 +3189,7 @@ export namespace messages {
|
||||
audio?: ReturnType<typeof AudioInfo.prototype.toObject>;
|
||||
filename?: string;
|
||||
contentPath?: string;
|
||||
video?: ReturnType<typeof VideoInfo.prototype.toObject>;
|
||||
} = {};
|
||||
if (this.content != null) {
|
||||
data.content = this.content;
|
||||
@@ -3124,6 +3209,9 @@ export namespace messages {
|
||||
if (this.contentPath != null) {
|
||||
data.contentPath = this.contentPath;
|
||||
}
|
||||
if (this.video != null) {
|
||||
data.video = this.video.toObject();
|
||||
}
|
||||
return data;
|
||||
}
|
||||
serialize(): Uint8Array;
|
||||
@@ -3142,6 +3230,8 @@ export namespace messages {
|
||||
writer.writeString(5, this.filename);
|
||||
if (this.contentPath.length)
|
||||
writer.writeString(6, this.contentPath);
|
||||
if (this.has_video)
|
||||
writer.writeMessage(7, this.video, () => this.video.serialize(writer));
|
||||
if (!w)
|
||||
return writer.getResultBuffer();
|
||||
}
|
||||
@@ -3169,6 +3259,9 @@ export namespace messages {
|
||||
case 6:
|
||||
message.contentPath = reader.readString();
|
||||
break;
|
||||
case 7:
|
||||
reader.readMessage(message.video, () => message.video = VideoInfo.deserialize(reader));
|
||||
break;
|
||||
default: reader.skipField();
|
||||
}
|
||||
}
|
||||
@@ -9128,6 +9221,7 @@ export namespace messages {
|
||||
filters?: MessageFilters;
|
||||
pagination?: Pagination;
|
||||
sortBy?: SortBy;
|
||||
merge?: OptionalBool;
|
||||
}) {
|
||||
super();
|
||||
pb_1.Message.initialize(this, Array.isArray(data) ? data : [], 0, -1, [], this.#one_of_decls);
|
||||
@@ -9144,6 +9238,9 @@ export namespace messages {
|
||||
if ("sortBy" in data && data.sortBy != undefined) {
|
||||
this.sortBy = data.sortBy;
|
||||
}
|
||||
if ("merge" in data && data.merge != undefined) {
|
||||
this.merge = data.merge;
|
||||
}
|
||||
}
|
||||
}
|
||||
get session() {
|
||||
@@ -9182,11 +9279,21 @@ export namespace messages {
|
||||
get has_sortBy() {
|
||||
return pb_1.Message.getField(this, 4) != null;
|
||||
}
|
||||
get merge() {
|
||||
return pb_1.Message.getWrapperField(this, OptionalBool, 5) as OptionalBool;
|
||||
}
|
||||
set merge(value: OptionalBool) {
|
||||
pb_1.Message.setWrapperField(this, 5, value);
|
||||
}
|
||||
get has_merge() {
|
||||
return pb_1.Message.getField(this, 5) != null;
|
||||
}
|
||||
static fromObject(data: {
|
||||
session?: ReturnType<typeof Session.prototype.toObject>;
|
||||
filters?: ReturnType<typeof MessageFilters.prototype.toObject>;
|
||||
pagination?: ReturnType<typeof Pagination.prototype.toObject>;
|
||||
sortBy?: ReturnType<typeof SortBy.prototype.toObject>;
|
||||
merge?: ReturnType<typeof OptionalBool.prototype.toObject>;
|
||||
}): GetMessagesRequest {
|
||||
const message = new GetMessagesRequest({});
|
||||
if (data.session != null) {
|
||||
@@ -9201,6 +9308,9 @@ export namespace messages {
|
||||
if (data.sortBy != null) {
|
||||
message.sortBy = SortBy.fromObject(data.sortBy);
|
||||
}
|
||||
if (data.merge != null) {
|
||||
message.merge = OptionalBool.fromObject(data.merge);
|
||||
}
|
||||
return message;
|
||||
}
|
||||
toObject() {
|
||||
@@ -9209,6 +9319,7 @@ export namespace messages {
|
||||
filters?: ReturnType<typeof MessageFilters.prototype.toObject>;
|
||||
pagination?: ReturnType<typeof Pagination.prototype.toObject>;
|
||||
sortBy?: ReturnType<typeof SortBy.prototype.toObject>;
|
||||
merge?: ReturnType<typeof OptionalBool.prototype.toObject>;
|
||||
} = {};
|
||||
if (this.session != null) {
|
||||
data.session = this.session.toObject();
|
||||
@@ -9222,6 +9333,9 @@ export namespace messages {
|
||||
if (this.sortBy != null) {
|
||||
data.sortBy = this.sortBy.toObject();
|
||||
}
|
||||
if (this.merge != null) {
|
||||
data.merge = this.merge.toObject();
|
||||
}
|
||||
return data;
|
||||
}
|
||||
serialize(): Uint8Array;
|
||||
@@ -9236,6 +9350,8 @@ export namespace messages {
|
||||
writer.writeMessage(3, this.pagination, () => this.pagination.serialize(writer));
|
||||
if (this.has_sortBy)
|
||||
writer.writeMessage(4, this.sortBy, () => this.sortBy.serialize(writer));
|
||||
if (this.has_merge)
|
||||
writer.writeMessage(5, this.merge, () => this.merge.serialize(writer));
|
||||
if (!w)
|
||||
return writer.getResultBuffer();
|
||||
}
|
||||
@@ -9257,6 +9373,9 @@ export namespace messages {
|
||||
case 4:
|
||||
reader.readMessage(message.sortBy, () => message.sortBy = SortBy.deserialize(reader));
|
||||
break;
|
||||
case 5:
|
||||
reader.readMessage(message.merge, () => message.merge = OptionalBool.deserialize(reader));
|
||||
break;
|
||||
default: reader.skipField();
|
||||
}
|
||||
}
|
||||
@@ -9604,6 +9723,7 @@ export namespace messages {
|
||||
sortBy?: SortBy;
|
||||
pagination?: Pagination;
|
||||
filter?: ChatFilter;
|
||||
merge?: OptionalBool;
|
||||
}) {
|
||||
super();
|
||||
pb_1.Message.initialize(this, Array.isArray(data) ? data : [], 0, -1, [], this.#one_of_decls);
|
||||
@@ -9620,6 +9740,9 @@ export namespace messages {
|
||||
if ("filter" in data && data.filter != undefined) {
|
||||
this.filter = data.filter;
|
||||
}
|
||||
if ("merge" in data && data.merge != undefined) {
|
||||
this.merge = data.merge;
|
||||
}
|
||||
}
|
||||
}
|
||||
get session() {
|
||||
@@ -9658,11 +9781,21 @@ export namespace messages {
|
||||
get has_filter() {
|
||||
return pb_1.Message.getField(this, 4) != null;
|
||||
}
|
||||
get merge() {
|
||||
return pb_1.Message.getWrapperField(this, OptionalBool, 5) as OptionalBool;
|
||||
}
|
||||
set merge(value: OptionalBool) {
|
||||
pb_1.Message.setWrapperField(this, 5, value);
|
||||
}
|
||||
get has_merge() {
|
||||
return pb_1.Message.getField(this, 5) != null;
|
||||
}
|
||||
static fromObject(data: {
|
||||
session?: ReturnType<typeof Session.prototype.toObject>;
|
||||
sortBy?: ReturnType<typeof SortBy.prototype.toObject>;
|
||||
pagination?: ReturnType<typeof Pagination.prototype.toObject>;
|
||||
filter?: ReturnType<typeof ChatFilter.prototype.toObject>;
|
||||
merge?: ReturnType<typeof OptionalBool.prototype.toObject>;
|
||||
}): GetChatsRequest {
|
||||
const message = new GetChatsRequest({});
|
||||
if (data.session != null) {
|
||||
@@ -9677,6 +9810,9 @@ export namespace messages {
|
||||
if (data.filter != null) {
|
||||
message.filter = ChatFilter.fromObject(data.filter);
|
||||
}
|
||||
if (data.merge != null) {
|
||||
message.merge = OptionalBool.fromObject(data.merge);
|
||||
}
|
||||
return message;
|
||||
}
|
||||
toObject() {
|
||||
@@ -9685,6 +9821,7 @@ export namespace messages {
|
||||
sortBy?: ReturnType<typeof SortBy.prototype.toObject>;
|
||||
pagination?: ReturnType<typeof Pagination.prototype.toObject>;
|
||||
filter?: ReturnType<typeof ChatFilter.prototype.toObject>;
|
||||
merge?: ReturnType<typeof OptionalBool.prototype.toObject>;
|
||||
} = {};
|
||||
if (this.session != null) {
|
||||
data.session = this.session.toObject();
|
||||
@@ -9698,6 +9835,9 @@ export namespace messages {
|
||||
if (this.filter != null) {
|
||||
data.filter = this.filter.toObject();
|
||||
}
|
||||
if (this.merge != null) {
|
||||
data.merge = this.merge.toObject();
|
||||
}
|
||||
return data;
|
||||
}
|
||||
serialize(): Uint8Array;
|
||||
@@ -9712,6 +9852,8 @@ export namespace messages {
|
||||
writer.writeMessage(3, this.pagination, () => this.pagination.serialize(writer));
|
||||
if (this.has_filter)
|
||||
writer.writeMessage(4, this.filter, () => this.filter.serialize(writer));
|
||||
if (this.has_merge)
|
||||
writer.writeMessage(5, this.merge, () => this.merge.serialize(writer));
|
||||
if (!w)
|
||||
return writer.getResultBuffer();
|
||||
}
|
||||
@@ -9733,6 +9875,9 @@ export namespace messages {
|
||||
case 4:
|
||||
reader.readMessage(message.filter, () => message.filter = ChatFilter.deserialize(reader));
|
||||
break;
|
||||
case 5:
|
||||
reader.readMessage(message.merge, () => message.merge = OptionalBool.deserialize(reader));
|
||||
break;
|
||||
default: reader.skipField();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,6 +120,7 @@ goog.exportSymbol('proto.messages.SubscribePresenceRequest', null, global);
|
||||
goog.exportSymbol('proto.messages.UpdateContactRequest', null, global);
|
||||
goog.exportSymbol('proto.messages.UpdateParticipantsRequest', null, global);
|
||||
goog.exportSymbol('proto.messages.UpsertLabelRequest', null, global);
|
||||
goog.exportSymbol('proto.messages.VideoInfo', null, global);
|
||||
goog.exportSymbol('proto.messages.vCardContact', null, global);
|
||||
/**
|
||||
* Generated by JsPbCodeGenerator.
|
||||
@@ -772,6 +773,27 @@ if (goog.DEBUG && !COMPILED) {
|
||||
*/
|
||||
proto.messages.AudioInfo.displayName = 'proto.messages.AudioInfo';
|
||||
}
|
||||
/**
|
||||
* Generated by JsPbCodeGenerator.
|
||||
* @param {Array=} opt_data Optional initial data array, typically from a
|
||||
* server response, or constructed directly in Javascript. The array is used
|
||||
* in place and becomes part of the constructed object. It is not cloned.
|
||||
* If no data is provided, the constructed object will be empty, but still
|
||||
* valid.
|
||||
* @extends {jspb.Message}
|
||||
* @constructor
|
||||
*/
|
||||
proto.messages.VideoInfo = function(opt_data) {
|
||||
jspb.Message.initialize(this, opt_data, 0, -1, null, null);
|
||||
};
|
||||
goog.inherits(proto.messages.VideoInfo, jspb.Message);
|
||||
if (goog.DEBUG && !COMPILED) {
|
||||
/**
|
||||
* @public
|
||||
* @override
|
||||
*/
|
||||
proto.messages.VideoInfo.displayName = 'proto.messages.VideoInfo';
|
||||
}
|
||||
/**
|
||||
* Generated by JsPbCodeGenerator.
|
||||
* @param {Array=} opt_data Optional initial data array, typically from a
|
||||
@@ -7698,6 +7720,136 @@ proto.messages.AudioInfo.prototype.setWaveform = function(value) {
|
||||
|
||||
|
||||
|
||||
if (jspb.Message.GENERATE_TO_OBJECT) {
|
||||
/**
|
||||
* Creates an object representation of this proto.
|
||||
* Field names that are reserved in JavaScript and will be renamed to pb_name.
|
||||
* Optional fields that are not set will be set to undefined.
|
||||
* To access a reserved field use, foo.pb_<name>, eg, foo.pb_default.
|
||||
* For the list of reserved names please see:
|
||||
* net/proto2/compiler/js/internal/generator.cc#kKeyword.
|
||||
* @param {boolean=} opt_includeInstance Deprecated. whether to include the
|
||||
* JSPB instance for transitional soy proto support:
|
||||
* http://goto/soy-param-migration
|
||||
* @return {!Object}
|
||||
*/
|
||||
proto.messages.VideoInfo.prototype.toObject = function(opt_includeInstance) {
|
||||
return proto.messages.VideoInfo.toObject(opt_includeInstance, this);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Static version of the {@see toObject} method.
|
||||
* @param {boolean|undefined} includeInstance Deprecated. Whether to include
|
||||
* the JSPB instance for transitional soy proto support:
|
||||
* http://goto/soy-param-migration
|
||||
* @param {!proto.messages.VideoInfo} msg The msg instance to transform.
|
||||
* @return {!Object}
|
||||
* @suppress {unusedLocalVariables} f is only used for nested messages
|
||||
*/
|
||||
proto.messages.VideoInfo.toObject = function(includeInstance, msg) {
|
||||
var f, obj = {
|
||||
duration: jspb.Message.getFloatingPointFieldWithDefault(msg, 1, 0.0)
|
||||
};
|
||||
|
||||
if (includeInstance) {
|
||||
obj.$jspbMessageInstance = msg;
|
||||
}
|
||||
return obj;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Deserializes binary data (in protobuf wire format).
|
||||
* @param {jspb.ByteSource} bytes The bytes to deserialize.
|
||||
* @return {!proto.messages.VideoInfo}
|
||||
*/
|
||||
proto.messages.VideoInfo.deserializeBinary = function(bytes) {
|
||||
var reader = new jspb.BinaryReader(bytes);
|
||||
var msg = new proto.messages.VideoInfo;
|
||||
return proto.messages.VideoInfo.deserializeBinaryFromReader(msg, reader);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Deserializes binary data (in protobuf wire format) from the
|
||||
* given reader into the given message object.
|
||||
* @param {!proto.messages.VideoInfo} msg The message object to deserialize into.
|
||||
* @param {!jspb.BinaryReader} reader The BinaryReader to use.
|
||||
* @return {!proto.messages.VideoInfo}
|
||||
*/
|
||||
proto.messages.VideoInfo.deserializeBinaryFromReader = function(msg, reader) {
|
||||
while (reader.nextField()) {
|
||||
if (reader.isEndGroup()) {
|
||||
break;
|
||||
}
|
||||
var field = reader.getFieldNumber();
|
||||
switch (field) {
|
||||
case 1:
|
||||
var value = /** @type {number} */ (reader.readFloat());
|
||||
msg.setDuration(value);
|
||||
break;
|
||||
default:
|
||||
reader.skipField();
|
||||
break;
|
||||
}
|
||||
}
|
||||
return msg;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Serializes the message to binary data (in protobuf wire format).
|
||||
* @return {!Uint8Array}
|
||||
*/
|
||||
proto.messages.VideoInfo.prototype.serializeBinary = function() {
|
||||
var writer = new jspb.BinaryWriter();
|
||||
proto.messages.VideoInfo.serializeBinaryToWriter(this, writer);
|
||||
return writer.getResultBuffer();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Serializes the given message to binary data (in protobuf wire
|
||||
* format), writing to the given BinaryWriter.
|
||||
* @param {!proto.messages.VideoInfo} message
|
||||
* @param {!jspb.BinaryWriter} writer
|
||||
* @suppress {unusedLocalVariables} f is only used for nested messages
|
||||
*/
|
||||
proto.messages.VideoInfo.serializeBinaryToWriter = function(message, writer) {
|
||||
var f = undefined;
|
||||
f = message.getDuration();
|
||||
if (f !== 0.0) {
|
||||
writer.writeFloat(
|
||||
1,
|
||||
f
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* optional float duration = 1;
|
||||
* @return {number}
|
||||
*/
|
||||
proto.messages.VideoInfo.prototype.getDuration = function() {
|
||||
return /** @type {number} */ (jspb.Message.getFloatingPointFieldWithDefault(this, 1, 0.0));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {number} value
|
||||
* @return {!proto.messages.VideoInfo} returns this
|
||||
*/
|
||||
proto.messages.VideoInfo.prototype.setDuration = function(value) {
|
||||
return jspb.Message.setProto3FloatField(this, 1, value);
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
if (jspb.Message.GENERATE_TO_OBJECT) {
|
||||
/**
|
||||
* Creates an object representation of this proto.
|
||||
@@ -7732,7 +7884,8 @@ proto.messages.Media.toObject = function(includeInstance, msg) {
|
||||
mimetype: jspb.Message.getFieldWithDefault(msg, 3, ""),
|
||||
audio: (f = msg.getAudio()) && proto.messages.AudioInfo.toObject(includeInstance, f),
|
||||
filename: jspb.Message.getFieldWithDefault(msg, 5, ""),
|
||||
contentpath: jspb.Message.getFieldWithDefault(msg, 6, "")
|
||||
contentpath: jspb.Message.getFieldWithDefault(msg, 6, ""),
|
||||
video: (f = msg.getVideo()) && proto.messages.VideoInfo.toObject(includeInstance, f)
|
||||
};
|
||||
|
||||
if (includeInstance) {
|
||||
@@ -7794,6 +7947,11 @@ proto.messages.Media.deserializeBinaryFromReader = function(msg, reader) {
|
||||
var value = /** @type {string} */ (reader.readString());
|
||||
msg.setContentpath(value);
|
||||
break;
|
||||
case 7:
|
||||
var value = new proto.messages.VideoInfo;
|
||||
reader.readMessage(value,proto.messages.VideoInfo.deserializeBinaryFromReader);
|
||||
msg.setVideo(value);
|
||||
break;
|
||||
default:
|
||||
reader.skipField();
|
||||
break;
|
||||
@@ -7866,6 +8024,14 @@ proto.messages.Media.serializeBinaryToWriter = function(message, writer) {
|
||||
f
|
||||
);
|
||||
}
|
||||
f = message.getVideo();
|
||||
if (f != null) {
|
||||
writer.writeMessage(
|
||||
7,
|
||||
f,
|
||||
proto.messages.VideoInfo.serializeBinaryToWriter
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -8020,6 +8186,43 @@ proto.messages.Media.prototype.setContentpath = function(value) {
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* optional VideoInfo video = 7;
|
||||
* @return {?proto.messages.VideoInfo}
|
||||
*/
|
||||
proto.messages.Media.prototype.getVideo = function() {
|
||||
return /** @type{?proto.messages.VideoInfo} */ (
|
||||
jspb.Message.getWrapperField(this, proto.messages.VideoInfo, 7));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {?proto.messages.VideoInfo|undefined} value
|
||||
* @return {!proto.messages.Media} returns this
|
||||
*/
|
||||
proto.messages.Media.prototype.setVideo = function(value) {
|
||||
return jspb.Message.setWrapperField(this, 7, value);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Clears the message field making it undefined.
|
||||
* @return {!proto.messages.Media} returns this
|
||||
*/
|
||||
proto.messages.Media.prototype.clearVideo = function() {
|
||||
return this.setVideo(undefined);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether this field is set.
|
||||
* @return {boolean}
|
||||
*/
|
||||
proto.messages.Media.prototype.hasVideo = function() {
|
||||
return jspb.Message.getField(this, 7) != null;
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -18928,7 +19131,8 @@ proto.messages.GetMessagesRequest.toObject = function(includeInstance, msg) {
|
||||
session: (f = msg.getSession()) && proto.messages.Session.toObject(includeInstance, f),
|
||||
filters: (f = msg.getFilters()) && proto.messages.MessageFilters.toObject(includeInstance, f),
|
||||
pagination: (f = msg.getPagination()) && proto.messages.Pagination.toObject(includeInstance, f),
|
||||
sortby: (f = msg.getSortby()) && proto.messages.SortBy.toObject(includeInstance, f)
|
||||
sortby: (f = msg.getSortby()) && proto.messages.SortBy.toObject(includeInstance, f),
|
||||
merge: (f = msg.getMerge()) && proto.messages.OptionalBool.toObject(includeInstance, f)
|
||||
};
|
||||
|
||||
if (includeInstance) {
|
||||
@@ -18985,6 +19189,11 @@ proto.messages.GetMessagesRequest.deserializeBinaryFromReader = function(msg, re
|
||||
reader.readMessage(value,proto.messages.SortBy.deserializeBinaryFromReader);
|
||||
msg.setSortby(value);
|
||||
break;
|
||||
case 5:
|
||||
var value = new proto.messages.OptionalBool;
|
||||
reader.readMessage(value,proto.messages.OptionalBool.deserializeBinaryFromReader);
|
||||
msg.setMerge(value);
|
||||
break;
|
||||
default:
|
||||
reader.skipField();
|
||||
break;
|
||||
@@ -19046,6 +19255,14 @@ proto.messages.GetMessagesRequest.serializeBinaryToWriter = function(message, wr
|
||||
proto.messages.SortBy.serializeBinaryToWriter
|
||||
);
|
||||
}
|
||||
f = message.getMerge();
|
||||
if (f != null) {
|
||||
writer.writeMessage(
|
||||
5,
|
||||
f,
|
||||
proto.messages.OptionalBool.serializeBinaryToWriter
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -19197,6 +19414,43 @@ proto.messages.GetMessagesRequest.prototype.hasSortby = function() {
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* optional OptionalBool merge = 5;
|
||||
* @return {?proto.messages.OptionalBool}
|
||||
*/
|
||||
proto.messages.GetMessagesRequest.prototype.getMerge = function() {
|
||||
return /** @type{?proto.messages.OptionalBool} */ (
|
||||
jspb.Message.getWrapperField(this, proto.messages.OptionalBool, 5));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {?proto.messages.OptionalBool|undefined} value
|
||||
* @return {!proto.messages.GetMessagesRequest} returns this
|
||||
*/
|
||||
proto.messages.GetMessagesRequest.prototype.setMerge = function(value) {
|
||||
return jspb.Message.setWrapperField(this, 5, value);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Clears the message field making it undefined.
|
||||
* @return {!proto.messages.GetMessagesRequest} returns this
|
||||
*/
|
||||
proto.messages.GetMessagesRequest.prototype.clearMerge = function() {
|
||||
return this.setMerge(undefined);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether this field is set.
|
||||
* @return {boolean}
|
||||
*/
|
||||
proto.messages.GetMessagesRequest.prototype.hasMerge = function() {
|
||||
return jspb.Message.getField(this, 5) != null;
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -19882,7 +20136,8 @@ proto.messages.GetChatsRequest.toObject = function(includeInstance, msg) {
|
||||
session: (f = msg.getSession()) && proto.messages.Session.toObject(includeInstance, f),
|
||||
sortby: (f = msg.getSortby()) && proto.messages.SortBy.toObject(includeInstance, f),
|
||||
pagination: (f = msg.getPagination()) && proto.messages.Pagination.toObject(includeInstance, f),
|
||||
filter: (f = msg.getFilter()) && proto.messages.ChatFilter.toObject(includeInstance, f)
|
||||
filter: (f = msg.getFilter()) && proto.messages.ChatFilter.toObject(includeInstance, f),
|
||||
merge: (f = msg.getMerge()) && proto.messages.OptionalBool.toObject(includeInstance, f)
|
||||
};
|
||||
|
||||
if (includeInstance) {
|
||||
@@ -19939,6 +20194,11 @@ proto.messages.GetChatsRequest.deserializeBinaryFromReader = function(msg, reade
|
||||
reader.readMessage(value,proto.messages.ChatFilter.deserializeBinaryFromReader);
|
||||
msg.setFilter(value);
|
||||
break;
|
||||
case 5:
|
||||
var value = new proto.messages.OptionalBool;
|
||||
reader.readMessage(value,proto.messages.OptionalBool.deserializeBinaryFromReader);
|
||||
msg.setMerge(value);
|
||||
break;
|
||||
default:
|
||||
reader.skipField();
|
||||
break;
|
||||
@@ -20000,6 +20260,14 @@ proto.messages.GetChatsRequest.serializeBinaryToWriter = function(message, write
|
||||
proto.messages.ChatFilter.serializeBinaryToWriter
|
||||
);
|
||||
}
|
||||
f = message.getMerge();
|
||||
if (f != null) {
|
||||
writer.writeMessage(
|
||||
5,
|
||||
f,
|
||||
proto.messages.OptionalBool.serializeBinaryToWriter
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -20151,6 +20419,43 @@ proto.messages.GetChatsRequest.prototype.hasFilter = function() {
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* optional OptionalBool merge = 5;
|
||||
* @return {?proto.messages.OptionalBool}
|
||||
*/
|
||||
proto.messages.GetChatsRequest.prototype.getMerge = function() {
|
||||
return /** @type{?proto.messages.OptionalBool} */ (
|
||||
jspb.Message.getWrapperField(this, proto.messages.OptionalBool, 5));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {?proto.messages.OptionalBool|undefined} value
|
||||
* @return {!proto.messages.GetChatsRequest} returns this
|
||||
*/
|
||||
proto.messages.GetChatsRequest.prototype.setMerge = function(value) {
|
||||
return jspb.Message.setWrapperField(this, 5, value);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Clears the message field making it undefined.
|
||||
* @return {!proto.messages.GetChatsRequest} returns this
|
||||
*/
|
||||
proto.messages.GetChatsRequest.prototype.clearMerge = function() {
|
||||
return this.setMerge(undefined);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether this field is set.
|
||||
* @return {boolean}
|
||||
*/
|
||||
proto.messages.GetChatsRequest.prototype.hasMerge = function() {
|
||||
return jspb.Message.getField(this, 5) != null;
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -65,6 +65,8 @@ import {
|
||||
GetChatMessageQuery,
|
||||
GetChatMessagesFilter,
|
||||
GetChatMessagesQuery,
|
||||
GetChatsOverviewParams,
|
||||
GetChatsParams,
|
||||
MessageSortField,
|
||||
OverviewFilter,
|
||||
ReadChatMessagesQuery,
|
||||
@@ -914,6 +916,7 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
|
||||
return data.id;
|
||||
}
|
||||
|
||||
@Activity()
|
||||
async rejectCall(from: string, id: string): Promise<void> {
|
||||
const request = new messages.RejectCallRequest({
|
||||
session: this.session,
|
||||
@@ -1077,6 +1080,7 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
|
||||
};
|
||||
}
|
||||
|
||||
@Activity()
|
||||
async checkNumberStatus(
|
||||
request: CheckNumberStatusQuery,
|
||||
): Promise<WANumberExistResult> {
|
||||
@@ -1855,24 +1859,27 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
|
||||
pagination.sortOrder = SortOrder.DESC;
|
||||
}
|
||||
const chats = await this.getChats(pagination, filter);
|
||||
const merge = (pagination as GetChatsOverviewParams).merge ?? true;
|
||||
|
||||
const promises = [];
|
||||
for (const chat of chats) {
|
||||
promises.push(this.fetchChatSummary(chat));
|
||||
promises.push(this.fetchChatSummary(chat, merge));
|
||||
}
|
||||
const result = await Promise.all(promises);
|
||||
return result;
|
||||
}
|
||||
|
||||
protected async fetchChatSummary(chat): Promise<ChatSummary> {
|
||||
protected async fetchChatSummary(chat, merge: boolean): Promise<ChatSummary> {
|
||||
const id = toCusFormat(chat.id);
|
||||
const name = chat.name;
|
||||
const picture = await this.getContactProfilePicture(chat.id, false);
|
||||
const messages = await this.getChatMessages(
|
||||
chat.id,
|
||||
{ limit: 1, offset: 0, downloadMedia: false },
|
||||
{},
|
||||
);
|
||||
const lastMessageQuery: GetChatMessagesQuery = {
|
||||
limit: 1,
|
||||
offset: 0,
|
||||
downloadMedia: false,
|
||||
merge,
|
||||
};
|
||||
const messages = await this.getChatMessages(chat.id, lastMessageQuery, {});
|
||||
const message = messages.length > 0 ? messages[0] : null;
|
||||
return {
|
||||
id: id,
|
||||
@@ -1897,6 +1904,8 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
|
||||
pagination: PaginationParams,
|
||||
filter: OverviewFilter | null = null,
|
||||
) {
|
||||
const merge =
|
||||
(pagination as GetChatsParams | GetChatsOverviewParams).merge ?? true;
|
||||
if (pagination.sortBy === ChatSortField.CONVERSATION_TIMESTAMP) {
|
||||
pagination.sortBy = 'timestamp';
|
||||
}
|
||||
@@ -1920,6 +1929,7 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
|
||||
filter: new messages.ChatFilter({
|
||||
jids: jids,
|
||||
}),
|
||||
merge: optional(merge, messages.OptionalBool),
|
||||
});
|
||||
const response = await promisify(this.client.GetChats)(request);
|
||||
const data = parseJsonList(response);
|
||||
@@ -1932,6 +1942,7 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
|
||||
filter: GetChatMessagesFilter,
|
||||
) {
|
||||
const downloadMedia = query.downloadMedia;
|
||||
const merge = query.merge ?? true;
|
||||
let jid: messages.OptionalString;
|
||||
if (chatId === 'all') {
|
||||
jid = null;
|
||||
@@ -1969,6 +1980,7 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
|
||||
limit: query.limit,
|
||||
offset: query.offset,
|
||||
}),
|
||||
merge: optional(merge, messages.OptionalBool),
|
||||
});
|
||||
const response = await promisify(this.client.GetMessages)(request);
|
||||
const msgs = parseJsonList(response);
|
||||
|
||||
@@ -64,10 +64,19 @@ export function ToGroupParticipant(
|
||||
interface GroupParticipantUpdate {
|
||||
id: string;
|
||||
author: string;
|
||||
participants: string[];
|
||||
participants: Array<string | NOWEBGroupParticipant>;
|
||||
action: ParticipantAction;
|
||||
}
|
||||
|
||||
function getParticipantId(
|
||||
participant: string | NOWEBGroupParticipant,
|
||||
): string | undefined {
|
||||
if (typeof participant === 'string') {
|
||||
return participant;
|
||||
}
|
||||
return participant?.id;
|
||||
}
|
||||
|
||||
export function ToGroupV2Participants(
|
||||
update: GroupParticipantUpdate,
|
||||
): GroupV2ParticipantsEvent {
|
||||
@@ -92,7 +101,8 @@ export function ToGroupV2Participants(
|
||||
break;
|
||||
}
|
||||
|
||||
const participants: GroupParticipant[] = update.participants.map((id) => {
|
||||
const participants: GroupParticipant[] = update.participants.map((item) => {
|
||||
const id = getParticipantId(item);
|
||||
return {
|
||||
id: toCusFormat(id),
|
||||
role: role,
|
||||
@@ -131,7 +141,11 @@ export function ToGroupV2LeaveEvent(
|
||||
return null;
|
||||
}
|
||||
const meId = esm.b.jidNormalizedUser(me.id);
|
||||
if (!update.participants.includes(meId)) {
|
||||
const includesMe = update.participants.some((participant) => {
|
||||
const id = getParticipantId(participant);
|
||||
return id === meId;
|
||||
});
|
||||
if (!includesMe) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -91,6 +91,8 @@ import {
|
||||
GetChatMessageQuery,
|
||||
GetChatMessagesFilter,
|
||||
GetChatMessagesQuery,
|
||||
GetChatsOverviewParams,
|
||||
GetChatsParams,
|
||||
OverviewFilter,
|
||||
PinDuration,
|
||||
ReadChatMessagesQuery,
|
||||
@@ -906,6 +908,7 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
|
||||
/**
|
||||
* Other methods
|
||||
*/
|
||||
@Activity()
|
||||
async checkNumberStatus(
|
||||
request: CheckNumberStatusQuery,
|
||||
): Promise<WANumberExistResult> {
|
||||
@@ -925,6 +928,7 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
|
||||
return this.generateMessageID();
|
||||
}
|
||||
|
||||
@Activity()
|
||||
async rejectCall(from: string, id: string): Promise<void> {
|
||||
const jid = toJID(this.ensureSuffix(from));
|
||||
await this.sock.rejectCall(id, jid);
|
||||
@@ -954,24 +958,63 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
|
||||
}
|
||||
|
||||
@Activity()
|
||||
public editMessage(
|
||||
public async editMessage(
|
||||
chatId: string,
|
||||
messageId: string,
|
||||
request: EditMessageRequest,
|
||||
) {
|
||||
const jid = toJID(this.ensureSuffix(chatId));
|
||||
const key = parseMessageIdSerialized(messageId);
|
||||
const message = {
|
||||
const stored = await this.store
|
||||
?.loadMessage(key.remoteJid, key.id)
|
||||
.catch(() => null);
|
||||
const content = extractMessageContent(stored?.message);
|
||||
let editedMessage = undefined;
|
||||
if (content?.imageMessage) {
|
||||
editedMessage = {
|
||||
imageMessage: {
|
||||
caption: request.text,
|
||||
},
|
||||
};
|
||||
} else if (content?.videoMessage) {
|
||||
editedMessage = {
|
||||
videoMessage: {
|
||||
caption: request.text,
|
||||
},
|
||||
};
|
||||
} else if (content?.documentMessage) {
|
||||
editedMessage = {
|
||||
documentMessage: {
|
||||
caption: request.text,
|
||||
},
|
||||
};
|
||||
} else if (content?.documentWithCaptionMessage?.message?.documentMessage) {
|
||||
editedMessage = {
|
||||
documentWithCaptionMessage: {
|
||||
message: {
|
||||
documentMessage: {
|
||||
caption: request.text,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
let message: any = {
|
||||
text: request.text,
|
||||
mentions: request.mentions?.map(toJID),
|
||||
edit: key,
|
||||
editedMessage: editedMessage,
|
||||
linkPreview: this.getLinkPreview(request),
|
||||
linkPreviewHighQuality: request.linkPreviewHighQuality,
|
||||
};
|
||||
const options = {
|
||||
messageId: this.generateMessageID(),
|
||||
};
|
||||
return this.sock.sendMessage(jid, message, options);
|
||||
if (isJidNewsletter(jid)) {
|
||||
// Newsletter edits reuse the original message ID
|
||||
options.messageId = key.id;
|
||||
}
|
||||
return await this.sock.sendMessage(jid, message, options);
|
||||
}
|
||||
|
||||
@Activity()
|
||||
@@ -1136,10 +1179,12 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
|
||||
) {
|
||||
const downloadMedia = query.downloadMedia;
|
||||
const pagination = query as PaginationParams;
|
||||
const merge = query.merge ?? true;
|
||||
const messages = await this.store.getMessagesByJid(
|
||||
toJID(chatId),
|
||||
filter,
|
||||
pagination,
|
||||
merge,
|
||||
);
|
||||
|
||||
const promises = [];
|
||||
@@ -1165,7 +1210,12 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
|
||||
query: GetChatMessageQuery,
|
||||
): Promise<null | WAMessage> {
|
||||
const key = parseMessageIdSerialized(messageId, true);
|
||||
const message = await this.store.getMessageById(toJID(chatId), key.id);
|
||||
const merge = query.merge ?? true;
|
||||
const message = await this.store.getMessageById(
|
||||
toJID(chatId),
|
||||
key.id,
|
||||
merge,
|
||||
);
|
||||
if (!message) return null;
|
||||
return await this.processIncomingMessage(message, query.downloadMedia);
|
||||
}
|
||||
@@ -1252,7 +1302,8 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
|
||||
*/
|
||||
|
||||
async getChats(pagination: PaginationParams) {
|
||||
const chats = await this.store.getChats(pagination, true);
|
||||
const merge = (pagination as GetChatsParams).merge ?? true;
|
||||
const chats = await this.store.getChats(pagination, true, undefined, merge);
|
||||
// Remove unreadCount, it's not ready yet
|
||||
chats.forEach((chat) => delete chat.unreadCount);
|
||||
return chats;
|
||||
@@ -1262,6 +1313,7 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
|
||||
pagination: PaginationParams,
|
||||
filter?: OverviewFilter,
|
||||
): Promise<ChatSummary[]> {
|
||||
const merge = (pagination as GetChatsOverviewParams).merge ?? true;
|
||||
// Convert customer format IDs to JID format if filter is provided
|
||||
let jidFilter;
|
||||
if (filter?.ids && filter.ids.length > 0) {
|
||||
@@ -1270,19 +1322,27 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
|
||||
};
|
||||
}
|
||||
|
||||
const chats = await this.store.getChats(pagination, false, jidFilter);
|
||||
const chats = await this.store.getChats(
|
||||
pagination,
|
||||
false,
|
||||
jidFilter,
|
||||
merge,
|
||||
);
|
||||
// Remove unreadCount, it's not ready yet
|
||||
chats.forEach((chat) => delete chat.unreadCount);
|
||||
|
||||
const promises = [];
|
||||
for (const chat of chats) {
|
||||
promises.push(this.fetchChatSummary(chat));
|
||||
promises.push(this.fetchChatSummary(chat, merge));
|
||||
}
|
||||
const result = await Promise.all(promises);
|
||||
return result;
|
||||
}
|
||||
|
||||
protected async fetchChatSummary(chat: Chat): Promise<ChatSummary> {
|
||||
protected async fetchChatSummary(
|
||||
chat: Chat,
|
||||
merge: boolean,
|
||||
): Promise<ChatSummary> {
|
||||
const id = toCusFormat(chat.id);
|
||||
let name = chat.name;
|
||||
if (!name) {
|
||||
@@ -1292,11 +1352,13 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
|
||||
name = contact?.name || contact?.notify;
|
||||
}
|
||||
const picture = await this.getContactProfilePicture(chat.id, false);
|
||||
const messages = await this.getChatMessages(
|
||||
chat.id,
|
||||
{ limit: 1, offset: 0, downloadMedia: false },
|
||||
{},
|
||||
);
|
||||
const lastMessageQuery: GetChatMessagesQuery = {
|
||||
limit: 1,
|
||||
offset: 0,
|
||||
downloadMedia: false,
|
||||
merge: merge,
|
||||
};
|
||||
const messages = await this.getChatMessages(chat.id, lastMessageQuery, {});
|
||||
const message = messages.length > 0 ? messages[0] : null;
|
||||
return {
|
||||
id: id,
|
||||
|
||||
@@ -11,6 +11,7 @@ export interface IChatRepository {
|
||||
pagination: PaginationParams,
|
||||
broadcast: boolean,
|
||||
filter?: OverviewFilter,
|
||||
merge?: boolean,
|
||||
): Promise<Chat[]>;
|
||||
|
||||
getById(id: string): Promise<Chat | null>;
|
||||
|
||||
@@ -14,9 +14,10 @@ export interface IMessagesRepository {
|
||||
jid: string,
|
||||
filter: GetChatMessagesFilter,
|
||||
pagination: PaginationParams,
|
||||
merge?: boolean,
|
||||
): Promise<any[]>;
|
||||
|
||||
getByJidById(jid: string, id: string): Promise<any | null>;
|
||||
getByJidById(jid: string, id: string, merge?: boolean): Promise<any | null>;
|
||||
|
||||
updateByJidAndId(jid: string, id: string, update: any): Promise<boolean>;
|
||||
|
||||
|
||||
@@ -31,14 +31,20 @@ export interface INowebStore {
|
||||
chatId: string,
|
||||
filter: GetChatMessagesFilter,
|
||||
pagination: PaginationParams,
|
||||
merge?: boolean,
|
||||
): Promise<any>;
|
||||
|
||||
getMessageById(chatId: string, messageId: string): Promise<any>;
|
||||
getMessageById(
|
||||
chatId: string,
|
||||
messageId: string,
|
||||
merge?: boolean,
|
||||
): Promise<any>;
|
||||
|
||||
getChats(
|
||||
pagination: PaginationParams,
|
||||
broadcast: boolean,
|
||||
filter?: OverviewFilter,
|
||||
merge?: boolean,
|
||||
): Promise<Chat[]>;
|
||||
|
||||
getChat(jid: string): Promise<Chat | null>;
|
||||
|
||||
@@ -2,7 +2,10 @@ import type { Chat, Contact, GroupMetadata, proto } from '@adiwajshing/baileys';
|
||||
import type makeWASocket from '@adiwajshing/baileys';
|
||||
import type { Label } from '@adiwajshing/baileys/lib/Types/Label';
|
||||
import { BadRequestException } from '@nestjs/common';
|
||||
import { GetChatMessagesFilter } from '@waha/structures/chats.dto';
|
||||
import {
|
||||
GetChatMessagesFilter,
|
||||
OverviewFilter,
|
||||
} from '@waha/structures/chats.dto';
|
||||
import { LidToPhoneNumber } from '@waha/structures/lids.dto';
|
||||
import {
|
||||
LimitOffsetParams,
|
||||
@@ -57,15 +60,25 @@ export class NowebInMemoryStore implements INowebStore {
|
||||
chatId: string,
|
||||
filter: GetChatMessagesFilter,
|
||||
pagination: PaginationParams,
|
||||
merge?: boolean,
|
||||
): Promise<any> {
|
||||
throw new BadRequestException(this.errorMessage);
|
||||
}
|
||||
|
||||
getMessageById(chatId: string, messageId: string): Promise<any> {
|
||||
getMessageById(
|
||||
chatId: string,
|
||||
messageId: string,
|
||||
merge?: boolean,
|
||||
): Promise<any> {
|
||||
throw new BadRequestException(this.errorMessage);
|
||||
}
|
||||
|
||||
getChats(pagination: PaginationParams, broadcast: boolean): Promise<Chat[]> {
|
||||
getChats(
|
||||
pagination: PaginationParams,
|
||||
broadcast: boolean,
|
||||
filter?: OverviewFilter,
|
||||
merge?: boolean,
|
||||
): Promise<Chat[]> {
|
||||
throw new BadRequestException(this.errorMessage);
|
||||
}
|
||||
|
||||
|
||||
@@ -44,6 +44,7 @@ import { INowebStorage } from './INowebStorage';
|
||||
import { INowebStore } from './INowebStore';
|
||||
import { LabelAssociationType } from '../labels/LabelAssociationType';
|
||||
import esm from '@waha/vendor/esm';
|
||||
import { StatusStringToStatus } from '@waha/core/utils/acks';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const AsyncLock = require('async-lock');
|
||||
@@ -130,7 +131,7 @@ export class NowebPersistentStore implements INowebStore {
|
||||
return {
|
||||
id: message.key.remoteJid,
|
||||
lid: lid,
|
||||
jid: pn,
|
||||
phoneNumber: pn,
|
||||
};
|
||||
})
|
||||
.filter(Boolean);
|
||||
@@ -249,6 +250,9 @@ export class NowebPersistentStore implements INowebStore {
|
||||
private async syncMessagesHistory(messages) {
|
||||
const realMessages = messages.filter(esm.b.isRealMessage);
|
||||
messages = messages.filter((msg) => this.jids.include(msg.key.remoteJid));
|
||||
for (const message of messages) {
|
||||
message.status = StatusStringToStatus(message.status);
|
||||
}
|
||||
await this.messagesRepo.upsert(realMessages);
|
||||
this.logger.info(
|
||||
`history sync - '${messages.length}' got messages, '${realMessages.length}' real messages`,
|
||||
@@ -288,7 +292,11 @@ export class NowebPersistentStore implements INowebStore {
|
||||
);
|
||||
continue;
|
||||
}
|
||||
const message = await this.messagesRepo.getByJidById(jid, update.key.id);
|
||||
let message = await this.messagesRepo.getByJidById(jid, update.key.id);
|
||||
if (!message) {
|
||||
// Fallback to just id
|
||||
message = await this.messagesRepo.getById(update.key.id);
|
||||
}
|
||||
if (!message) {
|
||||
this.logger.warn(
|
||||
`got update for non-existent message. update: '${JSON.stringify(
|
||||
@@ -599,24 +607,35 @@ export class NowebPersistentStore implements INowebStore {
|
||||
chatId: string,
|
||||
filter: GetChatMessagesFilter,
|
||||
pagination: PaginationParams,
|
||||
merge: boolean = true,
|
||||
): Promise<any> {
|
||||
pagination.sortBy = 'messageTimestamp';
|
||||
pagination.sortOrder = pagination.sortOrder || SortOrder.DESC;
|
||||
return this.messagesRepo.getAllByJid(chatId, filter, pagination);
|
||||
return this.messagesRepo.getAllByJid(chatId, filter, pagination, merge);
|
||||
}
|
||||
|
||||
getMessageById(chatId: string, messageId: string): Promise<any> {
|
||||
return this.messagesRepo.getByJidById(chatId, messageId);
|
||||
getMessageById(
|
||||
chatId: string,
|
||||
messageId: string,
|
||||
merge: boolean = true,
|
||||
): Promise<any> {
|
||||
return this.messagesRepo.getByJidById(chatId, messageId, merge);
|
||||
}
|
||||
|
||||
getChats(
|
||||
pagination: PaginationParams,
|
||||
broadcast: boolean,
|
||||
filter?: OverviewFilter,
|
||||
merge: boolean = true,
|
||||
): Promise<Chat[]> {
|
||||
pagination.sortBy ||= 'conversationTimestamp';
|
||||
pagination.sortOrder ||= SortOrder.DESC;
|
||||
return this.chatRepo.getAllWithMessages(pagination, broadcast, filter);
|
||||
return this.chatRepo.getAllWithMessages(
|
||||
pagination,
|
||||
broadcast,
|
||||
filter,
|
||||
merge,
|
||||
);
|
||||
}
|
||||
|
||||
async getChat(jid: string): Promise<Chat | null> {
|
||||
|
||||
@@ -2,6 +2,7 @@ import type { Chat } from '@adiwajshing/baileys';
|
||||
import { SqlKVRepository } from '@waha/core/storage/sql/SqlKVRepository';
|
||||
import { OverviewFilter } from '@waha/structures/chats.dto';
|
||||
import { PaginationParams } from '@waha/structures/pagination.dto';
|
||||
import { Knex } from 'knex';
|
||||
|
||||
export class SqlChatMethods {
|
||||
constructor(private repository: SqlKVRepository<any>) {}
|
||||
@@ -10,23 +11,112 @@ export class SqlChatMethods {
|
||||
pagination: PaginationParams,
|
||||
broadcast: boolean,
|
||||
filter?: OverviewFilter,
|
||||
merge: boolean = true,
|
||||
): Promise<Chat[]> {
|
||||
// Get chats with conversationTimestamp is not Null
|
||||
let query = this.repository.select().whereNotNull('conversationTimestamp');
|
||||
const knex = this.repository.getKnex();
|
||||
const tableName = this.repository.table;
|
||||
let baseQuery = this.repository
|
||||
.select()
|
||||
.whereNotNull(`${tableName}.conversationTimestamp`);
|
||||
|
||||
if (!broadcast) {
|
||||
// filter out chat by id if it ends at @newsletter or @broadcast
|
||||
query = query
|
||||
.andWhereNot('id', 'like', '%@broadcast')
|
||||
.andWhereNot('id', 'like', '%@newsletter');
|
||||
if (!merge) {
|
||||
baseQuery = this.applyNonMergedFilters(baseQuery, tableName, {
|
||||
broadcast,
|
||||
filter,
|
||||
});
|
||||
const pagedQuery = this.repository.pagination(baseQuery, pagination);
|
||||
const rows = await pagedQuery;
|
||||
return rows.map((row) => this.repository.parse(row));
|
||||
}
|
||||
|
||||
// Filter by IDs if provided
|
||||
if (filter?.ids && filter.ids.length > 0) {
|
||||
query = query.whereIn('id', filter.ids);
|
||||
const annotatedQuery = this.annotateWithPnJid(knex, baseQuery, {
|
||||
tableName,
|
||||
broadcast,
|
||||
filter,
|
||||
}).as('annotated_chats');
|
||||
|
||||
const dedupedQuery = knex
|
||||
.select('*')
|
||||
.from(
|
||||
knex
|
||||
.select(
|
||||
'annotated_chats.*',
|
||||
knex.raw(
|
||||
'ROW_NUMBER() OVER (PARTITION BY annotated_chats.primary_jid ORDER BY annotated_chats."conversationTimestamp" DESC, annotated_chats.primary_priority ASC) as __rownum',
|
||||
),
|
||||
)
|
||||
.from(annotatedQuery)
|
||||
.as('ranked_chats'),
|
||||
)
|
||||
.where('__rownum', 1);
|
||||
|
||||
const pagedQuery = this.repository.pagination(dedupedQuery, pagination);
|
||||
const rows = await pagedQuery;
|
||||
|
||||
return rows.map((row) => {
|
||||
const chat = this.repository.parse(row);
|
||||
if (merge && row.primary_jid) {
|
||||
chat.id = row.primary_jid;
|
||||
}
|
||||
return chat;
|
||||
});
|
||||
}
|
||||
|
||||
private annotateWithPnJid(
|
||||
knex: Knex,
|
||||
query: Knex.QueryBuilder,
|
||||
opts: {
|
||||
tableName: string;
|
||||
broadcast: boolean;
|
||||
filter?: OverviewFilter;
|
||||
},
|
||||
) {
|
||||
const pnExpr = this.buildPnJidExpr(opts.tableName);
|
||||
let annotated = query
|
||||
.leftJoin('lid_map', 'lid_map.id', `${opts.tableName}.id`)
|
||||
.select(
|
||||
knex.raw(`${pnExpr} as primary_jid`),
|
||||
knex.raw(
|
||||
`CASE WHEN ${opts.tableName}.id LIKE '%@lid' THEN 1 ELSE 0 END as primary_priority`,
|
||||
),
|
||||
);
|
||||
|
||||
if (!opts.broadcast) {
|
||||
annotated = annotated
|
||||
.andWhereNot(`${opts.tableName}.id`, 'like', '%@broadcast')
|
||||
.andWhereNot(`${opts.tableName}.id`, 'like', '%@newsletter');
|
||||
}
|
||||
|
||||
query = this.repository.pagination(query, pagination);
|
||||
return await this.repository.all(query);
|
||||
if (opts.filter?.ids && opts.filter.ids.length > 0) {
|
||||
annotated = annotated.andWhere((builder) => {
|
||||
builder
|
||||
.whereIn(`${opts.tableName}.id`, opts.filter.ids)
|
||||
.orWhereIn('lid_map.pn', opts.filter.ids);
|
||||
});
|
||||
}
|
||||
|
||||
return annotated;
|
||||
}
|
||||
|
||||
private buildPnJidExpr(tableName: string) {
|
||||
const column = `"${tableName}"."id"`;
|
||||
return `CASE WHEN ${column} LIKE '%@lid' THEN COALESCE(lid_map.pn, ${column}) ELSE ${column} END`;
|
||||
}
|
||||
|
||||
private applyNonMergedFilters(
|
||||
query: Knex.QueryBuilder,
|
||||
tableName: string,
|
||||
opts: { broadcast: boolean; filter?: OverviewFilter },
|
||||
) {
|
||||
let filtered = query;
|
||||
if (!opts.broadcast) {
|
||||
filtered = filtered
|
||||
.andWhereNot(`${tableName}.id`, 'like', '%@broadcast')
|
||||
.andWhereNot(`${tableName}.id`, 'like', '%@newsletter');
|
||||
}
|
||||
if (opts.filter?.ids && opts.filter.ids.length > 0) {
|
||||
filtered = filtered.whereIn(`${tableName}.id`, opts.filter.ids);
|
||||
}
|
||||
return filtered;
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,17 @@
|
||||
import { ALL_JID } from '@waha/core/engines/noweb/session.noweb.core';
|
||||
import { SqlKVRepository } from '@waha/core/storage/sql/SqlKVRepository';
|
||||
import { AckToStatus } from '@waha/core/utils/acks';
|
||||
import { isLidUser } from '@waha/core/utils/jids';
|
||||
import { GetChatMessagesFilter } from '@waha/structures/chats.dto';
|
||||
import { PaginationParams } from '@waha/structures/pagination.dto';
|
||||
import { Knex } from 'knex';
|
||||
import { INowebLidPNRepository } from '../INowebLidPNRepository';
|
||||
|
||||
export class SqlMessagesMethods {
|
||||
constructor(private repository: SqlKVRepository<any>) {}
|
||||
constructor(
|
||||
private repository: SqlKVRepository<any>,
|
||||
private lidRepository?: INowebLidPNRepository,
|
||||
) {}
|
||||
|
||||
upsert(messages: any[]): Promise<void> {
|
||||
return this.repository.upsertMany(messages);
|
||||
@@ -15,10 +21,17 @@ export class SqlMessagesMethods {
|
||||
jid: string,
|
||||
filter: GetChatMessagesFilter,
|
||||
pagination: PaginationParams,
|
||||
merge: boolean = true,
|
||||
): Promise<any[]> {
|
||||
let query = this.repository.select();
|
||||
const tableName = this.repository.table;
|
||||
if (jid !== ALL_JID) {
|
||||
query = this.repository.select().where({ jid: jid });
|
||||
if (merge) {
|
||||
const pnJid = await this.resolvePnJid(jid);
|
||||
query = this.applyPnJidFilter(query, pnJid);
|
||||
} else {
|
||||
query = query.where(`${tableName}.jid`, jid);
|
||||
}
|
||||
}
|
||||
if (filter['filter.timestamp.lte'] != null) {
|
||||
query = query.where(
|
||||
@@ -34,10 +47,11 @@ export class SqlMessagesMethods {
|
||||
filter['filter.timestamp.gte'],
|
||||
);
|
||||
}
|
||||
const dataColumn = `${this.repository.table}.data`;
|
||||
if (filter['filter.fromMe'] != null) {
|
||||
// filter by data json inside
|
||||
const [sql, value] = this.repository.filterJson(
|
||||
'data',
|
||||
dataColumn,
|
||||
'key.fromMe',
|
||||
filter['filter.fromMe'],
|
||||
);
|
||||
@@ -45,18 +59,39 @@ export class SqlMessagesMethods {
|
||||
}
|
||||
if (filter['filter.ack'] != null) {
|
||||
const status = AckToStatus(filter['filter.ack']);
|
||||
const [sql, value] = this.repository.filterJson('data', 'status', status);
|
||||
const [sql, value] = this.repository.filterJson(
|
||||
dataColumn,
|
||||
'status',
|
||||
status,
|
||||
);
|
||||
query = query.whereRaw(sql, [value]);
|
||||
}
|
||||
query = this.repository.pagination(query, pagination);
|
||||
return this.repository.all(query);
|
||||
}
|
||||
|
||||
async getByJidById(jid: string, id: string): Promise<any> {
|
||||
async getByJidById(
|
||||
jid: string,
|
||||
id: string,
|
||||
merge: boolean = true,
|
||||
): Promise<any> {
|
||||
if (jid === ALL_JID) {
|
||||
return this.repository.getBy({ id: id });
|
||||
}
|
||||
return this.repository.getBy({ jid: jid, id: id });
|
||||
const tableName = this.repository.table;
|
||||
const baseQuery = this.repository.select().where(`${tableName}.id`, id);
|
||||
let query;
|
||||
if (merge) {
|
||||
const pnJid = await this.resolvePnJid(jid);
|
||||
query = this.applyPnJidFilter(baseQuery, pnJid);
|
||||
} else {
|
||||
query = baseQuery.andWhere(`${tableName}.jid`, jid);
|
||||
}
|
||||
const rows = await query.limit(1);
|
||||
if (!rows.length) {
|
||||
return null;
|
||||
}
|
||||
return this.repository.parse(rows[0]);
|
||||
}
|
||||
|
||||
async updateByJidAndId(
|
||||
@@ -83,4 +118,39 @@ export class SqlMessagesMethods {
|
||||
deleteAllByJid(jid: string): Promise<void> {
|
||||
return this.repository.deleteBy({ jid: jid });
|
||||
}
|
||||
|
||||
private applyPnJidFilter(
|
||||
query: Knex.QueryBuilder,
|
||||
pnJid: string,
|
||||
): Knex.QueryBuilder {
|
||||
const tableName = this.repository.table;
|
||||
const pnExpr = this.buildPnJidExpr(tableName, 'jid');
|
||||
return query
|
||||
.leftJoin('lid_map', 'lid_map.id', `${tableName}.jid`)
|
||||
.whereRaw(`${pnExpr} = ?`, [pnJid]);
|
||||
}
|
||||
|
||||
private async resolvePnJid(jid: string): Promise<string> {
|
||||
if (!isLidUser(jid)) {
|
||||
return jid;
|
||||
}
|
||||
if (this.lidRepository) {
|
||||
const mapped = await this.lidRepository.findPNByLid(jid);
|
||||
if (mapped) {
|
||||
return mapped;
|
||||
}
|
||||
}
|
||||
const row = await this.repository
|
||||
.getKnex()
|
||||
.select('pn')
|
||||
.from('lid_map')
|
||||
.where('id', jid)
|
||||
.first();
|
||||
return row?.pn || jid;
|
||||
}
|
||||
|
||||
private buildPnJidExpr(tableName: string, column: string) {
|
||||
const colRef = `"${tableName}"."${column}"`;
|
||||
return `CASE WHEN ${colRef} LIKE '%@lid' THEN COALESCE(lid_map.pn, ${colRef}) ELSE ${colRef} END`;
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,7 @@ export class NOWEBSqlite3KVRepository<
|
||||
return JSON.stringify(data, esm.b.BufferJSON.replacer);
|
||||
}
|
||||
|
||||
protected parse(row: any): any {
|
||||
public parse(row: any): any {
|
||||
return JSON.parse(row.data, esm.b.BufferJSON.reviver);
|
||||
}
|
||||
|
||||
|
||||
@@ -30,7 +30,13 @@ export class Sqlite3ChatRepository
|
||||
pagination: PaginationParams,
|
||||
broadcast: boolean,
|
||||
filter?: OverviewFilter,
|
||||
merge?: boolean,
|
||||
): Promise<Chat[]> {
|
||||
return this.methods.getAllWithMessages(pagination, broadcast, filter);
|
||||
return this.methods.getAllWithMessages(
|
||||
pagination,
|
||||
broadcast,
|
||||
filter,
|
||||
merge,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -6,17 +6,26 @@ import { PaginationParams } from '@waha/structures/pagination.dto';
|
||||
|
||||
import { IMessagesRepository } from '../IMessagesRepository';
|
||||
import { NOWEBSqlite3KVRepository } from './NOWEBSqlite3KVRepository';
|
||||
import { INowebLidPNRepository } from '../INowebLidPNRepository';
|
||||
import Knex from 'knex';
|
||||
|
||||
export class Sqlite3MessagesRepository
|
||||
extends NOWEBSqlite3KVRepository<any>
|
||||
implements IMessagesRepository
|
||||
{
|
||||
constructor(
|
||||
knex: Knex.Knex,
|
||||
private readonly lidRepository: INowebLidPNRepository,
|
||||
) {
|
||||
super(knex);
|
||||
}
|
||||
|
||||
get schema() {
|
||||
return NowebMessagesSchema;
|
||||
}
|
||||
|
||||
get methods() {
|
||||
return new SqlMessagesMethods(this);
|
||||
return new SqlMessagesMethods(this, this.lidRepository);
|
||||
}
|
||||
|
||||
get metadata() {
|
||||
@@ -31,12 +40,13 @@ export class Sqlite3MessagesRepository
|
||||
jid: string,
|
||||
filter: GetChatMessagesFilter,
|
||||
pagination: PaginationParams,
|
||||
merge?: boolean,
|
||||
): Promise<any[]> {
|
||||
return this.methods.getAllByJid(jid, filter, pagination);
|
||||
return this.methods.getAllByJid(jid, filter, pagination, merge);
|
||||
}
|
||||
|
||||
async getByJidById(jid: string, id: string): Promise<any> {
|
||||
return this.methods.getByJidById(jid, id);
|
||||
async getByJidById(jid: string, id: string, merge?: boolean): Promise<any> {
|
||||
return this.methods.getByJidById(jid, id, merge);
|
||||
}
|
||||
|
||||
async updateByJidAndId(
|
||||
|
||||
@@ -19,6 +19,7 @@ import { KNEX_SQLITE_CLIENT } from '@waha/core/env';
|
||||
export class Sqlite3Storage extends INowebStorage {
|
||||
private readonly tables: Schema[];
|
||||
private readonly knex: Knex.Knex;
|
||||
private lidRepository: INowebLidPNRepository | null = null;
|
||||
|
||||
constructor(filePath: string) {
|
||||
super();
|
||||
@@ -84,10 +85,17 @@ export class Sqlite3Storage extends INowebStorage {
|
||||
}
|
||||
|
||||
getMessagesRepository() {
|
||||
return new Sqlite3MessagesRepository(this.knex);
|
||||
return new Sqlite3MessagesRepository(this.knex, this.getLidRepository());
|
||||
}
|
||||
|
||||
getLidPNRepository(): INowebLidPNRepository {
|
||||
return new Sqlite3LidPNRepository(this.knex);
|
||||
return this.getLidRepository();
|
||||
}
|
||||
|
||||
private getLidRepository(): INowebLidPNRepository {
|
||||
if (!this.lidRepository) {
|
||||
this.lidRepository = new Sqlite3LidPNRepository(this.knex);
|
||||
}
|
||||
return this.lidRepository;
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,12 @@ export function extractMediaContent(
|
||||
content?.videoMessage ||
|
||||
content?.audioMessage ||
|
||||
content?.ptvMessage ||
|
||||
content?.stickerMessage;
|
||||
content?.stickerMessage ||
|
||||
content?.templateMessage?.hydratedTemplate?.imageMessage ||
|
||||
content?.templateMessage?.hydratedTemplate?.videoMessage ||
|
||||
content?.templateMessage?.interactiveMessageTemplate?.header
|
||||
?.imageMessage ||
|
||||
content?.templateMessage?.interactiveMessageTemplate?.header?.videoMessage;
|
||||
if (mediaContent) {
|
||||
return mediaContent;
|
||||
}
|
||||
|
||||
@@ -185,6 +185,9 @@ import {
|
||||
WAHA_CLIENT_BROWSER_NAME,
|
||||
WAHA_CLIENT_DEVICE_NAME,
|
||||
} from '@waha/core/env';
|
||||
import { removeSingletonFiles } from '@waha/core/utils/chrome';
|
||||
import { killProcessesByPatterns } from '@waha/core/utils/processes';
|
||||
import { IsChrome } from '@waha/version';
|
||||
|
||||
export interface WebJSConfig {
|
||||
webVersion?: string;
|
||||
@@ -271,6 +274,11 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
|
||||
};
|
||||
}
|
||||
|
||||
protected getUserDataDir(): string {
|
||||
const base = process.env.WAHA_LOCAL_STORE_BASE_DIR || './.sessions';
|
||||
return `${base}/webjs/default/session-${this.name}`;
|
||||
}
|
||||
|
||||
protected async buildClient() {
|
||||
const clientOptions = this.getClientOptions();
|
||||
const base = process.env.WAHA_LOCAL_STORE_BASE_DIR || './.sessions';
|
||||
@@ -358,6 +366,16 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
|
||||
PAGE_CALL_ERROR_EVENT,
|
||||
(event: CallErrorEvent) => {
|
||||
if (event.error instanceof ProtocolError) {
|
||||
if (this.shouldIgnoreProtocolError(event.error)) {
|
||||
this.logger.warn(
|
||||
`ProtocolError when calling page method: ${String(
|
||||
event.method,
|
||||
)}, ignoring...`,
|
||||
);
|
||||
this.logger.warn(event.error);
|
||||
return;
|
||||
}
|
||||
|
||||
this.logger.error(
|
||||
`ProtocolError when calling page method: ${String(
|
||||
event.method,
|
||||
@@ -393,6 +411,13 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
|
||||
}
|
||||
|
||||
async start() {
|
||||
await killProcessesByPatterns(
|
||||
[IsChrome ? 'chrome' : 'chromium', `--a-waha-session=${this.name}`],
|
||||
'SIGKILL',
|
||||
this.logger,
|
||||
);
|
||||
await removeSingletonFiles(this.getUserDataDir());
|
||||
|
||||
this.status = WAHASessionStatus.STARTING;
|
||||
await this.init().catch((err) => {
|
||||
this.logger.error('Failed to start the client');
|
||||
@@ -418,6 +443,16 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
|
||||
this.restartClient();
|
||||
}
|
||||
|
||||
/**
|
||||
* Certain Puppeteer ProtocolErrors (e.g. Network.getResponseBody) are harmless.
|
||||
* Ignore them so we do not thrash the session state machine.
|
||||
* https://github.com/devlikeapro/waha/issues/1918
|
||||
*/
|
||||
private shouldIgnoreProtocolError(error: ProtocolError): boolean {
|
||||
const message = error?.message ?? String(error ?? '');
|
||||
return message.includes('Network.getResponseBody');
|
||||
}
|
||||
|
||||
async unpair() {
|
||||
this.unpairing = true;
|
||||
this.shouldRestart = false;
|
||||
@@ -674,6 +709,7 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
|
||||
return screenshot as Buffer;
|
||||
}
|
||||
|
||||
@Activity()
|
||||
async checkNumberStatus(
|
||||
request: CheckNumberStatusQuery,
|
||||
): Promise<WANumberExistResult> {
|
||||
@@ -717,6 +753,7 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
|
||||
/**
|
||||
* Other methods
|
||||
*/
|
||||
@Activity()
|
||||
async rejectCall(from: string, id: string): Promise<void> {
|
||||
const peerJid = normalizeJid(this.ensureSuffix(from));
|
||||
const call = new CallInstance(this.whatsapp, null);
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
export interface IWPPAuthManager {
|
||||
/**
|
||||
* Called BEFORE WPP browser starts.
|
||||
* Downloads and extracts any existing remote session to userDataDir.
|
||||
*/
|
||||
beforeStart(): Promise<void>;
|
||||
|
||||
/**
|
||||
* Called AFTER the session reaches WORKING status.
|
||||
* Waits for stabilization, saves first snapshot, then starts periodic backup.
|
||||
*/
|
||||
afterConnected(): Promise<void>;
|
||||
|
||||
/**
|
||||
* Stops the periodic backup runner.
|
||||
*/
|
||||
stop(): Promise<void>;
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
export interface WPPConfig {
|
||||
webVersion?: string;
|
||||
puppeteerArgs: string[];
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
import { Whatsapp as WPPWhatsapp } from '@wppconnect-team/wppconnect';
|
||||
import type {
|
||||
PoolMessageOptions,
|
||||
TextMessageOptions,
|
||||
} from '@wppconnect/wa-js/dist/chat';
|
||||
import type { EditMessageOptions } from '@wppconnect/wa-js/dist/chat/functions/editMessage';
|
||||
import type { TextStatusOptions } from '@wppconnect/wa-js/dist/status/functions/sendTextStatus';
|
||||
|
||||
//
|
||||
// Event data types extracted directly from wppconnect on* method signatures.
|
||||
// Using Parameters<> keeps these automatically in sync with wppconnect:
|
||||
// if an inline type changes upstream, TypeScript will catch mismatches here.
|
||||
//
|
||||
|
||||
// Extracts the last parameter of a function (handles overloaded on* methods
|
||||
// that accept an optional id filter as the first arg before the callback).
|
||||
type LastParam<F extends (...args: any[]) => any> = Parameters<F> extends [
|
||||
...any[],
|
||||
infer Last,
|
||||
]
|
||||
? Last
|
||||
: never;
|
||||
|
||||
// Extracts the event data type: first arg of the (last) callback parameter.
|
||||
type WppEventData<Method extends (...args: any[]) => any> =
|
||||
LastParam<Method> extends (...args: any[]) => any
|
||||
? Parameters<LastParam<Method>>[0]
|
||||
: never;
|
||||
|
||||
type WppTextMessageOptions = TextMessageOptions;
|
||||
type WppMentionedList = WppTextMessageOptions extends {
|
||||
mentionedList?: infer Value;
|
||||
}
|
||||
? Value
|
||||
: string[];
|
||||
|
||||
export type WppPresenceEvent = WppEventData<WPPWhatsapp['onPresenceChanged']>;
|
||||
export type WppParticipantEvent = WppEventData<
|
||||
WPPWhatsapp['onParticipantsChanged']
|
||||
>;
|
||||
|
||||
export enum WppParticipantAction {
|
||||
ADD = 'add',
|
||||
JOIN = 'join',
|
||||
REMOVE = 'remove',
|
||||
LEAVE = 'leave',
|
||||
LEAVER = 'leaver',
|
||||
PROMOTE = 'promote',
|
||||
DEMOTE = 'demote',
|
||||
}
|
||||
|
||||
export enum WppParticipantOperation {
|
||||
ADD = 'add',
|
||||
REMOVE = 'remove',
|
||||
PROMOTE = 'promote',
|
||||
DEMOTE = 'demote',
|
||||
}
|
||||
|
||||
export type WppReactionEvent = WppEventData<WPPWhatsapp['onReactionMessage']>;
|
||||
export type WppRevokedMessageEvent = WppEventData<
|
||||
WPPWhatsapp['onRevokedMessage']
|
||||
>;
|
||||
export type WppPollResponseEvent = WppEventData<WPPWhatsapp['onPollResponse']>;
|
||||
export type WppUpdateLabelEvent = WppEventData<WPPWhatsapp['onUpdateLabel']>;
|
||||
export type WppIncomingCallEvent = WppEventData<WPPWhatsapp['onIncomingCall']>;
|
||||
export type WppSendPollOptions = PoolMessageOptions;
|
||||
export type WppSendTextOptions = WppTextMessageOptions;
|
||||
export type WppSendTextStatusOptions = Omit<
|
||||
TextStatusOptions,
|
||||
'backgroundColor'
|
||||
> & {
|
||||
backgroundColor?: string;
|
||||
};
|
||||
export type WppEditMessageOptions = EditMessageOptions & {
|
||||
mentions?: WppMentionedList;
|
||||
};
|
||||
|
||||
// onMessageEdit callback has three separate args: (chat: Wid, id: string, msg: Message).
|
||||
// fromWppCallback packs them into a tuple since args.length > 1.
|
||||
export type WppMessageEditArgs = Parameters<
|
||||
LastParam<WPPWhatsapp['onMessageEdit']>
|
||||
>;
|
||||
@@ -0,0 +1,216 @@
|
||||
import { toCusFormat } from '@waha/core/utils/jids';
|
||||
import { SerializeMsgKey } from '@waha/core/utils/ids';
|
||||
import { WAMessageReaction } from '@waha/structures/responses.dto';
|
||||
import { WAHAPresenceStatus } from '@waha/structures/enums.dto';
|
||||
import {
|
||||
GroupId,
|
||||
GroupParticipant,
|
||||
GroupParticipantRole,
|
||||
} from '@waha/structures/groups.dto';
|
||||
import {
|
||||
GroupParticipantType,
|
||||
GroupV2LeaveEvent,
|
||||
GroupV2ParticipantsEvent,
|
||||
GroupV2UpdateEvent,
|
||||
} from '@waha/structures/groups.events.dto';
|
||||
import { MeInfo } from '@waha/structures/sessions.dto';
|
||||
import { LabelChatAssociation } from '@waha/structures/labels.dto';
|
||||
import { WAHAChatPresences } from '@waha/structures/presence.dto';
|
||||
import {
|
||||
WppParticipantAction,
|
||||
WppParticipantEvent,
|
||||
WppPresenceEvent,
|
||||
WppReactionEvent,
|
||||
WppUpdateLabelEvent,
|
||||
} from '@waha/core/engines/wpp/WppTypes';
|
||||
|
||||
//
|
||||
// Converters
|
||||
//
|
||||
|
||||
function presenceStateToWAHA(
|
||||
state: string,
|
||||
isOnline?: boolean,
|
||||
): WAHAPresenceStatus {
|
||||
switch (state) {
|
||||
case 'available':
|
||||
return WAHAPresenceStatus.ONLINE;
|
||||
case 'composing':
|
||||
case 'typing':
|
||||
return WAHAPresenceStatus.TYPING;
|
||||
case 'recording':
|
||||
return WAHAPresenceStatus.RECORDING;
|
||||
case 'unavailable':
|
||||
return WAHAPresenceStatus.OFFLINE;
|
||||
default:
|
||||
return isOnline ? WAHAPresenceStatus.ONLINE : WAHAPresenceStatus.OFFLINE;
|
||||
}
|
||||
}
|
||||
|
||||
export function WppPresenceToPresence(
|
||||
data: WppPresenceEvent,
|
||||
): WAHAChatPresences {
|
||||
const chatId = toCusFormat(data.id);
|
||||
|
||||
// Group: iterate over per-participant states when available
|
||||
if (data.isGroup && data.participants?.length) {
|
||||
return {
|
||||
id: chatId,
|
||||
presences: data.participants.map((p) => ({
|
||||
participant: toCusFormat(p.id),
|
||||
lastKnownPresence: presenceStateToWAHA(p.state),
|
||||
lastSeen: null,
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
id: chatId,
|
||||
presences: [
|
||||
{
|
||||
participant: chatId,
|
||||
lastKnownPresence: presenceStateToWAHA(data.state, data.isOnline),
|
||||
lastSeen: null,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
export function WppParticipantsToGroupV2Participants(
|
||||
data: WppParticipantEvent,
|
||||
): GroupV2ParticipantsEvent | null {
|
||||
let type: GroupParticipantType;
|
||||
let role: GroupParticipantRole;
|
||||
|
||||
switch (data.action) {
|
||||
case WppParticipantAction.ADD:
|
||||
case WppParticipantAction.JOIN:
|
||||
type = GroupParticipantType.JOIN;
|
||||
role = GroupParticipantRole.PARTICIPANT;
|
||||
break;
|
||||
case WppParticipantAction.REMOVE:
|
||||
case WppParticipantAction.LEAVE as any:
|
||||
case WppParticipantAction.LEAVER:
|
||||
type = GroupParticipantType.LEAVE;
|
||||
role = GroupParticipantRole.LEFT;
|
||||
break;
|
||||
case WppParticipantAction.PROMOTE:
|
||||
type = GroupParticipantType.PROMOTE;
|
||||
role = GroupParticipantRole.ADMIN;
|
||||
break;
|
||||
case WppParticipantAction.DEMOTE:
|
||||
type = GroupParticipantType.DEMOTE;
|
||||
role = GroupParticipantRole.PARTICIPANT;
|
||||
break;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
||||
const group: GroupId = { id: toCusFormat(data.groupId) };
|
||||
const participants: GroupParticipant[] = data.who.map((id) => ({
|
||||
id: id,
|
||||
pn: null,
|
||||
role: role,
|
||||
}));
|
||||
|
||||
return {
|
||||
group: group,
|
||||
type: type,
|
||||
timestamp: Math.floor(Date.now() / 1000),
|
||||
participants: participants,
|
||||
_data: data,
|
||||
};
|
||||
}
|
||||
|
||||
export function WppParticipantsIsMyJoin(
|
||||
data: WppParticipantEvent,
|
||||
me: MeInfo | null | undefined,
|
||||
): boolean {
|
||||
const joinActions = [WppParticipantAction.ADD, WppParticipantAction.JOIN];
|
||||
if (!joinActions.includes(data.action as WppParticipantAction)) return false;
|
||||
return data.who.some((id) => id === me?.id || id === me?.lid);
|
||||
}
|
||||
|
||||
export function WppParticipantsIsMyLeave(
|
||||
data: WppParticipantEvent,
|
||||
me: MeInfo | null | undefined,
|
||||
): boolean {
|
||||
const leaveActions = [
|
||||
WppParticipantAction.REMOVE,
|
||||
WppParticipantAction.LEAVE,
|
||||
WppParticipantAction.LEAVER,
|
||||
];
|
||||
if (!leaveActions.includes(data.action as WppParticipantAction)) return false;
|
||||
return data.who.some((id) => id === me?.id || id === me?.lid);
|
||||
}
|
||||
|
||||
export function WppParticipantsToGroupV2Leave(
|
||||
data: WppParticipantEvent,
|
||||
): GroupV2LeaveEvent {
|
||||
return {
|
||||
timestamp: Math.floor(Date.now() / 1000),
|
||||
group: { id: toCusFormat(data.groupId) },
|
||||
_data: data,
|
||||
};
|
||||
}
|
||||
|
||||
export function WppReactionToMessageReaction(
|
||||
data: WppReactionEvent,
|
||||
): WAMessageReaction {
|
||||
// sender is present at runtime but absent from the installed package's type
|
||||
const raw = data as any;
|
||||
const sender: string =
|
||||
raw.sender?._serialized ?? raw.sender?.toString?.() ?? raw.sender ?? '';
|
||||
// remote is the chat ID (peer in DM, group JID in group chats)
|
||||
const remote = toCusFormat(raw.id?.remote ?? '');
|
||||
const isGroup = remote.includes('@g.us');
|
||||
// In DMs, the remote party IS the sender when sender field is missing
|
||||
const from = sender ? toCusFormat(sender) : isGroup ? '' : remote;
|
||||
const participant = sender ? toCusFormat(sender) : isGroup ? '' : remote;
|
||||
const id = SerializeMsgKey(data.id);
|
||||
const messageId = SerializeMsgKey(data.msgId);
|
||||
return {
|
||||
id: id,
|
||||
from: from,
|
||||
fromMe: raw.id?.fromMe ?? false,
|
||||
source: null,
|
||||
participant: participant,
|
||||
to: remote,
|
||||
timestamp: data.timestamp,
|
||||
reaction: {
|
||||
text: data.reactionText ?? '',
|
||||
messageId: messageId,
|
||||
},
|
||||
// @ts-ignore
|
||||
_data: data,
|
||||
};
|
||||
}
|
||||
|
||||
export function WppGp2ToGroupV2Update(msg: any): GroupV2UpdateEvent {
|
||||
const id = toCusFormat(msg.chatId);
|
||||
const group: Partial<Record<string, any>> = { id: id };
|
||||
if (msg.subtype === 'subject') {
|
||||
group.subject = msg.body;
|
||||
} else if (msg.subtype === 'description') {
|
||||
group.description = msg.body;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
timestamp: msg.timestamp,
|
||||
group: group as any,
|
||||
_data: msg,
|
||||
};
|
||||
}
|
||||
|
||||
export function WppUpdateLabelToAssociations(
|
||||
data: WppUpdateLabelEvent,
|
||||
): LabelChatAssociation[] {
|
||||
const chat = data.chat as any;
|
||||
const chatId: string = chat?.id?._serialized ?? chat?.id ?? chat ?? '';
|
||||
return data.ids.map((labelId, i) => ({
|
||||
labelId: labelId,
|
||||
label: (data.labels?.[i] ?? null) as any,
|
||||
chatId: toCusFormat(chatId),
|
||||
}));
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import { Observable } from 'rxjs';
|
||||
import { EnginePayload } from '@waha/structures/webhooks.dto';
|
||||
|
||||
/**
|
||||
* Wraps a WPPConnect `on*` listener method into a lazy RxJS Observable.
|
||||
* The listener is registered only when subscribed and disposed on teardown.
|
||||
*
|
||||
* @param event Event name label stored in EnginePayload, e.g. `'onMessage'`
|
||||
* @param fn Bound wppconnect listener invocation, e.g. `(cb) => wpp.onMessage(cb)`
|
||||
*/
|
||||
export function fromWppCallback(
|
||||
event: string,
|
||||
fn: (callback: (...args: any[]) => void) => { dispose: () => void },
|
||||
): Observable<EnginePayload> {
|
||||
return new Observable<EnginePayload>((subscriber) => {
|
||||
const { dispose } = fn((...args: any[]) => {
|
||||
// Multi-arg callbacks (e.g. onMessageEdit receives chat, id, msg) are
|
||||
// packed into an array so EnginePayload.data is always a single value.
|
||||
const data = args.length === 1 ? args[0] : args;
|
||||
subscriber.next({ event: event, data: data });
|
||||
});
|
||||
return () => dispose();
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
import { Observable, share } from 'rxjs';
|
||||
import { Whatsapp as WPPWhatsapp } from '@wppconnect-team/wppconnect';
|
||||
import { fromWppCallback } from '@waha/core/engines/wpp/reactive/fromWppCallback';
|
||||
import { EnginePayload } from '@waha/structures/webhooks.dto';
|
||||
|
||||
/**
|
||||
* One Observable per wppconnect on* listener method we subscribe to.
|
||||
* The compile-time guard below ensures every key exists on WPPWhatsapp:
|
||||
* if wppconnect removes a method TypeScript will error on that assertion.
|
||||
*/
|
||||
export interface WppStreams {
|
||||
onMessage: Observable<EnginePayload>;
|
||||
onAnyMessage: Observable<EnginePayload>;
|
||||
onAck: Observable<EnginePayload>;
|
||||
onMessageEdit: Observable<EnginePayload>;
|
||||
onNotificationMessage: Observable<EnginePayload>;
|
||||
onParticipantsChanged: Observable<EnginePayload>;
|
||||
onStateChange: Observable<EnginePayload>;
|
||||
onStreamChange: Observable<EnginePayload>;
|
||||
onIncomingCall: Observable<EnginePayload>;
|
||||
onInterfaceChange: Observable<EnginePayload>;
|
||||
onPresenceChanged: Observable<EnginePayload>;
|
||||
onLiveLocation: Observable<EnginePayload>;
|
||||
onAddedToGroup: Observable<EnginePayload>;
|
||||
onRevokedMessage: Observable<EnginePayload>;
|
||||
onReactionMessage: Observable<EnginePayload>;
|
||||
onPollResponse: Observable<EnginePayload>;
|
||||
onUpdateLabel: Observable<EnginePayload>;
|
||||
onOrderStatusUpdate: Observable<EnginePayload>;
|
||||
}
|
||||
|
||||
// Compile-time guard: every key of WppStreams must be a real method on WPPWhatsapp.
|
||||
// If wppconnect removes a listener method this line will produce a TypeScript error.
|
||||
type _AssertWppStreams = keyof WppStreams extends keyof WPPWhatsapp
|
||||
? true
|
||||
: never;
|
||||
export const _assertWppStreams: _AssertWppStreams = true;
|
||||
void _assertWppStreams;
|
||||
|
||||
/**
|
||||
* Builds a map of shared, lazy Observables — one per WPPConnect on* listener.
|
||||
*
|
||||
* Each Observable registers the underlying wppconnect callback only when
|
||||
* subscribed and disposes it on teardown. Using direct method calls
|
||||
* (rather than a generic loop) lets IDEs resolve types and navigate to
|
||||
* wppconnect's own declarations.
|
||||
*
|
||||
* WppStreams is the exhaustiveness guard: if a field is missing here TypeScript
|
||||
* will report a missing-property error on the object literal.
|
||||
*/
|
||||
export function buildWppStreams(wpp: WPPWhatsapp): WppStreams {
|
||||
return {
|
||||
onMessage: fromWppCallback('onMessage', (cb) => wpp.onMessage(cb)).pipe(
|
||||
share(),
|
||||
),
|
||||
|
||||
onAnyMessage: fromWppCallback('onAnyMessage', (cb) =>
|
||||
wpp.onAnyMessage(cb),
|
||||
).pipe(share()),
|
||||
|
||||
onAck: fromWppCallback('onAck', (cb) => wpp.onAck(cb)).pipe(share()),
|
||||
|
||||
onMessageEdit: fromWppCallback('onMessageEdit', (cb) =>
|
||||
wpp.onMessageEdit(cb),
|
||||
).pipe(share()),
|
||||
|
||||
onNotificationMessage: fromWppCallback('onNotificationMessage', (cb) =>
|
||||
wpp.onNotificationMessage(cb),
|
||||
).pipe(share()),
|
||||
|
||||
onParticipantsChanged: fromWppCallback('onParticipantsChanged', (cb) =>
|
||||
wpp.onParticipantsChanged(cb),
|
||||
).pipe(share()),
|
||||
|
||||
onStateChange: fromWppCallback('onStateChange', (cb) =>
|
||||
wpp.onStateChange(cb),
|
||||
).pipe(share()),
|
||||
|
||||
onStreamChange: fromWppCallback('onStreamChange', (cb) =>
|
||||
wpp.onStreamChange(cb),
|
||||
).pipe(share()),
|
||||
|
||||
onIncomingCall: fromWppCallback('onIncomingCall', (cb) =>
|
||||
wpp.onIncomingCall(cb),
|
||||
).pipe(share()),
|
||||
|
||||
onInterfaceChange: fromWppCallback('onInterfaceChange', (cb) =>
|
||||
wpp.onInterfaceChange(cb),
|
||||
).pipe(share()),
|
||||
|
||||
onPresenceChanged: fromWppCallback('onPresenceChanged', (cb) =>
|
||||
wpp.onPresenceChanged(cb),
|
||||
).pipe(share()),
|
||||
|
||||
onLiveLocation: fromWppCallback('onLiveLocation', (cb) =>
|
||||
wpp.onLiveLocation(cb),
|
||||
).pipe(share()),
|
||||
|
||||
onAddedToGroup: fromWppCallback('onAddedToGroup', (cb) =>
|
||||
wpp.onAddedToGroup(cb),
|
||||
).pipe(share()),
|
||||
|
||||
onRevokedMessage: fromWppCallback('onRevokedMessage', (cb) =>
|
||||
wpp.onRevokedMessage(cb),
|
||||
).pipe(share()),
|
||||
|
||||
onReactionMessage: fromWppCallback('onReactionMessage', (cb) =>
|
||||
wpp.onReactionMessage(cb),
|
||||
).pipe(share()),
|
||||
|
||||
onPollResponse: fromWppCallback('onPollResponse', (cb) =>
|
||||
wpp.onPollResponse(cb),
|
||||
).pipe(share()),
|
||||
|
||||
onUpdateLabel: fromWppCallback('onUpdateLabel', (cb) =>
|
||||
wpp.onUpdateLabel(cb),
|
||||
).pipe(share()),
|
||||
|
||||
onOrderStatusUpdate: fromWppCallback('onOrderStatusUpdate', (cb) =>
|
||||
wpp.onOrderStatusUpdate(cb),
|
||||
).pipe(share()),
|
||||
};
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -11,6 +11,7 @@ import {
|
||||
} from '@waha/apps/app_sdk/services/IAppsService';
|
||||
import { EngineBootstrap } from '@waha/core/abc/EngineBootstrap';
|
||||
import { GowsEngineConfigService } from '@waha/core/config/GowsEngineConfigService';
|
||||
import { WPPEngineConfigService } from '@waha/core/config/WPPEngineConfigService';
|
||||
import { WebJSEngineConfigService } from '@waha/core/config/WebJSEngineConfigService';
|
||||
import { WhatsappSessionGoWSCore } from '@waha/core/engines/gows/session.gows.core';
|
||||
import { WebhookConductor } from '@waha/core/integrations/webhooks/WebhookConductor';
|
||||
@@ -24,6 +25,7 @@ import { PinoLogger } from 'nestjs-pino';
|
||||
import { Observable, retry, share } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
|
||||
import { getNamespace, getSessionNamespace } from '../config';
|
||||
import { WhatsappConfigService } from '../config.service';
|
||||
import {
|
||||
WAHAEngine,
|
||||
@@ -42,6 +44,7 @@ import { populateSessionInfo, SessionManager } from './abc/manager.abc';
|
||||
import { SessionParams, WhatsappSession } from './abc/session.abc';
|
||||
import { EngineConfigService } from './config/EngineConfigService';
|
||||
import { WhatsappSessionNoWebCore } from './engines/noweb/session.noweb.core';
|
||||
import { WhatsappSessionWPPCore } from './engines/wpp/session.wpp.core';
|
||||
import { WhatsappSessionWebJSCore } from './engines/webjs/session.webjs.core';
|
||||
import { DOCS_URL } from './exceptions';
|
||||
import { getProxyConfig } from './helpers.proxy';
|
||||
@@ -84,6 +87,7 @@ export class SessionManagerCore extends SessionManager implements OnModuleInit {
|
||||
config: WhatsappConfigService,
|
||||
private engineConfigService: EngineConfigService,
|
||||
private webjsEngineConfigService: WebJSEngineConfigService,
|
||||
private wppEngineConfigService: WPPEngineConfigService,
|
||||
gowsConfigService: GowsEngineConfigService,
|
||||
log: PinoLogger,
|
||||
private mediaStorageFactory: MediaStorageFactory,
|
||||
@@ -104,7 +108,7 @@ export class SessionManagerCore extends SessionManager implements OnModuleInit {
|
||||
}),
|
||||
);
|
||||
|
||||
this.store = new LocalStoreCore(engineName.toLowerCase());
|
||||
this.store = new LocalStoreCore(getNamespace(), getSessionNamespace());
|
||||
this.sessionAuthRepository = new LocalSessionAuthRepository(this.store);
|
||||
this.clearStorage().catch((error) => {
|
||||
this.log.error({ error }, 'Error while clearing storage');
|
||||
@@ -114,6 +118,8 @@ export class SessionManagerCore extends SessionManager implements OnModuleInit {
|
||||
protected getEngine(engine: WAHAEngine): typeof WhatsappSession {
|
||||
if (engine === WAHAEngine.WEBJS) {
|
||||
return WhatsappSessionWebJSCore;
|
||||
} else if (engine === WAHAEngine.WPP) {
|
||||
return WhatsappSessionWPPCore;
|
||||
} else if (engine === WAHAEngine.NOWEB) {
|
||||
return WhatsappSessionNoWebCore;
|
||||
} else if (engine === WAHAEngine.GOWS) {
|
||||
@@ -206,6 +212,8 @@ export class SessionManagerCore extends SessionManager implements OnModuleInit {
|
||||
};
|
||||
if (this.EngineClass === WhatsappSessionWebJSCore) {
|
||||
sessionConfig.engineConfig = this.webjsEngineConfigService.getConfig();
|
||||
} else if (this.EngineClass === WhatsappSessionWPPCore) {
|
||||
sessionConfig.engineConfig = this.wppEngineConfigService.getConfig();
|
||||
} else if (this.EngineClass === WhatsappSessionGoWSCore) {
|
||||
sessionConfig.engineConfig = this.gowsConfigService.getConfig();
|
||||
}
|
||||
|
||||
@@ -10,15 +10,18 @@ export class LocalStoreCore extends LocalStore {
|
||||
protected readonly baseDirectory: string =
|
||||
process.env.WAHA_LOCAL_STORE_BASE_DIR || './.sessions';
|
||||
|
||||
private readonly engine: string;
|
||||
private readonly namespace: string;
|
||||
private readonly sessionNamespace: string;
|
||||
private knex: Knex.Knex;
|
||||
|
||||
constructor(engine: string) {
|
||||
constructor(namespace: string, sessionNamespace: string) {
|
||||
super();
|
||||
this.engine = engine;
|
||||
this.namespace = namespace;
|
||||
this.sessionNamespace = sessionNamespace;
|
||||
}
|
||||
|
||||
async init(sessionName?: string) {
|
||||
await fs.mkdir(this.getMainDirectory(), { recursive: true });
|
||||
await fs.mkdir(this.getEngineDirectory(), { recursive: true });
|
||||
if (!this.knex) {
|
||||
this.knex = this.buildKnex();
|
||||
@@ -40,11 +43,18 @@ export class LocalStoreCore extends LocalStore {
|
||||
return path.resolve(this.baseDirectory);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the directory where the main WAHA database (SQLite) is stored
|
||||
*/
|
||||
getMainDirectory() {
|
||||
return safeJoin(this.baseDirectory, this.namespace);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the directory where the engine sessions are stored
|
||||
*/
|
||||
getEngineDirectory() {
|
||||
return safeJoin(this.baseDirectory, this.engine);
|
||||
return safeJoin(this.baseDirectory, this.sessionNamespace);
|
||||
}
|
||||
|
||||
getSessionDirectory(name: string): string {
|
||||
@@ -67,8 +77,8 @@ export class LocalStoreCore extends LocalStore {
|
||||
}
|
||||
|
||||
buildKnex(): Knex.Knex {
|
||||
const engineDir = this.getEngineDirectory();
|
||||
const database = path.join(engineDir, 'waha.sqlite3');
|
||||
const mainDir = this.getMainDirectory();
|
||||
const database = path.join(mainDir, 'waha.sqlite3');
|
||||
return Knex({
|
||||
client: KNEX_SQLITE_CLIENT,
|
||||
connection: { filename: database },
|
||||
|
||||
@@ -210,8 +210,12 @@ export class SqlKVRepository<Entity> {
|
||||
/**
|
||||
* SQL helpers
|
||||
*/
|
||||
public getKnex(): Knex {
|
||||
return this.knex;
|
||||
}
|
||||
|
||||
public select() {
|
||||
return this.knex.select().from(this.table);
|
||||
return this.knex.select(`${this.table}.*`).from(this.table);
|
||||
}
|
||||
|
||||
protected delete() {
|
||||
@@ -219,7 +223,11 @@ export class SqlKVRepository<Entity> {
|
||||
}
|
||||
|
||||
public pagination(query: any, pagination?: PaginationParams) {
|
||||
const paginator = new this.Paginator(pagination, this.jsonQuery);
|
||||
const paginator = new this.Paginator(
|
||||
pagination,
|
||||
this.jsonQuery,
|
||||
this.table,
|
||||
);
|
||||
return paginator.apply(query);
|
||||
}
|
||||
|
||||
@@ -234,7 +242,7 @@ export class SqlKVRepository<Entity> {
|
||||
return JSON.stringify(data);
|
||||
}
|
||||
|
||||
protected parse(row: any) {
|
||||
public parse(row: any) {
|
||||
return JSON.parse(row.data);
|
||||
}
|
||||
|
||||
|
||||
+18
-1
@@ -1,4 +1,4 @@
|
||||
import { WAMessageAck } from '@waha/structures/enums.dto';
|
||||
import { WAMessageAck, WAMessageAckName } from '@waha/structures/enums.dto';
|
||||
|
||||
export function StatusToAck(status: number): WAMessageAck {
|
||||
return status - 1;
|
||||
@@ -7,3 +7,20 @@ export function StatusToAck(status: number): WAMessageAck {
|
||||
export function AckToStatus(ack: number): number {
|
||||
return ack + 1;
|
||||
}
|
||||
|
||||
export function StatusStringToStatus(status: number | string): number {
|
||||
switch (status) {
|
||||
case WAMessageAckName.ERROR:
|
||||
return AckToStatus(WAMessageAck.ERROR);
|
||||
case WAMessageAckName.PENDING:
|
||||
return AckToStatus(WAMessageAck.PENDING);
|
||||
case WAMessageAckName.SERVER:
|
||||
return AckToStatus(WAMessageAck.SERVER);
|
||||
case WAMessageAckName.READ:
|
||||
return AckToStatus(WAMessageAck.READ);
|
||||
case WAMessageAckName.PLAYED:
|
||||
return AckToStatus(WAMessageAck.PLAYED);
|
||||
default:
|
||||
return status as any;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import * as fs from 'fs-extra';
|
||||
import * as fsp from 'fs/promises';
|
||||
import * as path from 'path';
|
||||
|
||||
export async function removeSingletonFiles(dir: string): Promise<void> {
|
||||
const exists = await fs.pathExists(dir);
|
||||
if (!exists) {
|
||||
return;
|
||||
}
|
||||
const files = await fsp.readdir(dir);
|
||||
for (const file of files) {
|
||||
if (!file.startsWith('Singleton')) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
await fsp.rm(path.join(dir, file), { force: true });
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -57,6 +57,7 @@ export function MessagesForRead(
|
||||
offset: 0,
|
||||
limit: limit,
|
||||
downloadMedia: false,
|
||||
merge: true,
|
||||
};
|
||||
const afterMs = Date.now() - daysToMs(request.days);
|
||||
const after = Math.floor(afterMs / 1000);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { WAMessageKey } from '@adiwajshing/baileys';
|
||||
import { toJID } from '@waha/core/utils/jids';
|
||||
import { MessageId } from '@wppconnect-team/wppconnect';
|
||||
|
||||
/**
|
||||
* Parse message id from WAHA to engine
|
||||
@@ -38,3 +39,31 @@ export function SerializeMessageKey(key: WAMessageKey) {
|
||||
const participantStr = participant ? `_${participant}` : '';
|
||||
return `${fromMe ? 'true' : 'false'}_${remoteJid}_${id}${participantStr}`;
|
||||
}
|
||||
|
||||
export function SerializeMsgKey(key: string | MessageId) {
|
||||
if (typeof key == 'string') {
|
||||
return key;
|
||||
}
|
||||
if (key._serialized) {
|
||||
return key._serialized;
|
||||
}
|
||||
const k: WAMessageKey = {
|
||||
id: key.id,
|
||||
fromMe: key.fromMe,
|
||||
remoteJid: key.remote?._serialized || (key.remote as any),
|
||||
participant: (key as any).participant,
|
||||
};
|
||||
return SerializeMessageKey(k);
|
||||
}
|
||||
|
||||
export function Deserialized(
|
||||
value: string | { _serialized: string } | any,
|
||||
): string | null {
|
||||
if (typeof value == 'string') {
|
||||
return value;
|
||||
}
|
||||
if (value?._serialized) {
|
||||
return value._serialized;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
import { execSync } from 'child_process';
|
||||
import * as fs from 'fs';
|
||||
|
||||
interface Logger {
|
||||
debug: (obj: object, msg?: string) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds and kills all processes whose command line contains ALL of the given
|
||||
* patterns (AND logic). Never throws — errors are swallowed or logged.
|
||||
*/
|
||||
export async function killProcessesByPatterns(
|
||||
patterns: string[],
|
||||
signal: NodeJS.Signals = 'SIGKILL',
|
||||
logger?: Logger,
|
||||
): Promise<void> {
|
||||
try {
|
||||
const pids = findMatchingPids(patterns);
|
||||
for (const pid of pids) {
|
||||
try {
|
||||
killProcess(pid, signal);
|
||||
logger?.debug(
|
||||
{ pid: pid, patterns: patterns, signal: signal },
|
||||
'Sent signal to process',
|
||||
);
|
||||
} catch (err) {
|
||||
logger?.debug(
|
||||
{ pid: pid, err: err },
|
||||
'Failed to send signal to process',
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
logger?.debug({ err: err }, 'Error while killing processes');
|
||||
}
|
||||
}
|
||||
|
||||
function killProcess(pid: number, signal: NodeJS.Signals): void {
|
||||
if (process.platform === 'win32') {
|
||||
execSync(`taskkill /F /PID ${pid}`, { stdio: 'ignore' });
|
||||
} else {
|
||||
process.kill(pid, signal);
|
||||
}
|
||||
}
|
||||
|
||||
function matchesAll(cmdline: string, patterns: string[]): boolean {
|
||||
return patterns.every((p) => cmdline.includes(p));
|
||||
}
|
||||
|
||||
function findMatchingPids(patterns: string[]): number[] {
|
||||
switch (process.platform) {
|
||||
case 'linux':
|
||||
return findMatchingPidsLinux(patterns);
|
||||
case 'darwin':
|
||||
return findMatchingPidsDarwin(patterns);
|
||||
case 'win32':
|
||||
return findMatchingPidsWindows(patterns);
|
||||
default:
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
function findMatchingPidsLinux(patterns: string[]): number[] {
|
||||
const pids: number[] = [];
|
||||
let entries: string[];
|
||||
try {
|
||||
entries = fs.readdirSync('/proc');
|
||||
} catch {
|
||||
return pids;
|
||||
}
|
||||
for (const entry of entries) {
|
||||
const pid = parseInt(entry, 10);
|
||||
if (isNaN(pid)) continue;
|
||||
try {
|
||||
// /proc/$pid/cmdline args are null-separated on Linux
|
||||
const cmdline = fs
|
||||
.readFileSync(`/proc/${pid}/cmdline`, 'latin1')
|
||||
.split('\0')
|
||||
.join(' ');
|
||||
if (matchesAll(cmdline, patterns)) {
|
||||
pids.push(pid);
|
||||
}
|
||||
} catch {
|
||||
// process may have already exited
|
||||
}
|
||||
}
|
||||
return pids;
|
||||
}
|
||||
|
||||
function findMatchingPidsDarwin(patterns: string[]): number[] {
|
||||
const pids: number[] = [];
|
||||
try {
|
||||
const output = execSync('ps -eo pid= -o args=', { encoding: 'utf8' });
|
||||
for (const line of output.split('\n')) {
|
||||
const trimmed = line.trim();
|
||||
if (!trimmed) continue;
|
||||
const space = trimmed.indexOf(' ');
|
||||
if (space === -1) continue;
|
||||
const pid = parseInt(trimmed.slice(0, space), 10);
|
||||
const cmdline = trimmed.slice(space + 1);
|
||||
if (!isNaN(pid) && matchesAll(cmdline, patterns)) {
|
||||
pids.push(pid);
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// ps not available or failed
|
||||
}
|
||||
return pids;
|
||||
}
|
||||
|
||||
function findMatchingPidsWindows(patterns: string[]): number[] {
|
||||
const pids: number[] = [];
|
||||
try {
|
||||
// Use EncodedCommand to avoid shell quoting issues
|
||||
const script =
|
||||
'Get-CimInstance Win32_Process | ForEach-Object { ($_.ProcessId).ToString() + " " + $_.CommandLine }';
|
||||
const encoded = Buffer.from(script, 'utf16le').toString('base64');
|
||||
const output = execSync(
|
||||
`powershell -NoProfile -EncodedCommand ${encoded}`,
|
||||
{
|
||||
encoding: 'utf8',
|
||||
},
|
||||
);
|
||||
for (const line of output.split('\n')) {
|
||||
const trimmed = line.trim();
|
||||
if (!trimmed) continue;
|
||||
const space = trimmed.indexOf(' ');
|
||||
if (space === -1) continue;
|
||||
const pid = parseInt(trimmed.slice(0, space), 10);
|
||||
const cmdline = trimmed.slice(space + 1);
|
||||
if (!isNaN(pid) && matchesAll(cmdline, patterns)) {
|
||||
pids.push(pid);
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// powershell not available or failed
|
||||
}
|
||||
return pids;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import { parseVCardV3 } from './vcard';
|
||||
|
||||
describe('parseVCardV3', () => {
|
||||
it('parses vcard with waid', () => {
|
||||
const vcard =
|
||||
'BEGIN:VCARD\nVERSION:3.0\nFN:Jane Doe\nORG:Company Name;\nTEL;type=CELL;type=VOICE;waid=911111111111:+91 11111 11111\nEND:VCARD';
|
||||
const result = parseVCardV3(vcard);
|
||||
expect(result.fullName).toBe('Jane Doe');
|
||||
expect(result.phoneNumbers).toEqual(['+91 11111 11111']);
|
||||
expect(result.whatsappId).toBe('911111111111');
|
||||
});
|
||||
|
||||
it('parses vcard without waid', () => {
|
||||
const vcard =
|
||||
'BEGIN:VCARD\nVERSION:3.0\nFN:Jane Doe\nTEL;type=CELL;type=VOICE:+91 11111 11111\nEND:VCARD';
|
||||
const result = parseVCardV3(vcard);
|
||||
expect(result.fullName).toBe('Jane Doe');
|
||||
expect(result.phoneNumbers).toEqual(['+91 11111 11111']);
|
||||
expect(result.whatsappId).toBeNull();
|
||||
});
|
||||
});
|
||||
+15
-2
@@ -24,9 +24,14 @@ export function toVcardV3(data: Contact | VCardContact): string {
|
||||
return parts.join('\n');
|
||||
}
|
||||
|
||||
export function normalizePN(phoneNumber: string): string {
|
||||
return phoneNumber.replace(/[^0-9]/g, '');
|
||||
}
|
||||
|
||||
export interface SimpleVCardInfo {
|
||||
fullName: string;
|
||||
phoneNumbers: string[];
|
||||
whatsappId: string | null;
|
||||
}
|
||||
|
||||
export function parseVCardV3(vcardText: string): SimpleVCardInfo {
|
||||
@@ -46,14 +51,22 @@ export function parseVCardV3(vcardText: string): SimpleVCardInfo {
|
||||
}
|
||||
}
|
||||
|
||||
// Collect all TEL values
|
||||
// Collect all TEL values and waid parameter
|
||||
let waid: string | null = null;
|
||||
const phoneNumbers = comp
|
||||
.getAllProperties('tel')
|
||||
.map((p) => String(p.getFirstValue() ?? ''))
|
||||
.map((p) => {
|
||||
const w = p.getParameter('waid');
|
||||
if (w && !waid) {
|
||||
waid = String(w);
|
||||
}
|
||||
return String(p.getFirstValue() ?? '');
|
||||
})
|
||||
.filter((n) => n.length > 0);
|
||||
|
||||
return {
|
||||
fullName: fullName || '',
|
||||
phoneNumbers: phoneNumbers,
|
||||
whatsappId: waid,
|
||||
};
|
||||
}
|
||||
@@ -120,6 +120,17 @@ export class GetChatMessagesQuery extends PaginationParams {
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
downloadMedia: boolean = true;
|
||||
|
||||
@ApiProperty({
|
||||
example: true,
|
||||
required: false,
|
||||
description:
|
||||
'Merge LID (@lid) and phone-number (@c.us) chats referencing the same contact',
|
||||
})
|
||||
@Transform(BooleanString)
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
merge?: boolean = true;
|
||||
}
|
||||
|
||||
export class ReadChatMessagesQuery {
|
||||
@@ -161,6 +172,17 @@ export class GetChatMessageQuery {
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
downloadMedia: boolean = true;
|
||||
|
||||
@ApiProperty({
|
||||
example: true,
|
||||
required: false,
|
||||
description:
|
||||
'Merge LID (@lid) and phone-number (@c.us) chats referencing the same contact',
|
||||
})
|
||||
@Transform(BooleanString)
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
merge?: boolean = true;
|
||||
}
|
||||
|
||||
export enum ChatSortField {
|
||||
@@ -179,6 +201,19 @@ export class ChatsPaginationParams extends PaginationParams {
|
||||
sortBy?: string;
|
||||
}
|
||||
|
||||
export class GetChatsParams extends ChatsPaginationParams {
|
||||
@ApiProperty({
|
||||
example: true,
|
||||
required: false,
|
||||
description:
|
||||
'Merge LID (@lid) and phone-number (@c.us) chats referencing the same contact',
|
||||
})
|
||||
@Transform(BooleanString)
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
merge?: boolean = true;
|
||||
}
|
||||
|
||||
export enum PinDuration {
|
||||
DAY = 86400,
|
||||
WEEK = 604800,
|
||||
@@ -203,6 +238,19 @@ export class OverviewPaginationParams extends LimitOffsetParams {
|
||||
limit?: number = 20;
|
||||
}
|
||||
|
||||
export class GetChatsOverviewParams extends OverviewPaginationParams {
|
||||
@ApiProperty({
|
||||
example: true,
|
||||
required: false,
|
||||
description:
|
||||
'Merge LID (@lid) and phone-number (@c.us) chats referencing the same contact',
|
||||
})
|
||||
@Transform(BooleanString)
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
merge?: boolean = true;
|
||||
}
|
||||
|
||||
export class OverviewFilter {
|
||||
@IsOptional()
|
||||
@IsArray()
|
||||
@@ -218,8 +266,8 @@ export class OverviewFilter {
|
||||
|
||||
export class OverviewBodyRequest {
|
||||
@ValidateNested()
|
||||
@Type(() => OverviewPaginationParams)
|
||||
pagination: OverviewPaginationParams;
|
||||
@Type(() => GetChatsOverviewParams)
|
||||
pagination: GetChatsOverviewParams;
|
||||
|
||||
@ValidateNested()
|
||||
@Type(() => OverviewFilter)
|
||||
|
||||
@@ -227,7 +227,8 @@ export class LinkPreviewData {
|
||||
example: {
|
||||
url:
|
||||
process.env.WHATSAPP_SWAGGER_JPG_EXAMPLE_URL ||
|
||||
'https://github.com/devlikeapro/waha/raw/core/examples/waha.jpg',
|
||||
'https://picsum.photos/1024',
|
||||
// 'https://github.com/devlikeapro/waha/raw/core/examples/waha.jpg',
|
||||
},
|
||||
})
|
||||
image?: FileType;
|
||||
|
||||
@@ -51,6 +51,7 @@ export enum WAHASessionStatus {
|
||||
|
||||
export enum WAHAEngine {
|
||||
WEBJS = 'WEBJS',
|
||||
WPP = 'WPP',
|
||||
NOWEB = 'NOWEB',
|
||||
GOWS = 'GOWS',
|
||||
}
|
||||
|
||||
@@ -22,7 +22,8 @@ export class RemoteFile {
|
||||
@ApiProperty({
|
||||
example:
|
||||
process.env.WHATSAPP_SWAGGER_JPG_EXAMPLE_URL ||
|
||||
'https://github.com/devlikeapro/waha/raw/core/examples/waha.jpg',
|
||||
'https://picsum.photos/1024',
|
||||
// 'https://github.com/devlikeapro/waha/raw/core/examples/waha.jpg',
|
||||
})
|
||||
url: string;
|
||||
}
|
||||
@@ -130,7 +131,8 @@ export class FileURL {
|
||||
@ApiProperty({
|
||||
example:
|
||||
process.env.WHATSAPP_SWAGGER_JPG_EXAMPLE_URL ||
|
||||
'https://github.com/devlikeapro/waha/raw/core/examples/waha.jpg',
|
||||
'https://picsum.photos/1024',
|
||||
// 'https://github.com/devlikeapro/waha/raw/core/examples/waha.jpg',
|
||||
})
|
||||
url?: string;
|
||||
}
|
||||
|
||||
@@ -56,8 +56,12 @@ export class KnexPaginator extends Paginator {
|
||||
constructor(
|
||||
pagination: PaginationParams,
|
||||
protected jsonQuery: IJsonQuery,
|
||||
protected tableName?: string,
|
||||
) {
|
||||
super(pagination);
|
||||
if (tableName) {
|
||||
this.dataField = `${tableName}.data`;
|
||||
}
|
||||
}
|
||||
|
||||
protected sort(query: any) {
|
||||
|
||||
@@ -1,15 +1,20 @@
|
||||
import axios from 'axios';
|
||||
import { Agent as HttpsAgent } from 'https';
|
||||
import UserAgent from 'user-agents';
|
||||
|
||||
const InsecureHttpsAgent = new HttpsAgent({
|
||||
rejectUnauthorized: false,
|
||||
});
|
||||
|
||||
export async function fetchBuffer(url: string): Promise<Buffer> {
|
||||
const userAgent = new UserAgent();
|
||||
return axios
|
||||
.get(url, {
|
||||
responseType: 'arraybuffer',
|
||||
httpsAgent: InsecureHttpsAgent,
|
||||
headers: {
|
||||
'User-Agent': userAgent.toString(),
|
||||
},
|
||||
})
|
||||
.then((res) => {
|
||||
return Buffer.from(res.data);
|
||||
|
||||
@@ -0,0 +1,160 @@
|
||||
import { isPlainObject } from 'lodash';
|
||||
import type { Logger as PinoLogger } from 'pino';
|
||||
|
||||
type WinstonLogLevel =
|
||||
| 'error'
|
||||
| 'warn'
|
||||
| 'info'
|
||||
| 'http'
|
||||
| 'verbose'
|
||||
| 'debug'
|
||||
| 'silly';
|
||||
|
||||
type WinstonLogEntry = Record<string, unknown> & {
|
||||
level?: WinstonLogLevel | string;
|
||||
message?: unknown;
|
||||
};
|
||||
|
||||
type PinoLogMethod = 'error' | 'warn' | 'info' | 'debug' | 'trace';
|
||||
|
||||
// Wraps a Pino logger with a Winston-compatible interface.
|
||||
// WPPConnect expects a winston.Logger (which extends EventEmitter), but never
|
||||
// calls any EventEmitter methods — only log-level methods and .log(entry).
|
||||
// The cast in the call site (as unknown as Logger) is therefore safe.
|
||||
export class PinoWinstonAdapter {
|
||||
constructor(private readonly logger: PinoLogger) {}
|
||||
|
||||
log(
|
||||
levelOrEntry: string | WinstonLogEntry,
|
||||
message?: unknown,
|
||||
meta?: unknown,
|
||||
) {
|
||||
if (isPlainObject(levelOrEntry)) {
|
||||
const {
|
||||
level,
|
||||
message: entryMessage,
|
||||
...entryMeta
|
||||
} = levelOrEntry as WinstonLogEntry;
|
||||
this.write(level, entryMessage, entryMeta);
|
||||
return this;
|
||||
}
|
||||
this.write(levelOrEntry as string, message, meta);
|
||||
return this;
|
||||
}
|
||||
|
||||
error(message: unknown, ...meta: unknown[]) {
|
||||
this.write('error', message, meta[0]);
|
||||
return this;
|
||||
}
|
||||
|
||||
warn(message: unknown, ...meta: unknown[]) {
|
||||
this.write('warn', message, meta[0]);
|
||||
return this;
|
||||
}
|
||||
|
||||
info(message: unknown, ...meta: unknown[]) {
|
||||
this.write('info', message, meta[0]);
|
||||
return this;
|
||||
}
|
||||
|
||||
http(message: unknown, ...meta: unknown[]) {
|
||||
this.write('http', message, meta[0]);
|
||||
return this;
|
||||
}
|
||||
|
||||
verbose(message: unknown, ...meta: unknown[]) {
|
||||
this.write('verbose', message, meta[0]);
|
||||
return this;
|
||||
}
|
||||
|
||||
debug(message: unknown, ...meta: unknown[]) {
|
||||
this.write('debug', message, meta[0]);
|
||||
return this;
|
||||
}
|
||||
|
||||
silly(message: unknown, ...meta: unknown[]) {
|
||||
this.write('silly', message, meta[0]);
|
||||
return this;
|
||||
}
|
||||
|
||||
private write(level: string | undefined, message: unknown, meta?: unknown) {
|
||||
const payload = this.normalizeMeta(meta);
|
||||
const method = this.getPinoMethod(level);
|
||||
|
||||
if (message instanceof Error) {
|
||||
this.logWithMethod(method, payload, message.message, message);
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof message === 'string') {
|
||||
this.logWithMethod(method, payload, message);
|
||||
return;
|
||||
}
|
||||
|
||||
if (message !== undefined) {
|
||||
payload.message = message;
|
||||
}
|
||||
this.logWithMethod(method, payload, '');
|
||||
}
|
||||
|
||||
private getPinoMethod(level?: string): PinoLogMethod {
|
||||
switch (level) {
|
||||
case 'error':
|
||||
return 'error';
|
||||
case 'warn':
|
||||
return 'warn';
|
||||
case 'debug':
|
||||
case 'verbose':
|
||||
return 'debug';
|
||||
case 'silly':
|
||||
return 'trace';
|
||||
case 'http':
|
||||
case 'info':
|
||||
default:
|
||||
return 'info';
|
||||
}
|
||||
}
|
||||
|
||||
private normalizeMeta(meta: unknown): Record<string, unknown> {
|
||||
if (isPlainObject(meta)) {
|
||||
return { ...(meta as Record<string, unknown>) };
|
||||
}
|
||||
if (meta instanceof Error) {
|
||||
return { err: meta };
|
||||
}
|
||||
if (meta !== undefined && meta !== null) {
|
||||
return { meta };
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
private logWithMethod(
|
||||
method: PinoLogMethod,
|
||||
payload: Record<string, unknown>,
|
||||
message: string,
|
||||
error?: Error,
|
||||
) {
|
||||
switch (method) {
|
||||
case 'error':
|
||||
if (error) {
|
||||
this.logger.error({ ...payload, err: error }, message);
|
||||
} else {
|
||||
this.logger.error(payload, message);
|
||||
}
|
||||
return;
|
||||
case 'warn':
|
||||
this.logger.warn(payload, message);
|
||||
return;
|
||||
case 'debug':
|
||||
this.logger.debug(payload, message);
|
||||
return;
|
||||
case 'trace':
|
||||
this.logger.trace(payload, message);
|
||||
return;
|
||||
case 'info':
|
||||
default:
|
||||
this.logger.info(payload, message);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
-2
@@ -34,7 +34,8 @@ export function getWorker() {
|
||||
}
|
||||
|
||||
function getBrowser() {
|
||||
return getEngineName() === WAHAEngine.WEBJS
|
||||
return getEngineName() === WAHAEngine.WEBJS ||
|
||||
getEngineName() === WAHAEngine.WPP
|
||||
? getBrowserExecutablePath()
|
||||
: null;
|
||||
}
|
||||
@@ -44,7 +45,7 @@ function getPlatform() {
|
||||
}
|
||||
|
||||
export const VERSION: WAHAEnvironment = {
|
||||
version: '2026.2.1',
|
||||
version: '2026.3.1',
|
||||
engine: getEngineName(),
|
||||
tier: getWAHAVersion(),
|
||||
browser: getBrowser(),
|
||||
|
||||
+2
-2
@@ -2,11 +2,11 @@
|
||||
"waha": {
|
||||
"gows": {
|
||||
"repo": "devlikeapro/gows",
|
||||
"ref": "v1.0.32"
|
||||
"ref": "v1.0.34"
|
||||
},
|
||||
"dashboard": {
|
||||
"repo": "devlikeapro/dashboard",
|
||||
"ref": "0cf1cb04fcfc878aade2b3c4f85f25f10ed0384f"
|
||||
"ref": "24d3aca7113688eb2abaad9ff3716cdff540ae59"
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user