Compare commits

..
29 Commits
Author SHA1 Message Date
devlikepro 8f867e0b5d [core] 2026.1.2
Release / WEBJS - chrome - amd64 - chrome (push) Waiting to run
Release / WEBJS - chromium - amd64 - latest (push) Waiting to run
Release / WEBJS - chromium - linux/arm64 - arm (push) Waiting to run
Release / GOWS - none - amd64 - gows (push) Waiting to run
Release / GOWS - none - linux/arm64 - gows-arm (push) Waiting to run
Release / NOWEB - none - amd64 - noweb (push) Waiting to run
Release / NOWEB - none - linux/arm64 - noweb-arm (push) Waiting to run
2026-01-12 16:52:10 +07:00
devlikepro d4f6ec18fd [core] GOWS - receive and send video note
fix #1798
2026-01-12 16:52:10 +07:00
devlikepro ac05dd83e9 [core] NOWEB - handle video note media 2026-01-12 16:50:35 +07:00
devlikepro 17c22f0cd4 [core] Up WEBSJ - fix send media to channels
fix #1775
2026-01-12 16:50:34 +07:00
devlikepro 971979c13f [core] Do not send double session.status
fix #1784
2026-01-12 16:50:34 +07:00
devlikepro 845e203637 [core] GOWS - stop sessions first, then grpc
fix #1784
2026-01-12 16:50:34 +07:00
devlikepro 7a2e1cd731 [core] GOWS - start session in the background 2026-01-12 16:50:34 +07:00
devlikepro 84ba011a4c [core] Up GOWS - add profiling 2026-01-12 16:50:33 +07:00
devlikepro 50399f1192 [core] Add associatedChildMessage to edited message
fix #1773
2026-01-12 16:50:33 +07:00
devlikepro a2517b248d [core] ChatWoot - handle not found app
fix #1290
fix #1790
2026-01-12 16:50:33 +07:00
devlikepro f02d95331a [core] ChatWoot - support @lid mention directly
fix #1767
closes devlikeapro/waha-plus#36
2026-01-12 16:50:32 +07:00
Berg Pinheiro cb1faff88c [core] ChatWoot - support @mention and @all in group messages
fix #1767
closes devlikeapro/waha-plus#36
2026-01-12 16:50:32 +07:00
devlikepro 3ff1c06eea [core] ChatWoot - add session info api in client 2026-01-12 16:50:32 +07:00
devlikepro c91d2c59eb [core] ChatWoot - add group participants api in client 2026-01-12 16:50:32 +07:00
devlikepro 4cf5a74d23 [core] Groups - allow @all be with other mentions - we resolve group participants anyway 2026-01-12 16:50:32 +07:00
devlikepro 13de42dbe0 [core] Up GOWS
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
fix #1749
2026-01-02 19:47:30 +07:00
devlikepro f99b86ed18 [core] WEBJS - can not read properties of null - client
fix #1763
closes #1764
2026-01-02 19:27:50 +07:00
devlikepro 07f6c78372 [core] 2026.1.1 2026-01-02 19:27:50 +07:00
devlikepro 83678553e3 [core] 2025.12.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-12-30 17:38:14 +07:00
devlikepro 9d1bbd780e [core] NOWEB - use device name if no browser 2025-12-30 17:38:14 +07:00
devlikepro 899f9f2e84 [core] WAHA_CLIENT_DEVICE_NAME in .env 2025-12-30 17:38:14 +07:00
devlikepro 1d1a083464 [core] GOWS, NOWEB - motion_video (WhatsApp Live Photo / Motion Photo) messages are not supported
fix #175
2025-12-30 17:38:14 +07:00
devlikepro fabc6753e6 [core] Up Dashboard 2025-12-30 17:38:14 +07:00
devlikepro a16f3b67fe [core] Add device and browser config - WEBJS, GOWS, NOWEB
fix #1109
2025-12-30 17:38:13 +07:00
devlikepro c0c3381a36 [core] WEBJS - Up user agent 2025-12-30 17:38:13 +07:00
devlikepro 212a27d9df [core] Up GOWS
fix #1746
2025-12-30 17:38:13 +07:00
devlikepro a18c1d82c1 [core] GOWS - use a new grpc client for each session 2025-12-30 17:38:13 +07:00
devlikepro 67349e4509 [core] WEBJS - fix empty /me when WORKING session
fix #1735

[core] WEBJS
2025-12-30 17:38:13 +07:00
devlikepro 293267536b [core] Add platform to /version 2025-12-30 17:38:12 +07:00
28 changed files with 475 additions and 69 deletions

No files matched your search

