Compare commits

..
62 Commits
Author SHA1 Message Date
devlikepro 495857ee41 [core] 2026.4.2
Release / WEBJS - chrome - amd64 - chrome (push) Waiting to run
Release / WEBJS - chromium - amd64 - latest (push) Waiting to run
Release / WEBJS - chromium - linux/arm64 - arm (push) Waiting to run
Release / GOWS - none - amd64 - gows (push) Waiting to run
Release / GOWS - none - linux/arm64 - gows-arm (push) Waiting to run
Release / NOWEB - none - amd64 - noweb (push) Waiting to run
Release / NOWEB - none - linux/arm64 - noweb-arm (push) Waiting to run
2026-04-17 12:48:34 +07:00
devlikepro e04e55c4c5 [core] Up Dashboard 2026-04-17 12:48:34 +07:00
devlikepro 743e740860 [core] ChatWoot - allow "queue stop check.version"
fix #2025
2026-04-17 12:48:33 +07:00
devlikepro 65623ec1ea [core] ChatWoot - sort queues 2026-04-17 12:48:33 +07:00
devlikepro 72a301c579 [core] ChatWoot - split check.version and check.tier
fix #2025
2026-04-17 12:48:33 +07:00
devlikepro f35fd8d7c6 Revert "[core] ChatWoot: conditional agent name prefix in WA to WhatsApp templates (#1988)"
This reverts commit 1fb82c7678.
fix #2030 - [CHATWOOT] - Native Chatwoot integration sends agent name even when disabled (GOWS)
2026-04-17 12:48:33 +07:00
devlikepro 0594ca0709 [core] Rebuild sharp so it doesn't require SSE4.2 in CPU
fix #1952 -  WebAssembly.Module(): Compiling function #99 failed: Wasm SIMD unsupported @+25226
fix #1961 - Unsupported CPU: Prebuilt binaries for linux-x64 require v2 microarchitecture
2026-04-17 12:48:33 +07:00
devlikepro 4e5ebab97e [core] CPU and OS notes 2026-04-17 12:48:33 +07:00
devlikepro ba976138b7 [core] 2026.4.1
Release / WEBJS - chrome - amd64 - chrome (push) Waiting to run
Release / WEBJS - chromium - amd64 - latest (push) Waiting to run
Release / WEBJS - chromium - linux/arm64 - arm (push) Waiting to run
Release / GOWS - none - amd64 - gows (push) Waiting to run
Release / GOWS - none - linux/arm64 - gows-arm (push) Waiting to run
Release / NOWEB - none - amd64 - noweb (push) Waiting to run
Release / NOWEB - none - linux/arm64 - noweb-arm (push) Waiting to run
2026-04-15 13:05:24 +07:00
devlikepro bb3328f5a3 [core] Up WEBJS - fix window is not defined - fix #1990 2026-04-15 13:05:24 +07:00
devlikepro 6db12d7913 [core] Up Dashboard - Chat UI media, message history, show more chats in overview 2026-04-15 13:05:23 +07:00
devlikepro d968fa90f1 [core] Correct WHATSAPP_FILES_LIFETIME behavior
fix #2018 - setTimeout 32-bit overflow deletes files immediately when WHATSAPP_FILES_LIFETIME > ~24.8 days
closes #2019 - prevent setTimeout overflow for large WHATSAPP_FILES_LIFETIME values
2026-04-15 13:05:23 +07:00
devlikepro 06c874fea5 [core] GOWS - await grp response when starting a new session
fix #2012
2026-04-15 13:05:23 +07:00
Berg Pinheiro d2b7d94607 [core] Chatwoot - fix LID contact sending failure due to device part in JID (#42)
Normalize LID JIDs using normalizeJid() before passing to LidContactInfo
so that the device part (e.g. :11 in 183...977:11@lid) is stripped before
being persisted as WA_CHAT_ID in Chatwoot custom attributes.

Without this fix, GOWS rejects sendText calls with the error
"message recipient must be a user JID with no device part" because
FindChatID returns the raw LID (with :11) that was stored during the
incoming message flow.

* [PLUS] fix Chatwoot normalize LID device part on read to unblock existing contacts

Introduce normalizeChatId() helper in ids.ts that strips the device part
(e.g. :11) from LID JIDs using normalizeJid(). Apply it in FindChatID and
GetAllChatIDs so that contacts already persisted with a raw LID such as
183...977:11@lid are transparently normalized at read time.

This covers the send path for contacts created before the WhatsAppContactInfo
factory fix, preventing GOWS from rejecting sendText calls with
"message recipient must be a user JID with no device part".
2026-04-15 13:05:23 +07:00
devlikepro c8f067229b [core] WEBJS - ignore 2 days or older reactions
mention #494
2026-04-15 13:05:23 +07:00
devlikepro 798d782869 [core] Add replyTo.media if possible - all engines 2026-04-15 13:05:23 +07:00
devlikepro f70bcd0b53 [core] ChatWoot - use media from replyTo.media if provided 2026-04-15 13:05:22 +07:00
devlikepro 17a7764b97 [core] Up ChatWoot to 4.12.1 2026-04-15 13:05:22 +07:00
devlikepro 58affc9fe2 [core] Up NOWEB - proto to 2.3000.1035920091 2026-04-15 13:05:22 +07:00
devlikepro 3678cb4392 [core] WEBJS - fix loading messages - fix #2005, fix #2013 2026-04-15 13:05:22 +07:00
devlikepro 6eda96908f [core] Calls App - add wait before decline and response 2026-04-15 13:05:22 +07:00
devlikepro ddf161bb2b [core] Up WEBJS - fix call.received event - fix #2014 2026-04-15 13:05:22 +07:00
devlikepro 5659dc7296 [core] WEBJS - use webjs exposeFunctionIfAbsent 2026-04-15 13:05:22 +07:00
devlikepro bca90199f1 [core] Apps - allow app to be disabled when app disabled in runtime 2026-04-15 13:05:21 +07:00
devlikepro bd263b071c [core] WPP - type fix 2026-04-15 13:05:21 +07:00
devlikepro e54603085c [core] Up WPP 2026-04-15 13:05:21 +07:00
devlikepro 7f7857141e [core] make gows fix 2026-04-15 13:05:21 +07:00
devlikepro 3b19dc3a61 [core] autodetect os and browser 2026-04-15 13:05:21 +07:00
devlikepro 06e5951c68 [core] links, commands 2026-04-15 13:05:21 +07:00
devlikepro bcf4ed07d8 [core] format .yarnrc.yml 2026-04-15 13:05:21 +07:00
Berg Pinheiro f711fcca98 [core] add Chatwoot support for WhatsApp status replies
Detect WhatsApp status replies in incoming messages, append localized status context to Chatwoot content, and attach quoted status media when available while preserving current non-status behavior.

closes #1995 - Reply message to WhatsApp Status Context in Chatwoot Messages
fixes #1991 - Include WhatsApp Status Context in Chatwoot Messages
2026-04-15 13:05:20 +07:00
devlikepro 32e57cab98 [core] NOWEB - fallback to message id if no found by jid and id 2026-04-15 13:05:20 +07:00
devlikepro b092d2bfe1 [core] WEBJS - try both fromMe: true|false for dm chat when getting a message by id 2026-04-15 13:05:20 +07:00
devlikepro f655f715d0 [core] WEBJS - treat as "from me" messages when requesting status messages by id 2026-04-15 13:05:20 +07:00
devlikepro e5c77c60f2 [core] WEBJS - populate replyTo with more fields 2026-04-15 13:05:20 +07:00
devlikepro a38d4145c2 [core] Increase timeout for media resolving to ~12s total 2026-04-15 13:05:20 +07:00
devlikepro 2c47b784b3 [core] Up GOWS
fix #1998 - GOWS - Messages sent via POST /api/sendFile not returned by GET messages API
fix #2009 - GOWS - /chats/overview "message": "2 UNKNOWN: no such column: jid"
2026-04-15 13:05:20 +07:00
devlikepro 724dc61717 [core] Up Dashboard - handle empty sender name for automated messages - fix #1983 2026-04-15 13:05:20 +07:00
devlikepro 9c44256209 [core] make up-dashboard 2026-04-15 13:05:20 +07:00
Berg Pinheiro 1fb82c7678 [core] ChatWoot: conditional agent name prefix in WA to WhatsApp templates (#1988)
Fix empty **: prefix when Add Agent Name is on but chatwoot.sender.name is empty (e.g. system/automated messages). Applies Mustache conditionals to default i18n for chatwoot.to.whatsapp.message.text and chatwoot.to.whatsapp.message.media.caption across all locales (fixes devlikeapro/waha#1983).

fixes #1983
fixes #1737
2026-04-06 09:54:18 +07:00
Daniel (Danai) Rudaev 55f7d4dac7 [core] NOWEB - use stream mode for media download to fix 0-byte audio files (#1997)
The 'buffer' mode in Baileys' downloadMediaMessage() silently returns
an empty buffer for audio/voice messages (PTT, OGG Opus), while images
download correctly. Switching to 'stream' mode and collecting chunks
manually resolves the issue.

Tested in production (WAHA Plus 2026.3.4, NOWEB engine, S3 storage):
- Before: voice messages saved as 0 bytes in S3
- After: voice messages saved with correct size (58,005 bytes confirmed)

Fixes #1996
2026-04-06 09:51:28 +07:00
Ali Kemal Özdemir 10e8869903 [core] exclude /jobs from global swagger basic auth (#1981) (fix #1679) 2026-03-27 18:46:20 +07:00
devlikepro 554d2bc6bc [core] 2026.3.4
Release / WEBJS - chrome - amd64 - chrome (push) Waiting to run
Release / WEBJS - chromium - amd64 - latest (push) Waiting to run
Release / WEBJS - chromium - linux/arm64 - arm (push) Waiting to run
Release / GOWS - none - amd64 - gows (push) Waiting to run
Release / GOWS - none - linux/arm64 - gows-arm (push) Waiting to run
Release / NOWEB - none - amd64 - noweb (push) Waiting to run
Release / NOWEB - none - linux/arm64 - noweb-arm (push) Waiting to run
2026-03-27 18:39:04 +07:00
devlikepro 99aabb0957 Merge remote-tracking branch 'core/core' into core
Release / WEBJS - chromium - amd64 - latest (push) Waiting to run
Release / WEBJS - chromium - linux/arm64 - arm (push) Waiting to run
Release / GOWS - none - amd64 - gows (push) Waiting to run
Release / GOWS - none - linux/arm64 - gows-arm (push) Waiting to run
Release / NOWEB - none - amd64 - noweb (push) Waiting to run
Release / WEBJS - chrome - amd64 - chrome (push) Waiting to run
Release / NOWEB - none - linux/arm64 - noweb-arm (push) Waiting to run
2026-03-25 10:27:41 +07:00
devlikepro 055b014c41 [core] 2026.3.3 2026-03-25 10:24:58 +07:00
devlikepro 172676ae6f [core] Up NOWEB 2026-03-25 10:24:58 +07:00
devlikepro 0056951485 [core] Add view once messages in "message" event
fix #1972
2026-03-25 10:24:57 +07:00
devlikepro 8c91792473 [core] Up WPP 2026-03-25 10:24:57 +07:00
devlikepro a26e89a2b7 [core] GOWS v1.0.36
- Add image sizes - fix #1402, fix #901
- Add WAHA_GOWS_DEVICE_* env variables - configuration to limit historical message sync depth  fix #1963
2026-03-25 10:24:57 +07:00
devlikepro 7674c4ca67 [core] GOWS - add displayName to sendContactVcard
fix #1978
2026-03-25 10:24:57 +07:00
devlikepro cc7c11e2fd [core] GOWS - no device part
fix #1977
2026-03-25 10:24:56 +07:00
devlikepro 9f3c28b6ac [core] Fix "Cannot read properties of undefined (reading 'isAdmin')"
fix #1969
2026-03-25 10:24:56 +07:00
devlikepro 3ca9545a58 [core] Up WEBJS - fix profile picture and channels list
fix #1707
fix #1947
fix #1959
2026-03-25 10:24:56 +07:00
devlikepro 4d08cffeab [core] NOWEB - optimization for CPU spikes (lid/c.us merge)
fix #1955
2026-03-25 10:24:56 +07:00
devlikepro 241340e0a0 [core] GOWS v1.0.35
GOWS - fix CPU Spikes in PostgreSQL - fix #1955
2026-03-25 10:24:56 +07:00
devlikepro 556f0fa775 [core] yarn - support all cpu/os in supportedArchitectures
"Unsupported CPU: Prebuilt binaries for linux-x64 require v2 microarchitecture" - fix #1952
2026-03-25 10:24:56 +07:00
devlikepro 975cf2f580 [core] Up "sharp" 2026-03-25 10:24:55 +07:00
Berg Pinheiro 90795a7eaf [core] Fix BR landline normalization in PhoneJidNormalizer (#1976)
Prevent unconditional insertion of digit 9 for Brazilian numbers with 8-digit local parts by preserving landline patterns (2..5) and keeping add-9 behavior for mobile/other cases.

fix #1974
2026-03-25 10:24:45 +07:00
Berg Pinheiro e64b60ef3a [core] Fix BR landline normalization in PhoneJidNormalizer (#1976)
Prevent unconditional insertion of digit 9 for Brazilian numbers with 8-digit local parts by preserving landline patterns (2..5) and keeping add-9 behavior for mobile/other cases.

fix #1974
2026-03-23 11:30:03 +07:00
devlikepro 03e9588581 [core] 2026.3.2
Release / WEBJS - chrome - amd64 - chrome (push) Waiting to run
Release / WEBJS - chromium - amd64 - latest (push) Waiting to run
Release / WEBJS - chromium - linux/arm64 - arm (push) Waiting to run
Release / GOWS - none - amd64 - gows (push) Waiting to run
Release / GOWS - none - linux/arm64 - gows-arm (push) Waiting to run
Release / NOWEB - none - amd64 - noweb (push) Waiting to run
Release / NOWEB - none - linux/arm64 - noweb-arm (push) Waiting to run
2026-03-05 17:12:33 +07:00
devlikepro 393f5ef7da [core] Fix "user_agents_1.default is not a constructor" when sending media 2026-03-05 17:12:33 +07:00
devlikepro f778678514 [core] do not fail fast 2026-03-05 17:12:32 +07:00
73 changed files with 1333 additions and 531 deletions

No files matched your search

+3 -1
View File
@@ -7,7 +7,9 @@
"Bash(npm show:*)",
"Bash(python3:*)",
"Bash(yarn test:unit:*)",
"Bash(npx tsc:*)"
"Bash(npx tsc:*)",
"Bash(pre-commit run:*)",
"Bash(yarn test:*)"
]
}
}
+1
View File
@@ -12,6 +12,7 @@ jobs:
${{ matrix.engine }} - ${{ matrix.browser }} - ${{ matrix.platform }} -
${{ matrix.tag }}
strategy:
fail-fast: false
matrix:
include:
# Chromium - x86
+20
View File
@@ -3,3 +3,23 @@ compressionLevel: mixed
enableGlobalCache: false
nodeLinker: node-modules
supportedArchitectures:
cpu:
- arm
- arm64
- ia32
- loong64
- mips
- mipsel
- ppc64
- riscv64
- s390
- s390x
- x64
- wasm32
os:
- current
- darwin
- linux
- win32
+5 -2
View File
@@ -37,6 +37,10 @@ This guide summarizes how to explore, modify, and validate the WhatsApp HTTP API
- **Utilities**: RxJS streams (`SwitchObservable`, `DefaultMap`) drive webhook
event fan-out. Prefer existing helpers in `src/utils` and `src/core/utils`
before adding bespoke logic.
- **OS** - the project works on any OS inside the Docker container; the base
image is defined in `Dockerfile`.
- **CPU** - the image must run on both `x86_64` (including pre-v2 CPUs without
SSE4.2) and `aarch64`.
## Repository Landmarks
@@ -111,8 +115,7 @@ 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:
- Do **not** run `pre-commit run --all-files` unless the user explicitly
asks.
- `pre-commit run --all-files`
- `yarn build`
- `yarn test --watchman=false`
4. Do **not** start the application yourself; ask the user to run it if runtime
-1
View File
@@ -1 +0,0 @@
- Read `AGENTS.md` for more instructions
Symlink
+1
View File
@@ -0,0 +1 @@
AGENTS.md
+15
View File
@@ -28,6 +28,21 @@ ADD . /git
RUN yarn install
RUN yarn build && find ./dist -name "*.d.ts" -delete
# Rebuild sharp from source on x86-64 so it runs on pre-v2 CPUs (no SSE4.2 requirement).
# The prebuilt @img/sharp-linux-x64 binary requires x86-64-v2; -march=x86-64 limits
# code generation to baseline x86-64 (SSE2 only). ARM64 prebuilts have no such
# constraint, so no rebuild is needed there.
# We call sharp's own install/build.js (not npm rebuild) so it first generates config.gypi
# with include/lib paths pointing to its bundled @img/sharp-libvips-linux-x64 package,
# then invokes node-gyp with those paths. Without this step node-gyp cannot find vips headers.
RUN if [ "$(uname -m)" = "x86_64" ]; then \
LIBVIPS_DEV_VER=$(node -e "require('/git/node_modules/sharp/package.json').devDependencies['@img/sharp-libvips-dev']" | tr -d '^') && \
npm install --prefix /tmp/sharp-dev --no-package-lock "@img/sharp-libvips-dev@${LIBVIPS_DEV_VER}" && \
cp -r /tmp/sharp-dev/node_modules/@img/sharp-libvips-dev /git/node_modules/@img/ && \
cd /git/node_modules/sharp && \
PATH="/git/node_modules/.bin:$PATH" CFLAGS="-march=x86-64" CXXFLAGS="-march=x86-64" node install/build.js; \
fi
#
# Dashboard
#
+5 -1
View File
@@ -45,6 +45,7 @@ up-webjs:
up-wpp:
yarn up @wppconnect-team/wppconnect
yarn up @wppconnect/wa-js
up-rust-bridge:
yarn up -R whatsapp-rust-bridge
@@ -57,9 +58,12 @@ proto-gows:
gows:
cd ../gows && \
export PATH=${HOME}/go/bin:${PATH} && \
(export PATH=${HOME}/go/bin:${PATH} || echo failed) && \
make all
up-dashboard:
node scripts/up-dashboard.js
copy-dashboard:
cd ../waha-hub/ui && \
make copy-waha
+1 -1
View File
@@ -24,7 +24,7 @@ services:
- 8.8.8.8
base: &base
image: chatwoot/chatwoot:v4.7.0
image: chatwoot/chatwoot:v4.12.1
env_file: .chatwoot.env ## Change this file for customized env variables
volumes:
- chatwoot_storage:/app/storage
+3 -2
View File
@@ -57,7 +57,8 @@
"@types/passport": "^1.0.17",
"@types/sqlite3": "^5.1.0",
"@types/ws": "^8.5.4",
"@wppconnect-team/wppconnect": "^1.40.1",
"@wppconnect-team/wppconnect": "^1.41.1",
"@wppconnect/wa-js": "^3.23.3",
"adm-zip": "0.5.10",
"agentkeepalive": "^4.5.0",
"async-lock": "^1.4.1",
@@ -105,7 +106,7 @@
"reflect-metadata": "^0.1.13",
"rimraf": "^4.3.0",
"rxjs": "^7.8.1",
"sharp": "^0.33.4",
"sharp": "^0.34.5",
"shell-quote": "^1.8.3",
"sqlite3": "^5.1.7",
"swagger-ui-express": "^4.1.4",
+34
View File
@@ -0,0 +1,34 @@
#!/usr/bin/env node
'use strict';
// eslint-disable-next-line @typescript-eslint/no-var-requires
const fs = require('fs');
// eslint-disable-next-line @typescript-eslint/no-var-requires
const { execSync } = require('child_process');
const CONFIG_FILE = 'waha.config.json';
const config = JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8'));
const { repo } = config.waha.dashboard;
if (!repo) {
console.error(`Missing dashboard.repo in ${CONFIG_FILE}`);
process.exit(1);
}
const branch = 'gh-pages';
console.log(`Fetching latest commit for ${repo}@${branch}...`);
const output = execSync(
`git ls-remote https://github.com/${repo} refs/heads/${branch}`,
{ encoding: 'utf8' },
);
const sha = output.split('\t')[0].trim();
if (!sha) {
console.error(`Could not resolve ref ${branch} for ${repo}`);
process.exit(1);
}
config.waha.dashboard.ref = sha;
fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2) + '\n', 'utf8');
console.log(`Updated ${CONFIG_FILE}: dashboard.ref = ${sha}`);
+1 -1
View File
@@ -81,7 +81,7 @@ class SessionsController {
@Req() req,
): Promise<SessionInfo[]> {
let sessions = await this.manager.getSessions(query.all);
if (!req.user.isAdmin) {
if (!req.user?.isAdmin) {
sessions = FilterSessions(req.ability, Action.Read, sessions);
}
if (query.expand?.includes(SessionExpand.apps)) {
@@ -90,10 +90,10 @@ export class AppsEnabledService implements IAppsService {
}
const service = this.getAppService(app);
if (!service && !AppRuntimeConfig.HasApp(app.app)) {
if (app.enabled && !service && !AppRuntimeConfig.HasApp(app.app)) {
throw new AppDisableError(app.app);
}
service.validate(app);
service?.validate(app);
// Only run beforeCreated when app is enabled (default true if omitted)
if (app.enabled !== false) {
await service.beforeCreated(app);
@@ -147,21 +147,21 @@ export class AppsEnabledService implements IAppsService {
}
const service = this.getAppService(app);
if (!service && !AppRuntimeConfig.HasApp(app.app)) {
if (app.enabled && !service && !AppRuntimeConfig.HasApp(app.app)) {
throw new AppDisableError(app.app);
}
service.validate(app);
service?.validate(app);
const hasEnabledChange = savedApp.enabled !== app.enabled;
if (hasEnabledChange) {
if (app.enabled) {
await service.beforeEnabled(savedApp, app);
await service?.beforeEnabled(savedApp, app);
} else {
await service.beforeDisabled(savedApp, app);
await service?.beforeDisabled(savedApp, app);
}
} else {
await service.beforeUpdated(savedApp, app);
await service?.beforeUpdated(savedApp, app);
}
await repo.update(app.id, app);
const updated = await repo.getById(app.id);
+22
View File
@@ -2,8 +2,10 @@ import { ApiProperty } from '@nestjs/swagger';
import { Type } from 'class-transformer';
import {
IsBoolean,
IsNumber,
IsOptional,
IsString,
Min,
ValidateNested,
} from 'class-validator';
@@ -23,6 +25,26 @@ export class CallsAppChannelConfig {
@IsOptional()
@IsString()
message?: string;
@ApiProperty({
description:
'Seconds to wait before declining the call. If not set or undefined, the call is declined immediately (0 seconds).',
required: false,
})
@IsOptional()
@IsNumber()
@Min(0)
waitBeforeDecline?: number;
@ApiProperty({
description:
'Seconds to wait before sending the auto-reply message. If not set or undefined, the message is sent immediately (0 seconds).',
required: false,
})
@IsOptional()
@IsNumber()
@Min(0)
waitBeforeResponse?: number;
}
export class CallsAppConfig {
+4
View File
@@ -92,10 +92,14 @@ export class CallsListener {
}
if (shouldReject) {
const waitBeforeDeclineMs = (config.waitBeforeDecline ?? 0) * 1000;
await sleep(waitBeforeDeclineMs);
await this.rejectCall(call);
}
if (shouldMessage) {
const waitBeforeResponseMs = (config.waitBeforeResponse ?? 0) * 1000;
await sleep(waitBeforeResponseMs);
await this.replyWithTyping(call.from, message);
}
}
+6
View File
@@ -6,6 +6,7 @@ import {
NoRetriesJobOptions,
} from '@waha/apps/app_sdk/constants';
import { MessageCleanupConsumer } from '@waha/apps/chatwoot/consumers/scheduled/message.cleanup';
import { CheckTierConsumer } from '@waha/apps/chatwoot/consumers/scheduled/check.tier';
import { ChatWootAppService } from '@waha/apps/chatwoot/services/ChatWootAppService';
import * as lodash from 'lodash';
@@ -51,6 +52,10 @@ const IMPORTS = lodash.flatten([
name: QueueName.SCHEDULED_CHECK_VERSION,
defaultJobOptions: merge(NoRetriesJobOptions, JobRemoveOptions),
}),
RegisterAppQueue({
name: QueueName.SCHEDULED_CHECK_TIER,
defaultJobOptions: merge(NoRetriesJobOptions, JobRemoveOptions),
}),
RegisterAppQueue({
name: QueueName.TASK_CONTACTS_PULL,
defaultJobOptions: merge(ExponentialRetriesJobOptions, JobRemoveOptions),
@@ -145,6 +150,7 @@ const PROVIDERS = [
// Scheduled
MessageCleanupConsumer,
CheckVersionConsumer,
CheckTierConsumer,
// Services
ChatWootWAHAQueueService,
ChatWootQueueService,
+12 -2
View File
@@ -20,8 +20,18 @@ export async function QueueStatus(ctx: QueueCommandContext, name: string) {
for (const status of result) {
status.name = QueueNameRepr(status.name);
}
// locked: true - last
result = lodash.sortBy(result, [(x) => !!x.locked, 'name']);
const categoryOrder = ['inbox', 'whatsapp', 'task', 'scheduled'];
const categoryIndex = (name: string) => {
const category = name.split(' | ')[0];
const index = categoryOrder.indexOf(category);
return index === -1 ? categoryOrder.length : index;
};
// locked: true - last, then by category (inbox/whatsapp/task/scheduled), then by name
result = lodash.sortBy(result, [
(x) => !!x.locked,
(x) => categoryIndex(x.name),
'name',
]);
const msg = ctx.l.r('cli.cmd.queue.status.result', {
queues: result,
});
+3
View File
@@ -17,6 +17,9 @@ export async function runText(
.map((line) => line.trimStart()) // remove indentation
.join(' ') // join with space
.trim(); // final cleanup
// Strip WhatsApp/markdown formatting characters so shell-quote doesn't
// treat them as glob operators and return non-string tokens
text = text.replace(/[*_~`]/g, '');
const output = new BufferedOutput();
const program = BuildProgram(commands, ctx, output);
const argv = parse(text);
+5 -12
View File
@@ -2,7 +2,7 @@ import { AttributeKey, INBOX_CONTACT_CHAT_ID } from '@waha/apps/chatwoot/const';
import * as lodash from 'lodash';
import { WhatsAppMessage } from '@waha/apps/chatwoot/storage';
import { buildMessageId } from '@waha/core/engines/noweb/session.noweb.core';
import { isLidUser } from '@waha/core/utils/jids';
import { isLidUser, normalizeJid } from '@waha/core/utils/jids';
export function GetJID(contact: any): string | null {
return contact?.custom_attributes?.[AttributeKey.WA_JID];
@@ -23,20 +23,13 @@ export function GetAllChatIDs(contact: any): Array<string> {
attrs[AttributeKey.WA_JID],
attrs[AttributeKey.WA_LID],
];
return lodash.uniq(ids.filter(Boolean));
return lodash.uniq(ids.filter(Boolean).map(normalizeJid));
}
export function FindChatID(contact: any): string | null {
if (GetChatID(contact)) {
return GetChatID(contact);
}
if (GetJID(contact)) {
return GetJID(contact);
}
if (GetLID(contact)) {
return GetLID(contact);
}
return null;
const chatId =
GetChatID(contact) ?? GetJID(contact) ?? GetLID(contact) ?? null;
return chatId ? normalizeJid(chatId) : null;
}
export function SerializeWhatsAppKey(message: WhatsAppMessage): string {
+1
View File
@@ -4,6 +4,7 @@ export enum QueueName {
//
SCHEDULED_MESSAGE_CLEANUP = 'chatwoot.scheduled | message.cleanup',
SCHEDULED_CHECK_VERSION = 'chatwoot.scheduled | check.version',
SCHEDULED_CHECK_TIER = 'chatwoot.scheduled | check.tier',
//
// Task
@@ -0,0 +1,42 @@
import { Processor } from '@nestjs/bullmq';
import { Injectable } from '@nestjs/common';
import { JOB_CONCURRENCY } from '@waha/apps/app_sdk/constants';
import { QueueName } from '@waha/apps/chatwoot/consumers/QueueName';
import { DIContainer } from '@waha/apps/chatwoot/di/DIContainer';
import { SessionManager } from '@waha/core/abc/manager.abc';
import { SUPPORT_US_URL } from '@waha/core/constants';
import { RMutexService } from '@waha/modules/rmutex/rmutex.service';
import { VERSION, WAHAVersion } from '@waha/version';
import { Job } from 'bullmq';
import { PinoLogger } from 'nestjs-pino';
import { TKey } from '@waha/apps/chatwoot/i18n/templates';
import { ChatWootScheduledConsumer } from './base';
@Processor(QueueName.SCHEDULED_CHECK_TIER, { concurrency: JOB_CONCURRENCY })
export class CheckTierConsumer extends ChatWootScheduledConsumer {
constructor(manager: SessionManager, log: PinoLogger, rmutex: RMutexService) {
super(manager, log, rmutex, CheckTierConsumer.name);
}
protected ErrorHeaderKey(): TKey {
return TKey.JOB_SCHEDULED_ERROR_HEADER;
}
protected async Process(container: DIContainer, job: Job): Promise<any> {
const logger = container.Logger();
const locale = container.Locale();
const conversation = await container
.ContactConversationService()
.InboxNotifications();
if (VERSION.tier !== WAHAVersion.CORE) {
logger.info('WAHA is not using the CORE version');
return;
}
logger.info('WAHA is using the CORE version');
const supportMessage = locale.key(TKey.WAHA_CORE_VERSION_USED).render({
supportUrl: SUPPORT_US_URL,
});
await conversation.incoming(supportMessage);
}
}
@@ -1,24 +1,19 @@
import { Processor } from '@nestjs/bullmq';
import { Injectable } from '@nestjs/common';
import { JOB_CONCURRENCY } from '@waha/apps/app_sdk/constants';
import { ILogger } from '@waha/apps/app_sdk/ILogger';
import { QueueName } from '@waha/apps/chatwoot/consumers/QueueName';
import { DIContainer } from '@waha/apps/chatwoot/di/DIContainer';
import { SessionManager } from '@waha/core/abc/manager.abc';
import { CHANGELOG_URL, SUPPORT_US_URL } from '@waha/core/constants';
import { CHANGELOG_URL } from '@waha/core/constants';
import { RMutexService } from '@waha/modules/rmutex/rmutex.service';
import { VERSION, WAHAVersion } from '@waha/version';
import { VERSION } from '@waha/version';
import axios from 'axios';
import { Job } from 'bullmq';
import { PinoLogger } from 'nestjs-pino';
import { ChatWootScheduledConsumer } from './base';
import { TKey } from '@waha/apps/chatwoot/i18n/templates';
import { ChatWootScheduledConsumer } from './base';
/**
* Scheduled consumer for version checking
* Periodically checks for new versions and notifies if updates are available
*/
@Processor(QueueName.SCHEDULED_CHECK_VERSION, { concurrency: JOB_CONCURRENCY })
export class CheckVersionConsumer extends ChatWootScheduledConsumer {
constructor(manager: SessionManager, log: PinoLogger, rmutex: RMutexService) {
@@ -29,34 +24,7 @@ export class CheckVersionConsumer extends ChatWootScheduledConsumer {
return TKey.JOB_SCHEDULED_ERROR_HEADER;
}
/**
* Process the version check job
* Checks for new versions and notifies if updates are available
*/
protected async Process(container: DIContainer, job: Job): Promise<any> {
await this.CheckWAHACoreVersion(container);
await this.CheckNewVersionAvailable(container);
}
private async CheckWAHACoreVersion(container: DIContainer) {
const logger = container.Logger();
const locale = container.Locale();
const conversation = await container
.ContactConversationService()
.InboxNotifications();
// If using the CORE version, send a message encouraging support
if (VERSION.tier !== WAHAVersion.CORE) {
logger.info('WAHA is not using the CORE version');
return;
}
logger.info('WAHA is using the CORE version');
const supportMessage = locale.key(TKey.WAHA_CORE_VERSION_USED).render({
supportUrl: SUPPORT_US_URL,
});
await conversation.incoming(supportMessage);
}
private async CheckNewVersionAvailable(container: DIContainer) {
const logger = container.Logger();
logger.info('Processing version check job');
const currentVersion = VERSION.version;
@@ -72,7 +40,6 @@ export class CheckVersionConsumer extends ChatWootScheduledConsumer {
`New version available: ${latestVersion} (current: ${currentVersion})`,
);
// Send a message to Chatwoot conversation about a new version
const locale = container.Locale();
const message = locale.key(TKey.WAHA_NEW_VERSION_AVAILABLE).render({
currentVersion: currentVersion,
@@ -212,7 +212,11 @@ class MessagesPullHandler {
// Perform some actions before any job started
if (options.pause) {
await this.queueManager.pause();
const queues = [
...this.queueManager.resolve('inbox'),
...this.queueManager.resolve('waha'),
];
await this.queueManager.pause(queues);
await this.activity.queue(true);
}
}
@@ -228,7 +232,11 @@ class MessagesPullHandler {
// Resume queues if paused
if (options.pause) {
await this.queueManager.resume();
const queues = [
...this.queueManager.resolve('inbox'),
...this.queueManager.resolve('waha'),
];
await this.queueManager.resume(queues);
await this.activity.queue(false);
}
// Progress summary
@@ -26,6 +26,7 @@ import {
PollMessage,
ListMessage,
ShareContactMessage,
StatusReplyMessage,
TextMessage,
UnsupportedMessage,
AlbumMessage,
@@ -86,6 +87,18 @@ export class MessageAnyHandler extends MessageBaseHandler<WAMessage> {
return msg;
}
converter = new StatusReplyMessage(
this.l,
this.logger,
this.waha,
this.job,
this.session,
);
msg = await converter.convert(payload, protoMessage);
if (msg) {
return msg;
}
converter = new TextMessage(this.l, this.logger, this.waha, this.job);
msg = await converter.convert(payload, null);
if (msg) {
@@ -13,6 +13,7 @@ import {
isJidStatusBroadcast,
isLidUser,
isPnUser,
normalizeJid,
} from '@waha/core/utils/jids';
import { UnknownJIDFormat } from '@waha/apps/chatwoot/errors';
import { E164Parser } from '@waha/core/utils/PhoneJidNormalizer';
@@ -270,7 +271,7 @@ export function WhatsAppContactInfo(
} else if (isJidBroadcast(chatId)) {
return new BroadcastContactInfo(session, chatId, locale);
} else if (isLidUser(chatId)) {
return new LidContactInfo(session, chatId, locale);
return new LidContactInfo(session, normalizeJid(chatId), locale);
} else if (isPnUser(chatId)) {
return new JidContactInfo(session, chatId, locale);
} else {
+11
View File
@@ -325,6 +325,8 @@ waha.new.version.available: |-
👉 تحقق مما هو جديد في [**🆕 سجل التغييرات**]({{{changelogUrl}}})
_لإيقاف هذه الإشعارات، أرسل_ `queue stop check.version`
waha.core.version.used: |-
❤️ **ادعم WAHA!**
@@ -487,6 +489,15 @@ whatsapp.to.chatwoot.message.pix: |-
💳 **تم إرسال PIX نسخ ولصق**
{{/pixData}}
whatsapp.to.chatwoot.message.status.reply: |-
📣 **رد على حالة واتساب**
🏷️ **النوع:** {{type}}
{{#quotedText}}
📝 **نص الحالة:** {{quotedText}}
{{/quotedText}}
{{content}}
whatsapp.to.chatwoot.message.unsupported: |-
⚠️ **هذا النوع من الرسائل غير مدعوم في هذا الصندوق الوارد.**
📱 يرجى فتح **WhatsApp** لعرضه.
+11
View File
@@ -325,6 +325,8 @@ waha.new.version.available: |-
👉 [**🆕 পরিবর্তন লগ**]({{{changelogUrl}}}) এ নতুন কি আছে তা দেখুন
_এই বিজ্ঞপ্তিগুলি অক্ষম করতে, পাঠান_ `queue stop check.version`
waha.core.version.used: |-
❤️ **WAHA সমর্থন করুন!**
@@ -487,6 +489,15 @@ whatsapp.to.chatwoot.message.pix: |-
💳 **PIX কপি এবং পেস্ট পাঠানো হয়েছে**
{{/pixData}}
whatsapp.to.chatwoot.message.status.reply: |-
📣 **WhatsApp স্ট্যাটাসের জবাব**
🏷️ **ধরন:** {{type}}
{{#quotedText}}
📝 **স্ট্যাটাসের টেক্সট:** {{quotedText}}
{{/quotedText}}
{{content}}
whatsapp.to.chatwoot.message.unsupported: |-
⚠️ **এই ধরনের বার্তা এই ইনবক্সে সমর্থিত নয়।**
📱 এটি দেখতে অনুগ্রহ করে **WhatsApp** খুলুন।
+11
View File
@@ -328,6 +328,8 @@ waha.new.version.available: |-
👉 Sehen Sie, was neu ist im [**🆕 Änderungsprotokoll**]({{{changelogUrl}}})
_Um diese Benachrichtigungen zu deaktivieren, senden Sie_ `queue stop check.version`
waha.core.version.used: |-
❤️ **Unterstützen Sie WAHA!**
@@ -490,6 +492,15 @@ whatsapp.to.chatwoot.message.pix: |-
💳 **PIX Kopieren und Einfügen gesendet**
{{/pixData}}
whatsapp.to.chatwoot.message.status.reply: |-
📣 **Antwort auf WhatsApp-Status**
🏷️ **Typ:** {{type}}
{{#quotedText}}
📝 **Status-Text:** {{quotedText}}
{{/quotedText}}
{{content}}
whatsapp.to.chatwoot.message.unsupported: |-
⚠️ **Dieser Nachrichtentyp wird in dieser Inbox nicht unterstützt.**
📱 Bitte öffne **WhatsApp**, um ihn anzusehen.
+11
View File
@@ -268,6 +268,15 @@ whatsapp.to.chatwoot.message.pix: |-
💳 **PIX Copy and Paste sent**
{{/pixData}}
whatsapp.to.chatwoot.message.status.reply: |-
📣 **Reply to WhatsApp Status**
🏷️ **Type:** {{type}}
{{#quotedText}}
📝 **Status Text:** {{quotedText}}
{{/quotedText}}
{{content}}
whatsapp.to.chatwoot.message.has.media.no.media: |-
{{#content}}
{{{content}}}
@@ -705,6 +714,8 @@ waha.new.version.available: |-
👉 Check out what's new in the [**🆕 Changelog**]({{{changelogUrl}}})
_To disable these notifications, send_ `queue stop check.version`
waha.core.version.used: |-
❤️ **Support WAHA!**
+11
View File
@@ -325,6 +325,8 @@ waha.new.version.available: |-
👉 Mira qué hay de nuevo en el [**🆕 Registro de Cambios**]({{{changelogUrl}}})
_Para desactivar estas notificaciones, envía_ `queue stop check.version`
waha.core.version.used: |-
❤️ **¡Apoya a WAHA!**
@@ -487,6 +489,15 @@ whatsapp.to.chatwoot.message.pix: |-
💳 **PIX Copiar y Pegar enviado**
{{/pixData}}
whatsapp.to.chatwoot.message.status.reply: |-
📣 **Respuesta al estado de WhatsApp**
🏷️ **Tipo:** {{type}}
{{#quotedText}}
📝 **Texto del Estado:** {{quotedText}}
{{/quotedText}}
{{content}}
whatsapp.to.chatwoot.message.unsupported: |-
⚠️ **Este tipo de mensaje no es compatible con esta bandeja de entrada.**
📱 Por favor, abre **WhatsApp** para verlo.
+11
View File
@@ -325,6 +325,8 @@ waha.new.version.available: |-
👉 موارد جدید را در [**🆕 تغییرات**]({{{changelogUrl}}}) مشاهده کنید
_برای غیرفعال کردن این اعلان‌ها، ارسال کنید_ `queue stop check.version`
waha.core.version.used: |-
❤️ **از WAHA پشتیبانی کنید!**
@@ -487,6 +489,15 @@ whatsapp.to.chatwoot.message.pix: |-
💳 **PIX کپی و چسباندن ارسال شد**
{{/pixData}}
whatsapp.to.chatwoot.message.status.reply: |-
📣 **پاسخ به وضعیت واتساپ**
🏷️ **نوع:** {{type}}
{{#quotedText}}
📝 **متن وضعیت:** {{quotedText}}
{{/quotedText}}
{{content}}
whatsapp.to.chatwoot.message.unsupported: |-
⚠️ **این نوع پیام در این صندوق ورودی پشتیبانی نمی‌شود.**
📱 لطفاً برای مشاهده آن **WhatsApp** را باز کنید.
+11
View File
@@ -325,6 +325,8 @@ waha.new.version.available: |-
👉 Découvrez les nouveautés dans le [**🆕 Journal des modifications**]({{{changelogUrl}}})
_Pour désactiver ces notifications, envoyez_ `queue stop check.version`
waha.core.version.used: |-
❤️ **Soutenez WAHA !**
@@ -487,6 +489,15 @@ whatsapp.to.chatwoot.message.pix: |-
💳 **PIX Copier et Coller envoyé**
{{/pixData}}
whatsapp.to.chatwoot.message.status.reply: |-
📣 **Réponse au statut WhatsApp**
🏷️ **Type :** {{type}}
{{#quotedText}}
📝 **Texte du Statut :** {{quotedText}}
{{/quotedText}}
{{content}}
whatsapp.to.chatwoot.message.unsupported: |-
⚠️ **Ce type de message n’est pas pris en charge dans cette boîte de réception.**
📱 Veuillez ouvrir **WhatsApp** pour l’afficher.
+11
View File
@@ -325,6 +325,8 @@ waha.new.version.available: |-
👉 בדוק מה חדש ב[**🆕 יומן שינויים**]({{{changelogUrl}}})
_כדי לבטל התראות אלו, שלח_ `queue stop check.version`
waha.core.version.used: |-
❤️ **תמוך ב-WAHA!**
@@ -487,6 +489,15 @@ whatsapp.to.chatwoot.message.pix: |-
💳 **PIX העתק והדבק נשלח**
{{/pixData}}
whatsapp.to.chatwoot.message.status.reply: |-
📣 **תגובה לסטטוס ב-WhatsApp**
🏷️ **סוג:** {{type}}
{{#quotedText}}
📝 **טקסט הסטטוס:** {{quotedText}}
{{/quotedText}}
{{content}}
whatsapp.to.chatwoot.message.unsupported: |-
⚠️ **סוג הודעה זה אינו נתמך בתיבה זו.**
📱 אנא פתח/י את **WhatsApp** כדי לצפות בזה.
+11
View File
@@ -325,6 +325,8 @@ waha.new.version.available: |-
👉 [**🆕 चेंजलॉग**]({{{changelogUrl}}}) में क्या नया है देखें
_इन सूचनाओं को अक्षम करने के लिए, भेजें_ `queue stop check.version`
waha.core.version.used: |-
❤️ **WAHA का समर्थन करें!**
@@ -487,6 +489,15 @@ whatsapp.to.chatwoot.message.pix: |-
💳 **PIX कॉपी और पेस्ट भेजा गया**
{{/pixData}}
whatsapp.to.chatwoot.message.status.reply: |-
📣 **WhatsApp स्टेटस का जवाब**
🏷️ **प्रकार:** {{type}}
{{#quotedText}}
📝 **स्टेटस टेक्स्ट:** {{quotedText}}
{{/quotedText}}
{{content}}
whatsapp.to.chatwoot.message.unsupported: |-
⚠️ **इस प्रकार का संदेश इस इनबॉक्स में समर्थित नहीं है।**
📱 इसे देखने के लिए कृपया **WhatsApp** खोलें।
+11
View File
@@ -325,6 +325,8 @@ waha.new.version.available: |-
👉 Lihat apa yang baru di [**🆕 Changelog**]({{{changelogUrl}}})
_Untuk menonaktifkan notifikasi ini, kirim_ `queue stop check.version`
waha.core.version.used: |-
❤️ **Dukung WAHA!**
@@ -487,6 +489,15 @@ whatsapp.to.chatwoot.message.pix: |-
💳 **PIX Salin dan Tempel terkirim**
{{/pixData}}
whatsapp.to.chatwoot.message.status.reply: |-
📣 **Balasan ke Status WhatsApp**
🏷️ **Tipe:** {{type}}
{{#quotedText}}
📝 **Teks Status:** {{quotedText}}
{{/quotedText}}
{{content}}
whatsapp.to.chatwoot.message.unsupported: |-
⚠️ **Jenis pesan ini tidak didukung di kotak masuk ini.**
📱 Silakan buka **WhatsApp** untuk melihatnya.
+11
View File
@@ -268,6 +268,15 @@ whatsapp.to.chatwoot.message.pix: |-
💳 **PIX Copia e Incolla inviato**
{{/pixData}}
whatsapp.to.chatwoot.message.status.reply: |-
📣 **Risposta allo stato di WhatsApp**
🏷️ **Tipo:** {{type}}
{{#quotedText}}
📝 **Testo dello Stato:** {{quotedText}}
{{/quotedText}}
{{content}}
whatsapp.to.chatwoot.message.has.media.no.media: |-
{{#content}}
{{{content}}}
@@ -705,6 +714,8 @@ waha.new.version.available: |-
👉 Scopri le novità nel [**🆕 Changelog**]({{{changelogUrl}}})
_Per disattivare queste notifiche, invia_ `queue stop check.version`
waha.core.version.used: |-
❤️ **Supporta WAHA!**
+11
View File
@@ -325,6 +325,8 @@ waha.new.version.available: |-
👉 [**🆕 ਬਦਲਾਅ ਲੌਗ**]({{{changelogUrl}}}) ਵਿੱਚ ਨਵਾਂ ਕੀ ਹੈ ਦੇਖੋ
_ਇਹ ਸੂਚਨਾਵਾਂ ਬੰਦ ਕਰਨ ਲਈ, ਭੇਜੋ_ `queue stop check.version`
waha.core.version.used: |-
❤️ **WAHA ਦਾ ਸਮਰਥਨ ਕਰੋ!**
@@ -487,6 +489,15 @@ whatsapp.to.chatwoot.message.pix: |-
💳 **PIX ਕਾਪੀ ਅਤੇ ਪੇਸਟ ਭੇਜਿਆ ਗਿਆ**
{{/pixData}}
whatsapp.to.chatwoot.message.status.reply: |-
📣 **WhatsApp ਸਟੇਟਸ ਦਾ ਜਵਾਬ**
🏷️ **ਕਿਸਮ:** {{type}}
{{#quotedText}}
📝 **ਸਟੇਟਸ ਲਿਖਤ:** {{quotedText}}
{{/quotedText}}
{{content}}
whatsapp.to.chatwoot.message.unsupported: |-
⚠️ **ਇਹ ਸੁਨੇਹੇ ਦੀ ਕਿਸਮ ਇਸ ਇਨਬਾਕਸ ਵਿੱਚ ਸਮਰਥਿਤ ਨਹੀਂ ਹੈ।**
📱 ਇਸਨੂੰ ਦੇਖਣ ਲਈ ਕਿਰਪਾ ਕਰਕੇ **WhatsApp** ਖੋਲ੍ਹੋ।
+11
View File
@@ -326,6 +326,8 @@ waha.new.version.available: |-
👉 Confira o que há de novo no [**🆕 Registro de Alterações**]({{{changelogUrl}}})
_Para desativar essas notificações, envie_ `queue stop check.version`
waha.core.version.used: |-
❤️ **Apoie o WAHA!**
@@ -492,6 +494,15 @@ whatsapp.to.chatwoot.message.pix: |-
💳 **PIX Copia e Cola enviado**
{{/pixData}}
whatsapp.to.chatwoot.message.status.reply: |-
📣 **Resposta a Status do WhatsApp**
🏷️ **Tipo:** {{type}}
{{#quotedText}}
📝 **Texto do Status:** {{quotedText}}
{{/quotedText}}
{{content}}
whatsapp.to.chatwoot.message.unsupported: |-
⚠️ **Esse tipo de mensagem não é suportado nesta caixa de entrada.**
📱 Por favor, abra o **WhatsApp** para visualizá-la.
+11
View File
@@ -325,6 +325,8 @@ waha.new.version.available: |-
👉 Узнайте, что нового в [**🆕 Списке изменений**]({{{changelogUrl}}})
_Чтобы отключить эти уведомления, отправьте_ `queue stop check.version`
waha.core.version.used: |-
❤️ **Поддержите WAHA!**
@@ -487,6 +489,15 @@ whatsapp.to.chatwoot.message.pix: |-
💳 **PIX Копировать и вставить отправлен**
{{/pixData}}
whatsapp.to.chatwoot.message.status.reply: |-
📣 **Ответ на статус WhatsApp**
🏷️ **Тип:** {{type}}
{{#quotedText}}
📝 **Текст Статуса:** {{quotedText}}
{{/quotedText}}
{{content}}
whatsapp.to.chatwoot.message.unsupported: |-
⚠️ **Этот тип сообщения не поддерживается в этом Inbox.**
📱 Пожалуйста, откройте **WhatsApp**, чтобы просмотреть.
+11
View File
@@ -325,6 +325,8 @@ waha.new.version.available: |-
👉 [**🆕 Değişiklik Günlüğü**]({{{changelogUrl}}}) içinde yenilikleri kontrol edin
_Bu bildirimleri devre dışı bırakmak için gönderin_ `queue stop check.version`
waha.core.version.used: |-
❤️ **WAHA'yı Destekleyin!**
@@ -487,6 +489,15 @@ whatsapp.to.chatwoot.message.pix: |-
💳 **PIX Kopyala ve Yapıştır gönderildi**
{{/pixData}}
whatsapp.to.chatwoot.message.status.reply: |-
📣 **WhatsApp Durumuna Yanıt**
🏷️ **Tür:** {{type}}
{{#quotedText}}
📝 **Durum Metni:** {{quotedText}}
{{/quotedText}}
{{content}}
whatsapp.to.chatwoot.message.unsupported: |-
⚠️ **Bu mesaj türü bu gelen kutusunda desteklenmiyor.**
📱 Görüntülemek için lütfen **WhatsApp’ı** açın.
+11
View File
@@ -325,6 +325,8 @@ waha.new.version.available: |-
👉 Перевірте, що нового у [**🆕 Журналі змін**]({{{changelogUrl}}})
_Щоб вимкнути ці сповіщення, надішліть_ `queue stop check.version`
waha.core.version.used: |-
❤️ **Підтримайте WAHA!**
@@ -487,6 +489,15 @@ whatsapp.to.chatwoot.message.pix: |-
💳 **PIX Копіювати та Вставити надіслано**
{{/pixData}}
whatsapp.to.chatwoot.message.status.reply: |-
📣 **Відповідь на статус WhatsApp**
🏷️ **Тип:** {{type}}
{{#quotedText}}
📝 **Текст Статусу:** {{quotedText}}
{{/quotedText}}
{{content}}
whatsapp.to.chatwoot.message.unsupported: |-
⚠️ **Цей тип повідомлення не підтримується в цих вхідних.**
📱 Будь ласка, відкрийте **WhatsApp**, щоб переглянути.
+11
View File
@@ -325,6 +325,8 @@ waha.new.version.available: |-
👉 [**🆕 تبدیلیوں کی فہرست**]({{{changelogUrl}}}) میں نیا کیا ہے دیکھیں
_ان اطلاعات کو بند کرنے کے لیے بھیجیں_ `queue stop check.version`
waha.core.version.used: |-
❤️ **WAHA کی حمایت کریں!**
@@ -487,6 +489,15 @@ whatsapp.to.chatwoot.message.pix: |-
💳 **PIX کاپی اور پیسٹ بھیج دیا گیا**
{{/pixData}}
whatsapp.to.chatwoot.message.status.reply: |-
📣 **WhatsApp اسٹیٹس کا جواب**
🏷️ **قسم:** {{type}}
{{#quotedText}}
📝 **اسٹیٹس کا متن:** {{quotedText}}
{{/quotedText}}
{{content}}
whatsapp.to.chatwoot.message.unsupported: |-
⚠️ **اس قسم کا پیغام اس انبکس میں معاونت یافتہ نہیں ہے۔**
📱 اسے دیکھنے کے لیے براہ کرم **WhatsApp** کھولیں۔
+11
View File
@@ -325,6 +325,8 @@ waha.new.version.available: |-
👉 查看[**🆕 更新日志**]({{{changelogUrl}}})
_要禁用这些通知,请发送_ `queue stop check.version`
waha.core.version.used: |-
❤️ **支持 WAHA!**
@@ -487,6 +489,15 @@ whatsapp.to.chatwoot.message.pix: |-
💳 **PIX 复制粘贴已发送**
{{/pixData}}
whatsapp.to.chatwoot.message.status.reply: |-
📣 **回复 WhatsApp 状态**
🏷️ **类型:**{{type}}
{{#quotedText}}
📝 **状态文本:**{{quotedText}}
{{/quotedText}}
{{content}}
whatsapp.to.chatwoot.message.unsupported: |-
⚠️ **此收件箱不支持此消息类型。**
📱 请打开 **WhatsApp** 查看。
+6
View File
@@ -54,6 +54,7 @@ export enum TKey {
WA_TO_CW_MESSAGE_POLL = 'whatsapp.to.chatwoot.message.poll',
WA_TO_CW_MESSAGE_EVENT = 'whatsapp.to.chatwoot.message.event',
WA_TO_CW_MESSAGE_PIX = 'whatsapp.to.chatwoot.message.pix',
WA_TO_CW_MESSAGE_STATUS_REPLY = 'whatsapp.to.chatwoot.message.status.reply',
WA_TO_CW_MESSAGE_UNSUPPORTED = 'whatsapp.to.chatwoot.message.unsupported',
WA_TO_CW_MESSAGE_FACEBOOK_AD = 'whatsapp.to.chatwoot.message.facebook.ad',
WA_TO_CW_MESSAGE_LIST = 'whatsapp.to.chatwoot.message.list',
@@ -189,6 +190,11 @@ export type TemplatePayloads = {
message: proto.Message | null;
pixData: PixTemplatePayload;
};
[TKey.WA_TO_CW_MESSAGE_STATUS_REPLY]: {
type: string;
quotedText?: string;
content: string;
};
[TKey.WA_TO_CW_MESSAGE_UNSUPPORTED]: { details: Link };
[TKey.WA_TO_CW_MESSAGE_FACEBOOK_AD]: {
payload: WAMessage;
@@ -0,0 +1,231 @@
import type { proto } from '@adiwajshing/baileys';
import { ILogger } from '@waha/apps/app_sdk/ILogger';
import { WAHASelf, WAHASessionAPI } from '@waha/apps/app_sdk/waha/WAHASelf';
import { ChatWootMessagePartial } from '@waha/apps/chatwoot/consumers/waha/base';
import { Locale } from '@waha/apps/chatwoot/i18n/locale';
import { TKey } from '@waha/apps/chatwoot/i18n/templates';
import { WhatsappToMarkdown } from '@waha/apps/chatwoot/messages/to/chatwoot/utils/markdown';
import { EngineHelper, QuotedMedia } from '@waha/apps/chatwoot/waha';
import { Jid } from '@waha/core/engines/const';
import { WAMessage } from '@waha/structures/responses.dto';
import { Job } from 'bullmq';
import { TextMessage } from './TextMessage';
import { isEmptyString } from './utils/text';
import { SendAttachment } from '@waha/apps/chatwoot/client/types';
import esm from '@waha/vendor/esm';
// eslint-disable-next-line @typescript-eslint/no-var-requires
const mime = require('mime-types');
type StatusReplyType = 'text' | 'image' | 'audio' | 'video' | 'unknown';
interface StatusQuotedMessageInfo {
data?: any;
id: string;
type: StatusReplyType;
text?: string;
}
export class StatusReplyMessage extends TextMessage {
constructor(
locale: Locale,
logger: ILogger,
waha: WAHASelf,
job: Job,
private readonly session: WAHASessionAPI,
) {
super(locale, logger, waha, job);
}
async convert(
payload: WAMessage,
protoMessage: proto.Message | null,
): Promise<ChatWootMessagePartial | null> {
if (!payload.replyTo?.id) {
return null;
}
if (!EngineHelper.IsReplyToStatus(payload, protoMessage)) {
return null;
}
let attachments: SendAttachment[] = [];
const quoted = this.getStatusQuotedMessageInfo(payload);
let wamessage = null; // aka "quoted from store"
// Media - Use provided replyTo.media if present
if (attachments.length === 0 && payload.replyTo.media) {
const extension = mime.extension(payload.replyTo.media.mimetype);
payload.replyTo.media.filename = `status-message.${extension}`;
const quotedAttachments = await super.getAttachments(payload.replyTo);
attachments = [...attachments, ...quotedAttachments];
}
// Media - fetch message by id from store and use it's "media"
if (attachments.length == 0) {
wamessage = await this.fetchStatusMessage(quoted.id);
if (wamessage?.media?.url) {
const extension = mime.extension(wamessage.media.mimetype);
wamessage.media.filename = `status-message.${extension}`;
const quotedAttachments = await super.getAttachments(wamessage);
attachments = [...attachments, ...quotedAttachments];
}
}
// Media - get media url, key and try to decrypt it
if (attachments.length == 0) {
const replyData = payload.replyTo._data;
const quotedMedia = EngineHelper.ExtractQuotedMedia(replyData);
if (quotedMedia) {
const attachment =
await this.downloadQuotedMediaAttachment(quotedMedia);
if (attachment) {
attachments.push(attachment);
}
}
}
// Act as regular text message, but we'll adjust some properties
const msg = await super.convert(payload, protoMessage);
// First goes Quoted attachments, then what sender sent
msg.attachments = [...attachments, ...msg.attachments];
// Wrap content with additional info
const quotedText = quoted.text ?? wamessage?.body ?? payload.replyTo?.body;
if (
isEmptyString(quotedText) &&
isEmptyString(msg.content) &&
attachments.length === 0
) {
// Nothing to send.
// Must check msg.content before wrapping it in status reply
return null;
}
msg.content = this.locale.r(TKey.WA_TO_CW_MESSAGE_STATUS_REPLY, {
type: quoted.type,
quotedText: WhatsappToMarkdown(quotedText),
content: msg.content,
});
return msg;
}
private getStatusQuotedMessageInfo(
payload: WAMessage,
): StatusQuotedMessageInfo {
const replyData = payload.replyTo._data;
const imageMessage =
replyData?.imageMessage || this.getWebjsMediaData(replyData, 'image');
if (imageMessage) {
return {
data: replyData,
id: payload.replyTo.id,
type: 'image',
text: imageMessage.caption ?? payload.replyTo.body ?? replyData?.body,
};
}
const videoMessage =
replyData?.videoMessage || this.getWebjsMediaData(replyData, 'video');
if (videoMessage) {
return {
data: replyData,
id: payload.replyTo.id,
type: 'video',
text: videoMessage.caption ?? payload.replyTo.body ?? replyData?.body,
};
}
const audioMessage =
replyData?.audioMessage || this.getWebjsMediaData(replyData, 'audio');
if (audioMessage) {
return {
data: replyData,
id: payload.replyTo.id,
type: 'audio',
text: audioMessage.caption ?? payload.replyTo.body ?? replyData?.body,
};
}
const quotedText =
replyData?.extendedTextMessage?.text ??
replyData?.conversation ??
payload.replyTo.body ??
replyData?.body;
return {
data: replyData,
id: payload.replyTo.id,
type: quotedText ? 'text' : 'unknown',
text: quotedText,
};
}
private getWebjsMediaData(
replyData: any,
expectedKind: 'image' | 'video' | 'audio',
): any | null {
if (!replyData) {
return null;
}
const kind = replyData.kind ?? replyData.type;
if (kind !== expectedKind) {
return null;
}
return replyData;
}
private async downloadQuotedMediaAttachment(
quotedMedia: QuotedMedia,
): Promise<SendAttachment | null> {
try {
const decrypt = !!quotedMedia.mediaKey;
const mediaKeyBuffer = decrypt
? Buffer.from(quotedMedia.mediaKey, 'base64')
: undefined;
const stream = await esm.b.downloadContentFromMessage(
{
mediaKey: mediaKeyBuffer,
directPath: quotedMedia.directPath,
url: quotedMedia.url,
},
quotedMedia.mediaType as any,
{},
decrypt,
);
const chunks: Buffer[] = [];
for await (const chunk of stream) {
chunks.push(chunk);
}
const buffer = Buffer.concat(chunks);
const extension = mime.extension(quotedMedia.mimetype);
const filename = `status-message.${extension}`;
return {
content: buffer.toString('base64'),
filename: filename,
encoding: 'base64',
};
} catch (error) {
this.logger.warn('Failed to download quoted status media directly');
this.logger.debug(error);
return null;
}
}
private async fetchStatusMessage(
replyMessageId: string,
): Promise<WAMessage | null> {
try {
const quotedMessage = await this.session.getMessageById(
Jid.BROADCAST,
replyMessageId,
true,
);
return quotedMessage || null;
} catch (error) {
this.logger.warn(
`Failed to fetch original status message '${replyMessageId}'`,
);
this.logger.debug(error);
return null;
}
}
}
@@ -11,16 +11,17 @@ import { MessageToChatWootConverter } from '@waha/apps/chatwoot/messages/to/chat
import { WhatsappToMarkdown } from '@waha/apps/chatwoot/messages/to/chatwoot/utils/markdown';
import { JobLink } from '@waha/apps/app_sdk/JobUtils';
import { Job } from 'bullmq';
import { WAMedia } from '@waha/structures/media.dto';
// eslint-disable-next-line @typescript-eslint/no-var-requires
const mime = require('mime-types');
export class TextMessage implements MessageToChatWootConverter {
constructor(
private readonly locale: Locale,
private readonly logger: ILogger,
private readonly waha: WAHASelf,
private readonly job: Job,
protected readonly locale: Locale,
protected readonly logger: ILogger,
protected readonly waha: WAHASelf,
protected readonly job: Job,
) {}
async convert(
@@ -52,12 +53,14 @@ export class TextMessage implements MessageToChatWootConverter {
}
return {
content: WhatsappToMarkdown(content),
attachments,
attachments: attachments,
private: undefined,
};
}
private async getAttachments(payload: WAMessage): Promise<SendAttachment[]> {
protected async getAttachments(payload: {
media?: WAMedia;
}): Promise<SendAttachment[]> {
const hasMedia = payload.media?.url;
if (!hasMedia) {
return [];
@@ -13,6 +13,7 @@ export { EventMessage } from './EventMessage';
export { PixMessage } from './PixMessage';
export { ListMessage } from './ListMessage';
export { AlbumMessage } from './AlbumMessage';
export { StatusReplyMessage } from './StatusReplyMessage';
export type Awaitable<T> = T | Promise<T>;
@@ -54,6 +54,21 @@ export class ChatWootScheduleService {
},
},
);
// Check the tier
const checkTierQueue = this.queueRegistry.queue(
QueueName.SCHEDULED_CHECK_TIER,
);
await checkTierQueue.upsertJobScheduler(
this.JobId(QueueName.SCHEDULED_CHECK_TIER, appId),
// Every Monday (1) at 14:00
{ pattern: '0 0 14 * * 1' },
{
data: {
app: appId,
session: sessionName,
},
},
);
}
async unschedule(appId: string, sessionName: string): Promise<void> {
@@ -71,6 +86,13 @@ export class ChatWootScheduleService {
await checkVersionQueue.removeJobScheduler(
this.JobId(QueueName.SCHEDULED_CHECK_VERSION, appId),
);
// Check the tier
const checkTierQueue = this.queueRegistry.queue(
QueueName.SCHEDULED_CHECK_TIER,
);
await checkTierQueue.removeJobScheduler(
this.JobId(QueueName.SCHEDULED_CHECK_TIER, appId),
);
// contacts
const contactsPullQueue = this.queueRegistry.queue(
+15 -2
View File
@@ -1,6 +1,7 @@
import { QueueName } from '../consumers/QueueName';
import { QueueRegistry } from '@waha/apps/chatwoot/services/QueueRegistry';
import { Injectable } from '@nestjs/common';
import { QueueNameRepr } from '@waha/apps/app_sdk/JobUtils';
const Managable = true;
const Locked = false;
@@ -18,7 +19,8 @@ export class QueueManager {
constructor(private readonly registry: QueueRegistry) {
this.queues = {
[QueueName.SCHEDULED_MESSAGE_CLEANUP]: Locked,
[QueueName.SCHEDULED_CHECK_VERSION]: Locked,
[QueueName.SCHEDULED_CHECK_VERSION]: Managable,
[QueueName.SCHEDULED_CHECK_TIER]: Locked,
[QueueName.TASK_CONTACTS_PULL]: Locked,
[QueueName.TASK_MESSAGES_PULL]: Locked,
[QueueName.WAHA_SESSION_STATUS]: Locked,
@@ -75,8 +77,19 @@ export class QueueManager {
case undefined:
return queues;
default:
default: {
const matches = queues.filter((q) => q.split(' | ')[1] === shortcut);
if (matches.length === 1) {
return [matches[0]];
}
if (matches.length > 1) {
const names = matches.map(QueueNameRepr).join(', ');
throw new Error(
`Ambiguous queue name "${shortcut}", matches: ${names}`,
);
}
return [shortcut as QueueName];
}
}
}
@@ -16,6 +16,8 @@ export class QueueRegistry {
private readonly scheduledMessageCleanupQueue: Queue,
@InjectQueue(QueueName.SCHEDULED_CHECK_VERSION)
private readonly scheduledCheckVersionQueue: Queue,
@InjectQueue(QueueName.SCHEDULED_CHECK_TIER)
private readonly scheduledCheckTierQueue: Queue,
@InjectQueue(QueueName.TASK_CONTACTS_PULL)
private readonly taskContactsPullQueue: Queue,
@InjectQueue(QueueName.TASK_MESSAGES_PULL)
@@ -55,6 +57,7 @@ export class QueueRegistry {
this.queues = {
[QueueName.SCHEDULED_MESSAGE_CLEANUP]: this.scheduledMessageCleanupQueue,
[QueueName.SCHEDULED_CHECK_VERSION]: this.scheduledCheckVersionQueue,
[QueueName.SCHEDULED_CHECK_TIER]: this.scheduledCheckTierQueue,
[QueueName.TASK_CONTACTS_PULL]: this.taskContactsPullQueue,
[QueueName.TASK_MESSAGES_PULL]: this.taskMessagesPullQueue,
[QueueName.WAHA_SESSION_STATUS]: this.wahaSessionStatusQueue,
+126
View File
@@ -1,18 +1,34 @@
import * as lodash from 'lodash';
import type { proto } from '@adiwajshing/baileys';
import { WhatsAppMessage } from '@waha/apps/chatwoot/storage';
import { WAHAEngine } from '@waha/structures/enums.dto';
import { getEngineName } from '@waha/version';
import { Message as MessageInstance } from 'whatsapp-web.js/src/structures';
import { Jid } from '@waha/core/engines/const';
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';
import { getContextInfo } from '@waha/core/utils/pwa';
export interface QuotedMedia {
url?: string;
directPath: string;
mediaKey?: string;
mimetype: string;
mediaType: string;
}
interface IEngineHelper {
ChatID(message: WAMessage | any): string;
CallChatID(call: CallData | any): string;
IsReplyToStatus(
message: WAMessage,
protoMessage: proto.Message | null,
): boolean;
WhatsAppMessageKeys(message: any): WhatsAppMessage;
IterateMessages<T extends { timestamp: number }>(
@@ -24,6 +40,8 @@ interface IEngineHelper {
FilterChatIdsForMessages(chats: string[]): string[];
SupportsAllChatForMessage(): boolean;
ExtractQuotedMedia(replyData: any): QuotedMedia | null;
}
class NOWEBHelper implements IEngineHelper {
@@ -35,6 +53,14 @@ class NOWEBHelper implements IEngineHelper {
return call.from;
}
IsReplyToStatus(
message: WAMessage,
protoMessage: proto.Message | null,
): boolean {
void message;
return getContextInfo(protoMessage)?.remoteJid === Jid.BROADCAST;
}
WhatsAppMessageKeys(message: any): WhatsAppMessage {
const timestamp = parseInt(message.messageTimestamp) * 1000;
return {
@@ -63,6 +89,10 @@ class NOWEBHelper implements IEngineHelper {
SupportsAllChatForMessage(): boolean {
return true;
}
ExtractQuotedMedia(replyData: any): QuotedMedia | null {
return extractProtoQuotedMedia(replyData);
}
}
class GOWSHelper implements IEngineHelper {
@@ -74,6 +104,14 @@ class GOWSHelper implements IEngineHelper {
return call._data?.CallCreator || call.from;
}
IsReplyToStatus(
message: WAMessage,
protoMessage: proto.Message | null,
): boolean {
void message;
return getContextInfo(protoMessage)?.remoteJid === Jid.BROADCAST;
}
/**
* Parse API response and get the data
* API Response depends on engine right now
@@ -107,6 +145,10 @@ class GOWSHelper implements IEngineHelper {
ContactIsMy(contact) {
return true;
}
ExtractQuotedMedia(replyData: any): QuotedMedia | null {
return extractProtoQuotedMedia(replyData, true);
}
}
class WEBJSHelper implements IEngineHelper {
@@ -118,6 +160,14 @@ class WEBJSHelper implements IEngineHelper {
return call.from;
}
IsReplyToStatus(
message: WAMessage,
protoMessage: proto.Message | null,
): boolean {
void protoMessage;
return message._data?.quotedRemoteJid === Jid.BROADCAST;
}
/**
* Parse API response and get the data for WEBJS engine
*/
@@ -161,6 +211,10 @@ class WEBJSHelper implements IEngineHelper {
ContactIsMy(contact) {
return contact.isMyContact;
}
ExtractQuotedMedia(replyData: any): QuotedMedia | null {
return extractBrowserQuotedMedia(replyData);
}
}
class WPPHelper implements IEngineHelper {
@@ -172,6 +226,14 @@ class WPPHelper implements IEngineHelper {
return call.from;
}
IsReplyToStatus(
message: WAMessage,
protoMessage: proto.Message | null,
): boolean {
void protoMessage;
return message._data?.quotedRemoteJid === Jid.BROADCAST;
}
/**
* Parse API response and get the data for WPP engine.
* WPP returns message.id as a composite string "fromMe_chatId_msgId".
@@ -217,6 +279,10 @@ class WPPHelper implements IEngineHelper {
ContactIsMy(contact) {
return contact.isMyContact;
}
ExtractQuotedMedia(replyData: any): QuotedMedia | null {
return extractBrowserQuotedMedia(replyData);
}
}
// Choose the right EngineHelper based on getEngineName() function
@@ -241,6 +307,66 @@ switch (getEngineName()) {
export const EngineHelper = engineHelper;
//
// Quoted media extraction helpers
//
function extractProtoQuotedMedia(
replyData: any,
capitalUrl = false,
): QuotedMedia | null {
if (!replyData) {
return null;
}
const mediaTypes: Array<[string, string]> = [
['imageMessage', 'image'],
['videoMessage', 'video'],
['audioMessage', 'audio'],
];
for (const [key, mediaType] of mediaTypes) {
const msg = replyData[key];
if (!msg) {
continue;
}
const url: string | undefined = capitalUrl ? msg.URL ?? msg.url : msg.url;
const directPath: string = msg.directPath;
const mimetype: string = msg.mimetype;
if (!directPath || !mimetype) {
continue;
}
return {
url: url,
directPath: directPath,
mediaKey: msg.mediaKey,
mimetype: mimetype,
mediaType: mediaType,
};
}
return null;
}
function extractBrowserQuotedMedia(replyData: any): QuotedMedia | null {
if (!replyData) {
return null;
}
const kind: string = replyData.kind ?? replyData.type;
if (kind !== 'image' && kind !== 'video' && kind !== 'audio') {
return null;
}
const directPath: string = replyData.directPath;
const mimetype: string = replyData.mimetype;
if (!directPath || !mimetype) {
return null;
}
return {
url: replyData.deprecatedMms3Url,
directPath: directPath,
mediaKey: replyData.mediaKey,
mimetype: mimetype,
mediaType: kind,
};
}
function preferPnChats(chats: string[]): string[] {
const unique = lodash.uniq(chats ?? []);
const hasPn = unique.some(isPnUser);
+2
View File
@@ -1,3 +1,5 @@
import { EngineHelper } from './engines';
import type { QuotedMedia } from './engines';
export { EngineHelper };
export type { QuotedMedia };
+2
View File
@@ -195,6 +195,8 @@ export class SwaggerConfiguratorCore {
'/ping',
'/ws',
'/webhooks/',
'/jobs',
'/jobs/',
...config.getExcludedFullPaths(),
]);
+4 -10
View File
@@ -1,3 +1,6 @@
import {
getBrowserExecutablePath as getBrowserExecutablePathAutodetect,
} from '@waha/core/abc/session.browser';
import {
CoreMediaConverter,
IMediaConverter,
@@ -142,15 +145,6 @@ const qrcode = require('qrcode-terminal');
axiosRetry(axios, { retries: 3 });
const CHROME_PATH = '/usr/bin/google-chrome-stable';
const CHROMIUM_PATH = '/usr/bin/chromium';
export function getBrowserExecutablePath() {
if (fs.existsSync(CHROME_PATH)) {
return CHROME_PATH;
}
return CHROMIUM_PATH;
}
export function ensureSuffix(phone) {
const suffix = '@c.us';
@@ -360,7 +354,7 @@ export abstract class WhatsappSession {
}
getBrowserExecutablePath() {
return getBrowserExecutablePath();
return getBrowserExecutablePathAutodetect();
}
getBrowserArgsForPuppeteer() {
+42
View File
@@ -0,0 +1,42 @@
import * as fs from 'fs';
//
// Browser executable path detection
//
// Ordered: Chrome paths first, then Chromium.
// Windows: only global Program Files paths (no per-user %LOCALAPPDATA%).
const BROWSER_PATHS: Record<string, string[]> = {
darwin: [
'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
'/Applications/Chromium.app/Contents/MacOS/Chromium',
],
win32: [
'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe',
'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe',
'C:\\Program Files\\Chromium\\Application\\chrome.exe',
],
linux: [
'/usr/bin/google-chrome',
'/usr/bin/google-chrome-stable',
'/usr/bin/chromium',
'/usr/bin/chromium-browser',
],
};
export function getBrowserExecutablePath(): string {
const paths = BROWSER_PATHS[process.platform] ?? BROWSER_PATHS['linux'];
for (const p of paths) {
if (fs.existsSync(p)) {
return p;
}
}
return paths[0];
}
// A path belongs to Google Chrome when it contains "google" in any casing.
// All Chrome paths include "Google" (macOS/Windows) or "google-chrome" (Linux),
// while Chromium paths never do.
export function isChromeExecutablePath(executablePath: string): boolean {
return executablePath.toLowerCase().includes('google');
}
+47 -27
View File
@@ -45,6 +45,7 @@ import { parseMessageIdSerialized } from '@waha/core/utils/ids';
import {
isJidBroadcast,
isJidGroup,
normalizeJid,
toCusFormat,
toJID,
} from '@waha/core/utils/jids';
@@ -164,7 +165,7 @@ import { promisify } from 'util';
import * as gows from './types';
import { MessageStatus } from './types';
import { isFromFullSync } from '@waha/core/engines/gows/appstate';
import { toVcardV3 } from '@waha/core/vcard';
import { parseVCardV3, toVcardV3 } from '@waha/core/vcard';
import { AckToStatus } from '@waha/core/utils/acks';
import { ParseEventResponseType } from '@waha/core/utils/events';
import { DistinctAck, DistinctMessages } from '@waha/core/utils/reactive';
@@ -313,7 +314,7 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
grpc.credentials.createInsecure(),
);
promisify(this.client.StartSession)(request).catch((err) => {
await promisify(this.client.StartSession)(request).catch((err) => {
this.logger.error('Failed to start the client');
this.logger.error(err, err.stack);
this.status = WAHASessionStatus.FAILED;
@@ -784,7 +785,7 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
@Activity()
async fetchContactProfilePicture(id: string): Promise<string> {
const jid = toJID(this.ensureSuffix(id));
const jid = normalizeJid(toJID(this.ensureSuffix(id)));
const request = new messages.ProfilePictureRequest({
jid: jid,
session: this.session,
@@ -920,7 +921,7 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
async rejectCall(from: string, id: string): Promise<void> {
const request = new messages.RejectCallRequest({
session: this.session,
from: toJID(this.ensureSuffix(from)),
from: normalizeJid(toJID(this.ensureSuffix(from))),
id: id,
});
await promisify(this.client.RejectCall)(request);
@@ -928,7 +929,7 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
@Activity()
async sendText(request: MessageTextRequest) {
const jid = toJID(this.ensureSuffix(request.chatId));
const jid = normalizeJid(toJID(this.ensureSuffix(request.chatId)));
const message = new messages.MessageRequest({
jid: jid,
text: request.text,
@@ -936,7 +937,9 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
linkPreview: request.linkPreview ?? true,
linkPreviewHighQuality: request.linkPreviewHighQuality,
replyTo: getMessageIdFromSerialized(request.reply_to),
mentions: request.mentions?.map((mention) => toJID(mention)),
mentions: request.mentions?.map((mention) =>
normalizeJid(toJID(mention)),
),
});
const response = await promisify(this.client.SendMessage)(message);
const data = response.toObject();
@@ -949,7 +952,7 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
messageId: string,
request: EditMessageRequest,
) {
const jid = toJID(this.ensureSuffix(chatId));
const jid = normalizeJid(toJID(this.ensureSuffix(chatId)));
const key = parseMessageIdSerialized(messageId, true);
const message = new messages.EditMessageRequest({
session: this.session,
@@ -966,8 +969,12 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
@Activity()
async sendContactVCard(request: MessageContactVcardRequest) {
const jid = toJID(this.ensureSuffix(request.chatId));
const contacts = request.contacts.map((el) => ({ vcard: toVcardV3(el) }));
const jid = normalizeJid(toJID(this.ensureSuffix(request.chatId)));
const contacts = request.contacts.map((el) => ({
displayName:
(el as any).fullName || parseVCardV3(el.vcard || '').fullName,
vcard: toVcardV3(el),
}));
const message = new messages.MessageRequest({
jid: jid,
session: this.session,
@@ -981,7 +988,7 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
@Activity()
async sendPoll(request: MessagePollRequest) {
const jid = toJID(request.chatId);
const jid = normalizeJid(toJID(request.chatId));
const message = new messages.MessageRequest({
jid: jid,
session: this.session,
@@ -1007,7 +1014,7 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
@Activity()
public async deleteMessage(chatId: string, messageId: string) {
const jid = toJID(this.ensureSuffix(chatId));
const jid = normalizeJid(toJID(this.ensureSuffix(chatId)));
const key = parseMessageIdSerialized(messageId);
const message = new messages.RevokeMessageRequest({
session: this.session,
@@ -1024,7 +1031,7 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
if (!contacts || contacts.length == 0) {
return [];
}
return contacts.map(toJID);
return contacts.map((c) => normalizeJid(toJID(c)));
}
@Activity()
@@ -1100,7 +1107,7 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
@Activity()
async sendLocation(request: MessageLocationRequest) {
const jid = toJID(this.ensureSuffix(request.chatId));
const jid = normalizeJid(toJID(this.ensureSuffix(request.chatId)));
const message = new messages.MessageRequest({
jid: jid,
session: this.session,
@@ -1184,7 +1191,7 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
const req = new messages.CreateGroupRequest({
session: this.session,
name: request.name,
participants: request.participants.map((p) => toJID(p.id)),
participants: request.participants.map((p) => normalizeJid(toJID(p.id))),
});
const response = await promisify(this.client.CreateGroup)(req);
const data = parseJson(response);
@@ -1359,7 +1366,7 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
participants: Array<Participant>,
action: messages.ParticipantAction,
): Promise<any> {
const jids = participants.map((p) => toJID(p.id));
const jids = participants.map((p) => normalizeJid(toJID(p.id)));
const req = new messages.UpdateParticipantsRequest({
session: this.session,
jid: id,
@@ -1412,7 +1419,7 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
@Activity()
async sendEvent(request: EventMessageRequest): Promise<WAMessage> {
const jid = toJID(this.ensureSuffix(request.chatId));
const jid = normalizeJid(toJID(this.ensureSuffix(request.chatId)));
const event = request.event;
// Create EventLocation if provided
@@ -1468,7 +1475,7 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
public async setPresence(presence: WAHAPresenceStatus, chatId?: string) {
let request: any;
let method: any;
const jid = chatId ? toJID(this.ensureSuffix(chatId)) : null;
const jid = chatId ? normalizeJid(toJID(this.ensureSuffix(chatId))) : null;
switch (presence) {
case WAHAPresenceStatus.ONLINE:
request = new messages.PresenceRequest({
@@ -1529,7 +1536,7 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
}
public async getPresence(chatId: string): Promise<WAHAChatPresences> {
const jid = toJID(chatId);
const jid = normalizeJid(toJID(chatId));
await this.subscribePresence(jid);
if (!(jid in this.presences.keys())) {
await sleep(1000);
@@ -1540,7 +1547,7 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
@Activity()
async subscribePresence(chatId: string) {
const jid = toJID(chatId);
const jid = normalizeJid(toJID(chatId));
const req = new messages.SubscribePresenceRequest({
session: this.session,
jid: jid,
@@ -1739,7 +1746,7 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
*/
@Activity()
public async upsertContact(chatId: string, body: ContactUpdateBody) {
const jid = toJID(chatId);
const jid = normalizeJid(toJID(chatId));
const request = new messages.UpdateContactRequest({
session: this.session,
jid: jid,
@@ -1759,7 +1766,7 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
}
public async getContact(query: ContactQuery) {
const jid = toJID(query.contactId);
const jid = normalizeJid(toJID(query.contactId));
const request = new messages.EntityByIdRequest({
session: this.session,
id: jid,
@@ -1832,7 +1839,7 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
public async findLIDByPhoneNumber(
phoneNumber: string,
): Promise<LidToPhoneNumber> {
const pn = toJID(phoneNumber);
const pn = normalizeJid(toJID(phoneNumber));
const request = new messages.EntityByIdRequest({
session: this.session,
id: pn,
@@ -1911,7 +1918,7 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
}
let jids = [];
if (filter?.ids && filter.ids.length > 0) {
jids = filter.ids.map((id) => toJID(id));
jids = filter.ids.map((id) => normalizeJid(toJID(id)));
}
const request = new messages.GetChatsRequest({
session: this.session,
@@ -1948,7 +1955,7 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
jid = null;
} else {
jid = new messages.OptionalString({
value: toJID(this.ensureSuffix(chatId)),
value: normalizeJid(toJID(this.ensureSuffix(chatId))),
});
}
@@ -2097,7 +2104,7 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
}
public async getChatLabels(chatId: string): Promise<Label[]> {
const jid = toJID(chatId);
const jid = normalizeJid(toJID(chatId));
const request = new messages.EntityByIdRequest({
session: this.session,
id: jid,
@@ -2109,7 +2116,7 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
@Activity()
public async chatsUnreadChat(chatId: string): Promise<any> {
const jid = toJID(this.ensureSuffix(chatId));
const jid = normalizeJid(toJID(this.ensureSuffix(chatId)));
const request = new messages.ChatUnreadRequest({
session: this.session,
jid: jid,
@@ -2121,7 +2128,7 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
@Activity()
public async putLabelsToChat(chatId: string, labels: LabelID[]) {
const jid = toJID(chatId);
const jid = normalizeJid(toJID(chatId));
const labelsIds = labels.map((label) => label.id);
const currentLabels = await this.getChatLabels(jid);
const currentLabelsIds = currentLabels.map((label) => label.id);
@@ -2189,6 +2196,16 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
const media = await this.downloadMediaSafe(message);
wamessage.media = media;
}
if (downloadMedia && wamessage.replyTo?.hasMedia) {
const msg = {
Message: wamessage.replyTo._data,
Info: {
Chat: message.Info.Chat,
ID: wamessage.replyTo.id || '',
},
};
wamessage.replyTo.media = await this.downloadMediaSafe(msg);
}
return wamessage;
}
@@ -2398,10 +2415,13 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
return null;
}
const body = extractBody(quotedMessage);
const mediaContent = extractMediaContent(quotedMessage);
return {
id: contextInfo.stanzaID,
participant: toCusFormat(contextInfo.participant),
body: body,
hasMedia: Boolean(mediaContent),
media: null,
_data: quotedMessage,
};
}
+45 -12
View File
@@ -1,5 +1,5 @@
import { Browsers, WABrowserDescription } from '@adiwajshing/baileys';
import makeWASocket, {
Browsers,
Chat,
Contact,
decryptPollVote,
@@ -19,6 +19,7 @@ import makeWASocket, {
PresenceData,
proto,
SocketConfig,
WABrowserDescription,
WAMessageContent,
WAMessageKey,
WAMessageUpdate,
@@ -183,13 +184,13 @@ import * as NodeCache from 'node-cache';
import {
filter,
fromEvent,
groupBy,
identity,
merge,
mergeAll,
mergeMap,
Observable,
partition,
groupBy,
share,
tap,
} from 'rxjs';
@@ -2420,13 +2421,16 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
shouldProcessIncomingMessage(message): boolean {
// if there is no text or media message
if (!message) return;
if (!message.message) return;
// View-once (self-destructing) messages arrive with key.isViewOnce=true but
// no message content (burned by sender). Allow them through so a webhook
// is still fired with key/timestamp metadata.
if (!message.message && !message.key?.isViewOnce) return;
// Ignore reactions, we have dedicated handler for that
if (message.message.reactionMessage) return;
if (message.message?.reactionMessage) return;
// Ignore poll votes, we have dedicated handler for that
if (message.message.pollUpdateMessage) return;
if (message.message?.pollUpdateMessage) return;
// Ignore calls, we have dedicated handler for that
if (message.message.call?.callKey) return;
if (message.message?.call?.callKey) return;
// Ignore revoke, we have a dedicated event for that
if (
message.message?.protocolMessage?.type ===
@@ -2479,9 +2483,24 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
return null;
}
// Media
if (downloadMedia) {
const media = await this.downloadMediaSafe(message);
wamessage.media = media;
if (downloadMedia && wamessage.hasMedia) {
wamessage.media = await this.downloadMediaSafe(message);
}
if (downloadMedia && wamessage.replyTo?.hasMedia) {
const mediaContent = extractMediaContent(wamessage.replyTo._data);
const m = {
message: wamessage.replyTo._data,
key: {
id:
wamessage.replyTo.id ||
mediaContent.fileSha256 ||
mediaContent.fileEncSha256 ||
mediaContent.mediaKeyTimestamp,
remoteJid: message.key.remoteJid,
},
};
wamessage.replyTo.media = await this.downloadMediaSafe(m);
}
return wamessage;
}
@@ -2530,6 +2549,7 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
}
protected extractReplyTo(message): ReplyToMessage | null {
if (!message) return null;
const msgType = getContentType(message);
const contextInfo = message[msgType]?.contextInfo;
if (!contextInfo) {
@@ -2540,10 +2560,15 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
return null;
}
const body = extractBody(quotedMessage);
const mediaContent = extractMediaContent(quotedMessage);
return {
id: contextInfo.stanzaId,
participant: toCusFormat(contextInfo.participant),
body: body,
// Media
hasMedia: Boolean(mediaContent),
media: null,
// Data
_data: quotedMessage,
};
}
@@ -2844,9 +2869,12 @@ export class NOWEBEngineMediaProcessor implements IMediaEngineProcessor<any> {
content.url = null;
}
return (await downloadMediaMessage(
// Use 'stream' mode instead of 'buffer' to fix 0-byte audio files
// 'buffer' mode silently returns empty buffer for audio/voice messages
// See: https://github.com/devlikeapro/waha/issues/1996
const stream = await downloadMediaMessage(
message,
'buffer',
'stream',
{},
{
logger: this.logger,
@@ -2855,7 +2883,12 @@ export class NOWEBEngineMediaProcessor implements IMediaEngineProcessor<any> {
).finally(() => {
// Set url back in case we removed it
content.url = url;
})) as Buffer;
});
const chunks: Buffer[] = [];
for await (const chunk of stream) {
chunks.push(chunk);
}
return Buffer.concat(chunks);
}
getFilename(message: any): string | null {
@@ -596,6 +596,9 @@ export class NowebPersistentStore implements INowebStore {
data = await this.messagesRepo.getById(id);
} else {
data = await this.messagesRepo.getByJidById(jid, id);
if (!data) {
data = await this.messagesRepo.getById(id);
}
}
if (!data) {
return null;
@@ -124,10 +124,17 @@ export class SqlMessagesMethods {
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]);
const knex = this.repository.getKnex();
// Use OR conditions on the raw jid column so the (jid, messageTimestamp) index
// can be used for both branches instead of a per-row CASE expression.
return query.where((builder) => {
builder
.where(`${tableName}.jid`, pnJid)
.orWhereIn(
`${tableName}.jid`,
knex.select('id').from('lid_map').where('pn', pnJid),
);
});
}
private async resolvePnJid(jid: string): Promise<string> {
@@ -148,9 +155,4 @@ export class SqlMessagesMethods {
.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`;
}
}
-21
View File
@@ -1,21 +0,0 @@
/**
* Expose a function to the page if it does not exist
*
* NOTE:
* Rewrite it to 'upsertFunction' after updating Puppeteer to 20.6 or higher
* using page.removeExposedFunction
* https://pptr.dev/api/puppeteer.page.removeExposedFunction
*
* @param {import(puppeteer).Page} page
* @param {string} name
* @param {Function} fn
*/
export async function exposeFunctionIfAbsent(page, name, fn) {
const exist = await page.evaluate((name) => {
return !!window[name];
}, name);
if (exist) {
return;
}
await page.exposeFunction(name, fn);
}
+124 -13
View File
@@ -1,4 +1,3 @@
import { exposeFunctionIfAbsent } from '@waha/core/engines/webjs/Puppeteer';
import { WebJSPresence } from '@waha/core/engines/webjs/types';
import { GetChatMessagesFilter } from '@waha/structures/chats.dto';
import { Label } from '@waha/structures/labels.dto';
@@ -26,6 +25,11 @@ const { LoadPaginator } = require('./_Paginator.js');
// eslint-disable-next-line @typescript-eslint/no-var-requires
const ChatFactory = require('whatsapp-web.js/src/factories/ChatFactory');
// eslint-disable-next-line @typescript-eslint/no-var-requires
const {
exposeFunctionIfAbsent,
} = require('whatsapp-web.js/src/util/Puppeteer');
export class WebjsClientCore extends Client {
public events = new EventEmitter();
private wpage: WPage = null;
@@ -300,25 +304,132 @@ export class WebjsClientCore extends Client {
// @ts-ignore
const chat = await window.WWebJS.getChat(chatId, { getAsModel: false });
let msgs = chat.msgs.getModelsArray().filter(msgFilter);
if (!chat) return [];
while (msgs.length < pagination.limit + pagination.offset) {
const loadedMessages =
let msgs = [];
// Try new WhatsApp API (>= 2.3000.1034162388) which replaced loadEarlierMsgs
// @ts-ignore
const WAWebDBMessageFindLocal = window.require(
'WAWebDBMessageFindLocal',
);
if (WAWebDBMessageFindLocal?.msgFindByDirection) {
const BATCH_SIZE = 20;
// Construct the initial anchor the same way wa-js does:
// serialize to string then reconstruct via MsgKey.fromString so the
// object has the exact shape msgFindByDirection expects.
const lastReceivedSerialized = chat.lastReceivedKey?._serialized;
if (!lastReceivedSerialized) return [];
// @ts-ignore
let currentAnchorKey = window.Store.MsgKey.fromString(
lastReceivedSerialized,
);
// msgFindByDirection is exclusive of the anchor; include the anchor
// message itself (the most recent message in the chat) upfront
// @ts-ignore
const anchorMsg = window.Store.Msg.get(lastReceivedSerialized);
if (anchorMsg) {
msgs.push(anchorMsg);
}
const neededFiltered = Number.isFinite(
pagination.limit + pagination.offset,
)
? pagination.limit + pagination.offset
: Infinity;
// @ts-ignore
const toModel = (m) => {
if (m && typeof m.serialize === 'function') return m;
const serializedId = m?.id?._serialized;
if (serializedId) {
// @ts-ignore
const stored = window.Store.Msg.get(serializedId);
if (stored) return stored;
}
// @ts-ignore
await window.Store.ConversationMsgs.loadEarlierMsgs(chat);
if (!loadedMessages || loadedMessages.length == 0) break;
return new window.Store.Msg.modelClass(m);
};
msgs = [...loadedMessages.filter(msgFilter), ...msgs];
msgs = msgs.sort((a, b) => b.t - a.t);
while (true) {
const result = await WAWebDBMessageFindLocal.msgFindByDirection({
anchor: currentAnchorKey,
count: BATCH_SIZE,
direction: 'before',
});
// Check if the earliest message is already outside the timerange filter
const earliest = msgs[msgs.length - 1];
if (earliest.t < (filter['filter.timestamp.gte'] || Infinity)) {
// Add only messages that pass the filter and stop loading more
break;
const batch = Array.isArray(result)
? result
: result?.messages || [];
if (!batch || batch.length === 0) break;
const batchModels = batch.map(toModel);
// msgFindByDirection returns newest-first (descending) so prepend
// to keep the accumulated list in approximate ascending order
msgs = [...batchModels, ...msgs];
// Deduplicate by serialized id — the same Backbone model object can
// appear in multiple batches when anchors overlap
const seenIds = new Set();
msgs = msgs.filter((m) => {
const sid = m?.id?._serialized;
if (!sid || seenIds.has(sid)) return false;
seenIds.add(sid);
return true;
});
// Stop once we have enough messages that pass the filter
if (msgs.filter(msgFilter).length >= neededFiltered) break;
// Stop if the oldest message in this batch is already before the
// lower timestamp bound - no earlier messages can be in range
if (filter['filter.timestamp.gte'] != null) {
const batchMinT = batchModels.reduce(
(min, m) => Math.min(min, m.t ?? Infinity),
Infinity,
);
if (batchMinT < filter['filter.timestamp.gte']) break;
}
// No more messages available
if (batch.length < BATCH_SIZE) break;
// msgFindByDirection returns newest-first, so the LAST element is
// the oldest message in this batch — use it as the next anchor to
// walk further back in history without overlap
const oldestInBatch = batchModels[batchModels.length - 1];
const oldestSerialized = oldestInBatch?.id?._serialized;
if (!oldestSerialized) break;
// @ts-ignore
currentAnchorKey = window.Store.MsgKey.fromString(oldestSerialized);
}
} else {
// Legacy fallback: loadEarlierMsgs loop
msgs = chat.msgs.getModelsArray();
while (msgs.length < pagination.limit + pagination.offset) {
const loadedMessages =
// @ts-ignore
await window.Store.ConversationMsgs.loadEarlierMsgs(
chat,
chat.msgs,
);
if (!loadedMessages || loadedMessages.length == 0) break;
msgs = [...loadedMessages, ...msgs];
msgs = msgs.sort((a, b) => b.t - a.t);
const earliest = msgs[msgs.length - 1];
if (earliest.t < (filter['filter.timestamp.gte'] || Infinity)) {
break;
}
}
}
msgs = msgs.filter(msgFilter);
// Always sort newest first before applying the pagination window.
msgs = msgs.sort((a, b) => b.t - a.t);
+72 -6
View File
@@ -173,8 +173,10 @@ import {
import { WAJSPresenceChatStateType, WebJSPresence } from './types';
import {
isJidCus,
isJidGroup,
isJidStatusBroadcast,
isLidUser,
normalizeJid,
toCusFormat,
} from '@waha/core/utils/jids';
@@ -205,6 +207,7 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
private engineStateCheckDelayedJob: SingleDelayedJobRunner;
private shouldRestart: boolean;
private lastQRDate: Date = null;
private static readonly REACTION_MAX_AGE_MS = 2 * 24 * 60 * 60 * 1000;
whatsapp: WebjsClientCore;
protected qr: QR;
@@ -1028,7 +1031,50 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
messageId: string,
query: GetChatMessageQuery,
): Promise<null | WAMessage> {
const message = await this.whatsapp.getMessageById(messageId);
chatId = this.ensureSuffix(chatId);
// WEBJS waits the serializer messageId
// {fromMe}_{chatId}_{id}[_{participant}]
if (isJidStatusBroadcast(chatId) && !messageId.includes('_')) {
// For status - resolve it as "my" if no details provided
const me = this.getSessionMeInfo();
const lid = me.lid || (await this.whatsapp.findLIDByPhoneNumber(me.id));
messageId = SerializeMessageKey({
fromMe: true,
id: messageId,
remoteJid: Jid.BROADCAST,
participant: lid,
});
}
let message: Message | null = null;
if (isLidUser(chatId) || isJidCus(chatId)) {
// If in DM chats and not fromMe specified - try both fromMe and not
if (!messageId.includes('_')) {
// FromMe - true
if (!message) {
const id = SerializeMessageKey({
fromMe: true,
id: messageId,
remoteJid: chatId,
});
message = await this.whatsapp.getMessageById(id);
}
// FromMe - false
if (!message) {
const id = SerializeMessageKey({
fromMe: false,
id: messageId,
remoteJid: chatId,
});
message = await this.whatsapp.getMessageById(id);
}
}
}
if (!message) {
message = await this.whatsapp.getMessageById(messageId);
}
if (!message) return null;
if (
isJidGroup(message.id.remote) ||
@@ -1946,6 +1992,12 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
const media = await this.downloadMediaSafe(message);
wamessage.media = media;
}
if (downloadMedia && wamessage.replyTo?.hasMedia) {
const quotedMessage = await message.getQuotedMessage().catch(() => null);
if (quotedMessage) {
wamessage.replyTo.media = await this.downloadMediaSafe(quotedMessage);
}
}
return wamessage;
}
@@ -1975,6 +2027,14 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
}
}
// Ignore reactions older than 2 days to prevent stale reactions
// when session reconnects and replays buffered events
const twoDaysAgoSec =
(Date.now() - WhatsappSessionWebJSCore.REACTION_MAX_AGE_MS) / 1000;
if (reaction.timestamp < twoDaysAgoSec) {
return null;
}
const source = this.getMessageSource(reaction.id.id);
return {
id: reaction.id._serialized,
@@ -2100,15 +2160,21 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
}
protected extractReplyTo(message: Message): ReplyToMessage | null {
const rawData: any = message.rawData;
// @ts-ignore
const quotedMsg = message.rawData?.quotedMsg;
if (!quotedMsg) {
const quotedMsg = rawData?.quotedMsg;
const quotedStanzaId = rawData?.quotedStanzaID || rawData?.quotedStanzaId;
if (!quotedMsg && !quotedStanzaId) {
return;
}
const quotedParticipant =
rawData?.quotedParticipant || quotedMsg?.author || quotedMsg?.from;
return {
id: quotedMsg.id?.id,
participant: quotedMsg.author || quotedMsg.from,
body: quotedMsg.caption || quotedMsg.body,
id: quotedStanzaId || quotedMsg?.id?.id,
participant: quotedParticipant,
body: quotedMsg?.caption || quotedMsg?.body,
hasMedia: Boolean(quotedMsg?.directPath),
media: null,
_data: quotedMsg,
};
}
+10 -1
View File
@@ -1091,7 +1091,7 @@ export class WhatsappSessionWPPCore extends WhatsappSession {
//
@Activity()
public createGroup(request: CreateGroupRequest) {
public createGroup(request: CreateGroupRequest): Promise<any> {
const participants = request.participants.map((participant) =>
this.ensureSuffix(participant.id),
);
@@ -2089,12 +2089,15 @@ export class WhatsappSessionWPPCore extends WhatsappSession {
quotedMessage?.author ||
quotedMessage?.from ||
null;
const hasMedia = getHasMedia(quotedMessage);
return {
id: quotedMessageId,
participant: toCusFormat(
Deserialized(quotedParticipant) || quotedParticipant,
),
body: getMessageBody(quotedMessage),
hasMedia: hasMedia,
media: null,
_data: quotedMessage,
};
}
@@ -2105,6 +2108,12 @@ export class WhatsappSessionWPPCore extends WhatsappSession {
const media = await this.downloadMediaSafe(message);
wamessage.media = media;
}
if (downloadMedia && wamessage.replyTo?.hasMedia) {
const quotedMessage = message?.quotedMsg || message?._data?.quotedMsg;
if (quotedMessage) {
wamessage.replyTo.media = await this.downloadMediaSafe(quotedMessage);
}
}
return wamessage;
}
+2 -2
View File
@@ -17,8 +17,8 @@ export class MediaManager implements IMediaManager {
// https://github.com/IndigoUnited/node-promise-retry
RETRY_OPTIONS = {
retries: 5,
minTimeout: 100,
maxTimeout: 500,
minTimeout: 1000,
maxTimeout: 3000,
};
constructor(
+31 -13
View File
@@ -5,7 +5,7 @@ import * as path from 'path';
import { Logger } from 'pino';
import fs = require('fs');
import { fileExists } from '@waha/utils/files';
import { deleteAsync } from 'del';
import { setLongTimeout } from '@waha/utils/promiseTimeout';
// eslint-disable-next-line @typescript-eslint/no-var-requires
const writeFileAtomic = require('write-file-atomic');
@@ -58,16 +58,33 @@ export class MediaLocalStorage implements IMediaStorage {
return;
}
if (fs.existsSync(this.filesFolder)) {
deleteAsync([`${this.filesFolder}/*`], { force: true }).then((paths) => {
if (paths.length === 0) {
return;
}
this.log.info('Deleted files and directories:\n', paths.join('\n'));
});
} else {
if (!fs.existsSync(this.filesFolder)) {
fs.mkdirSync(this.filesFolder);
this.log.info(`Directory '${this.filesFolder}' created from scratch`);
return;
}
const now = Date.now();
const entries = await fsp.readdir(this.filesFolder, { recursive: true });
for (const entry of entries) {
try {
const filepath = path.join(this.filesFolder, entry as string);
const stat = await fsp.stat(filepath);
if (!stat.isFile()) {
continue;
}
// Schedule removal:
// - expired files get a random jitter to spread deletions out,
// - non-expired files are scheduled for the time they have left.
const age = now - stat.mtimeMs;
const expired = age >= this.lifetimeMs;
const left = this.lifetimeMs - age;
const jitter = Math.random() * 10 * SECOND;
const remaining = expired ? jitter : left;
this.postponeRemoval(filepath, remaining);
} catch (err) {
this.log.warn(`Failed to schedule removal for file ${entry}`, err);
}
}
}
@@ -80,16 +97,17 @@ export class MediaLocalStorage implements IMediaStorage {
return path.resolve(`${this.filesFolder}/${filepath}`);
}
private postponeRemoval(filepath: string) {
private postponeRemoval(filepath: string, delayMs: number | null = null) {
if (this.lifetimeMs === 0) {
return;
}
setTimeout(
const delay = delayMs ?? this.lifetimeMs;
setLongTimeout(
() =>
fs.unlink(filepath, () => {
this.log.info(`File ${filepath} was removed`);
this.log.debug(`File ${filepath} was removed`);
}),
this.lifetimeMs,
delay,
);
}
+9 -16
View File
@@ -20,25 +20,18 @@ describe('E164Parser.fromJid', () => {
expect(E164Parser.fromJid('@s.whatsapp.net')).toBeNull();
});
it('applies Brazil add-9 rule when local has 8 digits (any first digit)', () => {
// +55 <DDD:2> <local:8>
expect(E164Parser.fromJid('553188888888@s.whatsapp.net')).toBe(
'+5531988888888',
it('does not add 9 for BR landline (local starts with 2..5)', () => {
expect(E164Parser.fromJid('558540423147@s.whatsapp.net')).toBe(
'+558540423147',
);
expect(E164Parser.fromJid('558820181896@s.whatsapp.net')).toBe(
'+558820181896',
);
});
it('554999111111 => 5549999111111', () => {
expect(E164Parser.fromJid('554999111111@s.whatsapp.net')).toBe(
'+5549999111111',
);
});
it('adds 9 even when local already starts with 9 (Brazil)', () => {
expect(E164Parser.fromJid('553198888888@s.whatsapp.net')).toBe(
'+5531998888888',
);
expect(E164Parser.fromJid('553199999999@s.whatsapp.net')).toBe(
'+5531999999999',
it('adds 9 for BR mobile/others (local not starting with 2..5)', () => {
expect(E164Parser.fromJid('558591203123@s.whatsapp.net')).toBe(
'+5585991203123',
);
});
+9 -1
View File
@@ -56,7 +56,15 @@ export class PhoneJidNormalizer {
}
const RULES = [
// Brazil - ensure mobile numbers have 9 digits after DDD
// Brazil landline (fixed line) heuristic:
// +55 <DDD:2> <local:8> where local[0] is 2..5 => do NOT add extra 9
{
name: 'br-no-add-9-for-landline',
re: /^\+55(\d{2})([2-5]\d{7})$/,
replace: '+55$1$2',
},
// Brazil mobile/others:
// +55 <DDD:2> <local:8> => add extra 9 after DDD
{
name: 'br-add-9-after-ddd',
re: /^\+55(\d{2})(\d{8})$/,
+9
View File
@@ -3,6 +3,7 @@
*/
import { normalizeMessageContent, proto } from '@adiwajshing/baileys';
import esm from '@waha/vendor/esm';
export function IsEditedMessage(message: proto.IMessage): boolean {
message = normalizeMessageContent(message);
@@ -37,3 +38,11 @@ export function IsHistorySyncNotification(message: proto.IMessage): boolean {
}
return true;
}
export function getContextInfo(
protoMessage: proto.Message | null,
): proto.IContextInfo | null {
const type = esm.b.getContentType(protoMessage);
const message = protoMessage[type] as any;
return message?.contextInfo;
}
+11
View File
@@ -3,6 +3,7 @@ import {
ChatIdProperty,
MessageIdOnlyProperty,
} from '@waha/structures/properties.dto';
import { WAMedia } from '@waha/structures/media.dto';
export class ReplyToMessage {
@MessageIdOnlyProperty()
@@ -16,6 +17,16 @@ export class ReplyToMessage {
})
body?: string;
@ApiProperty({
description: 'Indicates if the message has media available for download',
})
hasMedia: boolean;
@ApiProperty({
description: 'Media object for the message if any and downloaded',
})
media?: WAMedia;
@ApiProperty({
description: "Raw data from reply's message",
})
+2 -1
View File
@@ -1,6 +1,7 @@
import axios from 'axios';
import { Agent as HttpsAgent } from 'https';
import UserAgent from 'user-agents';
// @ts-ignore
import * as UserAgent from 'user-agents';
const InsecureHttpsAgent = new HttpsAgent({
rejectUnauthorized: false,
+13
View File
@@ -53,3 +53,16 @@ export async function waitUntil(
}
return result;
}
/**
* Handle setTimeout 32-bit overflow
*/
export function setLongTimeout(callback, delayMs) {
const MAX_DELAY = 2_147_483_647; // ~24.8 days
if (delayMs > MAX_DELAY) {
setTimeout(() => setLongTimeout(callback, delayMs - MAX_DELAY), MAX_DELAY);
} else {
setTimeout(callback, delayMs);
}
}
+8 -3
View File
@@ -1,7 +1,10 @@
// eslint-disable-next-line @typescript-eslint/no-var-requires
import { getEngineName } from '@waha/config';
import { getBrowserExecutablePath } from './core/abc/session.abc';
import {
getBrowserExecutablePath,
isChromeExecutablePath,
} from './core/abc/session.browser';
import { WAHAEngine } from './structures/enums.dto';
import { WAHAEnvironment } from './structures/environment.dto';
@@ -45,7 +48,7 @@ function getPlatform() {
}
export const VERSION: WAHAEnvironment = {
version: '2026.3.1',
version: '2026.4.2',
engine: getEngineName(),
tier: getWAHAVersion(),
browser: getBrowser(),
@@ -53,6 +56,8 @@ export const VERSION: WAHAEnvironment = {
worker: getWorker(),
};
export const IsChrome = VERSION.browser?.includes('chrome');
export const IsChrome = VERSION.browser
? isChromeExecutablePath(VERSION.browser)
: false;
export { getEngineName };
+2 -2
View File
@@ -2,11 +2,11 @@
"waha": {
"gows": {
"repo": "devlikeapro/gows",
"ref": "v1.0.34"
"ref": "v1.0.37"
},
"dashboard": {
"repo": "devlikeapro/dashboard",
"ref": "24d3aca7113688eb2abaad9ff3716cdff540ae59"
"ref": "9ea5668d6052cd0b1e68c6b6ba58b6f161b14907"
}
}
}
+45 -317
View File
@@ -7,7 +7,7 @@ __metadata:
"@adiwajshing/baileys@github:devlikeapro/Baileys#fork-master-2026-02-11":
version: 7.0.0-rc.9
resolution: "@adiwajshing/baileys@https://github.com/devlikeapro/Baileys.git#commit=6d0f3ae29b8d810b1d1a2bb1901764c653714074"
resolution: "@adiwajshing/baileys@https://github.com/devlikeapro/Baileys.git#commit=92a9f60f6ab1eeb93ee9957e536f2e355f859189"
dependencies:
"@cacheable/node-cache": "npm:^1.4.0"
"@hapi/boom": "npm:^9.1.3"
@@ -32,7 +32,7 @@ __metadata:
optional: true
link-preview-js:
optional: true
checksum: 10/4dc784f0c27f08f5d1f96764da65e4d35692bfffea26460384e0c0f9ce38e8a4b8f89f76efc0ac14bb1c1514d3a9832d7e2bb22a14a94d8aa12fc4fb7ee3c75f
checksum: 10/0ff00980c1abf919de39170082677b71a774b1b4dc1d748556fb696d67ed68540b0d5c018ba6bfbfae4b7af6eea0e8c705f06b44248538ce0e1016fd35e676e3
languageName: node
linkType: hard
@@ -1286,15 +1286,6 @@ __metadata:
languageName: node
linkType: hard
"@emnapi/runtime@npm:^1.2.0":
version: 1.4.3
resolution: "@emnapi/runtime@npm:1.4.3"
dependencies:
tslib: "npm:^2.4.0"
checksum: 10/4f90852a1a5912982cc4e176b6420556971bcf6a85ee23e379e2455066d616219751367dcf43e6a6eaf41ea7e95ba9dc830665a52b5d979dfe074237d19578f8
languageName: node
linkType: hard
"@emnapi/runtime@npm:^1.7.0":
version: 1.8.1
resolution: "@emnapi/runtime@npm:1.8.1"
@@ -1383,18 +1374,6 @@ __metadata:
languageName: node
linkType: hard
"@img/sharp-darwin-arm64@npm:0.33.5":
version: 0.33.5
resolution: "@img/sharp-darwin-arm64@npm:0.33.5"
dependencies:
"@img/sharp-libvips-darwin-arm64": "npm:1.0.4"
dependenciesMeta:
"@img/sharp-libvips-darwin-arm64":
optional: true
conditions: os=darwin & cpu=arm64
languageName: node
linkType: hard
"@img/sharp-darwin-arm64@npm:0.34.5":
version: 0.34.5
resolution: "@img/sharp-darwin-arm64@npm:0.34.5"
@@ -1407,18 +1386,6 @@ __metadata:
languageName: node
linkType: hard
"@img/sharp-darwin-x64@npm:0.33.5":
version: 0.33.5
resolution: "@img/sharp-darwin-x64@npm:0.33.5"
dependencies:
"@img/sharp-libvips-darwin-x64": "npm:1.0.4"
dependenciesMeta:
"@img/sharp-libvips-darwin-x64":
optional: true
conditions: os=darwin & cpu=x64
languageName: node
linkType: hard
"@img/sharp-darwin-x64@npm:0.34.5":
version: 0.34.5
resolution: "@img/sharp-darwin-x64@npm:0.34.5"
@@ -1431,13 +1398,6 @@ __metadata:
languageName: node
linkType: hard
"@img/sharp-libvips-darwin-arm64@npm:1.0.4":
version: 1.0.4
resolution: "@img/sharp-libvips-darwin-arm64@npm:1.0.4"
conditions: os=darwin & cpu=arm64
languageName: node
linkType: hard
"@img/sharp-libvips-darwin-arm64@npm:1.2.4":
version: 1.2.4
resolution: "@img/sharp-libvips-darwin-arm64@npm:1.2.4"
@@ -1445,13 +1405,6 @@ __metadata:
languageName: node
linkType: hard
"@img/sharp-libvips-darwin-x64@npm:1.0.4":
version: 1.0.4
resolution: "@img/sharp-libvips-darwin-x64@npm:1.0.4"
conditions: os=darwin & cpu=x64
languageName: node
linkType: hard
"@img/sharp-libvips-darwin-x64@npm:1.2.4":
version: 1.2.4
resolution: "@img/sharp-libvips-darwin-x64@npm:1.2.4"
@@ -1459,13 +1412,6 @@ __metadata:
languageName: node
linkType: hard
"@img/sharp-libvips-linux-arm64@npm:1.0.4":
version: 1.0.4
resolution: "@img/sharp-libvips-linux-arm64@npm:1.0.4"
conditions: os=linux & cpu=arm64 & libc=glibc
languageName: node
linkType: hard
"@img/sharp-libvips-linux-arm64@npm:1.2.4":
version: 1.2.4
resolution: "@img/sharp-libvips-linux-arm64@npm:1.2.4"
@@ -1473,13 +1419,6 @@ __metadata:
languageName: node
linkType: hard
"@img/sharp-libvips-linux-arm@npm:1.0.5":
version: 1.0.5
resolution: "@img/sharp-libvips-linux-arm@npm:1.0.5"
conditions: os=linux & cpu=arm & libc=glibc
languageName: node
linkType: hard
"@img/sharp-libvips-linux-arm@npm:1.2.4":
version: 1.2.4
resolution: "@img/sharp-libvips-linux-arm@npm:1.2.4"
@@ -1501,13 +1440,6 @@ __metadata:
languageName: node
linkType: hard
"@img/sharp-libvips-linux-s390x@npm:1.0.4":
version: 1.0.4
resolution: "@img/sharp-libvips-linux-s390x@npm:1.0.4"
conditions: os=linux & cpu=s390x & libc=glibc
languageName: node
linkType: hard
"@img/sharp-libvips-linux-s390x@npm:1.2.4":
version: 1.2.4
resolution: "@img/sharp-libvips-linux-s390x@npm:1.2.4"
@@ -1515,13 +1447,6 @@ __metadata:
languageName: node
linkType: hard
"@img/sharp-libvips-linux-x64@npm:1.0.4":
version: 1.0.4
resolution: "@img/sharp-libvips-linux-x64@npm:1.0.4"
conditions: os=linux & cpu=x64 & libc=glibc
languageName: node
linkType: hard
"@img/sharp-libvips-linux-x64@npm:1.2.4":
version: 1.2.4
resolution: "@img/sharp-libvips-linux-x64@npm:1.2.4"
@@ -1529,13 +1454,6 @@ __metadata:
languageName: node
linkType: hard
"@img/sharp-libvips-linuxmusl-arm64@npm:1.0.4":
version: 1.0.4
resolution: "@img/sharp-libvips-linuxmusl-arm64@npm:1.0.4"
conditions: os=linux & cpu=arm64 & libc=musl
languageName: node
linkType: hard
"@img/sharp-libvips-linuxmusl-arm64@npm:1.2.4":
version: 1.2.4
resolution: "@img/sharp-libvips-linuxmusl-arm64@npm:1.2.4"
@@ -1543,13 +1461,6 @@ __metadata:
languageName: node
linkType: hard
"@img/sharp-libvips-linuxmusl-x64@npm:1.0.4":
version: 1.0.4
resolution: "@img/sharp-libvips-linuxmusl-x64@npm:1.0.4"
conditions: os=linux & cpu=x64 & libc=musl
languageName: node
linkType: hard
"@img/sharp-libvips-linuxmusl-x64@npm:1.2.4":
version: 1.2.4
resolution: "@img/sharp-libvips-linuxmusl-x64@npm:1.2.4"
@@ -1557,18 +1468,6 @@ __metadata:
languageName: node
linkType: hard
"@img/sharp-linux-arm64@npm:0.33.5":
version: 0.33.5
resolution: "@img/sharp-linux-arm64@npm:0.33.5"
dependencies:
"@img/sharp-libvips-linux-arm64": "npm:1.0.4"
dependenciesMeta:
"@img/sharp-libvips-linux-arm64":
optional: true
conditions: os=linux & cpu=arm64 & libc=glibc
languageName: node
linkType: hard
"@img/sharp-linux-arm64@npm:0.34.5":
version: 0.34.5
resolution: "@img/sharp-linux-arm64@npm:0.34.5"
@@ -1581,18 +1480,6 @@ __metadata:
languageName: node
linkType: hard
"@img/sharp-linux-arm@npm:0.33.5":
version: 0.33.5
resolution: "@img/sharp-linux-arm@npm:0.33.5"
dependencies:
"@img/sharp-libvips-linux-arm": "npm:1.0.5"
dependenciesMeta:
"@img/sharp-libvips-linux-arm":
optional: true
conditions: os=linux & cpu=arm & libc=glibc
languageName: node
linkType: hard
"@img/sharp-linux-arm@npm:0.34.5":
version: 0.34.5
resolution: "@img/sharp-linux-arm@npm:0.34.5"
@@ -1629,18 +1516,6 @@ __metadata:
languageName: node
linkType: hard
"@img/sharp-linux-s390x@npm:0.33.5":
version: 0.33.5
resolution: "@img/sharp-linux-s390x@npm:0.33.5"
dependencies:
"@img/sharp-libvips-linux-s390x": "npm:1.0.4"
dependenciesMeta:
"@img/sharp-libvips-linux-s390x":
optional: true
conditions: os=linux & cpu=s390x & libc=glibc
languageName: node
linkType: hard
"@img/sharp-linux-s390x@npm:0.34.5":
version: 0.34.5
resolution: "@img/sharp-linux-s390x@npm:0.34.5"
@@ -1653,18 +1528,6 @@ __metadata:
languageName: node
linkType: hard
"@img/sharp-linux-x64@npm:0.33.5":
version: 0.33.5
resolution: "@img/sharp-linux-x64@npm:0.33.5"
dependencies:
"@img/sharp-libvips-linux-x64": "npm:1.0.4"
dependenciesMeta:
"@img/sharp-libvips-linux-x64":
optional: true
conditions: os=linux & cpu=x64 & libc=glibc
languageName: node
linkType: hard
"@img/sharp-linux-x64@npm:0.34.5":
version: 0.34.5
resolution: "@img/sharp-linux-x64@npm:0.34.5"
@@ -1677,18 +1540,6 @@ __metadata:
languageName: node
linkType: hard
"@img/sharp-linuxmusl-arm64@npm:0.33.5":
version: 0.33.5
resolution: "@img/sharp-linuxmusl-arm64@npm:0.33.5"
dependencies:
"@img/sharp-libvips-linuxmusl-arm64": "npm:1.0.4"
dependenciesMeta:
"@img/sharp-libvips-linuxmusl-arm64":
optional: true
conditions: os=linux & cpu=arm64 & libc=musl
languageName: node
linkType: hard
"@img/sharp-linuxmusl-arm64@npm:0.34.5":
version: 0.34.5
resolution: "@img/sharp-linuxmusl-arm64@npm:0.34.5"
@@ -1701,18 +1552,6 @@ __metadata:
languageName: node
linkType: hard
"@img/sharp-linuxmusl-x64@npm:0.33.5":
version: 0.33.5
resolution: "@img/sharp-linuxmusl-x64@npm:0.33.5"
dependencies:
"@img/sharp-libvips-linuxmusl-x64": "npm:1.0.4"
dependenciesMeta:
"@img/sharp-libvips-linuxmusl-x64":
optional: true
conditions: os=linux & cpu=x64 & libc=musl
languageName: node
linkType: hard
"@img/sharp-linuxmusl-x64@npm:0.34.5":
version: 0.34.5
resolution: "@img/sharp-linuxmusl-x64@npm:0.34.5"
@@ -1725,15 +1564,6 @@ __metadata:
languageName: node
linkType: hard
"@img/sharp-wasm32@npm:0.33.5":
version: 0.33.5
resolution: "@img/sharp-wasm32@npm:0.33.5"
dependencies:
"@emnapi/runtime": "npm:^1.2.0"
conditions: cpu=wasm32
languageName: node
linkType: hard
"@img/sharp-wasm32@npm:0.34.5":
version: 0.34.5
resolution: "@img/sharp-wasm32@npm:0.34.5"
@@ -1750,13 +1580,6 @@ __metadata:
languageName: node
linkType: hard
"@img/sharp-win32-ia32@npm:0.33.5":
version: 0.33.5
resolution: "@img/sharp-win32-ia32@npm:0.33.5"
conditions: os=win32 & cpu=ia32
languageName: node
linkType: hard
"@img/sharp-win32-ia32@npm:0.34.5":
version: 0.34.5
resolution: "@img/sharp-win32-ia32@npm:0.34.5"
@@ -1764,13 +1587,6 @@ __metadata:
languageName: node
linkType: hard
"@img/sharp-win32-x64@npm:0.33.5":
version: 0.33.5
resolution: "@img/sharp-win32-x64@npm:0.33.5"
conditions: os=win32 & cpu=x64
languageName: node
linkType: hard
"@img/sharp-win32-x64@npm:0.34.5, @img/sharp-win32-x64@npm:^0.34.5":
version: 0.34.5
resolution: "@img/sharp-win32-x64@npm:0.34.5"
@@ -4575,13 +4391,13 @@ __metadata:
languageName: node
linkType: hard
"@wppconnect-team/wppconnect@npm:^1.40.1":
version: 1.40.1
resolution: "@wppconnect-team/wppconnect@npm:1.40.1"
"@wppconnect-team/wppconnect@npm:^1.41.1":
version: 1.41.1
resolution: "@wppconnect-team/wppconnect@npm:1.41.1"
dependencies:
"@img/sharp-win32-x64": "npm:^0.34.5"
"@wppconnect/wa-js": "npm:^3.22.1"
"@wppconnect/wa-version": "npm:^1.5.3099"
"@wppconnect/wa-js": "npm:^3.23.2"
"@wppconnect/wa-version": "npm:^1.5.3379"
atob: "npm:^2.1.2"
axios: "npm:^1.13.5"
boxen: "npm:^5.1.2"
@@ -4615,24 +4431,31 @@ __metadata:
optional: true
fsevents:
optional: true
checksum: 10/561706bc5a4ba38c20f7a641f6cbdcaf82e8ef9cae7a74eda7cbf21cea2f69be8ce26913f033d43dbfffef23544846b3ab7fb47ac19d33b98efdd624ee7639ff
checksum: 10/b3eaadb669819bc5b380f5b18365c4b4e72569062790764b6d9b1b063351cd4b1b1e904a76f1e3f1b7a3b51e81aa5bbda116870086023c6c4360b64de560bcb3
languageName: node
linkType: hard
"@wppconnect/wa-js@npm:^3.22.1":
version: 3.22.1
resolution: "@wppconnect/wa-js@npm:3.22.1"
checksum: 10/9315d28a22814a695301ceba19943b13e204f473663d800ebc69d437dec6b379294c606db279411b12cdee92e7496e4e0bc4790862b9e33cedb3b94274670f69
"@wppconnect/wa-js@npm:^3.23.2":
version: 3.23.2
resolution: "@wppconnect/wa-js@npm:3.23.2"
checksum: 10/68ec7e73d3c0dc078956890941988645cc585dda6f63426f27fbd01e0c0e47f56f3bffa61469a486c9e93f9cff9ae56ab8ec5915a9a7c51fa6327c026885784a
languageName: node
linkType: hard
"@wppconnect/wa-version@npm:^1.5.3099":
version: 1.5.3336
resolution: "@wppconnect/wa-version@npm:1.5.3336"
"@wppconnect/wa-js@npm:^3.23.3":
version: 3.23.3
resolution: "@wppconnect/wa-js@npm:3.23.3"
checksum: 10/8b4407f3f90f781f63800b1023943ff1978ced614c4f9b7075e6470d5feac8176efe25a44b327234c5d2d265b2cf4f490c3fa09dea63720a94be6489cf98cd30
languageName: node
linkType: hard
"@wppconnect/wa-version@npm:^1.5.3379":
version: 1.5.3523
resolution: "@wppconnect/wa-version@npm:1.5.3523"
dependencies:
node-fetch: "npm:^2.7.0"
semver: "npm:^7.7.3"
checksum: 10/e2ae0b0ddb8cf1d4d5354a8017dfcc3d7506122105c8ff97332454928b4883e7c2b70c7b8e12e0abe298e3c6ed97b782abd80d0502864e6eb872c23c8398c947
semver: "npm:^7.7.4"
checksum: 10/b17403a96bae203db9db672c39f243702b979dc9da5a9c23082a9c0b63d67aa8e5e374aeca0ed24817fbd45eb9e906431753199ddb15decb77a9e4c67d624341
languageName: node
linkType: hard
@@ -6086,13 +5909,6 @@ __metadata:
languageName: node
linkType: hard
"color-name@npm:^1.0.0, color-name@npm:~1.1.4":
version: 1.1.4
resolution: "color-name@npm:1.1.4"
checksum: 10/b0445859521eb4021cd0fb0cc1a75cecf67fceecae89b63f62b201cca8d345baf8b952c966862a9d9a2632987d4f6581f0ec8d957dfacece86f0a7919316f610
languageName: node
linkType: hard
"color-name@npm:^2.0.0":
version: 2.1.0
resolution: "color-name@npm:2.1.0"
@@ -6100,13 +5916,10 @@ __metadata:
languageName: node
linkType: hard
"color-string@npm:^1.9.0":
version: 1.9.1
resolution: "color-string@npm:1.9.1"
dependencies:
color-name: "npm:^1.0.0"
simple-swizzle: "npm:^0.2.2"
checksum: 10/72aa0b81ee71b3f4fb1ac9cd839cdbd7a011a7d318ef58e6cb13b3708dca75c7e45029697260488709f1b1c7ac4e35489a87e528156c1e365917d1c4ccb9b9cd
"color-name@npm:~1.1.4":
version: 1.1.4
resolution: "color-name@npm:1.1.4"
checksum: 10/b0445859521eb4021cd0fb0cc1a75cecf67fceecae89b63f62b201cca8d345baf8b952c966862a9d9a2632987d4f6581f0ec8d957dfacece86f0a7919316f610
languageName: node
linkType: hard
@@ -6128,16 +5941,6 @@ __metadata:
languageName: node
linkType: hard
"color@npm:^4.2.3":
version: 4.2.3
resolution: "color@npm:4.2.3"
dependencies:
color-convert: "npm:^2.0.1"
color-string: "npm:^1.9.0"
checksum: 10/b23f5e500a79ea22428db43d1a70642d983405c0dd1f95ef59dbdb9ba66afbb4773b334fa0b75bb10b0552fd7534c6b28d4db0a8b528f91975976e70973c0152
languageName: node
linkType: hard
"color@npm:^5.0.2":
version: 5.0.3
resolution: "color@npm:5.0.3"
@@ -6637,7 +6440,7 @@ __metadata:
languageName: node
linkType: hard
"detect-libc@npm:^2.0.0, detect-libc@npm:^2.0.1, detect-libc@npm:^2.0.3":
"detect-libc@npm:^2.0.0, detect-libc@npm:^2.0.1":
version: 2.0.4
resolution: "detect-libc@npm:2.0.4"
checksum: 10/136e995f8c5ffbc515955b0175d441b967defd3d5f2268e89fa695e9c7170d8bed17993e31a34b04f0fad33d844a3a598e0fd519a8e9be3cad5f67662d96fee0
@@ -8300,13 +8103,6 @@ __metadata:
languageName: node
linkType: hard
"is-arrayish@npm:^0.3.1":
version: 0.3.2
resolution: "is-arrayish@npm:0.3.2"
checksum: 10/81a78d518ebd8b834523e25d102684ee0f7e98637136d3bdc93fd09636350fa06f1d8ca997ea28143d4d13cb1b69c0824f082db0ac13e1ab3311c10ffea60ade
languageName: node
linkType: hard
"is-binary-path@npm:~2.1.0":
version: 2.1.0
resolution: "is-binary-path@npm:2.1.0"
@@ -12166,7 +11962,7 @@ __metadata:
languageName: node
linkType: hard
"semver@npm:^7.3.5, semver@npm:^7.5.3, semver@npm:^7.5.4, semver@npm:^7.6.3, semver@npm:^7.7.2":
"semver@npm:^7.3.5, semver@npm:^7.5.3, semver@npm:^7.5.4, semver@npm:^7.7.2":
version: 7.7.2
resolution: "semver@npm:7.7.2"
bin:
@@ -12184,6 +11980,15 @@ __metadata:
languageName: node
linkType: hard
"semver@npm:^7.7.4":
version: 7.7.4
resolution: "semver@npm:7.7.4"
bin:
semver: bin/semver.js
checksum: 10/26bdc6d58b29528f4142d29afb8526bc335f4fc04c4a10f2b98b217f277a031c66736bf82d3d3bb354a2f6a3ae50f18fd62b053c4ac3f294a3d10a61f5075b75
languageName: node
linkType: hard
"send@npm:^1.1.0, send@npm:^1.2.0":
version: 1.2.0
resolution: "send@npm:1.2.0"
@@ -12257,7 +12062,7 @@ __metadata:
languageName: node
linkType: hard
"sharp@npm:0.34.5":
"sharp@npm:0.34.5, sharp@npm:^0.34.5":
version: 0.34.5
resolution: "sharp@npm:0.34.5"
dependencies:
@@ -12341,75 +12146,6 @@ __metadata:
languageName: node
linkType: hard
"sharp@npm:^0.33.4":
version: 0.33.5
resolution: "sharp@npm:0.33.5"
dependencies:
"@img/sharp-darwin-arm64": "npm:0.33.5"
"@img/sharp-darwin-x64": "npm:0.33.5"
"@img/sharp-libvips-darwin-arm64": "npm:1.0.4"
"@img/sharp-libvips-darwin-x64": "npm:1.0.4"
"@img/sharp-libvips-linux-arm": "npm:1.0.5"
"@img/sharp-libvips-linux-arm64": "npm:1.0.4"
"@img/sharp-libvips-linux-s390x": "npm:1.0.4"
"@img/sharp-libvips-linux-x64": "npm:1.0.4"
"@img/sharp-libvips-linuxmusl-arm64": "npm:1.0.4"
"@img/sharp-libvips-linuxmusl-x64": "npm:1.0.4"
"@img/sharp-linux-arm": "npm:0.33.5"
"@img/sharp-linux-arm64": "npm:0.33.5"
"@img/sharp-linux-s390x": "npm:0.33.5"
"@img/sharp-linux-x64": "npm:0.33.5"
"@img/sharp-linuxmusl-arm64": "npm:0.33.5"
"@img/sharp-linuxmusl-x64": "npm:0.33.5"
"@img/sharp-wasm32": "npm:0.33.5"
"@img/sharp-win32-ia32": "npm:0.33.5"
"@img/sharp-win32-x64": "npm:0.33.5"
color: "npm:^4.2.3"
detect-libc: "npm:^2.0.3"
semver: "npm:^7.6.3"
dependenciesMeta:
"@img/sharp-darwin-arm64":
optional: true
"@img/sharp-darwin-x64":
optional: true
"@img/sharp-libvips-darwin-arm64":
optional: true
"@img/sharp-libvips-darwin-x64":
optional: true
"@img/sharp-libvips-linux-arm":
optional: true
"@img/sharp-libvips-linux-arm64":
optional: true
"@img/sharp-libvips-linux-s390x":
optional: true
"@img/sharp-libvips-linux-x64":
optional: true
"@img/sharp-libvips-linuxmusl-arm64":
optional: true
"@img/sharp-libvips-linuxmusl-x64":
optional: true
"@img/sharp-linux-arm":
optional: true
"@img/sharp-linux-arm64":
optional: true
"@img/sharp-linux-s390x":
optional: true
"@img/sharp-linux-x64":
optional: true
"@img/sharp-linuxmusl-arm64":
optional: true
"@img/sharp-linuxmusl-x64":
optional: true
"@img/sharp-wasm32":
optional: true
"@img/sharp-win32-ia32":
optional: true
"@img/sharp-win32-x64":
optional: true
checksum: 10/9f153578cb02735359cbcc874f52b56b8074ed997498c35255c7099d4f4f506f6ddf83a437a55242c7ad4f979336660504b6c78e29d6933f4981dedbdae5ce09
languageName: node
linkType: hard
"shebang-command@npm:^2.0.0":
version: 2.0.0
resolution: "shebang-command@npm:2.0.0"
@@ -12513,15 +12249,6 @@ __metadata:
languageName: node
linkType: hard
"simple-swizzle@npm:^0.2.2":
version: 0.2.2
resolution: "simple-swizzle@npm:0.2.2"
dependencies:
is-arrayish: "npm:^0.3.1"
checksum: 10/c6dffff17aaa383dae7e5c056fbf10cf9855a9f79949f20ee225c04f06ddde56323600e0f3d6797e82d08d006e93761122527438ee9531620031c08c9e0d73cc
languageName: node
linkType: hard
"simple-yenc@npm:^1.0.4":
version: 1.0.4
resolution: "simple-yenc@npm:1.0.4"
@@ -13826,7 +13553,8 @@ __metadata:
"@types/supertest": "npm:^2.0.8"
"@types/user-agents": "npm:^1"
"@types/ws": "npm:^8.5.4"
"@wppconnect-team/wppconnect": "npm:^1.40.1"
"@wppconnect-team/wppconnect": "npm:^1.41.1"
"@wppconnect/wa-js": "npm:^3.23.3"
adm-zip: "npm:0.5.10"
agentkeepalive: "npm:^4.5.0"
async-lock: "npm:^1.4.1"
@@ -13881,7 +13609,7 @@ __metadata:
reflect-metadata: "npm:^0.1.13"
rimraf: "npm:^4.3.0"
rxjs: "npm:^7.8.1"
sharp: "npm:^0.33.4"
sharp: "npm:^0.34.5"
shell-quote: "npm:^1.8.3"
sqlite3: "npm:^5.1.7"
supertest: "npm:^4.0.2"
@@ -14005,7 +13733,7 @@ __metadata:
"whatsapp-web.js@github:devlikeapro/whatsapp-web.js#fork-main-2026-02-18":
version: 1.34.6
resolution: "whatsapp-web.js@https://github.com/devlikeapro/whatsapp-web.js.git#commit=5f6acd9f03bd4ddfabe2db955d0b8c24d7f80b08"
resolution: "whatsapp-web.js@https://github.com/devlikeapro/whatsapp-web.js.git#commit=d9f107887470bd4349cd9f639fb9cb1d63027efc"
dependencies:
archiver: "npm:^5.3.1"
fluent-ffmpeg: "npm:2.1.3"
@@ -14022,7 +13750,7 @@ __metadata:
optional: true
unzipper:
optional: true
checksum: 10/938a22fe1515f565200be3d82d540d678ca495043d46a9c7697d0a5b1958c5707478ff855aa41f4e88f52e383ca2ea1f9e80dec874a6a4b34f2b0f3e4d518f86
checksum: 10/705f16bd7c70afae7d8a76fbfc640fbb2be7d8d99695a694f0953507716c65cbbd44f68dcf5298bc5d61fa8cc55dd5d2ec9bc3d1cfc51281a35723b936c0994c
languageName: node
linkType: hard