Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
495857ee41 | ||
|
|
e04e55c4c5 | ||
|
|
743e740860 | ||
|
|
65623ec1ea | ||
|
|
72a301c579 | ||
|
|
f35fd8d7c6 | ||
|
|
0594ca0709 | ||
|
|
4e5ebab97e | ||
|
|
ba976138b7 | ||
|
|
bb3328f5a3 | ||
|
|
6db12d7913 | ||
|
|
d968fa90f1 | ||
|
|
06c874fea5 | ||
|
|
d2b7d94607 | ||
|
|
c8f067229b | ||
|
|
798d782869 | ||
|
|
f70bcd0b53 | ||
|
|
17a7764b97 | ||
|
|
58affc9fe2 | ||
|
|
3678cb4392 | ||
|
|
6eda96908f | ||
|
|
ddf161bb2b | ||
|
|
5659dc7296 | ||
|
|
bca90199f1 | ||
|
|
bd263b071c | ||
|
|
e54603085c | ||
|
|
7f7857141e | ||
|
|
3b19dc3a61 | ||
|
|
06e5951c68 | ||
|
|
bcf4ed07d8 | ||
|
|
f711fcca98 | ||
|
|
32e57cab98 | ||
|
|
b092d2bfe1 | ||
|
|
f655f715d0 | ||
|
|
e5c77c60f2 | ||
|
|
a38d4145c2 | ||
|
|
2c47b784b3 | ||
|
|
724dc61717 | ||
|
|
9c44256209 | ||
|
|
1fb82c7678 | ||
|
|
55f7d4dac7 | ||
|
|
10e8869903 | ||
|
|
554d2bc6bc | ||
|
|
99aabb0957 | ||
|
|
055b014c41 | ||
|
|
172676ae6f | ||
|
|
0056951485 | ||
|
|
8c91792473 | ||
|
|
a26e89a2b7 | ||
|
|
7674c4ca67 | ||
|
|
cc7c11e2fd | ||
|
|
9f3c28b6ac | ||
|
|
3ca9545a58 | ||
|
|
4d08cffeab | ||
|
|
241340e0a0 | ||
|
|
556f0fa775 | ||
|
|
975cf2f580 | ||
|
|
90795a7eaf | ||
|
|
e64b60ef3a | ||
|
|
03e9588581 | ||
|
|
393f5ef7da | ||
|
|
f778678514 |
No files matched your search
@@ -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:*)"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,7 @@ jobs:
|
||||
${{ matrix.engine }} - ${{ matrix.browser }} - ${{ matrix.platform }} -
|
||||
${{ matrix.tag }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
# Chromium - x86
|
||||
|
||||
+20
@@ -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
|
||||
@@ -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
|
||||
|
||||
+15
@@ -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
|
||||
#
|
||||
|
||||
@@ -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
|
||||
@@ -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
@@ -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",
|
||||
|
||||
@@ -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}`);
|
||||
@@ -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);
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,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,
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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** لعرضه.
|
||||
|
||||
@@ -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** খুলুন।
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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!**
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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** را باز کنید.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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** כדי לצפות בזה.
|
||||
|
||||
@@ -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** खोलें।
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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!**
|
||||
|
||||
|
||||
@@ -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** ਖੋਲ੍ਹੋ।
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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**, чтобы просмотреть.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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**, щоб переглянути.
|
||||
|
||||
@@ -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** کھولیں۔
|
||||
|
||||
@@ -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** 查看。
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { EngineHelper } from './engines';
|
||||
import type { QuotedMedia } from './engines';
|
||||
|
||||
export { EngineHelper };
|
||||
export type { QuotedMedia };
|
||||
@@ -195,6 +195,8 @@ export class SwaggerConfiguratorCore {
|
||||
'/ping',
|
||||
'/ws',
|
||||
'/webhooks/',
|
||||
'/jobs',
|
||||
'/jobs/',
|
||||
...config.getExcludedFullPaths(),
|
||||
]);
|
||||
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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`;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -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',
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -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})$/,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
@@ -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,
|
||||
|
||||
@@ -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
@@ -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
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in new issue
Block a user