Compare commits

...
22 Commits
Author SHA1 Message Date
devlikepro 3b6fa3343d [core] 2025.6.4
Release / WEBJS - chrome - amd64 - chrome (push) Waiting to run
Release / WEBJS - chromium - amd64 - latest (push) Waiting to run
Release / WEBJS - chromium - linux/arm64 - arm (push) Waiting to run
Release / GOWS - none - amd64 - gows (push) Waiting to run
Release / GOWS - none - linux/arm64 - gows-arm (push) Waiting to run
Release / NOWEB - none - amd64 - noweb (push) Waiting to run
Release / NOWEB - none - linux/arm64 - noweb-arm (push) Waiting to run
2025-06-21 17:34:41 +07:00
devlikepro 469cdc9b1d [core] no xvfb if not installed (noweb/gows image) 2025-06-21 17:34:41 +07:00
devlikepro 8a179e7194 [core] NOWEB - sleep for sqlite3 in batch 2025-06-21 17:34:41 +07:00
devlikepro c133795753 [core] NOWEB - log length, not json
fix devlikeapro/waha#1054
2025-06-21 17:34:41 +07:00
devlikepro 941ace7461 [core] 2025.6.3
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
2025-06-16 13:05:20 +07:00
devlikepro 8589420f5d [core] do not stop ws socket in gateway 2025-06-16 13:05:20 +07:00
devlikepro ece24e182f [core] WEBJS - xvfb 2025-06-16 13:05:19 +07:00
devlikepro 38e5bb7bdf [core] NOWEB - Add timeout 5s for async lock 2025-06-16 13:05:19 +07:00
devlikepro d09e931827 [core] chrome 137.0.7151.103-1 2025-06-16 13:05:19 +07:00
devlikepro 3f3f1e1c20 [core] Inline WAMessage in message.edited 2025-06-16 13:05:18 +07:00
devlikepro 32f2468ba8 [core] Add WAHAWebhookMessageEdited to webhooks list 2025-06-16 13:05:17 +07:00
devlikepro 1d6540e0d9 [core] editedMessageId 2025-06-16 13:05:17 +07:00
devlikepro 31da4bfca1 [core] WEBJS - message.edited
fix #1041
2025-06-16 13:05:17 +07:00
devlikepro 41d49894e2 [core] WEBJS - message.revoked add revokedMessageId 2025-06-16 13:05:17 +07:00
devlikepro 6dcdd15b1c [core] message.revoked - add revokedMessageId - NOWEB, GOWS 2025-06-16 13:05:17 +07:00
devlikepro c2b275fd90 [core] NOWEB - message.edited 2025-06-16 13:05:16 +07:00
devlikepro e4838ef3ef [core] GOWS - message.edited
fix #916
2025-06-16 13:05:16 +07:00
devlikepro 55ca4660d7 [core] GOWS - message.revoked
fix #917
2025-06-16 13:05:15 +07:00
devlikepro c16c903ec4 [core] node 22.16 2025-06-16 13:05:15 +07:00
devlikepro 937cf38ff6 [core] Up libs 2025-06-16 13:05:15 +07:00
devlikepro 07f76e854d [core] Update axios 2025-06-16 13:05:15 +07:00
devlikepro cde1d084c6 [core] NOWEB fix issue with can not read property of null 2025-06-16 13:05:14 +07:00
19 changed files with 2711 additions and 2591 deletions

No files matched your search