+2
View File
@@ -22,6 +22,8 @@ WHATSAPP_SWAGGER_ENABLED=True
# WhatsApp engine (WEBJS is default, GOWS or NOWEB for better performance)
WHATSAPP_DEFAULT_ENGINE=WEBJS
# WAHA_CLIENT_DEVICE_NAME=YourApp
# Base URL for the API (used for webhooks, file URLs, etc.)
WAHA_BASE_URL=http://localhost:3000
# WAHA_PUBLIC_URL=http://localhost:3000
+2
View File
@@ -32,6 +32,8 @@ WAHA_PUBLIC_URL=http://localhost:3000
# WhatsApp engine (WEBJS is default, GOWS or NOWEB for better performance)
WHATSAPP_DEFAULT_ENGINE=GOWS
# WAHA_CLIENT_DEVICE_NAME=YourApp
# =================
# ===== APPS ======
# =================
+19
View File
@@ -146,6 +146,17 @@ export class WAHASelf {
.then((response) => response.data);
}
async getGroupParticipants(
session: string,
groupId: string,
opts?: RequestOptions,
) {
const url = `/api/${session}/groups/${groupId}/participants/v2`;
return await this.client
.get(url, { signal: opts?.signal })
.then((response) => response.data);
}
async getChannel(
session: string,
channelId: string,
@@ -371,6 +382,10 @@ export class WAHASessionAPI {
return this.api.getGroup(this.session, groupId, opts);
}
getGroupParticipants(groupId: string, opts?: RequestOptions): Promise<any> {
return this.api.getGroupParticipants(this.session, groupId, opts);
}
getChannel(channelId: string, opts?: RequestOptions): Promise<Channel> {
return this.api.getChannel(this.session, channelId, opts);
}
@@ -459,4 +474,8 @@ export class WAHASessionAPI {
findLIDByPN(pn: string, opts?: RequestOptions) {
return this.api.findLIDByPN(this.session, pn, opts);
}
getSessionInfo(opts?: RequestOptions) {
return this.api.get(this.session, opts);
}
}
+10 -1
View File
@@ -8,6 +8,7 @@ import { InboxData } from '@waha/apps/chatwoot/consumers/types';
import { DIContainer } from '@waha/apps/chatwoot/di/DIContainer';
import {
ChatIDNotFoundForContactError,
ChatWootAppNotFoundError,
PhoneNumberNotFoundInWhatsAppError,
} from '@waha/apps/chatwoot/errors';
import { WAHASessionAPI } from '@waha/apps/app_sdk/waha/WAHASelf';
@@ -43,7 +44,11 @@ export abstract class ChatWootInboxMessageConsumer extends AppConsumer {
const knex = this.manager.store.getWAHADatabase();
this.appRepository = new AppRepository(knex);
const logger = new JobLoggerWrapper(job, this.logger);
const app = await this.appRepository.getById(appId);
const app = await this.appRepository.findEnabledAppById(appId);
if (!app) {
logger.warn(`Chatwoot app not found or disabled: ${appId}`);
throw new ChatWootAppNotFoundError(appId);
}
return new DIContainer(app.pk, app.config, logger, knex);
}
@@ -86,6 +91,10 @@ export abstract class ChatWootInboxMessageConsumer extends AppConsumer {
await this.ReportErrorRecovered(job, body);
return result;
} catch (err) {
if (err instanceof ChatWootAppNotFoundError) {
this.logger.warn(err.message);
throw err;
}
await this.ReportErrorForMessage(job, err, body);
throw err;
}
@@ -16,6 +16,7 @@ import {
MessageMappingService,
} from '@waha/apps/chatwoot/storage';
import { MarkdownToWhatsApp } from '@waha/apps/chatwoot/messages/to/whatsapp/markdown';
import { parseMentionsFromText } from '@waha/apps/chatwoot/messages/to/whatsapp/mentions';
import { SessionManager } from '@waha/core/abc/manager.abc';
import { RMutexService } from '@waha/modules/rmutex/rmutex.service';
import {
@@ -36,6 +37,7 @@ import {
LinkPreview,
} from '@waha/apps/chatwoot/dto/config.dto';
import { Locale } from '@waha/apps/chatwoot/i18n/locale';
import { isJidGroup } from '@waha/core/utils/jids';
// eslint-disable-next-line @typescript-eslint/no-var-requires
const mime = require('mime-types');
@@ -90,7 +92,16 @@ export class MessageHandler {
return;
}
const content = MarkdownToWhatsApp(message.content);
let content: string = message.content;
let mentions: null | string[] = null;
if (isJidGroup(chatId)) {
// Parse any mention in the text and get a new clean text if required
const parsed = parseMentionsFromText(message.content);
content = parsed.text;
mentions = parsed.mentions;
}
content = MarkdownToWhatsApp(content);
const results = [];
let part = 0; // Start from 0 and increment before each send/check
const replyTo = await this.getReplyTo(message).catch((err) => {
@@ -116,7 +127,7 @@ export class MessageHandler {
content: content,
chatwoot: body,
});
const msg = await this.sendTextMessage(chatId, text, replyTo);
const msg = await this.sendTextMessage(chatId, text, replyTo, mentions);
results.push(msg);
await this.saveMapping(message, msg, part);
this.logger.info(`Text message sent: ${msg.id}`);
@@ -139,7 +150,7 @@ export class MessageHandler {
chatwoot: body,
singleAttachment: attachments.length == 1,
});
const msg = await this.sendFile(chatId, caption, file, replyTo);
const msg = await this.sendFile(chatId, caption, replyTo, mentions, file);
this.logger.info(
`File message sent: ${msg.id} - ${file.data_url} - ${file.file_type}`,
);
@@ -196,6 +207,7 @@ export class MessageHandler {
chatId: string,
content: string,
replyTo: string,
mentions: string[] | null,
) {
const request: MessageTextRequest = {
session: '',
@@ -206,6 +218,7 @@ export class MessageHandler {
this.config.linkPreview,
),
linkPreviewHighQuality: this.config.linkPreview == LinkPreview.HQ,
mentions: mentions,
};
const session = this.session;
await session.readMessages(chatId);
@@ -219,8 +232,9 @@ export class MessageHandler {
private async sendFile(
chatId: string,
content: string,
file: MessageAttachment,
replyTo: string,
mentions: string[] | null,
file: MessageAttachment,
) {
const url = file.data_url;
let filename = url.split('/').pop();
@@ -245,6 +259,7 @@ export class MessageHandler {
url: url,
mimetype: mimetype,
},
mentions: mentions,
};
return session.sendImage(imageRequest);
case 'video':
@@ -262,6 +277,7 @@ export class MessageHandler {
filename: filename,
},
convert: true,
mentions: mentions,
};
return session.sendVideo(videoRequest);
case 'audio':
@@ -288,6 +304,7 @@ export class MessageHandler {
url: url,
mimetype: mimetype,
},
mentions: mentions,
};
return session.sendFile(fileRequest);
}
+10 -1
View File
@@ -11,6 +11,7 @@ import { PinoLogger } from 'nestjs-pino';
import { AppRepository } from '../../storage';
import { TKey } from '@waha/apps/chatwoot/i18n/templates';
import { ChatWootAppNotFoundError } from '@waha/apps/chatwoot/errors';
/**
* Base class for ChatWoot scheduled consumers
@@ -35,7 +36,11 @@ export abstract class ChatWootScheduledConsumer extends AppConsumer {
const knex = this.manager.store.getWAHADatabase();
this.appRepository = new AppRepository(knex);
const logger = new JobLoggerWrapper(job, this.logger);
const app = await this.appRepository.getById(appId);
const app = await this.appRepository.findEnabledAppById(appId);
if (!app) {
logger.warn(`Chatwoot app not found or disabled: ${appId}`);
throw new ChatWootAppNotFoundError(appId);
}
return new DIContainer(app.pk, app.config, logger, knex);
}
@@ -62,6 +67,10 @@ export abstract class ChatWootScheduledConsumer extends AppConsumer {
await this.ReportErrorRecovered(job);
return result;
} catch (err) {
if (err instanceof ChatWootAppNotFoundError) {
this.logger.warn(err.message);
throw err;
}
await this.ReportErrorForJob(job, err);
throw err;
}
+10 -1
View File
@@ -13,6 +13,7 @@ import { AppRepository } from '../../storage';
import { TKey } from '@waha/apps/chatwoot/i18n/templates';
import { SignalRace } from '@waha/utils/abortable';
import { IsCommandsChat } from '@waha/apps/chatwoot/client/ids';
import { ChatWootAppNotFoundError } from '@waha/apps/chatwoot/errors';
/**
* Base class for ChatWoot background task consumers
@@ -36,7 +37,11 @@ export abstract class ChatWootTaskConsumer extends AppConsumer {
const knex = this.manager.store.getWAHADatabase();
this.appRepository = new AppRepository(knex);
const logger = new JobLoggerWrapper(job, this.logger);
const app = await this.appRepository.getById(appId);
const app = await this.appRepository.findEnabledAppById(appId);
if (!app) {
logger.warn(`Chatwoot app not found or disabled: ${appId}`);
throw new ChatWootAppNotFoundError(appId);
}
return new DIContainer(app.pk, app.config, logger, knex);
}
@@ -68,6 +73,10 @@ export abstract class ChatWootTaskConsumer extends AppConsumer {
await this.ReportErrorRecovered(job);
return result;
} catch (err) {
if (err instanceof ChatWootAppNotFoundError) {
this.logger.warn(err.message);
throw err;
}
this.ReportErrorForJob(job, err).catch((exc) =>
this.logger.error(`Failed to report error for job ${job.id}: ${exc}`),
);
+10 -1
View File
@@ -12,6 +12,7 @@ import { WhatsAppChatIdKey } from '@waha/apps/chatwoot/consumers/mutex';
import { EventData } from '@waha/apps/chatwoot/consumers/types';
import { WhatsAppContactInfo } from '@waha/apps/chatwoot/contacts/WhatsAppContactInfo';
import { DIContainer } from '@waha/apps/chatwoot/di/DIContainer';
import { ChatWootAppNotFoundError } from '@waha/apps/chatwoot/errors';
import { Locale } from '@waha/apps/chatwoot/i18n/locale';
import { WAHASelf, WAHASessionAPI } from '@waha/apps/app_sdk/waha/WAHASelf';
import {
@@ -83,6 +84,10 @@ export abstract class ChatWootWAHABaseConsumer extends AppConsumer {
await this.ReportErrorRecovered(errorReportInfo, job);
return result;
} catch (err) {
if (err instanceof ChatWootAppNotFoundError) {
this.logger.warn(err.message);
throw err;
}
await this.ReportErrorForMessage(errorReportInfo, job, err);
throw err;
}
@@ -95,7 +100,11 @@ export abstract class ChatWootWAHABaseConsumer extends AppConsumer {
const knex = this.manager.store.getWAHADatabase();
this.appRepository = new AppRepository(knex);
const logger = new JobLoggerWrapper(job, this.logger);
const app = await this.appRepository.getById(appId);
const app = await this.appRepository.findEnabledAppById(appId);
if (!app) {
logger.warn(`Chatwoot app not found or disabled: ${appId}`);
throw new ChatWootAppNotFoundError(appId);
}
return new DIContainer(app.pk, app.config, logger, knex);
}
@@ -45,6 +45,12 @@ export class ChatWootErrorReporter {
// There's retries more left - ignore it for now
return;
}
if (!conversation) {
this.logger.error(
'Chatwoot error report skipped: conversation not ready',
);
return;
}
const attempts = {
current: this.job.attemptsMade + 1,
max: this.job.opts?.attempts || 1,
@@ -87,6 +93,12 @@ export class ChatWootErrorReporter {
// https://github.com/devlikeapro/waha/issues/1395
return null;
if (!conversation) {
this.logger.warn(
'Chatwoot success report skipped: conversation not ready',
);
return;
}
const template = this.l.key(TKey.JOB_REPORT_SUCCEEDED);
const attempts = {
current: this.job.attemptsMade + 1,
+6
View File
@@ -15,3 +15,9 @@ export class UnknownJIDFormat extends Error {
super(`WhatsApp Chat ID is not recognized: ${jid}`);
}
}
export class ChatWootAppNotFoundError extends Error {
constructor(public appId: string) {
super(`Chatwoot app not found or disabled: ${appId}`);
}
}
@@ -0,0 +1,115 @@
import { parseMentionsFromText } from './mentions';
describe('parseMentionsFromText', () => {
it('returns null mentions when none exist', () => {
const input = 'Hello there';
expect(parseMentionsFromText(input)).toEqual({
text: 'Hello there',
mentions: null,
});
});
it('parses @all and removes it from the text', () => {
const input = 'Hello @all';
expect(parseMentionsFromText(input)).toEqual({
text: 'Hello',
mentions: ['all'],
});
});
it('parses @ALL case-insensitively', () => {
const input = 'Hello @ALL';
expect(parseMentionsFromText(input)).toEqual({
text: 'Hello',
mentions: ['all'],
});
});
it('uses a fallback text when only @all is present', () => {
const input = '@all';
expect(parseMentionsFromText(input)).toEqual({
text: ' ',
mentions: ['all'],
});
});
it('parses phone number mentions with a suffix', () => {
const input = 'Ping @558591203123';
expect(parseMentionsFromText(input)).toEqual({
text: 'Ping @558591203123',
mentions: ['558591203123@c.us'],
});
});
it('deduplicates phone number mentions', () => {
const input = 'Hi @1111111 and @1111111';
expect(parseMentionsFromText(input)).toEqual({
text: 'Hi @1111111 and @1111111',
mentions: ['1111111@c.us'],
});
});
it('handles @all alongside phone mentions', () => {
const input = '@all Hi @558591203123';
expect(parseMentionsFromText(input)).toEqual({
text: 'Hi @558591203123',
mentions: ['all', '558591203123@c.us'],
});
});
it('ignores quoted @all text', () => {
const input = "You can send '@all' to chat";
expect(parseMentionsFromText(input)).toEqual({
text: "You can send '@all' to chat",
mentions: null,
});
});
it('ignores email-like mentions', () => {
const input = 'Reach us at email@12341domain.com';
expect(parseMentionsFromText(input)).toEqual({
text: 'Reach us at email@12341domain.com',
mentions: null,
});
});
it('ignores @all when followed by letters', () => {
const input = 'Please ping @allx';
expect(parseMentionsFromText(input)).toEqual({
text: 'Please ping @allx',
mentions: null,
});
});
it('ignores phone mentions followed by letters', () => {
const input = 'Call @1234567abc';
expect(parseMentionsFromText(input)).toEqual({
text: 'Call @1234567abc',
mentions: null,
});
});
it('ignores @all inside email-like text', () => {
const input = 'Contact email@alldomain.com for details';
expect(parseMentionsFromText(input)).toEqual({
text: 'Contact email@alldomain.com for details',
mentions: null,
});
});
it('accepts phone mentions with trailing punctuation', () => {
const input = 'Ping @1231234, then @5555555.';
expect(parseMentionsFromText(input)).toEqual({
text: 'Ping @1231234, then @5555555.',
mentions: ['1231234@c.us', '5555555@c.us'],
});
});
it('parses @lid mentions without c.us suffix', () => {
const input = 'Notify @123123@lid now';
expect(parseMentionsFromText(input)).toEqual({
text: 'Notify @123123@lid now',
mentions: ['123123@lid'],
});
});
});
@@ -0,0 +1,79 @@
import { ensureSuffix } from '@waha/core/abc/session.abc';
import { isJidGroup } from '@waha/core/utils/jids';
export interface ParsedMentions {
text: string;
mentions?: string[];
}
/**
* Parse mentions from text message
* Supports:
* - @all - mentions all group participants
* - @558591203123 - mentions specific phone number
*
* @param text - The message text
* @returns Parsed mentions with cleaned text
*/
export function parseMentionsFromText(text: string): ParsedMentions {
const mentions: string[] = [];
let content = text;
// Regex to match @all (case-insensitive, word boundary)
// Use separate regex instances to avoid lastIndex issues with global flag
const allRegexTest = /(^|\s)@all\b/gi;
if (allRegexTest.test(text)) {
mentions.push('all');
// Remove @all from text - WAHA endpoint doesn't display it, only mentions all participants
// Create a new regex instance for replace to avoid lastIndex issues
const allRegexReplace = /(^|\s)@all\b/gi;
content = content.replace(allRegexReplace, '$1').trim();
}
const lidRegex = /(^|\s)@(\d{6,15})@lid\b/g;
const lidMatches = text.matchAll(lidRegex);
for (const match of lidMatches) {
const lidNumber = match[2];
const formattedLid = `${lidNumber}@lid`;
if (!mentions.includes(formattedLid)) {
mentions.push(formattedLid);
}
}
// Regex to match @ followed by phone number
// Matches: @558591203123, @5585991203123, etc.
// Phone numbers: 7-15 digits (international format)
const phoneRegex = /(^|\s)@(\d{7,15})(?!@lid)\b/g;
const phoneMatches = text.matchAll(phoneRegex);
for (const match of phoneMatches) {
const phoneNumber = match[2];
// Format phone number to @c.us format
const formattedJid = ensureSuffix(phoneNumber);
if (!mentions.includes(formattedJid)) {
mentions.push(formattedJid);
}
}
if (mentions.length == 0) {
// Nothing found
return {
text: text,
mentions: null,
};
}
// Ensure text is not empty when there are mentions
// If text becomes empty after removing mentions, use a default message
content = content.trim();
if (content === '') {
// When only mentions are present, use a minimal text to satisfy DTO requirements
// The mentions array will handle the actual mentions
content = ' ';
}
return {
text: content,
mentions: mentions,
};
}
+2 -2
View File
@@ -50,7 +50,6 @@ import * as lodash from 'lodash';
import * as NodeCache from 'node-cache';
import { Logger } from 'pino';
import {
BehaviorSubject,
catchError,
delay,
filter,
@@ -228,7 +227,8 @@ export abstract class WhatsappSession {
engineConfig,
ignore,
}: SessionParams) {
this.status$ = new BehaviorSubject(WAHASessionStatus.STOPPED);
this._status = WAHASessionStatus.STOPPED;
this.status$ = new Subject<WAHASessionStatus>();
this.name = name;
this.proxyConfig = proxyConfig;
@@ -22,7 +22,7 @@ export class GowsEventStreamObservable extends Observable<EnginePayload> {
},
) {
super((subscriber) => {
logger.debug('Creating grpc stream...');
logger.debug('Creating grpc client and stream...');
logger.setBindings({ id: rand() });
const { client, stream } = factory();
this._client = client;
@@ -41,6 +41,13 @@ export class GowsEventStreamObservable extends Observable<EnginePayload> {
logger.warn({ err }, 'Failed to cancel gRPC stream');
}
logger.debug({ reason }, 'Closing gRPC client...');
try {
client.close();
} catch (err) {
logger.warn({ err }, 'Failed to close gRPC client');
}
await sleep(this.CLIENT_CLOSE_TIMEOUT);
};
+8 -24
View File
@@ -1,25 +1,15 @@
import * as grpc from '@grpc/grpc-js';
import { messages } from '@waha/core/engines/gows/grpc/gows';
import { Pool, SizedPool } from '@waha/core/engines/gows/pools';
let CLIENTS: Pool<messages.MessageServiceClient> | null = null;
let STREAM_CLIENTS: Pool<messages.EventStreamClient> | null = null;
export const GetMessageServiceClient = (
session: string,
address: string,
credentials: grpc.ChannelCredentials,
): messages.MessageServiceClient => {
if (!CLIENTS) {
const factory = () => {
return new messages.MessageServiceClient(address, credentials, {
'grpc.max_send_message_length': 128 * 1024 * 1024,
'grpc.max_receive_message_length': 128 * 1024 * 1024,
});
};
CLIENTS = new SizedPool(16, factory);
}
return CLIENTS.get(session);
return new messages.MessageServiceClient(address, credentials, {
'grpc.max_send_message_length': 128 * 1024 * 1024,
'grpc.max_receive_message_length': 128 * 1024 * 1024,
});
};
export const GetEventStreamClient = (
@@ -27,14 +17,8 @@ export const GetEventStreamClient = (
address: string,
credentials: grpc.ChannelCredentials,
): messages.EventStreamClient => {
if (!STREAM_CLIENTS) {
const factory = () => {
return new messages.EventStreamClient(address, credentials, {
'grpc.max_send_message_length': 128 * 1024 * 1024,
'grpc.max_receive_message_length': 128 * 1024 * 1024,
});
};
STREAM_CLIENTS = new SizedPool(16, factory);
}
return STREAM_CLIENTS.get(session);
return new messages.EventStreamClient(address, credentials, {
'grpc.max_send_message_length': 128 * 1024 * 1024,
'grpc.max_receive_message_length': 128 * 1024 * 1024,
});
};
+2 -1
View File
@@ -23,7 +23,8 @@ export namespace messages {
IMAGE = 0,
AUDIO = 1,
VIDEO = 2,
DOCUMENT = 3
DOCUMENT = 3,
PTV = 4
}
export enum Presence {
AVAILABLE = 0,
+2 -1
View File
@@ -21564,7 +21564,8 @@ proto.messages.MediaType = {
IMAGE: 0,
AUDIO: 1,
VIDEO: 2,
DOCUMENT: 3
DOCUMENT: 3,
PTV: 4
};
/**
+6 -8
View File
@@ -288,14 +288,12 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
grpc.credentials.createInsecure(),
);
try {
await promisify(this.client.StartSession)(request);
} catch (err) {
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;
throw err;
}
});
}
protected getProxyUrl(config: ProxyConfig): string {
@@ -766,14 +764,14 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
async stop(): Promise<void> {
this.cleanupPresenceTimeout();
if (this.client) {
const response = await promisify(this.client.StopSession)(this.session);
response.toObject();
}
this.status = WAHASessionStatus.STOPPED;
this.events?.stop();
this.stopEvents();
this.mediaManager.close();
if (this.client) {
await promisify(this.client.StopSession)(this.session);
this.client?.close();
}
}
public async requestCode(phoneNumber: string, method: string, params?: any) {
+41 -2
View File
@@ -1,4 +1,4 @@
import type { WABrowserDescription } from '@adiwajshing/baileys';
import { Browsers, WABrowserDescription } from '@adiwajshing/baileys';
import makeWASocket, {
Chat,
Contact,
@@ -205,6 +205,10 @@ import {
import { extractWALocation } from '@waha/core/engines/waproto/locaiton';
import { extractVCards } from '@waha/core/engines/waproto/vcards';
import { Activity } from '@waha/core/abc/activity';
import {
WAHA_CLIENT_BROWSER_NAME,
WAHA_CLIENT_DEVICE_NAME,
} from '@waha/core/env';
// eslint-disable-next-line @typescript-eslint/no-var-requires
const promiseRetry = require('promise-retry');
@@ -314,8 +318,37 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
}
getSocketConfig(agents: Agents | undefined, state): Partial<SocketConfig> {
// Detect browser
let browser = ['Ubuntu', 'Chrome', '22.04.4'] as WABrowserDescription;
let deviceName =
this.sessionConfig?.client?.deviceName ?? WAHA_CLIENT_DEVICE_NAME;
let browserName =
this.sessionConfig?.client?.browserName ?? WAHA_CLIENT_BROWSER_NAME;
if (browserName && !deviceName) {
browser = Browsers.appropriate(browserName);
} else if (!browserName && deviceName) {
browser = [deviceName, 'Chrome', '22.04.4'];
} else if (browserName && deviceName) {
switch (deviceName) {
case 'Mac OS':
case 'MacOS':
case 'macos':
browser = Browsers.macOS(browserName);
break;
case 'ubuntu':
case 'Ubuntu':
browser = Browsers.ubuntu(browserName);
break;
case 'windows':
case 'Windows':
browser = Browsers.windows(browserName);
break;
default:
browser = [deviceName, browserName, '22.04.4'];
}
}
const fullSyncEnabled = this.sessionConfig?.noweb?.store?.fullSync || false;
const browser = ['Ubuntu', 'Chrome', '20.0.04'] as WABrowserDescription;
let markOnlineOnConnect = this.sessionConfig?.noweb?.markOnline;
if (markOnlineOnConnect == undefined) {
markOnlineOnConnect = true;
@@ -2877,6 +2910,12 @@ export function extractBody(message): string | null {
// @ts-ignore - AudioMessage doesn't have caption field
body = mediaContent?.caption;
}
if (!body && content.protocolMessage?.editedMessage) {
body = extractBody(content.protocolMessage.editedMessage);
}
if (!body && content.associatedChildMessage?.message) {
body = extractBody(content.associatedChildMessage.message);
}
// Response for buttons
if (!body) {
body = content.templateButtonReplyMessage?.selectedDisplayText;
+8 -1
View File
@@ -10,8 +10,15 @@ export function extractMediaContent(
content?.imageMessage ||
content?.videoMessage ||
content?.audioMessage ||
content?.ptvMessage ||
content?.stickerMessage;
return mediaContent;
if (mediaContent) {
return mediaContent;
}
if (content?.associatedChildMessage?.message) {
return extractMediaContent(content.associatedChildMessage.message);
}
return null;
}
interface Long {
+47 -7
View File
@@ -121,11 +121,7 @@ import {
} from '@waha/structures/responses.dto';
import { BrowserTraceQuery } from '@waha/structures/server.debug.dto';
import { MeInfo } from '@waha/structures/sessions.dto';
import {
DeleteStatusRequest,
StatusRequest,
TextStatus,
} from '@waha/structures/status.dto';
import { DeleteStatusRequest, TextStatus } from '@waha/structures/status.dto';
import {
EnginePayload,
PollVote as WAHAPollVote,
@@ -185,6 +181,10 @@ import {
import { Activity } from '@waha/core/abc/activity';
import { CallData } from '@waha/structures/calls.dto';
import { Jid } from '@waha/core/engines/const';
import {
WAHA_CLIENT_BROWSER_NAME,
WAHA_CLIENT_DEVICE_NAME,
} from '@waha/core/env';
export interface WebJSConfig {
webVersion?: string;
@@ -246,6 +246,10 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
// add at the start
args.unshift(`--a-waha-timestamp=${new Date()}`);
args.unshift(`--a-waha-session=${this.name}`);
const deviceName =
this.sessionConfig?.client?.deviceName ?? WAHA_CLIENT_DEVICE_NAME;
const browserName =
this.sessionConfig?.client?.browserName ?? WAHA_CLIENT_BROWSER_NAME;
return {
puppeteer: {
protocolTimeout: 300_000,
@@ -255,7 +259,9 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
dumpio: this.isDebugEnabled(),
},
userAgent:
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36',
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36',
deviceName: deviceName,
browserName: browserName,
webVersion: webVersion,
webVersionCache: {
type: cacheType,
@@ -521,10 +527,29 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
}
});
this.whatsapp.on(Events.AUTHENTICATED, (args) => {
this.whatsapp.on(Events.AUTHENTICATED, async (args) => {
this.status = WAHASessionStatus.WORKING;
this.qr.save('');
this.logger.info({ args: args }, `Session has been authenticated!`);
// Try to get client info from puppeter if nothing set
// Fix https://github.com/devlikeapro/waha/issues/1735
await sleep(3_000);
if (!this.whatsapp.info) {
// try to load client info few times with a delay
for (let attempt = 0; attempt < 3; attempt++) {
await this.loadClientInfo().catch((error) =>
this.logger.error(
error,
`Failed to load client info, attempt ${attempt + 1}`,
),
);
if (this.whatsapp.info) {
break;
}
await sleep(3_000);
}
}
});
this.whatsapp.on(Events.AUTHENTICATION_FAILURE, (args) => {
@@ -585,6 +610,21 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
});
}
private async loadClientInfo() {
const data = await this.whatsapp.pupPage.evaluate(() => {
return {
// @ts-ignore
...window.Store.Conn.serialize(),
wid:
// @ts-ignore
window.Store.User.getMaybeMePnUser() ||
// @ts-ignore
window.Store.User.getMaybeMeLidUser(),
};
});
this.whatsapp.info = data as any;
}
/**
* START - Methods for API
*/
+8
View File
@@ -22,3 +22,11 @@ if (KNEX_SQLITE_CLIENT != 'sqlite3' && KNEX_SQLITE_CLIENT != 'better-sqlite3') {
KNEX_SQLITE_CLIENT = 'sqlite3';
}
export { KNEX_SQLITE_CLIENT };
//
// Client config
//
export const WAHA_CLIENT_DEVICE_NAME =
process.env.WAHA_CLIENT_DEVICE_NAME || null;
export const WAHA_CLIENT_BROWSER_NAME =
process.env.WAHA_CLIENT_BROWSER_NAME || null;
-5
View File
@@ -16,9 +16,4 @@ export function validateRequestMentions(request: {
`"mentions":["all"] can be used only in group chats, not in '${request.chatId}'`,
);
}
if (request.mentions.length > 1) {
throw new UnprocessableEntityException(
`"mentions":["all"] cannot be used with other mentions`,
);
}
}
+5
View File
@@ -20,4 +20,9 @@ export class WAHAEnvironment {
example: '/usr/path/to/bin/google-chrome',
})
browser: string;
@ApiProperty({
example: 'linux/x86',
})
platform: string;
}
+23
View File
@@ -155,6 +155,16 @@ export class IgnoreConfig {
broadcast?: boolean;
}
export class ClientSessionConfig {
@IsString()
@IsOptional()
deviceName?: string;
@IsString()
@IsOptional()
browserName?: string;
}
export class SessionConfig {
@ValidateNested({ each: true })
@Type(() => WebhookConfig)
@@ -204,6 +214,19 @@ export class SessionConfig {
@IsOptional()
ignore?: IgnoreConfig;
@ApiProperty({
description:
"How connected session renders in device - in format 'Browser (Device)' - Firefox (MacOS)",
example: {
browserName: 'Firefox',
deviceName: 'MacOS',
},
})
@ValidateNested()
@Type(() => ClientSessionConfig)
@IsOptional()
client?: ClientSessionConfig;
@ApiProperty({
example: {
store: {
+13 -3
View File
@@ -29,12 +29,22 @@ export function getWAHAVersion(): WAHAVersion {
return WAHAVersion.CORE;
}
function getBrowser() {
return getEngineName() === WAHAEngine.WEBJS
? getBrowserExecutablePath()
: null;
}
function getPlatform() {
return `${process.platform}/${process.arch}`;
}
export const VERSION: WAHAEnvironment = {
version: '2025.12.2',
version: '2026.1.2',
engine: getEngineName(),
tier: getWAHAVersion(),
browser:
getEngineName() === WAHAEngine.WEBJS ? getBrowserExecutablePath() : null,
browser: getBrowser(),
platform: getPlatform(),
};
export const IsChrome = VERSION.browser?.includes('chrome');
+2 -2
View File
@@ -2,11 +2,11 @@
"waha": {
"gows": {
"repo": "devlikeapro/gows",
"ref": "v1.0.26"
"ref": "v1.0.29"
},
"dashboard": {
"repo": "devlikeapro/dashboard",
"ref": "6d62eda142f95b5a6b40614f83d9c12e52957f85"
"ref": "6030cf5e08f2a6e6d09a7a70a1c4194baf0beb55"
}
}
}
+4 -4
View File
@@ -7,7 +7,7 @@ __metadata:
"@adiwajshing/baileys@github:devlikeapro/Baileys#fork-master-2025-12-17":
version: 7.0.0-rc.9
resolution: "@adiwajshing/baileys@https://github.com/devlikeapro/Baileys.git#commit=425b11f5b2d98c808210fab78afc6257f27f2fe5"
resolution: "@adiwajshing/baileys@https://github.com/devlikeapro/Baileys.git#commit=3d54943ebd83c56f1b5739c4d28da3f9af01020a"
dependencies:
"@cacheable/node-cache": "npm:^1.4.0"
"@hapi/boom": "npm:^9.1.3"
@@ -31,7 +31,7 @@ __metadata:
optional: true
link-preview-js:
optional: true
checksum: 10/2b58dffc216fe6518e3f72532de60765e7d8c371cdc17221b79c98d74752a5b6c08f5ea2af4543b12bd6c55f1434d1b6b697e4b31f33fea50069b7b4b774feb1
checksum: 10/6ec9803fea06e359f1e883c1e050b511350ec272cfb9e3fc3a14c1346ccc8bc18c4bfb3d22f90877d5aaf4f973b02cb8fda9f5a7ab801138d17e766c36b05dfc
languageName: node
linkType: hard
@@ -12756,7 +12756,7 @@ __metadata:
"whatsapp-web.js@github:devlikeapro/whatsapp-web.js#fork-main-2025-12-17":
version: 1.34.3
resolution: "whatsapp-web.js@https://github.com/devlikeapro/whatsapp-web.js.git#commit=8349fcd26fd2bbede0e6739b0da9b9ce97fe6c14"
resolution: "whatsapp-web.js@https://github.com/devlikeapro/whatsapp-web.js.git#commit=8823818e1db005aeca3cb75cf48622493c5e4cd2"
dependencies:
"@pedroslopez/moduleraid": "npm:^5.0.2"
archiver: "npm:^5.3.1"
@@ -12774,7 +12774,7 @@ __metadata:
optional: true
unzipper:
optional: true
checksum: 10/b53255dfd1c0336232f4f4683e67779bea2f002242084961ecec59844595414a80f7172859eb4a8983e9dd6c276de6a97bfdec5135c7d4ec6ee5ed638a6e8346
checksum: 10/3bcbb476ccf1fc330beccb67b26720b212a37d74d166291c346430feef955a85ffb5948f3802a7f14f9f620fbd7edcd292b049b54e77c80ce9c5f7f2c2474878
languageName: node
linkType: hard