Compare commits

..
16 Commits
Author SHA1 Message Date
devlikepro 9e11312b75 [core] 2026.5.1
Release / WEBJS - chrome - amd64 - chrome (push) Waiting to run
Release / WEBJS - chromium - amd64 - latest (push) Waiting to run
Release / WEBJS - chromium - linux/arm64 - arm (push) Waiting to run
Release / GOWS - none - amd64 - gows (push) Waiting to run
Release / GOWS - none - linux/arm64 - gows-arm (push) Waiting to run
Release / NOWEB - none - amd64 - noweb (push) Waiting to run
Release / NOWEB - none - linux/arm64 - noweb-arm (push) Waiting to run
2026-05-26 12:21:20 +07:00
devlikepro 1c7ce47dbb [core] Up Dashboard 2026-05-26 12:21:20 +07:00
devlikepro 1d72e2666d [core] WEBJS on PostgreSQL - fix #2090
Session stuck in 'starting' or 'stoped' state due to Node.js Buffer limit reached when loading large RemoteAuth database entries (PostgreSQL/WebJS) #2090
2026-05-26 12:21:20 +07:00
devlikepro 7ae2f7999e [core] GOWS - send video - fix gif to mp4 format fix #2077 2026-05-26 12:21:20 +07:00
devlikepro 68dc4ad642 [core] NOWEB message.edit fix #2072 2026-05-26 12:21:19 +07:00
devlikepro e8f63aeaad [core] GOWS sticker placeholder URL 2026-05-26 12:21:19 +07:00
devlikepro f4e19c7664 [core] Up NOWEB 2026-05-26 12:21:19 +07:00
devlikepro 6d8ef2e375 [core] Up WPP 2026-05-26 12:21:19 +07:00
devlikepro 024cbffb13 [core] Up GOWS
Fix #2084 - All outbound messages fail with server returned error 400 until session restart
Fix #2085 - MediaRetry to also trigger on ciphertext hash mismatch (not only 403)
Fix #2080 - status@broadcast batch timeouts, add WAHA_GOWS_STATUS_PARTICIPANTS_BATCH_SIZE
2026-05-26 12:21:19 +07:00
devlikepro ad399d9b01 [core] watch ignore patterns 2026-05-26 12:21:19 +07:00
devlikepro 6053c162c0 [core] fix Dockerfile warning 2026-05-26 12:21:19 +07:00
Berg Pinheiro 6654a7b3d0 [core] GOWS sticker download: drop placeholder a.whatsapp.net URL (#45)
Problem:
- stickerMessage often has URL https://a.whatsapp.net with no path for
  encrypted media. The previous fix only copied uppercase URL to lowercase url,
  so DownloadMedia still tried HTTP GET on that host (for example DNS lookup
  failures) instead of using directPath and media keys.
- Lottie (application/was) stickers usually ship a full mmg.whatsapp.net URL,
  so they worked; image/webp stickers with the placeholder broke.

Solution:
- Treat a.whatsapp.net with an empty path as a placeholder: clone the message
  and delete both URL and url on stickerMessage before gRPC DownloadMedia.
- For real CDN URLs, keep mirroring URL to url when url is missing.
2026-05-26 12:21:19 +07:00
Berg Pinheiro 4ff1c01e38 [core] Convert GOWS Lottie stickers (application/was) to animated WebP - fix #2039 closes devlikeapro/waha-plus#43
Lottie stickers arrive from WhatsApp as a ZIP archive (mimetype
application/was) containing animation/animation.json. This change
intercepts those stickers in the GOWS Plus session, renders each
animation frame off-screen, and delivers an animated WebP to the
webhook instead of the raw ZIP.

Implementation:
- src/plus/utils/lottie-converter.ts (new):
  - Extracts animation.json from the ZIP via adm-zip
  - Renders frames with @lottiefiles/dotlottie-web + @napi-rs/canvas
    (HTMLCanvasElement polyfill required; ImageData must NOT be polyfilled
    to avoid per-frame pixel caching in WASM memory)
  - Drives frames manually via dotLottie.setFrame(i) after 'load' for
    reliable synchronous capture
  - Encodes each RGBA frame to single-frame WebP with sharp (quality=80)
  - Assembles animated WebP via node-webpmux with full alpha transparency
  - Frame delay derived from animation fps; clamped to >=30ms
  - Fallback delay configurable via WAHA_LOTTIE_DEFAULT_DELAY_MS env var
  - sharp/adm-zip/node-webpmux loaded via require() — their module.exports
    is the callable itself with no .default, so ESM default import resolves
    to undefined at runtime

- src/plus/engines/gows/session.gows.plus.ts:
  - Overrides downloadMedia with LottieAwareGOWSEngineMediaProcessor
  - normalizeStickerUrl: fixes GOWS URL field case mismatch (URL vs url)
    that caused an infinite network loop on Lottie downloads
  - Reports mimetype image/webp and extension .webp for Lottie stickers

- package.json: add @lottiefiles/dotlottie-web, @napi-rs/canvas,
  node-webpmux to dependencies
2026-05-26 12:21:18 +07:00
devlikepro dfe0a3c8c4 [core] Up Dashboard. WEBJS e2e and disappearing setting messages - fix #2046 2026-05-26 12:21:18 +07:00
devlikepro 04121bda52 [core] Up GOWS. Handle encrypted message edits - fix #2062 closes devlikapro/waha-plus#44 2026-05-26 12:21:18 +07:00
devlikepro 7b6f522f92 [core] POST /api/<session>/chats/overview - require pagination
fix #2070
2026-05-26 12:21:18 +07:00
17 changed files with 1020 additions and 52 deletions

No files matched your search

+1 -1
View File
@@ -247,7 +247,7 @@ ENV CHOKIDAR_INTERVAL=5000
ENV WAHA_ZIPPER=ZIPUNZIP
# GOWS - use libc DNS resolver
ENV GODEBUG netdns=cgo
ENV GODEBUG=netdns=cgo
# Run command, etc
EXPOSE 3000
+1
View File
@@ -3,6 +3,7 @@
"sourceRoot": "src",
"compilerOptions": {
"plugins": ["@nestjs/swagger"],
"watchPathIgnorePatterns": ["node_modules", "src/dashboard", "dist"],
"assets": [
"dashboard/**",
"core/engines/webjs/*",
+6 -2
View File
@@ -39,7 +39,9 @@
"@casl/ability": "^6.8.0",
"@figuro/chatwoot-sdk": "^1.1.17",
"@liaoliaots/nestjs-redis": "^9",
"@lottiefiles/dotlottie-web": "^0.72.1",
"@modelcontextprotocol/sdk": "^1.29.0",
"@napi-rs/canvas": "^1.0.0",
"@nestjs/axios": "^3.0.2",
"@nestjs/bullmq": "^11.0.2",
"@nestjs/common": "^11.0.0",
@@ -58,8 +60,8 @@
"@types/passport": "^1.0.17",
"@types/sqlite3": "^5.1.0",
"@types/ws": "^8.5.4",
"@wppconnect-team/wppconnect": "^2.0.2",
"@wppconnect/wa-js": "^4.1.0",
"@wppconnect-team/wppconnect": "^2.2.1",
"@wppconnect/wa-js": "^4.3.0",
"adm-zip": "0.5.10",
"agentkeepalive": "^4.5.0",
"async-lock": "^1.4.1",
@@ -94,9 +96,11 @@
"mustache": "^4.2.0",
"nestjs-pino": "^4.1.0",
"node-cache": "5.1.2",
"node-webpmux": "^3.2.1",
"passport": "^0.7.0",
"passport-headerapikey": "^1.2.2",
"pg": "^8.13.1",
"pg-copy-streams": "^6.0.0",
"pino-http": "^10.2.0",
"pino-pretty": "^11.2.1",
"pretty-bytes": "5.6.0",
+46
View File
@@ -3000,6 +3000,8 @@ export namespace messages {
#one_of_decls: number[][] = [];
constructor(data?: any[] | {
duration?: number;
gifPlayback?: boolean;
externalShareFullVideoDurationInSeconds?: number;
}) {
super();
pb_1.Message.initialize(this, Array.isArray(data) ? data : [], 0, -1, [], this.#one_of_decls);
@@ -3007,6 +3009,12 @@ export namespace messages {
if ("duration" in data && data.duration != undefined) {
this.duration = data.duration;
}
if ("gifPlayback" in data && data.gifPlayback != undefined) {
this.gifPlayback = data.gifPlayback;
}
if ("externalShareFullVideoDurationInSeconds" in data && data.externalShareFullVideoDurationInSeconds != undefined) {
this.externalShareFullVideoDurationInSeconds = data.externalShareFullVideoDurationInSeconds;
}
}
}
get duration() {
@@ -3015,22 +3023,50 @@ export namespace messages {
set duration(value: number) {
pb_1.Message.setField(this, 1, value);
}
get gifPlayback() {
return pb_1.Message.getFieldWithDefault(this, 2, false) as boolean;
}
set gifPlayback(value: boolean) {
pb_1.Message.setField(this, 2, value);
}
get externalShareFullVideoDurationInSeconds() {
return pb_1.Message.getFieldWithDefault(this, 3, 0) as number;
}
set externalShareFullVideoDurationInSeconds(value: number) {
pb_1.Message.setField(this, 3, value);
}
static fromObject(data: {
duration?: number;
gifPlayback?: boolean;
externalShareFullVideoDurationInSeconds?: number;
}): VideoInfo {
const message = new VideoInfo({});
if (data.duration != null) {
message.duration = data.duration;
}
if (data.gifPlayback != null) {
message.gifPlayback = data.gifPlayback;
}
if (data.externalShareFullVideoDurationInSeconds != null) {
message.externalShareFullVideoDurationInSeconds = data.externalShareFullVideoDurationInSeconds;
}
return message;
}
toObject() {
const data: {
duration?: number;
gifPlayback?: boolean;
externalShareFullVideoDurationInSeconds?: number;
} = {};
if (this.duration != null) {
data.duration = this.duration;
}
if (this.gifPlayback != null) {
data.gifPlayback = this.gifPlayback;
}
if (this.externalShareFullVideoDurationInSeconds != null) {
data.externalShareFullVideoDurationInSeconds = this.externalShareFullVideoDurationInSeconds;
}
return data;
}
serialize(): Uint8Array;
@@ -3039,6 +3075,10 @@ export namespace messages {
const writer = w || new pb_1.BinaryWriter();
if (this.duration != 0)
writer.writeFloat(1, this.duration);
if (this.gifPlayback != false)
writer.writeBool(2, this.gifPlayback);
if (this.externalShareFullVideoDurationInSeconds != 0)
writer.writeUint32(3, this.externalShareFullVideoDurationInSeconds);
if (!w)
return writer.getResultBuffer();
}
@@ -3051,6 +3091,12 @@ export namespace messages {
case 1:
message.duration = reader.readFloat();
break;
case 2:
message.gifPlayback = reader.readBool();
break;
case 3:
message.externalShareFullVideoDurationInSeconds = reader.readUint32();
break;
default: reader.skipField();
}
}
+61 -1
View File
@@ -7749,7 +7749,9 @@ proto.messages.VideoInfo.prototype.toObject = function(opt_includeInstance) {
*/
proto.messages.VideoInfo.toObject = function(includeInstance, msg) {
var f, obj = {
duration: jspb.Message.getFloatingPointFieldWithDefault(msg, 1, 0.0)
duration: jspb.Message.getFloatingPointFieldWithDefault(msg, 1, 0.0),
gifplayback: jspb.Message.getBooleanFieldWithDefault(msg, 2, false),
externalsharefullvideodurationinseconds: jspb.Message.getFieldWithDefault(msg, 3, 0)
};
if (includeInstance) {
@@ -7790,6 +7792,14 @@ proto.messages.VideoInfo.deserializeBinaryFromReader = function(msg, reader) {
var value = /** @type {number} */ (reader.readFloat());
msg.setDuration(value);
break;
case 2:
var value = /** @type {boolean} */ (reader.readBool());
msg.setGifplayback(value);
break;
case 3:
var value = /** @type {number} */ (reader.readUint32());
msg.setExternalsharefullvideodurationinseconds(value);
break;
default:
reader.skipField();
break;
@@ -7826,6 +7836,20 @@ proto.messages.VideoInfo.serializeBinaryToWriter = function(message, writer) {
f
);
}
f = message.getGifplayback();
if (f) {
writer.writeBool(
2,
f
);
}
f = message.getExternalsharefullvideodurationinseconds();
if (f !== 0) {
writer.writeUint32(
3,
f
);
}
};
@@ -7847,6 +7871,42 @@ proto.messages.VideoInfo.prototype.setDuration = function(value) {
};
/**
* optional bool gifPlayback = 2;
* @return {boolean}
*/
proto.messages.VideoInfo.prototype.getGifplayback = function() {
return /** @type {boolean} */ (jspb.Message.getBooleanFieldWithDefault(this, 2, false));
};
/**
* @param {boolean} value
* @return {!proto.messages.VideoInfo} returns this
*/
proto.messages.VideoInfo.prototype.setGifplayback = function(value) {
return jspb.Message.setProto3BooleanField(this, 2, value);
};
/**
* optional uint32 externalShareFullVideoDurationInSeconds = 3;
* @return {number}
*/
proto.messages.VideoInfo.prototype.getExternalsharefullvideodurationinseconds = function() {
return /** @type {number} */ (jspb.Message.getFieldWithDefault(this, 3, 0));
};
/**
* @param {number} value
* @return {!proto.messages.VideoInfo} returns this
*/
proto.messages.VideoInfo.prototype.setExternalsharefullvideodurationinseconds = function(value) {
return jspb.Message.setProto3IntField(this, 3, value);
};
+51 -1
View File
@@ -39,6 +39,7 @@ import {
NotImplementedByEngineError,
} from '@waha/core/exceptions';
import { IMediaEngineProcessor } from '@waha/core/media/IMediaEngineProcessor';
import { LottieMediaProcessorWrapper } from '@waha/core/media/LottieMediaProcessorWrapper';
import { QR } from '@waha/core/QR';
import { ExtractMessageKeysForRead } from '@waha/core/utils/convertors';
import { parseMessageIdSerialized } from '@waha/core/utils/ids';
@@ -2224,7 +2225,10 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
}
protected async downloadMedia(message) {
const processor = new GOWSEngineMediaProcessor(this);
let processor: IMediaEngineProcessor<any> = new GOWSEngineMediaProcessor(
this,
);
processor = new LottieMediaProcessorWrapper(processor, this.logger);
const media = await this.mediaManager.processMedia(
processor,
message,
@@ -2551,6 +2555,50 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
}
}
/**
* Many encrypted stickers carry URL "https://a.whatsapp.net" with no path. If
* that string is passed to DownloadMedia, the Go client may attempt HTTP GET to
* that host instead of decrypting via directPath. Real CDN links use hosts
* such as mmg.whatsapp.net with a full path.
*
* GOWS also exposes stickerMessage.URL (uppercase); some paths expect url
* (lowercase). We mirror URL -> url only for real HTTP-style URLs.
*/
function isPlaceholderWhatsAppMediaUrl(url: unknown): boolean {
if (typeof url !== 'string' || url.length === 0) {
return false;
}
try {
const parsed = new URL(url.trim());
if (parsed.hostname !== 'a.whatsapp.net') {
return false;
}
const path = parsed.pathname.replace(/\/+$/, '');
return path === '';
} catch {
return false;
}
}
function normalizeGowsStickerUrlForDownload(message: any): any {
const sticker = message?.Message?.stickerMessage;
if (!sticker) {
return message;
}
if (isPlaceholderWhatsAppMediaUrl(sticker.URL)) {
delete sticker.URL;
}
if (isPlaceholderWhatsAppMediaUrl(sticker.url)) {
delete sticker.url;
}
if (sticker.URL && !sticker.url) {
sticker.url = sticker.URL;
}
return message;
}
export class GOWSEngineMediaProcessor implements IMediaEngineProcessor<any> {
constructor(public session: WhatsappSessionGoWSCore) {}
@@ -2574,6 +2622,8 @@ export class GOWSEngineMediaProcessor implements IMediaEngineProcessor<any> {
async getMediaBuffer(message: any): Promise<Buffer | null> {
const mediaDownloadTimeoutMs = 600_000; // 10 minutes
message = normalizeGowsStickerUrlForDownload(message);
const data = JSON.stringify(message.Message);
const tmpdir = new TmpDir(
this.session.logger,
+124 -7
View File
@@ -66,6 +66,7 @@ import { toVcardV3 } from '@waha/core/vcard';
import { createAgentProxy } from '@waha/core/helpers.proxy';
import type { Agent } from 'https';
import { IMediaEngineProcessor } from '@waha/core/media/IMediaEngineProcessor';
import { LottieMediaProcessorWrapper } from '@waha/core/media/LottieMediaProcessorWrapper';
import { QR } from '@waha/core/QR';
import { AckToStatus, StatusToAck } from '@waha/core/utils/acks';
import { pairs } from '@waha/utils/pairs';
@@ -204,7 +205,13 @@ import { Agents } from '@waha/core/engines/noweb/types';
import {
IsEditedMessage,
IsHistorySyncNotification,
IsSecretEncryptedMessageEdit,
} from '@waha/core/utils/pwa';
import {
decryptSecretEncryptedMessageEditProto,
getOrigSenderJidForMsgSecret,
jidToNonAD,
} from '@waha/core/utils/secretEncryptedMessageEdit';
import { extractWALocation } from '@waha/core/engines/waproto/locaiton';
import { extractVCards } from '@waha/core/engines/waproto/vcards';
import { Activity } from '@waha/core/abc/activity';
@@ -2116,14 +2123,25 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
// Handle edited messages
const messagesEdited$ = messagesUpsert$.pipe(
filter((message) => IsEditedMessage(message.message)),
filter(
(message) =>
IsEditedMessage(message.message) ||
IsSecretEncryptedMessageEdit(message.message),
),
mergeMap(async (message): Promise<WAMessageEditedBody> => {
const waMessage = this.toWAMessage(message);
// Extract the body from editedMessage using extractBody function
const content = normalizeMessageContent(message.message);
const body = extractBody(content.protocolMessage.editedMessage) || '';
// Extract the original message ID from protocolMessage.key
const editedMessageId = content.protocolMessage.key?.id;
let body = '';
let editedMessageId: string | undefined;
if (IsEditedMessage(message.message)) {
const content = normalizeMessageContent(message.message);
body = extractBody(content.protocolMessage.editedMessage) || '';
editedMessageId = content.protocolMessage.key?.id;
} else if (IsSecretEncryptedMessageEdit(message.message)) {
const sem = message.message.secretEncryptedMessage;
editedMessageId = sem.targetMessageKey?.id;
body =
(await this.tryDecryptNOWEBSecretMessageEdit(message, sem)) || '';
}
return {
...waMessage,
body: body,
@@ -2443,6 +2461,8 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
return;
// Ignore edit, we have a dedicated event for that
if (IsEditedMessage(message.message)) return;
// Ignore secret-encrypted message edits (mobile app format), dedicated handler routes them
if (IsSecretEncryptedMessageEdit(message.message)) return;
// Ignore history sync notifications
if (IsHistorySyncNotification(message.message)) return;
@@ -2473,6 +2493,99 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
return true;
}
protected async tryDecryptNOWEBSecretMessageEdit(
editMessage: proto.IWebMessageInfo,
sem: proto.Message.ISecretEncryptedMessage,
): Promise<string> {
const targetKey = sem.targetMessageKey;
const origMsgId = targetKey?.id;
if (!origMsgId) {
return '';
}
const jidsToTry = [targetKey.remoteJid, editMessage.key?.remoteJid].filter(
Boolean,
);
let stored: proto.IWebMessageInfo | undefined;
for (const jid of jidsToTry) {
stored = await this.store?.loadMessage(jid, origMsgId);
if (stored) {
break;
}
}
if (!stored) {
this.logger.debug(
{ origMsgId: origMsgId },
'NOWEB message edit decrypt: original message not found in store',
);
return '';
}
const secretBytes =
normalizeMessageContent(stored.message)?.messageContextInfo
?.messageSecret ?? stored.message?.messageContextInfo?.messageSecret;
if (!secretBytes || secretBytes.length !== 32) {
this.logger.debug(
{ origMsgId: origMsgId },
'NOWEB message edit decrypt: missing messageSecret on original',
);
return '';
}
const origSecret = Buffer.from(secretBytes);
const encPayload = sem.encPayload ? Buffer.from(sem.encPayload) : null;
const encIv = sem.encIv ? Buffer.from(sem.encIv) : null;
if (!encPayload || !encIv) {
return '';
}
const editInfo = {
Chat: editMessage.key?.remoteJid,
Sender:
editMessage.key?.participant ||
(editMessage.key?.fromMe ? undefined : editMessage.key?.remoteJid),
};
const modificationSenderJid = jidToNonAD(editInfo.Sender || '');
const primaryOrig = getOrigSenderJidForMsgSecret(editInfo, {
fromMe: targetKey.fromMe,
remoteJID: targetKey.remoteJid,
participant: targetKey.participant,
});
const candidates: string[] = [primaryOrig];
const remoteNonAD = targetKey.remoteJid
? jidToNonAD(targetKey.remoteJid)
: '';
if (remoteNonAD && !candidates.includes(remoteNonAD)) {
candidates.push(remoteNonAD);
}
const participantNonAD = targetKey.participant
? jidToNonAD(targetKey.participant)
: '';
if (participantNonAD && !candidates.includes(participantNonAD)) {
candidates.push(participantNonAD);
}
let lastErr: unknown;
for (const origSenderJid of candidates) {
try {
const decoded = decryptSecretEncryptedMessageEditProto({
encPayload: encPayload,
encIv: encIv,
origMsgId: origMsgId,
origSenderJid: origSenderJid,
modificationSenderJid: modificationSenderJid,
origMsgSecret: origSecret,
});
const text = extractBody(decoded) || '';
if (text) {
return text;
}
} catch (err) {
lastErr = err;
}
}
this.logger.debug(
{ err: lastErr, origMsgId: origMsgId, candidates: candidates },
'NOWEB message edit decrypt: AES-GCM or protobuf decode failed',
);
return '';
}
protected async processIncomingMessage(
message,
downloadMedia: boolean,
@@ -2770,7 +2883,11 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
}
protected async downloadMedia(message): Promise<WAMedia | null> {
const processor = new NOWEBEngineMediaProcessor(this, this.loggerBuilder);
let processor: IMediaEngineProcessor<any> = new NOWEBEngineMediaProcessor(
this,
this.loggerBuilder,
);
processor = new LottieMediaProcessorWrapper(processor, this.logger);
return this.mediaManager.processMedia(processor, message, this.name);
}
+3 -1
View File
@@ -33,6 +33,7 @@ import {
NotImplementedByEngineError,
} from '@waha/core/exceptions';
import { IMediaEngineProcessor } from '@waha/core/media/IMediaEngineProcessor';
import { LottieMediaProcessorWrapper } from '@waha/core/media/LottieMediaProcessorWrapper';
import { QR } from '@waha/core/QR';
import { StatusToAck } from '@waha/core/utils/acks';
import {
@@ -2224,7 +2225,8 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
}
protected async downloadMedia(message: Message) {
const processor = new WEBJSEngineMediaProcessor();
let processor = new WEBJSEngineMediaProcessor();
processor = new LottieMediaProcessorWrapper(processor, this.logger);
const media = await this.mediaManager.processMedia(
processor,
message,
+3 -1
View File
@@ -126,6 +126,7 @@ import {
NotImplementedByEngineError,
} from '@waha/core/exceptions';
import { IMediaEngineProcessor } from '@waha/core/media/IMediaEngineProcessor';
import { LottieMediaProcessorWrapper } from '@waha/core/media/LottieMediaProcessorWrapper';
import { IWPPAuthManager } from '@waha/core/engines/wpp/IWPPAuthManager';
import { QR } from '@waha/core/QR';
import { removeSingletonFiles } from '@waha/core/utils/chrome';
@@ -2118,7 +2119,8 @@ export class WhatsappSessionWPPCore extends WhatsappSession {
}
protected async downloadMedia(message: any): Promise<WAMedia | null> {
const processor = new WPPEngineMediaProcessor(this.wpp);
let processor = new WPPEngineMediaProcessor(this.wpp);
processor = new LottieMediaProcessorWrapper(processor, this.logger);
return this.mediaManager.processMedia(processor, message, this.name);
}
@@ -0,0 +1,69 @@
import { IMediaEngineProcessor } from '@waha/core/media/IMediaEngineProcessor';
import { convertLottieZipToWebp } from '@waha/core/utils/lottie-converter';
const LOTTIE_MIMETYPE = 'application/was';
/**
* Wraps any engine media processor to transparently convert Lottie stickers
* (mimetype `application/was`, delivered as a ZIP archive) into animated WebP.
* The inner processor downloads the raw ZIP; this wrapper converts it before
* the result reaches the media storage layer, so callers see `image/webp`.
*/
export class LottieMediaProcessorWrapper implements IMediaEngineProcessor<any> {
constructor(
private inner: IMediaEngineProcessor<any>,
private logger: any,
) {}
hasMedia(msg: any): boolean {
return this.inner.hasMedia(msg);
}
getChatId(msg: any): string {
return this.inner.getChatId(msg);
}
getMessageId(msg: any): string {
return this.inner.getMessageId(msg);
}
getMimetype(msg: any): string {
const mime = this.inner.getMimetype(msg);
return mime === LOTTIE_MIMETYPE ? 'image/webp' : mime;
}
getFilename(msg: any): string | null {
if (this.inner.getMimetype(msg) !== LOTTIE_MIMETYPE) {
return this.inner.getFilename(msg);
}
return `${this.inner.getMessageId(msg)}.webp`;
}
async getMediaBuffer(msg: any): Promise<Buffer | null> {
const buffer = await this.inner.getMediaBuffer(msg);
if (!buffer || !buffer.length) {
return null;
}
if (this.inner.getMimetype(msg) !== LOTTIE_MIMETYPE) {
return buffer;
}
const id = this.inner.getMessageId(msg);
this.logger.info(
`Converting Lottie sticker '${id}' to WebP (input ${buffer.length} bytes)...`,
);
try {
const webp = await convertLottieZipToWebp(buffer);
this.logger.info(
`Lottie sticker '${id}' converted to WebP (${webp.byteLength} bytes)`,
);
return webp;
} catch (err) {
this.logger.warn(
{ err: err },
`Lottie conversion failed for '${id}', returning original buffer`,
);
return buffer;
}
}
}
+284
View File
@@ -0,0 +1,284 @@
import { createCanvas, Canvas } from '@napi-rs/canvas';
import { DotLottie } from '@lottiefiles/dotlottie-web';
// sharp, adm-zip and node-webpmux are CJS modules whose module.exports is the
// callable/constructor itself — static default imports resolve to undefined at
// runtime because there is no .default property. require() is the correct interop.
// eslint-disable-next-line @typescript-eslint/no-require-imports
const sharp = require('sharp');
// eslint-disable-next-line @typescript-eslint/no-require-imports
const AdmZip = require('adm-zip');
// eslint-disable-next-line @typescript-eslint/no-require-imports
const { Image: WebpImage } = require('node-webpmux');
// DotLottie._draw() guards canvas rendering behind:
// canvas instanceof HTMLCanvasElement || canvas instanceof OffscreenCanvas
// Both are undefined in Node.js, so the guard fails and _draw() returns without
// calling putImageData – producing a fully black canvas. Registering the
// @napi-rs/canvas Canvas constructor as HTMLCanvasElement makes the instanceof
// check pass so DotLottie acquires the 2D context and paints frames normally.
//
// ImageData must NOT be registered globally. When it is, DotLottie uses:
// new ImageData(Uint8ClampedArray_view_into_WASM_memory, w, h)
// @napi-rs/canvas copies the bytes once at construction and never re-reads the
// WASM buffer, so every frame produces the same cached pixels. Without a global
// ImageData, DotLottie falls back to:
// ctx.createImageData(w, h) + imageData.data.set(currentPixelBuffer)
// which copies fresh pixel data from the WASM buffer on every frame.
const globalAny = global as Record<string, unknown>;
if (!globalAny['HTMLCanvasElement']) {
globalAny['HTMLCanvasElement'] = Canvas;
}
export class LottieConvertError extends Error {
constructor(message: string, options?: { cause?: unknown }) {
super(message);
this.name = 'LottieConvertError';
if (options?.cause !== undefined) {
(this as any).cause = options.cause;
}
}
}
const STICKER_SIZE = 256;
const envDelayMs = parseInt(
process.env['WAHA_LOTTIE_DEFAULT_DELAY_MS'] ?? '',
10,
);
const DEFAULT_DELAY_MS = envDelayMs > 0 ? envDelayMs : 100;
function isZipBuffer(buffer: Buffer): boolean {
return (
buffer.length >= 4 &&
buffer[0] === 0x50 &&
buffer[1] === 0x4b &&
buffer[2] === 0x03 &&
buffer[3] === 0x04
);
}
function extractAnimationJson(zipBuffer: Buffer): string | null {
const zip = new AdmZip(zipBuffer);
const entry =
zip.getEntry('animation/animation.json') ??
zip.getEntries().find((e) => e.entryName.endsWith('.json'));
if (!entry) {
return null;
}
return zip.readAsText(entry);
}
export async function convertLottieZipToWebp(
zipBuffer: Buffer,
): Promise<Buffer> {
if (!isZipBuffer(zipBuffer)) {
throw new LottieConvertError('Lottie buffer is not a valid ZIP');
}
let animationJson: string | null;
try {
animationJson = extractAnimationJson(zipBuffer);
} catch (err) {
throw new LottieConvertError(`Failed to unzip Lottie file: ${err}`, {
cause: err,
});
}
if (!animationJson) {
throw new LottieConvertError('Lottie ZIP has no animation.json entry');
}
let animationData: Record<string, unknown>;
try {
animationData = JSON.parse(animationJson);
} catch (err) {
throw new LottieConvertError(
`Lottie animation.json is not valid JSON: ${err}`,
{
cause: err,
},
);
}
return renderLottieToWebp(animationData);
}
function renderLottieToWebp(
animationData: Record<string, unknown>,
): Promise<Buffer> {
return new Promise<Buffer>((resolve, reject) => {
let settled = false;
function finish(result: Buffer) {
if (settled) return;
settled = true;
resolve(result);
}
function fail(reason: unknown) {
if (settled) return;
settled = true;
reject(
reason instanceof LottieConvertError
? reason
: new LottieConvertError(String(reason), { cause: reason }),
);
}
let canvas: Canvas;
let dotLottie: DotLottie;
try {
canvas = createCanvas(STICKER_SIZE, STICKER_SIZE);
} catch (err) {
return fail(err);
}
// DotLottie dispatches 'load' via setTimeout. Instead of relying on the
// setImmediate-based animation loop (timing-dependent, unreliable for
// frame capture), we drive frames manually with setFrame(i) after load.
// setFrame() renders synchronously: set_frame → render → putImageData.
try {
dotLottie = new DotLottie({
loop: false,
useFrameInterpolation: false,
autoplay: false,
canvas: canvas as unknown as HTMLCanvasElement,
data: animationData,
renderConfig: {
autoResize: false,
devicePixelRatio: 1,
freezeOnOffscreen: false,
},
});
} catch (err) {
return fail(err);
}
function onLoad() {
if (settled) return;
const totalFrames = dotLottie.totalFrames;
const duration = dotLottie.duration;
let delayMs: number;
let framePixels: Buffer[];
try {
if (totalFrames <= 0) {
throw new Error(
`Animation has no frames (totalFrames=${totalFrames})`,
);
}
let rawDelayMs = DEFAULT_DELAY_MS;
if (duration > 0 && isFinite(duration)) {
const fps = totalFrames / duration;
const computed = Math.round(1000 / fps);
if (computed > 0 && isFinite(computed)) {
rawDelayMs = computed;
}
}
// WebP delay is in milliseconds. Clamp to at least 30ms to stay above
// the ~20ms threshold where some players impose their own minimum.
// When clamping, subsample frames proportionally so total duration is
// preserved — e.g. a 60fps animation must not play 1.8× too slowly
// because all 180 frames are kept at 30ms each.
delayMs = Math.max(30, rawDelayMs);
const frameStep = delayMs / rawDelayMs;
const ctx = canvas.getContext('2d');
if (!ctx) {
throw new Error('canvas.getContext("2d") returned null');
}
// Collect raw RGBA pixels. When frameStep > 1 we subsample frames to
// keep playback speed correct despite the minimum-delay clamp.
// Pass the float position to setFrame so the renderer interpolates
// smoothly along the animation curve rather than snapping to the
// nearest integer frame (which causes uneven motion).
framePixels = [];
for (let t = 0; t < totalFrames; t += frameStep) {
// setFrame(0) is a no-op (already rendered after load), so skip it.
if (t > 0) {
dotLottie.setFrame(t);
}
framePixels.push(
Buffer.from(
ctx.getImageData(0, 0, STICKER_SIZE, STICKER_SIZE).data,
),
);
}
} catch (err) {
dotLottie.destroy();
fail(err);
return;
}
dotLottie.destroy();
// Encode each RGBA frame as a single-frame WebP, then assemble into an
// animated WebP via node-webpmux. This preserves full alpha transparency
// and avoids GIF's 256-colour palette limitation.
if (framePixels.length === 1) {
sharp(framePixels[0], {
raw: { width: STICKER_SIZE, height: STICKER_SIZE, channels: 4 },
})
.webp({ quality: 80, effort: 3 })
.toBuffer()
.then(finish)
.catch(fail);
return;
}
Promise.all(
framePixels.map((px) =>
sharp(px, {
raw: { width: STICKER_SIZE, height: STICKER_SIZE, channels: 4 },
})
.webp({ quality: 80, effort: 3 })
.toBuffer(),
),
)
.then((webpFrames) =>
Promise.all(
webpFrames.map((buf) =>
WebpImage.generateFrame({
buffer: buf,
delay: delayMs,
x: 0,
y: 0,
blend: false,
dispose: true,
}),
),
),
)
.then((frames) =>
WebpImage.save(null, {
width: STICKER_SIZE,
height: STICKER_SIZE,
bgColor: [0, 0, 0, 0],
loops: 0,
frames: frames,
}),
)
.then(finish)
.catch(fail);
}
function onLoadError(payload: any) {
if (settled) return;
dotLottie.destroy();
fail(
new LottieConvertError(
`DotLottie failed to load animation: ${
payload?.error ?? 'unknown error'
}`,
{ cause: payload?.error },
),
);
}
dotLottie.addEventListener('load', onLoad);
dotLottie.addEventListener('loadError', onLoadError);
});
}
+13
View File
@@ -22,6 +22,19 @@ export function IsEditedMessage(message: proto.IMessage): boolean {
return true;
}
export function IsSecretEncryptedMessageEdit(
message: proto.IMessage | null | undefined,
): boolean {
const sem = message?.secretEncryptedMessage;
if (!sem) {
return false;
}
return (
sem.secretEncType ===
proto.Message.SecretEncryptedMessage.SecretEncType.MESSAGE_EDIT
);
}
export function IsHistorySyncNotification(message: proto.IMessage): boolean {
message = normalizeMessageContent(message);
if (!message) {
@@ -0,0 +1,117 @@
/**
* Decrypt secret-encrypted message edits (secretEncType MESSAGE_EDIT).
*
* Mirrors whatsmeow's generateMsgSecretKey + gcmutil.Decrypt for EncSecretMessageEdit.
* @see https://github.com/tulir/whatsmeow/blob/main/msgsecret.go
*
* Key derivation uses Baileys' own hkdf (whatsapp-rust-bridge), which passes nil/undefined
* salt = 32-byte zeros, matching Go's golang.org/x/crypto/hkdf with nil salt.
*/
import { aesDecryptGCM, hkdf, proto } from '@adiwajshing/baileys';
const ENC_SECRET_MESSAGE_EDIT = 'Message Edit';
export function jidToNonAD(jid: string): string {
if (!jid || !jid.includes('@')) {
return jid;
}
const [user, server] = jid.split('@', 2);
return `${user.split(':')[0]}@${server || ''}`;
}
/**
* Mirrors whatsmeow getOrigSenderFromKey — resolves the JID used as the
* "origMsgSender" slot in the HKDF info block.
*/
export function getOrigSenderJidForMsgSecret(
editMessageInfo: { Chat?: string; Sender?: string },
targetMessageKey: {
fromMe?: boolean;
FromMe?: boolean;
remoteJID?: string;
RemoteJID?: string;
participant?: string;
Participant?: string;
},
): string {
const chat = editMessageInfo.Chat || '';
const server = chat.includes('@') ? chat.split('@')[1] : '';
const sender = editMessageInfo.Sender || '';
const fromMe =
targetMessageKey.fromMe === true || targetMessageKey.FromMe === true;
const remoteJid =
targetMessageKey.remoteJID || targetMessageKey.RemoteJID || '';
const participant =
targetMessageKey.participant || targetMessageKey.Participant || '';
if (fromMe) {
return jidToNonAD(sender);
}
if (server === 's.whatsapp.net' || server === 'lid') {
return jidToNonAD(remoteJid);
}
return jidToNonAD(participant);
}
/**
* Derives the AES-256-GCM key for a MESSAGE_EDIT secret-encrypted payload.
*
* Matches whatsmeow's generateMsgSecretKey:
* useCaseSecret = msgId + origSenderJid + modSenderJid + "Message Edit"
* key = HKDF-SHA256(ikm=origMsgSecret, salt=nil→32×0, info=useCaseSecret)
*/
export function generateMsgSecretKeyForMessageEdit(
origMsgId: string,
origSenderJid: string,
modificationSenderJid: string,
origMsgSecret: Uint8Array,
): Buffer {
const useCaseInfo = Buffer.concat([
Buffer.from(origMsgId, 'utf8'),
Buffer.from(jidToNonAD(origSenderJid), 'utf8'),
Buffer.from(jidToNonAD(modificationSenderJid), 'utf8'),
Buffer.from(ENC_SECRET_MESSAGE_EDIT, 'utf8'),
]);
// No salt → Rust bridge uses 32 zero bytes, matching whatsmeow nil salt.
// info must be passed as latin1 string so each byte maps 1:1 (same as binary concat).
return Buffer.from(
hkdf(origMsgSecret, 32, { info: useCaseInfo.toString('latin1') }),
);
}
/**
* Decrypts a secretEncryptedMessage (MESSAGE_EDIT) payload and decodes the
* resulting proto.Message. Throws on AES-GCM auth failure or proto decode error.
*/
export function decryptSecretEncryptedMessageEditProto(params: {
encPayload: Uint8Array;
encIv: Uint8Array;
origMsgId: string;
origSenderJid: string;
modificationSenderJid: string;
origMsgSecret: Uint8Array;
}): proto.Message {
const {
encPayload,
encIv,
origMsgId,
origSenderJid,
modificationSenderJid,
origMsgSecret,
} = params;
const secretKey = generateMsgSecretKeyForMessageEdit(
origMsgId,
origSenderJid,
modificationSenderJid,
origMsgSecret,
);
// MESSAGE_EDIT uses an empty additionalData (unlike Poll Vote which uses msgId+voterJid)
const plaintext = aesDecryptGCM(
encPayload,
secretKey,
encIv,
Buffer.alloc(0),
);
return proto.Message.decode(plaintext);
}
+4 -1
View File
@@ -11,6 +11,7 @@ import { Transform, Type } from 'class-transformer';
import {
IsArray,
IsBoolean,
IsDefined,
IsEnum,
IsNumber,
IsOptional,
@@ -265,13 +266,15 @@ export class OverviewFilter {
}
export class OverviewBodyRequest {
@IsDefined()
@ValidateNested()
@Type(() => GetChatsOverviewParams)
pagination: GetChatsOverviewParams;
@IsOptional()
@ValidateNested()
@Type(() => OverviewFilter)
filter: OverviewFilter;
filter?: OverviewFilter;
}
export class ChatSummary {
+1 -1
View File
@@ -48,7 +48,7 @@ function getPlatform() {
}
export const VERSION: WAHAEnvironment = {
version: '2026.4.3',
version: '2026.5.1',
engine: getEngineName(),
tier: getWAHAVersion(),
browser: getBrowser(),
+2 -2
View File
@@ -2,11 +2,11 @@
"waha": {
"gows": {
"repo": "devlikeapro/gows",
"ref": "v1.0.38"
"ref": "v1.0.40"
},
"dashboard": {
"repo": "devlikeapro/dashboard",
"ref": "35c218fc9d365552ac356a07103f61205acee0d3"
"ref": "47244c7d9202e0f4c1b23d12d45d5c208cd1184f"
}
}
}
+234 -34
View File
@@ -6,19 +6,19 @@ __metadata:
cacheKey: 10
"@adiwajshing/baileys@github:devlikeapro/Baileys#fork-master-2026-04-28":
version: 7.0.0-rc10
resolution: "@adiwajshing/baileys@https://github.com/devlikeapro/Baileys.git#commit=a56f2c88eac278b589101278a2d5e8f343fd0321"
version: 7.0.0-rc13
resolution: "@adiwajshing/baileys@https://github.com/devlikeapro/Baileys.git#commit=f619b7e841450140e444a0e93ff3f04ab50519b6"
dependencies:
"@cacheable/node-cache": "npm:^1.4.0"
"@hapi/boom": "npm:^9.1.3"
async-mutex: "npm:^0.5.0"
libsignal: "git+https://github.com/whiskeysockets/libsignal-node"
libsignal: "npm:^6.0.0"
lru-cache: "npm:^11.1.0"
music-metadata: "npm:^11.12.3"
p-queue: "npm:^9.0.0"
pino: "npm:^9.6"
protobufjs: "npm:^7.5.6"
whatsapp-rust-bridge: "npm:0.5.3"
whatsapp-rust-bridge: "npm:0.5.4"
ws: "npm:^8.13.0"
peerDependencies:
audio-decode: ^2.1.3
@@ -32,7 +32,7 @@ __metadata:
optional: true
link-preview-js:
optional: true
checksum: 10/a6eb9438f1d32ccad2d46c8ee84e8e3c448c6ba43bd0d28f2b88f414bcbe46d5e1802a59a8a2866ed73c02e13ada98c35aa996a4545b9db02068a5ca8ad41116
checksum: 10/535e52bf0b5bad381c985fcead24cb8c262a97662b9aaf3a4c0081c47ba02204e9f015e96dfb043f9d8e75295498e3e7468ee056f82f832af882a321c475d7bd
languageName: node
linkType: hard
@@ -2250,6 +2250,13 @@ __metadata:
languageName: node
linkType: hard
"@lottiefiles/dotlottie-web@npm:^0.72.1":
version: 0.72.1
resolution: "@lottiefiles/dotlottie-web@npm:0.72.1"
checksum: 10/79bec5037fb07f38a676d32a11597277c0b15c8ad9716dbde008fe79b2e4e0374aaefcffd9bb68672a8bb35d02b57af4ed01a857238166f8ed85786ad29756b1
languageName: node
linkType: hard
"@lukeed/csprng@npm:^1.0.0":
version: 1.1.0
resolution: "@lukeed/csprng@npm:1.1.0"
@@ -2367,6 +2374,129 @@ __metadata:
languageName: node
linkType: hard
"@napi-rs/canvas-android-arm64@npm:1.0.0":
version: 1.0.0
resolution: "@napi-rs/canvas-android-arm64@npm:1.0.0"
conditions: os=android & cpu=arm64
languageName: node
linkType: hard
"@napi-rs/canvas-darwin-arm64@npm:1.0.0":
version: 1.0.0
resolution: "@napi-rs/canvas-darwin-arm64@npm:1.0.0"
conditions: os=darwin & cpu=arm64
languageName: node
linkType: hard
"@napi-rs/canvas-darwin-x64@npm:1.0.0":
version: 1.0.0
resolution: "@napi-rs/canvas-darwin-x64@npm:1.0.0"
conditions: os=darwin & cpu=x64
languageName: node
linkType: hard
"@napi-rs/canvas-linux-arm-gnueabihf@npm:1.0.0":
version: 1.0.0
resolution: "@napi-rs/canvas-linux-arm-gnueabihf@npm:1.0.0"
conditions: os=linux & cpu=arm
languageName: node
linkType: hard
"@napi-rs/canvas-linux-arm64-gnu@npm:1.0.0":
version: 1.0.0
resolution: "@napi-rs/canvas-linux-arm64-gnu@npm:1.0.0"
conditions: os=linux & cpu=arm64 & libc=glibc
languageName: node
linkType: hard
"@napi-rs/canvas-linux-arm64-musl@npm:1.0.0":
version: 1.0.0
resolution: "@napi-rs/canvas-linux-arm64-musl@npm:1.0.0"
conditions: os=linux & cpu=arm64 & libc=musl
languageName: node
linkType: hard
"@napi-rs/canvas-linux-riscv64-gnu@npm:1.0.0":
version: 1.0.0
resolution: "@napi-rs/canvas-linux-riscv64-gnu@npm:1.0.0"
conditions: os=linux & cpu=riscv64 & libc=glibc
languageName: node
linkType: hard
"@napi-rs/canvas-linux-x64-gnu@npm:1.0.0":
version: 1.0.0
resolution: "@napi-rs/canvas-linux-x64-gnu@npm:1.0.0"
conditions: os=linux & cpu=x64 & libc=glibc
languageName: node
linkType: hard
"@napi-rs/canvas-linux-x64-musl@npm:1.0.0":
version: 1.0.0
resolution: "@napi-rs/canvas-linux-x64-musl@npm:1.0.0"
conditions: os=linux & cpu=x64 & libc=musl
languageName: node
linkType: hard
"@napi-rs/canvas-win32-arm64-msvc@npm:1.0.0":
version: 1.0.0
resolution: "@napi-rs/canvas-win32-arm64-msvc@npm:1.0.0"
conditions: os=win32 & cpu=arm64
languageName: node
linkType: hard
"@napi-rs/canvas-win32-x64-msvc@npm:1.0.0":
version: 1.0.0
resolution: "@napi-rs/canvas-win32-x64-msvc@npm:1.0.0"
conditions: os=win32 & cpu=x64
languageName: node
linkType: hard
"@napi-rs/canvas@npm:^1.0.0":
version: 1.0.0
resolution: "@napi-rs/canvas@npm:1.0.0"
dependencies:
"@napi-rs/canvas-android-arm64": "npm:1.0.0"
"@napi-rs/canvas-darwin-arm64": "npm:1.0.0"
"@napi-rs/canvas-darwin-x64": "npm:1.0.0"
"@napi-rs/canvas-linux-arm-gnueabihf": "npm:1.0.0"
"@napi-rs/canvas-linux-arm64-gnu": "npm:1.0.0"
"@napi-rs/canvas-linux-arm64-musl": "npm:1.0.0"
"@napi-rs/canvas-linux-riscv64-gnu": "npm:1.0.0"
"@napi-rs/canvas-linux-x64-gnu": "npm:1.0.0"
"@napi-rs/canvas-linux-x64-musl": "npm:1.0.0"
"@napi-rs/canvas-win32-arm64-msvc": "npm:1.0.0"
"@napi-rs/canvas-win32-x64-msvc": "npm:1.0.0"
dependenciesMeta:
"@napi-rs/canvas-android-arm64":
optional: true
"@napi-rs/canvas-darwin-arm64":
optional: true
"@napi-rs/canvas-darwin-x64":
optional: true
"@napi-rs/canvas-linux-arm-gnueabihf":
optional: true
"@napi-rs/canvas-linux-arm64-gnu":
optional: true
"@napi-rs/canvas-linux-arm64-musl":
optional: true
"@napi-rs/canvas-linux-riscv64-gnu":
optional: true
"@napi-rs/canvas-linux-x64-gnu":
optional: true
"@napi-rs/canvas-linux-x64-musl":
optional: true
"@napi-rs/canvas-win32-arm64-msvc":
optional: true
"@napi-rs/canvas-win32-x64-msvc":
optional: true
canvas:
built: true
skia-canvas:
built: true
checksum: 10/1b2838833988c98d86105980ccd52fb53c4059e30732c7bebdf21774d83a351bf389337c8e6eb4d55fc31359accb90e27d8d80c94cc3bb08d415ab0586227464
languageName: node
linkType: hard
"@nestjs/axios@npm:^3.0.2":
version: 3.1.3
resolution: "@nestjs/axios@npm:3.1.3"
@@ -2900,6 +3030,13 @@ __metadata:
languageName: node
linkType: hard
"@protobufjs/eventemitter@npm:^1.1.1":
version: 1.1.1
resolution: "@protobufjs/eventemitter@npm:1.1.1"
checksum: 10/a54dc1aff4475ffad4fdf3235c71a553f5e40e3b4cf6a2e217151895a61cb4eb0be20d63791db22441ca25e594671f1021977133f9939540750231ff7d8e9dd6
languageName: node
linkType: hard
"@protobufjs/fetch@npm:^1.1.0":
version: 1.1.0
resolution: "@protobufjs/fetch@npm:1.1.0"
@@ -2910,6 +3047,15 @@ __metadata:
languageName: node
linkType: hard
"@protobufjs/fetch@npm:^1.1.1":
version: 1.1.1
resolution: "@protobufjs/fetch@npm:1.1.1"
dependencies:
"@protobufjs/aspromise": "npm:^1.1.1"
checksum: 10/427cf2da8c69b494b0df3b2fb1f43c97f0f71ca2c8ef8232dac7e44f2527ad0cc9cecb243eda14a918e86018bfa6d54d92252240d2b37ed205b13adb5506fa1d
languageName: node
linkType: hard
"@protobufjs/float@npm:^1.0.2":
version: 1.0.2
resolution: "@protobufjs/float@npm:1.0.2"
@@ -2931,6 +3077,13 @@ __metadata:
languageName: node
linkType: hard
"@protobufjs/inquire@npm:^1.1.2":
version: 1.1.2
resolution: "@protobufjs/inquire@npm:1.1.2"
checksum: 10/259756489c75a751552df60d18f82503d2534855646397b96b91cf15807fa852e99bd9eb73dabb64da37aec7913844032ecb031a4326d82aae622f5e4c2f8a17
languageName: node
linkType: hard
"@protobufjs/path@npm:^1.1.2":
version: 1.1.2
resolution: "@protobufjs/path@npm:1.1.2"
@@ -4464,15 +4617,15 @@ __metadata:
languageName: node
linkType: hard
"@wppconnect-team/wppconnect@npm:^2.0.2":
version: 2.0.2
resolution: "@wppconnect-team/wppconnect@npm:2.0.2"
"@wppconnect-team/wppconnect@npm:^2.2.1":
version: 2.2.1
resolution: "@wppconnect-team/wppconnect@npm:2.2.1"
dependencies:
"@img/sharp-win32-x64": "npm:^0.34.5"
"@wppconnect/wa-js": "npm:^4.1.0"
"@wppconnect/wa-version": "npm:^1.5.3829"
"@wppconnect/wa-js": "npm:^4.3.0"
"@wppconnect/wa-version": "npm:^1.5.3941"
atob: "npm:^2.1.2"
axios: "npm:^1.16.0"
axios: "npm:^1.16.1"
boxen: "npm:^5.1.2"
catch-exit: "npm:^2.0.0"
chalk: "npm:~4.1.2"
@@ -4485,7 +4638,7 @@ __metadata:
logform: "npm:^2.7.0"
lookpath: "npm:^1.2.3"
mime-types: "npm:^3.0.2"
puppeteer: "npm:^24.37.5"
puppeteer: "npm:^24.43.1"
puppeteer-extra: "npm:^3.3.6"
puppeteer-extra-plugin-stealth: "npm:^2.11.2"
puppeteer-extra-plugin-user-data-dir: "npm:^2.4.1"
@@ -4498,30 +4651,30 @@ __metadata:
tmp: "npm:^0.2.5"
tree-kill: "npm:^1.2.2"
winston: "npm:^3.19.0"
ws: "npm:^8.19.0"
ws: "npm:^8.21.0"
dependenciesMeta:
"@img/sharp-win32-x64":
optional: true
fsevents:
optional: true
checksum: 10/b1e5d83174e5786d17ceceec0917d077428d8f3b15f4f9e0df667f23b350d2fd984f322e7fc69270751e55aa10a4747ec95c7f5a6c5d7a6048094520e0e0686d
checksum: 10/d7940928c2874ddd0e2412b0628d2750c2457bf9b08a8840e79de24e8666f792e2e7a78c0bc16c13c4ab870e7d37683ad1b08522fb9ea7da90f695e5b21cc044
languageName: node
linkType: hard
"@wppconnect/wa-js@npm:^4.1.0":
version: 4.1.0
resolution: "@wppconnect/wa-js@npm:4.1.0"
checksum: 10/e807b6f3b0bdd1db0400937fe24fbdd27414d09067c734e4a7fa852155be28cda57cf64a6b7547c7f9559bd483e933a321156d87e39579bb342d7bbdcf273c9d
"@wppconnect/wa-js@npm:^4.3.0":
version: 4.3.0
resolution: "@wppconnect/wa-js@npm:4.3.0"
checksum: 10/844e63b58b62d4057ab51f114b3f9b12e13637a026b6df50c5f53801181a3800065342c0f79478db10d43dfe96a6ec60bf9d8fab7840caaf94970a774572279d
languageName: node
linkType: hard
"@wppconnect/wa-version@npm:^1.5.3829":
version: 1.5.3874
resolution: "@wppconnect/wa-version@npm:1.5.3874"
"@wppconnect/wa-version@npm:^1.5.3941":
version: 1.5.4014
resolution: "@wppconnect/wa-version@npm:1.5.4014"
dependencies:
node-fetch: "npm:^2.7.0"
semver: "npm:^7.7.4"
checksum: 10/37812da2cb3f63c93a6519251da3bc61f52fdd874842a9d31792430ff56e533e9d3f42feed9630bf1c77ec3a5e4d7347ffa4732d58e742d8463019f7770fb9c1
checksum: 10/97916af06fc2d1817cb3122233577b39047c46aa607ba0a14ff8d0ba9decd62a1d0220fc658da97dea378f433cc4b3bae89960d25320ae456d905b687ba40ad3
languageName: node
linkType: hard
@@ -9359,16 +9512,6 @@ __metadata:
languageName: node
linkType: hard
"libsignal@git+https://github.com/whiskeysockets/libsignal-node":
version: 2.0.1
resolution: "libsignal@https://github.com/whiskeysockets/libsignal-node.git#commit=e81ecfc32eb74951d789ab37f7e341ab66d5fff1"
dependencies:
curve25519-js: "npm:^0.0.4"
protobufjs: "npm:6.8.8"
checksum: 10/b3244ebb2f2580bda960180fcb91a20f47dfd68acc04ab3fff3163cd7205876307fb4a50af8830977d650c5719389a8acf547bf63fb722bcd3273e8fbdb94b95
languageName: node
linkType: hard
"libsignal@github:WhiskeySockets/libsignal-node#master":
version: 2.0.1
resolution: "libsignal@https://github.com/WhiskeySockets/libsignal-node.git#commit=e81ecfc32eb74951d789ab37f7e341ab66d5fff1"
@@ -9379,6 +9522,16 @@ __metadata:
languageName: node
linkType: hard
"libsignal@npm:^6.0.0":
version: 6.0.0
resolution: "libsignal@npm:6.0.0"
dependencies:
curve25519-js: "npm:^0.0.4"
protobufjs: "npm:^7.5.5"
checksum: 10/d2e2463f9bb630fe1c8bb2c6dd8d52312040dce8ab6acefd4a9c37a080156e22db52198d13fa4b5d16bc63ff4b01420f2748e6f5c05b1fd010316cf8bb69f5a8
languageName: node
linkType: hard
"lighthouse-logger@npm:^1.0.0":
version: 1.4.2
resolution: "lighthouse-logger@npm:1.4.2"
@@ -10498,6 +10651,13 @@ __metadata:
languageName: node
linkType: hard
"node-webpmux@npm:^3.2.1":
version: 3.2.1
resolution: "node-webpmux@npm:3.2.1"
checksum: 10/8797ebe4846707443629800c70d95c592ab1c18f313b360dd26832b8443efa9b89966269ad6b1e3aa59a18e790406b29a55e19c10212fc3068dc476e48cee6e9
languageName: node
linkType: hard
"nopt@npm:^5.0.0":
version: 5.0.0
resolution: "nopt@npm:5.0.0"
@@ -10597,6 +10757,13 @@ __metadata:
languageName: node
linkType: hard
"obuf@npm:^1.1.2":
version: 1.1.2
resolution: "obuf@npm:1.1.2"
checksum: 10/53ff4ab3a13cc33ba6c856cf281f2965c0aec9720967af450e8fd06cfd50aceeefc791986a16bcefa14e7898b3ca9acdfcf15b9d9a1b9c7e1366581a8ad6e65e
languageName: node
linkType: hard
"ogg-opus-decoder@npm:^1.6.12":
version: 1.7.0
resolution: "ogg-opus-decoder@npm:1.7.0"
@@ -11038,6 +11205,15 @@ __metadata:
languageName: node
linkType: hard
"pg-copy-streams@npm:^6.0.0":
version: 6.0.6
resolution: "pg-copy-streams@npm:6.0.6"
dependencies:
obuf: "npm:^1.1.2"
checksum: 10/58bd4a44f618e0706de402b401bad42fbb3b3743985ac5891536a239331469db3be734352955259bfdf46d1b772604a8da493edd16f0399f3685fcb4517bf165
languageName: node
linkType: hard
"pg-int8@npm:1.0.1":
version: 1.0.1
resolution: "pg-int8@npm:1.0.1"
@@ -11440,6 +11616,26 @@ __metadata:
languageName: node
linkType: hard
"protobufjs@npm:^7.5.5":
version: 7.6.1
resolution: "protobufjs@npm:7.6.1"
dependencies:
"@protobufjs/aspromise": "npm:^1.1.2"
"@protobufjs/base64": "npm:^1.1.2"
"@protobufjs/codegen": "npm:^2.0.5"
"@protobufjs/eventemitter": "npm:^1.1.1"
"@protobufjs/fetch": "npm:^1.1.1"
"@protobufjs/float": "npm:^1.0.2"
"@protobufjs/inquire": "npm:^1.1.2"
"@protobufjs/path": "npm:^1.1.2"
"@protobufjs/pool": "npm:^1.1.0"
"@protobufjs/utf8": "npm:^1.1.1"
"@types/node": "npm:>=13.7.0"
long: "npm:^5.3.2"
checksum: 10/010dcf77ab435da50d62887d57cc4b68384768e4d1cbe85948ec747996222ea659501b7c6b6d3426f96fdbaf59f577213788ce461c0938f89b8126569d8be7e2
languageName: node
linkType: hard
"protobufjs@npm:^7.5.6":
version: 7.5.6
resolution: "protobufjs@npm:7.5.6"
@@ -13795,7 +13991,9 @@ __metadata:
"@grpc/grpc-js": "npm:^1.14.1"
"@grpc/proto-loader": "npm:^0.8.0"
"@liaoliaots/nestjs-redis": "npm:^9"
"@lottiefiles/dotlottie-web": "npm:^0.72.1"
"@modelcontextprotocol/sdk": "npm:^1.29.0"
"@napi-rs/canvas": "npm:^1.0.0"
"@nestjs/axios": "npm:^3.0.2"
"@nestjs/bullmq": "npm:^11.0.2"
"@nestjs/cli": "npm:^11.0.0"
@@ -13824,8 +14022,8 @@ __metadata:
"@types/supertest": "npm:^2.0.8"
"@types/user-agents": "npm:^1"
"@types/ws": "npm:^8.5.4"
"@wppconnect-team/wppconnect": "npm:^2.0.2"
"@wppconnect/wa-js": "npm:^4.1.0"
"@wppconnect-team/wppconnect": "npm:^2.2.1"
"@wppconnect/wa-js": "npm:^4.3.0"
adm-zip: "npm:0.5.10"
agentkeepalive: "npm:^4.5.0"
async-lock: "npm:^1.4.1"
@@ -13864,10 +14062,12 @@ __metadata:
mustache: "npm:^4.2.0"
nestjs-pino: "npm:^4.1.0"
node-cache: "npm:5.1.2"
node-webpmux: "npm:^3.2.1"
oxlint: "npm:^1.13.0"
passport: "npm:^0.7.0"
passport-headerapikey: "npm:^1.2.2"
pg: "npm:^8.13.1"
pg-copy-streams: "npm:^6.0.0"
pino-http: "npm:^10.2.0"
pino-pretty: "npm:^11.2.1"
prettier: "npm:^1.19.1"