+1 -1
View File
@@ -1 +1 @@
v22.8
v22.16
+17 -2
View File
@@ -1,7 +1,7 @@
#
# Build
#
ARG NODE_VERSION=22.8-bullseye
ARG NODE_VERSION=22.16-bullseye
FROM node:${NODE_VERSION} AS build
ENV PUPPETEER_SKIP_DOWNLOAD=True
@@ -119,6 +119,21 @@ RUN if [ "$USE_BROWSER" = "chromium" ] || [ "$USE_BROWSER" = "chrome" ]; then \
&& rm -rf /var/lib/apt/lists/*; \
fi
# Install xvfb
RUN if [ "$USE_BROWSER" = "chromium" ] || [ "$USE_BROWSER" = "chrome" ]; then \
apt-get update && apt-get install -y --no-install-recommends \
xvfb \
libnss3 \
libxss1 \
libasound2 \
libatk-bridge2.0-0 \
libgtk-3-0 \
libdrm2 \
ca-certificates \
curl \
&& rm -rf /var/lib/apt/lists/*; \
fi
# Install Chromium
RUN if [ "$USE_BROWSER" = "chromium" ]; then \
apt-get update \
@@ -131,7 +146,7 @@ RUN if [ "$USE_BROWSER" = "chromium" ]; then \
# Install Chrome
# Available versions:
# https://www.ubuntuupdates.org/package/google_chrome/stable/main/base/google-chrome-stable
ARG CHROME_VERSION="137.0.7151.55-1"
ARG CHROME_VERSION="137.0.7151.103-1"
RUN if [ "$USE_BROWSER" = "chrome" ]; then \
wget --no-verbose -O /tmp/chrome.deb https://dl.google.com/linux/chrome/deb/pool/main/g/google-chrome-stable/google-chrome-stable_${CHROME_VERSION}_amd64.deb \
&& apt-get update \
+1 -1
View File
@@ -146,7 +146,7 @@ curl -d "{\"chatId\": \"${PHONE}@c.us\", \"text\": \"Hello from WhatsApp HTTP AP
## Start the project
1. Clone the repository
2. Make sure you're using node>=22
2. Make sure you're using node>=22 (check [.nvmrc](/.nvmrc) to get the version)
3. Run the following commands:
```bash
# Install dependencies
+10
View File
@@ -1,5 +1,15 @@
#!/bin/sh
# Check if Xvfb command exists
if command -v Xvfb > /dev/null 2>&1; then
# Start virtual X server in the background
Xvfb :99 -screen 0 1280x720x24 &
export DISPLAY=:99
sleep 2
else
echo "Xvfb command not found, skipping virtual X server setup"
fi
# Calculate UV_THREADPOOL_SIZE based on number of CPUs
cpus=$(node -e "const os = require('os'); console.log(os.cpus().length);")
uv_threadpool_size=$(($cpus * 1))
+3 -2
View File
@@ -49,7 +49,7 @@
"agentkeepalive": "^4.5.0",
"async-lock": "^1.4.1",
"audio-decode": "^2.2.2",
"axios": "^1.7.7",
"axios": "^1.9.0",
"axios-retry": "^4.5.0",
"better-sqlite3": "11.3.0",
"check-disk-space": "^3.4.0",
@@ -99,7 +99,8 @@
"ws": "^8.18.0",
"puppeteer": "^24.10.0",
"whatwg-url": "13.0.0",
"libsignal": "github:devlikeapro/libsignal-node#fork-master"
"libsignal": "github:devlikeapro/libsignal-node#fork-master",
"axios": "^1.9.0"
},
"devDependencies": {
"@grpc/grpc-js": "^1.13.4",
+1
View File
@@ -57,6 +57,7 @@ export abstract class SessionManager
protected gowsConfigService: GowsEngineConfigService,
) {
this.lock = new AsyncLock({
timeout: 5_000,
maxPending: Infinity,
maxExecutionTime: 30_000,
});
+4 -4
View File
@@ -108,13 +108,13 @@ export class WebsocketGatewayCore
// Cherry-pick from nestjs new version
// https://github.com/nestjs/nest/pull/13531/files
private async close(server: any) {
const closeEventSignal = new Promise((resolve, reject) =>
server.close((err) => (err ? reject(err) : resolve(undefined))),
);
// const closeEventSignal = new Promise((resolve, reject) =>
// server.close((err) => (err ? reject(err) : resolve(undefined))),
// );
for (const ws of server.clients) {
ws.terminate();
}
await closeEventSignal;
// await closeEventSignal;
}
afterInit(server: Server) {
@@ -129,6 +129,8 @@ import {
EnginePayload,
PollVotePayload,
WAMessageAckBody,
WAMessageEditedBody,
WAMessageRevokedBody,
} from '@waha/structures/webhooks.dto';
import { PaginatorInMemory } from '@waha/utils/Paginator';
import { sleep, waitUntil } from '@waha/utils/promiseTimeout';
@@ -407,6 +409,53 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
this.events2.get(WAHAEvents.MESSAGE).switch(messagesFromOthers$);
this.events2.get(WAHAEvents.MESSAGE_ANY).switch(messagesFromAll$);
// Handle revoked messages
const messagesRevoked$ = messages$.pipe(
filter((msg) => {
return (
msg?.Message?.protocolMessage?.type === 0 &&
msg?.Message?.protocolMessage?.key !== undefined
);
}),
mergeMap(async (message): Promise<WAMessageRevokedBody> => {
const afterMessage = await this.toWAMessage(message);
// Extract the revoked message ID from protocolMessage.key
const revokedMessageId = message.Message.protocolMessage.key?.ID;
return {
after: afterMessage,
before: null,
revokedMessageId: revokedMessageId,
_data: message,
};
}),
);
this.events2.get(WAHAEvents.MESSAGE_REVOKED).switch(messagesRevoked$);
// Handle edited messages
const messagesEdited$ = messages$.pipe(
filter((msg) => {
return (
msg?.Message?.protocolMessage?.type === 14 &&
msg?.Message?.protocolMessage?.editedMessage !== undefined
);
}),
mergeMap(async (message): Promise<WAMessageEditedBody> => {
const waMessage = await this.toWAMessage(message);
// Extract the body from editedMessage using extractBody function
const body =
this.extractBody(message.Message.protocolMessage.editedMessage) || '';
// Extract the original message ID from protocolMessage.key
const editedMessageId = message.Message.protocolMessage.key?.ID;
return {
...waMessage,
body: body,
editedMessageId: editedMessageId,
_data: message,
};
}),
);
this.events2.get(WAHAEvents.MESSAGE_EDITED).switch(messagesEdited$);
const receipt$ = all$.pipe(onlyEvent(WhatsMeowEvent.RECEIPT));
const messageAck$ = receipt$.pipe(
mergeMap(this.receiptToMessageAck.bind(this)),
+40 -2
View File
@@ -159,6 +159,7 @@ import {
PollVote,
PollVotePayload,
WAMessageAckBody,
WAMessageEditedBody,
WAMessageRevokedBody,
} from '@waha/structures/webhooks.dto';
import { LoggerBuilder } from '@waha/utils/logging';
@@ -595,7 +596,8 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
if (
protocolMsg !== null &&
protocolMsg !== undefined &&
protocolMsg.editedMessage
protocolMsg.editedMessage &&
protocolMsg.key
) {
this.sock?.ev.emit('messages.update', [
{
@@ -1829,15 +1831,45 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
),
mergeMap(async (message): Promise<WAMessageRevokedBody> => {
const afterMessage = await this.toWAMessage(message);
// Extract the revoked message ID from protocolMessage.key
const revokedMessageId = message.message.protocolMessage.key?.id;
return {
after: afterMessage,
before: null,
revokedMessageId: revokedMessageId,
_data: message,
};
}),
);
this.events2.get(WAHAEvents.MESSAGE_REVOKED).switch(messagesRevoked$);
// Handle edited messages
// @ts-ignore
const messagesEdited$ = messagesUpsert$.pipe(
filter(
(message) =>
// @ts-ignore
message.message?.protocolMessage?.type ===
proto.Message.ProtocolMessage.Type.MESSAGE_EDIT &&
message.message?.protocolMessage?.editedMessage !== undefined,
),
mergeMap(async (message): Promise<WAMessageEditedBody> => {
const waMessage = await this.toWAMessage(message);
// Extract the body from editedMessage using extractBody function
const body =
this.extractBody(message.message.protocolMessage.editedMessage) || '';
// Extract the original message ID from protocolMessage.key
const editedMessageId = message.message.protocolMessage.key?.id;
return {
...waMessage,
body: body,
editedMessageId: editedMessageId,
_data: message,
};
}),
);
this.events2.get(WAHAEvents.MESSAGE_EDITED).switch(messagesEdited$);
//
// Message Reactions
//
@@ -2101,12 +2133,18 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
if (message.message.pollUpdateMessage) return;
// Ignore calls, we have dedicated handler for that
if (message.message.call?.callKey) return;
// Ignore revoke, we have a dedicated handler for that
// Ignore revoke, we have a dedicated event for that
if (
message.message?.protocolMessage?.type ===
proto.Message.ProtocolMessage.Type.REVOKE
)
return;
// Ignore edit, we have a dedicated event for that
if (
message.message?.protocolMessage?.type ===
proto.Message.ProtocolMessage.Type.MESSAGE_EDIT
)
return;
if (
message.message?.protocolMessage?.type ===
proto.Message.ProtocolMessage.Type.EPHEMERAL_SYNC_RESPONSE
@@ -64,11 +64,13 @@ export class NowebPersistentStore implements INowebStore {
public presences: any;
private lock: any = new AsyncLock({
timeout: 5_000,
maxPending: Infinity,
maxExecutionTime: 60_000,
});
private groupsFetchLock: any = new AsyncLock({
timeout: 5_000,
maxPending: Infinity,
maxExecutionTime: 60_000,
});
@@ -19,6 +19,7 @@ const AsyncLock = require('async-lock');
// Default pending is 1000, set it to infinity
// https://github.com/rogierschouten/async-lock/issues/63
const fileLock = new AsyncLock({
timeout: 5_000,
maxPending: Infinity,
maxExecutionTime: 30_000,
});
@@ -111,6 +111,7 @@ import { StatusRequest, TextStatus } from '@waha/structures/status.dto';
import {
EnginePayload,
WAMessageAckBody,
WAMessageEditedBody,
WAMessageRevokedBody,
} from '@waha/structures/webhooks.dto';
import { PaginatorInMemory } from '@waha/utils/Paginator';
@@ -1418,9 +1419,12 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
const beforeMessage = event.before
? this.toWAMessage(event.before)
: null;
// Extract the revoked message ID from the protocolMessageKey.id field
const revokedMessageId = afterMessage?._data?.protocolMessageKey?.id;
return {
after: afterMessage,
before: beforeMessage,
revokedMessageId: revokedMessageId,
};
}),
);
@@ -1432,6 +1436,26 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
);
this.events2.get(WAHAEvents.MESSAGE_REACTION).switch(messagesReaction$);
const messageEdit$ = fromEvent(
this.whatsapp,
Events.MESSAGE_EDIT,
(message, newBody, prevBody) => {
return { message, newBody, prevBody };
},
);
const messagesEdit$ = messageEdit$.pipe(
map((event): WAMessageEditedBody => {
const message = this.toWAMessage(event.message);
return {
...message,
body: event.newBody,
editedMessageId: message._data?.id?.id,
_data: event,
};
}),
);
this.events2.get(WAHAEvents.MESSAGE_EDITED).switch(messagesEdit$);
const messageAckWEBJS$ = fromEvent(
this.whatsapp,
Events.MESSAGE_ACK,
+2 -4
View File
@@ -79,15 +79,13 @@ export class SqlKVRepository<Entity> {
}
}
private async upsertBatch(entities: Entity[]): Promise<void> {
protected async upsertBatch(entities: Entity[]): Promise<void> {
const all = entities.map((entity) => this.dump(entity));
// make it unique by .id
const data = lodash.uniqBy(all, (d: any) => d.id);
if (data.length != all.length) {
console.warn(
`WARNING - Duplicated entities for upsert batch: ${JSON.stringify(
entities,
)}`,
`WARNING - Duplicated entities for upsert batch: all=${all.length}, data=${data.length}`,
);
}
const columns = this.columns.map((c) => `"${c.fieldName}"`);
@@ -1,6 +1,7 @@
import { SqlKVRepository } from '@waha/core/storage/sql/SqlKVRepository';
import { Sqlite3Engine } from '@waha/core/storage/sqlite3/Sqlite3Engine';
import { Sqlite3JsonQuery } from '@waha/core/storage/sqlite3/Sqlite3JsonQuery';
import { sleep } from '@waha/utils/promiseTimeout';
import { Database } from 'better-sqlite3';
import Knex from 'knex';
@@ -18,4 +19,12 @@ export class Sqlite3KVRepository<Entity> extends SqlKVRepository<Entity> {
super(engine, knex);
this.db = db;
}
protected async upsertBatch(entities: Entity[]): Promise<void> {
await super.upsertBatch(entities);
// Give some time to the Node.js loop because we're using sync better-sqlite
if (entities.length >= this.UPSERT_BATCH_SIZE) {
await sleep(1);
}
}
}
+1
View File
@@ -8,6 +8,7 @@ export enum WAHAEvents {
MESSAGE_ACK = 'message.ack',
MESSAGE_WAITING = 'message.waiting',
MESSAGE_REVOKED = 'message.revoked',
MESSAGE_EDITED = 'message.edited',
STATE_CHANGE = 'state.change',
GROUP_JOIN = 'group.join',
GROUP_LEAVE = 'group.leave',
+25
View File
@@ -93,9 +93,24 @@ export class PollVotePayload {
export class WAMessageRevokedBody {
after?: WAMessage;
before?: WAMessage;
@ApiProperty({
description: 'ID of the message that was revoked',
example: 'A06CA7BB5DD8C8F705628CDB7E3A33C9',
})
revokedMessageId?: string;
_data?: any;
}
export class WAMessageEditedBody extends WAMessage {
@ApiProperty({
description: 'ID of the original message that was edited',
example: 'A06CA7BB5DD8C8F705628CDB7E3A33C9',
})
editedMessageId?: string;
}
export class WASessionStatusBody {
@ApiProperty({
example: 'default',
@@ -206,6 +221,16 @@ export class WAHAWebhookMessageRevoked extends WAHAWebhook {
payload: WAMessageRevokedBody;
}
export class WAHAWebhookMessageEdited extends WAHAWebhook {
@ApiProperty({
description:
'The event is triggered when a user edits a previously sent message.',
})
event = WAHAEvents.MESSAGE_EDITED;
payload: WAMessageEditedBody;
}
export class WAHAWebhookStateChange extends WAHAWebhook {
@ApiProperty({
description: 'It’s an internal engine’s state, not session status.',
+2
View File
@@ -21,6 +21,7 @@ import {
WAHAWebhookMessage,
WAHAWebhookMessageAck,
WAHAWebhookMessageAny,
WAHAWebhookMessageEdited,
WAHAWebhookMessageReaction,
WAHAWebhookMessageRevoked,
WAHAWebhookPollVote,
@@ -37,6 +38,7 @@ const WAHA_WEBHOOKS = [
WAHAWebhookMessageAny,
WAHAWebhookMessageAck,
WAHAWebhookMessageRevoked,
WAHAWebhookMessageEdited,
WebhookGroupV2Join,
WebhookGroupV2Leave,
WebhookGroupV2Update,
+1 -1
View File
@@ -33,7 +33,7 @@ export function getEngineName(): string {
}
export const VERSION: WAHAEnvironment = {
version: '2025.6.2',
version: '2025.6.4',
engine: getEngineName(),
tier: getWAHAVersion(),
browser:
+2518 -2574
View File
File diff suppressed because it is too large. Load diff