Compare commits

..
15 Commits
Author SHA1 Message Date
devlikepro 6ee50243a2 [core] 2025.8.2
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
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
2025-08-25 10:31:26 +07:00
devlikepro 3ec078f9c1 [core] Update Dashboard 2025-08-25 10:31:26 +07:00
devlikepro 4c3209be6d [core] GOWS v1.0.12 2025-08-25 10:31:26 +07:00
devlikepro 7fe8f9a8dc [core] put "stack" last in errors 2025-08-25 10:31:25 +07:00
devlikepro 7fa2b29418 [core] GOWS - /api/sendPollVote
fix #1077
2025-08-25 10:31:25 +07:00
devlikepro dd4edd5a85 [core] show the engine name in errors 2025-08-25 10:31:24 +07:00
devlikepro b85df5a0d3 [core] GOWS - /api/sendLocation
fix #1250
2025-08-25 10:31:24 +07:00
devlikepro 1a7bec3d4e [core] NOWEB - add title for location, convert to right jid 2025-08-25 10:31:23 +07:00
devlikepro 727c4b454f [core] GOWS - Fix poll key
fix #1028
2025-08-25 10:31:23 +07:00
devlikepro 4ba86b34a9 [core] Add me.lid - GOWS, NOWEB 2025-08-25 10:31:22 +07:00
devlikepro 277a35aed9 [core] Add /api/sendList - NOWEB, GOWS
fix #393
fix #482
fix #715
fix #938
2025-08-25 10:31:21 +07:00
devlikepro 28c1ffa09f [core] ChatWoot - fix link preview for big files issue
fix #1162
2025-08-25 10:31:21 +07:00
devlikepro 10595680a9 [core] Dashboard - add zh - Simplified Chinese
fix #1193
2025-08-25 10:31:21 +07:00
devlikepro 577298dbc5 [core] ChatWoot - add zh-CN - Simplified Chinese
fix #1193
2025-08-25 10:31:21 +07:00
devlikepro 49ca4dfbf7 [core] rm LocaleCode 2025-08-25 10:31:21 +07:00
18 changed files with 2722 additions and 77 deletions

No files matched your search

+26 -1
View File
@@ -16,6 +16,7 @@ import {
transformAck,
} from '@waha/structures/chats.dto';
import { SendButtonsRequest } from '@waha/structures/chatting.buttons.dto';
import { SendListRequest } from '@waha/structures/chatting.list.dto';
import { SessionManager } from '../core/abc/manager.abc';
import {
@@ -31,6 +32,7 @@ import {
MessageLinkPreviewRequest,
MessageLocationRequest,
MessagePollRequest,
MessagePollVoteRequest,
MessageReactionRequest,
MessageReplyRequest,
MessageStarRequest,
@@ -121,8 +123,9 @@ export class ChattingController {
@Post('/sendButtons')
@ApiOperation({
summary: 'Send buttons (interactive message)',
summary: 'Send buttons message (interactive)',
description: 'Send Buttons',
deprecated: true,
})
@UsePipes(new WAHAValidationPipe())
async sendButtons(@Body() request: SendButtonsRequest) {
@@ -130,6 +133,17 @@ export class ChattingController {
return whatsapp.sendButtons(request);
}
@Post('/sendList')
@ApiOperation({
summary: 'Send a list message (interactive)',
description: 'Send a List message with sections and rows',
})
@UsePipes(new WAHAValidationPipe())
async sendList(@Body() request: SendListRequest) {
const whatsapp = await this.manager.getWorkingSession(request.session);
return whatsapp.sendList(request);
}
@Post('/forwardMessage')
async forwardMessage(
@Body() request: MessageForwardRequest,
@@ -193,6 +207,17 @@ export class ChattingController {
return whatsapp.sendPoll(request);
}
@Post('/sendPollVote')
@ApiOperation({
summary: 'Vote on a poll',
description: 'Cast vote(s) on an existing poll message',
})
@UsePipes(new WAHAValidationPipe())
async sendPollVote(@Body() request: MessagePollVoteRequest) {
const whatsapp = await this.manager.getWorkingSession(request.session);
return whatsapp.sendPollVote(request);
}
@Post('/sendLocation')
async sendLocation(@Body() request: MessageLocationRequest) {
const whatsapp = await this.manager.getWorkingSession(request.session);
@@ -154,6 +154,8 @@ export class MessageHandler {
chatId: chatId,
text: content,
reply_to: replyTo,
linkPreview: false,
linkPreviewHighQuality: false,
};
const session = this.session;
await session.readMessages(chatId);
+182
View File
@@ -0,0 +1,182 @@
# Simplified Chinese translations for ChatWoot integration
LOCALE_NAME: '🇨🇳 中文(Simplified Chinese)'
APP_INBOX_CONTACT_NAME: 'WhatsApp 集成(WAHA)'
APP_INBOX_CONTACT_AVATAR_URL: 'https://raw.githubusercontent.com/devlikeapro/waha/core/logo.png'
JOB_ERROR_REPORT: |
⚠️ **{{{header}}}**
{{#attempts.nextDelay}}
🔄 将在 {{attempts.nextDelay}} 秒后重试...
{{/attempts.nextDelay}}
{{^attempts.nextDelay}}
❌ 没有更多重试机会。请稍后再试。
{{/attempts.nextDelay}}
尝试:{{{attempts.current}}} / {{{attempts.max}}}
详情:[{{{details.text}}}]({{{details.url}}})
{{#error}}
```
{{{error}}}
```
{{/error}}
JOB_SUCCEEDED_REPORT: |
✅ **成功**
尝试:{{{attempts.current}}} / {{{attempts.max}}}
详情:[{{{details.text}}}]({{{details.url}}})
WHATSAPP_ERROR_SENDING_MESSAGE: 无法在 WhatsApp 中发送消息。
WHATSAPP_ERROR_RECEIVING_MESSAGE: 无法接收来自 WhatsApp 的消息
WHATSAPP_ERROR_REMOVING_MESSAGE: 无法从 WhatsApp 中删除消息
APP_SCHEDULED_JOB_ERROR: 计划任务执行失败
MESSAGE_FROM_WHATSAPP: |
{{{text}}}
📱 *通过 WhatsApp 发送*
MESSAGE_FROM_API: |
{{{text}}}
📱 *通过 API 发送*
MESSAGE_REMOVED_IN_WHATSAPP: |
❌ *此消息已在 WhatsApp 中被删除*
MESSAGE_EDITED_IN_WHATSAPP: |
{{{text}}}
✏️ *已在 WhatsApp 中编辑*
WHATSAPP_GROUP_MESSAGE: |
{{{text}}}
👥 *{{{participant}}}*
WHATSAPP_REACTION_ADDED: |
*已反应* {{{emoji}}}
WHATSAPP_REACTION_REMOVED: |
*已移除反应*
WHATSAPP_CONTACT_GROUP_SUFFIX: '群组'
WHATSAPP_CONTACT_CHANNEL_SUFFIX: '频道'
# Aka Stories
WHATSAPP_CONTACT_STATUS_NAME: '状态'
APP_CONNECTED_MESSAGE: |
🔗 **已连接:WhatsApp 会话到 ChatWoot 收件箱**
✅ WhatsApp 会话 **{{{name}}}** 已成功连接到此 ChatWoot 收件箱。
此收件箱现已链接到你的**WhatsApp 账号**。你可以在这里直接收发消息——就像使用 WhatsApp 一样,但在 ChatWoot 内完成。
你也可以管理与此收件箱关联的 WhatsApp 会话。要查看可用操作,请向此对话发送 **help**。
📋 你可以执行的常见操作:
- 检查 WhatsApp 会话是否活动
- 重启或停止 WhatsApp 会话
- 登出并通过扫描新的二维码重新连接
如果一切正常,你就可以自由聊天。如果你看到警告,可能需要重新连接你的 WhatsApp 账号。
开始吧!🚀
APP_DISCONNECTED_MESSAGE: |
🔌 **已断开:从 ChatWoot 收件箱断开 WhatsApp 会话**
❌ WhatsApp 会话 **{{{name}}}** 已与此 ChatWoot 收件箱断开。
你将无法再从 WhatsApp 发送或接收消息。
如需重新连接,请联系你的管理员。
APP_UPDATED_MESSAGE: |
🔄 **已更新:WhatsApp 会话设置**
✅ WhatsApp 会话 **{{{name}}}** 的设置已更新。
APP_COMMANDS_LIST: |
你可以通过发送以下命令来管理**WhatsApp 集成**:
🖥️ 会话:
1. **status** - 获取当前会话状态。
2. **restart** - 重启会话——将尝试重新连接你的 WhatsApp 账号。
3. **start** - 启动会话。
4. **stop** - 停止会话。
5. **logout** - 登出会话,以便你可以扫描二维码并修复会话。
6. **qr** - 获取该会话的二维码。
7. **screenshot** - 获取会话截图(仅在 **WEBJS** 引擎中可用)。
8. **help** - 查看可用命令。
⚙️ 服务器:
- **server status** - 获取服务器版本和状态信息。
- **server reboot** - 优雅地重启服务器。
- **server reboot force** - 强制重启服务器。
向此对话发送**命令**(不区分大小写)或**命令编号**。
APP_HELP_REMINDER: |
发送 **help** 获取可用命令列表。
APP_SESSION_STATUS_CHANGE: |
{{{emoji}}} "{{{session}}}" 会话已切换为 {{{status}}} 状态
APP_SESSION_CURRENT_STATUS: |
{{{emoji}}} **{{{session}}}** 会话状态为 **{{{status}}}**
**WhatsApp 账号:**
📱 **{{{name}}} ({{{id}}})**
APP_DASHBOARD_LINK: |
访问 [WAHA 控制台]({{{url}}}) 以了解更多信息。
APP_SESSION_STATUS_WORKING: |
✅ 此 ChatWoot 收件箱已链接到**WhatsApp 账号**,可以正常收发消息!
📱 **{{{name}}} ({{{id}}})**
APP_SESSION_STATUS_ERROR: |
⚠️ **此 ChatWoot 收件箱已链接到 WhatsApp 账号,但尚未准备好收发消息!**
请检查会话状态,尝试重启,或登出后重新扫描二维码。
APP_SESSION_SCAN_QR_CODE: |
⚠️ **请在手机上扫描二维码,将 WhatsApp 账号与此 ChatWoot 收件箱关联!**
APP_SERVER_VERSION_AND_STATUS: |
**版本:**
```
{{{version}}}
```
**状态:**
```
{{{status}}}
```
APP_SERVER_REBOOT: |
服务器正在优雅重启...
APP_SERVER_REBOOT_FORCE: |
服务器正在强制重启...
APP_LOGOUT_SUCCESS: |
✅ 账号已成功取消授权!
APP_NEW_VERSION_AVAILABLE: |
🔄 **有新版本可用!**
当前版本:**{{{currentVersion}}}**
新版本:**{{{newVersion}}}**
👉 查看[**🆕 更新日志**]({{{changelogUrl}}})
APP_CORE_VERSION_USED: |
❤️ **支持 WAHA!**
你正在使用免费的 **WAHA Core** 版本。如果你觉得该项目有帮助,请考虑支持其开发。
你的支持是我们持续进行更新、修复和新增功能的唯一动力。
[**🎁 支持我们**]({{{supportUrl}}})
+19 -37
View File
@@ -4,42 +4,24 @@ import * as Mustache from 'mustache';
import * as path from 'path';
import * as yaml from 'yaml';
export enum LocaleCode {
EN_US = 'en-US',
AR_AE = 'ar-AE',
BN_BD = 'bn-BD',
PT_BR = 'pt-BR',
DE_DE = 'de-DE',
ES_ES = 'es-ES',
FR_FR = 'fr-FR',
ID_ID = 'id-ID',
HE_IL = 'he-IL',
HI_IN = 'hi-IN',
FA_IR = 'fa-IR',
PA_PK = 'pa-PK',
RU_RU = 'ru-RU',
TR_TR = 'tr-TR',
UK_UA = 'uk-UA',
UR_PK = 'ur-PK',
}
export const LOCALES = [
LocaleCode.EN_US,
LocaleCode.AR_AE,
LocaleCode.BN_BD,
LocaleCode.PT_BR,
LocaleCode.DE_DE,
LocaleCode.ES_ES,
LocaleCode.FR_FR,
LocaleCode.ID_ID,
LocaleCode.HE_IL,
LocaleCode.HI_IN,
LocaleCode.FA_IR,
LocaleCode.PA_PK,
LocaleCode.RU_RU,
LocaleCode.TR_TR,
LocaleCode.UK_UA,
LocaleCode.UR_PK,
'en-US',
'ar-AE',
'bn-BD',
'pt-BR',
'de-DE',
'es-ES',
'fr-FR',
'id-ID',
'he-IL',
'hi-IN',
'fa-IR',
'pa-PK',
'ru-RU',
'tr-TR',
'uk-UA',
'ur-PK',
'zh-CN',
];
export const DEFAULT_LOCALE = 'en-US';
@@ -67,12 +49,12 @@ function loadLocales(locales: string[]) {
}
let locales: Record<string, Record<string, string>> = {};
export const LOCALE_NAMES: Map<LocaleCode, string> = new Map();
export const LOCALE_NAMES: Map<string, string> = new Map();
try {
locales = loadLocales(LOCALES);
for (const [locale, strings] of Object.entries(locales)) {
LOCALE_NAMES.set(locale as LocaleCode, strings['LOCALE_NAME']);
LOCALE_NAMES.set(locale, strings['LOCALE_NAME']);
}
} catch (error) {
console.error('Error loading locales:', error);
+7
View File
@@ -0,0 +1,7 @@
import { WAHAEngine } from '@waha/structures/enums.dto';
export function getEngineName(): string {
// Load engine name from WHATSAPP_DEFAULT_ENGINE environment variable
// If not set - use WEBJS
return process.env.WHATSAPP_DEFAULT_ENGINE || WAHAEngine.WEBJS;
}
+10
View File
@@ -25,6 +25,7 @@ import {
ReadChatMessagesResponse,
} from '@waha/structures/chats.dto';
import { SendButtonsRequest } from '@waha/structures/chatting.buttons.dto';
import { SendListRequest } from '@waha/structures/chatting.list.dto';
import { BinaryFile, RemoteFile } from '@waha/structures/files.dto';
import { Label, LabelDTO, LabelID } from '@waha/structures/labels.dto';
import { LidToPhoneNumber } from '@waha/structures/lids.dto';
@@ -69,6 +70,7 @@ import {
MessageLinkPreviewRequest,
MessageLocationRequest,
MessagePollRequest,
MessagePollVoteRequest,
MessageReactionRequest,
MessageReplyRequest,
MessageStarRequest,
@@ -451,6 +453,10 @@ export abstract class WhatsappSession {
throw new NotImplementedByEngineError();
}
sendPollVote(request: MessagePollVoteRequest) {
throw new NotImplementedByEngineError();
}
abstract sendLocation(request: MessageLocationRequest);
sendLinkPreview(request: MessageLinkPreviewRequest) {
@@ -479,6 +485,10 @@ export abstract class WhatsappSession {
throw new NotImplementedByEngineError();
}
sendList(request: SendListRequest): Promise<any> {
throw new NotImplementedByEngineError();
}
sendButtonsReply(request: MessageButtonReply) {
throw new NotImplementedByEngineError();
}
+731
View File
@@ -3034,6 +3034,132 @@ export namespace messages {
return EventLocation.deserialize(bytes);
}
}
export class Location extends pb_1.Message {
#one_of_decls: number[][] = [[1]];
constructor(data?: any[] | ({
degreesLongitude?: number;
degreesLatitude?: number;
} & (({
name?: string;
})))) {
super();
pb_1.Message.initialize(this, Array.isArray(data) ? data : [], 0, -1, [], this.#one_of_decls);
if (!Array.isArray(data) && typeof data == "object") {
if ("name" in data && data.name != undefined) {
this.name = data.name;
}
if ("degreesLongitude" in data && data.degreesLongitude != undefined) {
this.degreesLongitude = data.degreesLongitude;
}
if ("degreesLatitude" in data && data.degreesLatitude != undefined) {
this.degreesLatitude = data.degreesLatitude;
}
}
}
get name() {
return pb_1.Message.getFieldWithDefault(this, 1, "") as string;
}
set name(value: string) {
pb_1.Message.setOneofField(this, 1, this.#one_of_decls[0], value);
}
get has_name() {
return pb_1.Message.getField(this, 1) != null;
}
get degreesLongitude() {
return pb_1.Message.getFieldWithDefault(this, 2, 0) as number;
}
set degreesLongitude(value: number) {
pb_1.Message.setField(this, 2, value);
}
get degreesLatitude() {
return pb_1.Message.getFieldWithDefault(this, 3, 0) as number;
}
set degreesLatitude(value: number) {
pb_1.Message.setField(this, 3, value);
}
get _name() {
const cases: {
[index: number]: "none" | "name";
} = {
0: "none",
1: "name"
};
return cases[pb_1.Message.computeOneofCase(this, [1])];
}
static fromObject(data: {
name?: string;
degreesLongitude?: number;
degreesLatitude?: number;
}): Location {
const message = new Location({});
if (data.name != null) {
message.name = data.name;
}
if (data.degreesLongitude != null) {
message.degreesLongitude = data.degreesLongitude;
}
if (data.degreesLatitude != null) {
message.degreesLatitude = data.degreesLatitude;
}
return message;
}
toObject() {
const data: {
name?: string;
degreesLongitude?: number;
degreesLatitude?: number;
} = {};
if (this.name != null) {
data.name = this.name;
}
if (this.degreesLongitude != null) {
data.degreesLongitude = this.degreesLongitude;
}
if (this.degreesLatitude != null) {
data.degreesLatitude = this.degreesLatitude;
}
return data;
}
serialize(): Uint8Array;
serialize(w: pb_1.BinaryWriter): void;
serialize(w?: pb_1.BinaryWriter): Uint8Array | void {
const writer = w || new pb_1.BinaryWriter();
if (this.has_name)
writer.writeString(1, this.name);
if (this.degreesLongitude != 0)
writer.writeDouble(2, this.degreesLongitude);
if (this.degreesLatitude != 0)
writer.writeDouble(3, this.degreesLatitude);
if (!w)
return writer.getResultBuffer();
}
static deserialize(bytes: Uint8Array | pb_1.BinaryReader): Location {
const reader = bytes instanceof pb_1.BinaryReader ? bytes : new pb_1.BinaryReader(bytes), message = new Location();
while (reader.nextField()) {
if (reader.isEndGroup())
break;
switch (reader.getFieldNumber()) {
case 1:
message.name = reader.readString();
break;
case 2:
message.degreesLongitude = reader.readDouble();
break;
case 3:
message.degreesLatitude = reader.readDouble();
break;
default: reader.skipField();
}
}
return message;
}
serializeBinary(): Uint8Array {
return this.serialize();
}
static deserializeBinary(bytes: Uint8Array): Location {
return Location.deserialize(bytes);
}
}
export class EventMessage extends pb_1.Message {
#one_of_decls: number[][] = [[2], [4]];
constructor(data?: any[] | ({
@@ -3263,6 +3389,9 @@ export namespace messages {
contacts?: vCardContact[];
event?: EventMessage;
poll?: PollMessage;
list?: ListMessage;
location?: Location;
pollVote?: PollVoteMessage;
}) {
super();
pb_1.Message.initialize(this, Array.isArray(data) ? data : [], 0, -1, [11, 13], this.#one_of_decls);
@@ -3312,6 +3441,15 @@ export namespace messages {
if ("poll" in data && data.poll != undefined) {
this.poll = data.poll;
}
if ("list" in data && data.list != undefined) {
this.list = data.list;
}
if ("location" in data && data.location != undefined) {
this.location = data.location;
}
if ("pollVote" in data && data.pollVote != undefined) {
this.pollVote = data.pollVote;
}
}
}
get session() {
@@ -3425,6 +3563,33 @@ export namespace messages {
get has_poll() {
return pb_1.Message.getField(this, 15) != null;
}
get list() {
return pb_1.Message.getWrapperField(this, ListMessage, 16) as ListMessage;
}
set list(value: ListMessage) {
pb_1.Message.setWrapperField(this, 16, value);
}
get has_list() {
return pb_1.Message.getField(this, 16) != null;
}
get location() {
return pb_1.Message.getWrapperField(this, Location, 17) as Location;
}
set location(value: Location) {
pb_1.Message.setWrapperField(this, 17, value);
}
get has_location() {
return pb_1.Message.getField(this, 17) != null;
}
get pollVote() {
return pb_1.Message.getWrapperField(this, PollVoteMessage, 18) as PollVoteMessage;
}
set pollVote(value: PollVoteMessage) {
pb_1.Message.setWrapperField(this, 18, value);
}
get has_pollVote() {
return pb_1.Message.getField(this, 18) != null;
}
static fromObject(data: {
session?: ReturnType<typeof Session.prototype.toObject>;
jid?: string;
@@ -3441,6 +3606,9 @@ export namespace messages {
contacts?: ReturnType<typeof vCardContact.prototype.toObject>[];
event?: ReturnType<typeof EventMessage.prototype.toObject>;
poll?: ReturnType<typeof PollMessage.prototype.toObject>;
list?: ReturnType<typeof ListMessage.prototype.toObject>;
location?: ReturnType<typeof Location.prototype.toObject>;
pollVote?: ReturnType<typeof PollVoteMessage.prototype.toObject>;
}): MessageRequest {
const message = new MessageRequest({});
if (data.session != null) {
@@ -3488,6 +3656,15 @@ export namespace messages {
if (data.poll != null) {
message.poll = PollMessage.fromObject(data.poll);
}
if (data.list != null) {
message.list = ListMessage.fromObject(data.list);
}
if (data.location != null) {
message.location = Location.fromObject(data.location);
}
if (data.pollVote != null) {
message.pollVote = PollVoteMessage.fromObject(data.pollVote);
}
return message;
}
toObject() {
@@ -3507,6 +3684,9 @@ export namespace messages {
contacts?: ReturnType<typeof vCardContact.prototype.toObject>[];
event?: ReturnType<typeof EventMessage.prototype.toObject>;
poll?: ReturnType<typeof PollMessage.prototype.toObject>;
list?: ReturnType<typeof ListMessage.prototype.toObject>;
location?: ReturnType<typeof Location.prototype.toObject>;
pollVote?: ReturnType<typeof PollVoteMessage.prototype.toObject>;
} = {};
if (this.session != null) {
data.session = this.session.toObject();
@@ -3553,6 +3733,15 @@ export namespace messages {
if (this.poll != null) {
data.poll = this.poll.toObject();
}
if (this.list != null) {
data.list = this.list.toObject();
}
if (this.location != null) {
data.location = this.location.toObject();
}
if (this.pollVote != null) {
data.pollVote = this.pollVote.toObject();
}
return data;
}
serialize(): Uint8Array;
@@ -3589,6 +3778,12 @@ export namespace messages {
writer.writeMessage(14, this.event, () => this.event.serialize(writer));
if (this.has_poll)
writer.writeMessage(15, this.poll, () => this.poll.serialize(writer));
if (this.has_list)
writer.writeMessage(16, this.list, () => this.list.serialize(writer));
if (this.has_location)
writer.writeMessage(17, this.location, () => this.location.serialize(writer));
if (this.has_pollVote)
writer.writeMessage(18, this.pollVote, () => this.pollVote.serialize(writer));
if (!w)
return writer.getResultBuffer();
}
@@ -3643,6 +3838,15 @@ export namespace messages {
case 15:
reader.readMessage(message.poll, () => message.poll = PollMessage.deserialize(reader));
break;
case 16:
reader.readMessage(message.list, () => message.list = ListMessage.deserialize(reader));
break;
case 17:
reader.readMessage(message.location, () => message.location = Location.deserialize(reader));
break;
case 18:
reader.readMessage(message.pollVote, () => message.pollVote = PollVoteMessage.deserialize(reader));
break;
default: reader.skipField();
}
}
@@ -3655,6 +3859,407 @@ export namespace messages {
return MessageRequest.deserialize(bytes);
}
}
export class Row extends pb_1.Message {
#one_of_decls: number[][] = [[2]];
constructor(data?: any[] | ({
title?: string;
rowId?: string;
} & (({
description?: string;
})))) {
super();
pb_1.Message.initialize(this, Array.isArray(data) ? data : [], 0, -1, [], this.#one_of_decls);
if (!Array.isArray(data) && typeof data == "object") {
if ("title" in data && data.title != undefined) {
this.title = data.title;
}
if ("description" in data && data.description != undefined) {
this.description = data.description;
}
if ("rowId" in data && data.rowId != undefined) {
this.rowId = data.rowId;
}
}
}
get title() {
return pb_1.Message.getFieldWithDefault(this, 1, "") as string;
}
set title(value: string) {
pb_1.Message.setField(this, 1, value);
}
get description() {
return pb_1.Message.getFieldWithDefault(this, 2, "") as string;
}
set description(value: string) {
pb_1.Message.setOneofField(this, 2, this.#one_of_decls[0], value);
}
get has_description() {
return pb_1.Message.getField(this, 2) != null;
}
get rowId() {
return pb_1.Message.getFieldWithDefault(this, 3, "") as string;
}
set rowId(value: string) {
pb_1.Message.setField(this, 3, value);
}
get _description() {
const cases: {
[index: number]: "none" | "description";
} = {
0: "none",
2: "description"
};
return cases[pb_1.Message.computeOneofCase(this, [2])];
}
static fromObject(data: {
title?: string;
description?: string;
rowId?: string;
}): Row {
const message = new Row({});
if (data.title != null) {
message.title = data.title;
}
if (data.description != null) {
message.description = data.description;
}
if (data.rowId != null) {
message.rowId = data.rowId;
}
return message;
}
toObject() {
const data: {
title?: string;
description?: string;
rowId?: string;
} = {};
if (this.title != null) {
data.title = this.title;
}
if (this.description != null) {
data.description = this.description;
}
if (this.rowId != null) {
data.rowId = this.rowId;
}
return data;
}
serialize(): Uint8Array;
serialize(w: pb_1.BinaryWriter): void;
serialize(w?: pb_1.BinaryWriter): Uint8Array | void {
const writer = w || new pb_1.BinaryWriter();
if (this.title.length)
writer.writeString(1, this.title);
if (this.has_description)
writer.writeString(2, this.description);
if (this.rowId.length)
writer.writeString(3, this.rowId);
if (!w)
return writer.getResultBuffer();
}
static deserialize(bytes: Uint8Array | pb_1.BinaryReader): Row {
const reader = bytes instanceof pb_1.BinaryReader ? bytes : new pb_1.BinaryReader(bytes), message = new Row();
while (reader.nextField()) {
if (reader.isEndGroup())
break;
switch (reader.getFieldNumber()) {
case 1:
message.title = reader.readString();
break;
case 2:
message.description = reader.readString();
break;
case 3:
message.rowId = reader.readString();
break;
default: reader.skipField();
}
}
return message;
}
serializeBinary(): Uint8Array {
return this.serialize();
}
static deserializeBinary(bytes: Uint8Array): Row {
return Row.deserialize(bytes);
}
}
export class Section extends pb_1.Message {
#one_of_decls: number[][] = [];
constructor(data?: any[] | {
title?: string;
rows?: Row[];
}) {
super();
pb_1.Message.initialize(this, Array.isArray(data) ? data : [], 0, -1, [2], this.#one_of_decls);
if (!Array.isArray(data) && typeof data == "object") {
if ("title" in data && data.title != undefined) {
this.title = data.title;
}
if ("rows" in data && data.rows != undefined) {
this.rows = data.rows;
}
}
}
get title() {
return pb_1.Message.getFieldWithDefault(this, 1, "") as string;
}
set title(value: string) {
pb_1.Message.setField(this, 1, value);
}
get rows() {
return pb_1.Message.getRepeatedWrapperField(this, Row, 2) as Row[];
}
set rows(value: Row[]) {
pb_1.Message.setRepeatedWrapperField(this, 2, value);
}
static fromObject(data: {
title?: string;
rows?: ReturnType<typeof Row.prototype.toObject>[];
}): Section {
const message = new Section({});
if (data.title != null) {
message.title = data.title;
}
if (data.rows != null) {
message.rows = data.rows.map(item => Row.fromObject(item));
}
return message;
}
toObject() {
const data: {
title?: string;
rows?: ReturnType<typeof Row.prototype.toObject>[];
} = {};
if (this.title != null) {
data.title = this.title;
}
if (this.rows != null) {
data.rows = this.rows.map((item: Row) => item.toObject());
}
return data;
}
serialize(): Uint8Array;
serialize(w: pb_1.BinaryWriter): void;
serialize(w?: pb_1.BinaryWriter): Uint8Array | void {
const writer = w || new pb_1.BinaryWriter();
if (this.title.length)
writer.writeString(1, this.title);
if (this.rows.length)
writer.writeRepeatedMessage(2, this.rows, (item: Row) => item.serialize(writer));
if (!w)
return writer.getResultBuffer();
}
static deserialize(bytes: Uint8Array | pb_1.BinaryReader): Section {
const reader = bytes instanceof pb_1.BinaryReader ? bytes : new pb_1.BinaryReader(bytes), message = new Section();
while (reader.nextField()) {
if (reader.isEndGroup())
break;
switch (reader.getFieldNumber()) {
case 1:
message.title = reader.readString();
break;
case 2:
reader.readMessage(message.rows, () => pb_1.Message.addToRepeatedWrapperField(message, 2, Row.deserialize(reader), Row));
break;
default: reader.skipField();
}
}
return message;
}
serializeBinary(): Uint8Array {
return this.serialize();
}
static deserializeBinary(bytes: Uint8Array): Section {
return Section.deserialize(bytes);
}
}
export class ListMessage extends pb_1.Message {
#one_of_decls: number[][] = [[2], [3]];
constructor(data?: any[] | ({
title?: string;
button?: string;
sections?: Section[];
} & (({
description?: string;
}) | ({
footer?: string;
})))) {
super();
pb_1.Message.initialize(this, Array.isArray(data) ? data : [], 0, -1, [5], this.#one_of_decls);
if (!Array.isArray(data) && typeof data == "object") {
if ("title" in data && data.title != undefined) {
this.title = data.title;
}
if ("description" in data && data.description != undefined) {
this.description = data.description;
}
if ("footer" in data && data.footer != undefined) {
this.footer = data.footer;
}
if ("button" in data && data.button != undefined) {
this.button = data.button;
}
if ("sections" in data && data.sections != undefined) {
this.sections = data.sections;
}
}
}
get title() {
return pb_1.Message.getFieldWithDefault(this, 1, "") as string;
}
set title(value: string) {
pb_1.Message.setField(this, 1, value);
}
get description() {
return pb_1.Message.getFieldWithDefault(this, 2, "") as string;
}
set description(value: string) {
pb_1.Message.setOneofField(this, 2, this.#one_of_decls[0], value);
}
get has_description() {
return pb_1.Message.getField(this, 2) != null;
}
get footer() {
return pb_1.Message.getFieldWithDefault(this, 3, "") as string;
}
set footer(value: string) {
pb_1.Message.setOneofField(this, 3, this.#one_of_decls[1], value);
}
get has_footer() {
return pb_1.Message.getField(this, 3) != null;
}
get button() {
return pb_1.Message.getFieldWithDefault(this, 4, "") as string;
}
set button(value: string) {
pb_1.Message.setField(this, 4, value);
}
get sections() {
return pb_1.Message.getRepeatedWrapperField(this, Section, 5) as Section[];
}
set sections(value: Section[]) {
pb_1.Message.setRepeatedWrapperField(this, 5, value);
}
get _description() {
const cases: {
[index: number]: "none" | "description";
} = {
0: "none",
2: "description"
};
return cases[pb_1.Message.computeOneofCase(this, [2])];
}
get _footer() {
const cases: {
[index: number]: "none" | "footer";
} = {
0: "none",
3: "footer"
};
return cases[pb_1.Message.computeOneofCase(this, [3])];
}
static fromObject(data: {
title?: string;
description?: string;
footer?: string;
button?: string;
sections?: ReturnType<typeof Section.prototype.toObject>[];
}): ListMessage {
const message = new ListMessage({});
if (data.title != null) {
message.title = data.title;
}
if (data.description != null) {
message.description = data.description;
}
if (data.footer != null) {
message.footer = data.footer;
}
if (data.button != null) {
message.button = data.button;
}
if (data.sections != null) {
message.sections = data.sections.map(item => Section.fromObject(item));
}
return message;
}
toObject() {
const data: {
title?: string;
description?: string;
footer?: string;
button?: string;
sections?: ReturnType<typeof Section.prototype.toObject>[];
} = {};
if (this.title != null) {
data.title = this.title;
}
if (this.description != null) {
data.description = this.description;
}
if (this.footer != null) {
data.footer = this.footer;
}
if (this.button != null) {
data.button = this.button;
}
if (this.sections != null) {
data.sections = this.sections.map((item: Section) => item.toObject());
}
return data;
}
serialize(): Uint8Array;
serialize(w: pb_1.BinaryWriter): void;
serialize(w?: pb_1.BinaryWriter): Uint8Array | void {
const writer = w || new pb_1.BinaryWriter();
if (this.title.length)
writer.writeString(1, this.title);
if (this.has_description)
writer.writeString(2, this.description);
if (this.has_footer)
writer.writeString(3, this.footer);
if (this.button.length)
writer.writeString(4, this.button);
if (this.sections.length)
writer.writeRepeatedMessage(5, this.sections, (item: Section) => item.serialize(writer));
if (!w)
return writer.getResultBuffer();
}
static deserialize(bytes: Uint8Array | pb_1.BinaryReader): ListMessage {
const reader = bytes instanceof pb_1.BinaryReader ? bytes : new pb_1.BinaryReader(bytes), message = new ListMessage();
while (reader.nextField()) {
if (reader.isEndGroup())
break;
switch (reader.getFieldNumber()) {
case 1:
message.title = reader.readString();
break;
case 2:
message.description = reader.readString();
break;
case 3:
message.footer = reader.readString();
break;
case 4:
message.button = reader.readString();
break;
case 5:
reader.readMessage(message.sections, () => pb_1.Message.addToRepeatedWrapperField(message, 5, Section.deserialize(reader), Section));
break;
default: reader.skipField();
}
}
return message;
}
serializeBinary(): Uint8Array {
return this.serialize();
}
static deserializeBinary(bytes: Uint8Array): ListMessage {
return ListMessage.deserialize(bytes);
}
}
export class MessageReaction extends pb_1.Message {
#one_of_decls: number[][] = [];
constructor(data?: any[] | {
@@ -9207,6 +9812,132 @@ export namespace messages {
return PollMessage.deserialize(bytes);
}
}
export class PollVoteMessage extends pb_1.Message {
#one_of_decls: number[][] = [[2]];
constructor(data?: any[] | ({
pollMessageId?: string;
options?: string[];
} & (({
pollServerId?: number;
})))) {
super();
pb_1.Message.initialize(this, Array.isArray(data) ? data : [], 0, -1, [3], this.#one_of_decls);
if (!Array.isArray(data) && typeof data == "object") {
if ("pollMessageId" in data && data.pollMessageId != undefined) {
this.pollMessageId = data.pollMessageId;
}
if ("pollServerId" in data && data.pollServerId != undefined) {
this.pollServerId = data.pollServerId;
}
if ("options" in data && data.options != undefined) {
this.options = data.options;
}
}
}
get pollMessageId() {
return pb_1.Message.getFieldWithDefault(this, 1, "") as string;
}
set pollMessageId(value: string) {
pb_1.Message.setField(this, 1, value);
}
get pollServerId() {
return pb_1.Message.getFieldWithDefault(this, 2, 0) as number;
}
set pollServerId(value: number) {
pb_1.Message.setOneofField(this, 2, this.#one_of_decls[0], value);
}
get has_pollServerId() {
return pb_1.Message.getField(this, 2) != null;
}
get options() {
return pb_1.Message.getFieldWithDefault(this, 3, []) as string[];
}
set options(value: string[]) {
pb_1.Message.setField(this, 3, value);
}
get _pollServerId() {
const cases: {
[index: number]: "none" | "pollServerId";
} = {
0: "none",
2: "pollServerId"
};
return cases[pb_1.Message.computeOneofCase(this, [2])];
}
static fromObject(data: {
pollMessageId?: string;
pollServerId?: number;
options?: string[];
}): PollVoteMessage {
const message = new PollVoteMessage({});
if (data.pollMessageId != null) {
message.pollMessageId = data.pollMessageId;
}
if (data.pollServerId != null) {
message.pollServerId = data.pollServerId;
}
if (data.options != null) {
message.options = data.options;
}
return message;
}
toObject() {
const data: {
pollMessageId?: string;
pollServerId?: number;
options?: string[];
} = {};
if (this.pollMessageId != null) {
data.pollMessageId = this.pollMessageId;
}
if (this.pollServerId != null) {
data.pollServerId = this.pollServerId;
}
if (this.options != null) {
data.options = this.options;
}
return data;
}
serialize(): Uint8Array;
serialize(w: pb_1.BinaryWriter): void;
serialize(w?: pb_1.BinaryWriter): Uint8Array | void {
const writer = w || new pb_1.BinaryWriter();
if (this.pollMessageId.length)
writer.writeString(1, this.pollMessageId);
if (this.has_pollServerId)
writer.writeInt64(2, this.pollServerId);
if (this.options.length)
writer.writeRepeatedString(3, this.options);
if (!w)
return writer.getResultBuffer();
}
static deserialize(bytes: Uint8Array | pb_1.BinaryReader): PollVoteMessage {
const reader = bytes instanceof pb_1.BinaryReader ? bytes : new pb_1.BinaryReader(bytes), message = new PollVoteMessage();
while (reader.nextField()) {
if (reader.isEndGroup())
break;
switch (reader.getFieldNumber()) {
case 1:
message.pollMessageId = reader.readString();
break;
case 2:
message.pollServerId = reader.readInt64();
break;
case 3:
pb_1.Message.addToRepeatedField(message, 3, reader.readString());
break;
default: reader.skipField();
}
}
return message;
}
serializeBinary(): Uint8Array {
return this.serialize();
}
static deserializeBinary(bytes: Uint8Array): PollVoteMessage {
return PollVoteMessage.deserialize(bytes);
}
}
interface GrpcUnaryServiceInterface<P, R> {
(message: P, metadata: grpc_1.Metadata, options: grpc_1.CallOptions, callback: grpc_1.requestCallback<R>): grpc_1.ClientUnaryCall;
(message: P, metadata: grpc_1.Metadata, callback: grpc_1.requestCallback<R>): grpc_1.ClientUnaryCall;
File diff suppressed because it is too large. Load diff
+102 -23
View File
@@ -1,11 +1,11 @@
import {
aggregateMessageKeysNotFromMe,
Contact,
getContentType,
getUrlFromDirectPath,
isJidGroup,
jidNormalizedUser,
normalizeMessageContent,
proto,
WAMessageKey,
} from '@adiwajshing/baileys';
import { isJidBroadcast } from '@adiwajshing/baileys/lib/WABinary/jid-utils';
@@ -35,6 +35,7 @@ import {
} from '@waha/core/engines/gows/helpers';
import { GowsAuthFactoryCore } from '@waha/core/engines/gows/store/GowsAuthFactoryCore';
import {
extractBody,
getDestination,
toCusFormat,
} from '@waha/core/engines/noweb/session.noweb.core';
@@ -80,6 +81,7 @@ import {
MessageLinkCustomPreviewRequest,
MessageLocationRequest,
MessagePollRequest,
MessagePollVoteRequest,
MessageReactionRequest,
MessageReplyRequest,
MessageTextRequest,
@@ -87,6 +89,7 @@ import {
SendSeenRequest,
WANumberExistResult,
} from '@waha/structures/chatting.dto';
import { SendListRequest } from '@waha/structures/chatting.list.dto';
import { ContactQuery, ContactUpdateBody } from '@waha/structures/contacts.dto';
import {
ACK_UNKNOWN,
@@ -96,7 +99,6 @@ import {
WAMessageAck,
} from '@waha/structures/enums.dto';
import {
EventCancelRequest,
EventMessageRequest,
EventResponse,
EventResponsePayload,
@@ -168,6 +170,7 @@ import {
isLabelChatAddedEvent,
isLabelUpsertEvent,
} from './labels.gows';
import IMessageKey = proto.IMessageKey;
enum WhatsMeowEvent {
CONNECTED = 'gows.ConnectedEventData',
@@ -307,6 +310,7 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
this.me = {
id: toCusFormat(jidNormalizedUser(data.ID)),
pushName: data.PushName,
lid: jidNormalizedUser(data.LID),
};
// @ts-ignore
this.me.jid = data.ID;
@@ -444,7 +448,7 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
const waMessage = await this.toWAMessage(message);
// Extract the body from editedMessage using extractBody function
const body =
this.extractBody(message.Message.protocolMessage.editedMessage) || '';
extractBody(message.Message.protocolMessage.editedMessage) || '';
// Extract the original message ID from protocolMessage.key
const editedMessageId = message.Message.protocolMessage.key?.ID;
return {
@@ -536,6 +540,7 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
onlyEvent(WhatsMeowEvent.POLL_VOTE_EVENT),
map(this.toPollVotePayload.bind(this)),
filter(Boolean),
share(),
);
// Split into successful and failed responses
@@ -792,6 +797,14 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
return this.messageResponse(jid, data);
}
sendPollVote(request: MessagePollVoteRequest) {
throw new AvailableInPlusVersion('Poll voting');
}
sendList(request: SendListRequest): Promise<any> {
throw new AvailableInPlusVersion();
}
public async deleteMessage(chatId: string, messageId: string) {
const jid = toJID(this.ensureSuffix(chatId));
const key = parseMessageIdSerialized(messageId);
@@ -881,8 +894,21 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
};
}
sendLocation(request: MessageLocationRequest) {
throw new Error('Method not implemented.');
async sendLocation(request: MessageLocationRequest) {
const jid = toJID(this.ensureSuffix(request.chatId));
const message = new messages.MessageRequest({
jid: jid,
session: this.session,
replyTo: getMessageIdFromSerialized(request.reply_to),
location: new messages.Location({
name: request.title,
degreesLatitude: request.latitude,
degreesLongitude: request.longitude,
}),
});
const response = await promisify(this.client.SendMessage)(message);
const data = response.toObject();
return this.messageResponse(jid, data);
}
forwardMessage(request: MessageForwardRequest): Promise<WAMessage> {
@@ -1883,7 +1909,7 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
protected toWAMessage(message): WAMessage {
const fromToParticipant = getFromToParticipant(message);
const id = buildMessageId(message);
const body = this.extractBody(message.Message);
const body = extractBody(message.Message);
const replyTo = this.extractReplyTo(message.Message);
let ack = statusToAck(message.Status);
if (ack === WAMessageAck.ERROR) {
@@ -1919,12 +1945,19 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
private toPollVotePayload(event: any): PollVotePayload {
// Extract event creation message key from the message
const creationKey = event.Message?.pollUpdateMessage.pollCreationMessageKey;
const key: WAMessageKey = {
const pollKey: IMessageKey = {
remoteJid: creationKey.remoteJID,
fromMe: creationKey.fromMe,
id: creationKey.ID,
participant: creationKey.participant,
};
const voteKey: IMessageKey = {
id: event.Info.ID,
remoteJid: event.Info.Chat,
participant: event.IsGroup ? event.Info.Sender : null,
fromMe: event.IsFromMe,
};
const key = fixPollCreationKey(voteKey, pollKey, this.me);
const fromToParticipant = getFromToParticipant(event);
const pollCreationKey = getDestination(key);
return {
@@ -1984,21 +2017,6 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
return sentDeviceId === myDeviceId ? MessageSource.API : MessageSource.APP;
}
private extractBody(message) {
if (!message) {
return null;
}
let body = message.Conversation || message.conversation;
if (!body) {
body = message.extendedTextMessage?.text;
}
if (!body) {
const media = extractMediaContent(message);
body = media?.caption;
}
return body;
}
protected extractReplyTo(message): ReplyToMessage | null {
const msgType = getContentType(message);
const contextInfo = message[msgType]?.contextInfo;
@@ -2009,7 +2027,7 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
if (!quotedMessage) {
return null;
}
const body = this.extractBody(quotedMessage);
const body = extractBody(quotedMessage);
return {
id: contextInfo.stanzaID,
participant: toCusFormat(contextInfo.participant),
@@ -2191,6 +2209,7 @@ function getFromToParticipant(message) {
from: info.Chat,
to: info.IsGroup ? info.Sender : null,
participant: info.IsGroup ? info.Sender : null,
fromMe: info.IsFromMe,
};
}
@@ -2243,3 +2262,63 @@ export function getMessageIdFromSerialized(serialized: string): string | null {
const key = parseMessageIdSerialized(serialized, true);
return key.id;
}
/**
* Poll creation keys are a bit tricky — they contain the data that the receiver uses.
* When recipients respond to a poll, they send back THEIR `message.id`.
*
* For example,
* - If we send a poll and in our system the ID is "true_{chatId}_{ID}",
* - The corresponding `pollCreationKey` will be "false_{ourId}_{ID}" —
* essentially the opposite of our message ID.
*
* The function inspects the key and,
* if it originates from us (based on `remoteJid` or `participant`),
* adjusts it to the correct value.
*/
function fixPollCreationKey(
vote: IMessageKey,
poll: IMessageKey,
me: MeInfo,
): IMessageKey {
// If the vote is from me, the pollCreationKey is already in my perspective
if (vote?.fromMe) {
return poll;
}
if (!me) {
return poll;
}
if (!poll) {
return poll;
}
// DM - my poll, not my vote
if (
toCusFormat(poll.remoteJid) == toCusFormat(me.id) ||
toCusFormat(poll.remoteJid) == toCusFormat(me.lid)
) {
return {
id: poll.id,
remoteJid: vote.remoteJid,
fromMe: true,
participant: undefined,
};
}
// Many Participants Chat - my poll, not my vote
if (
toCusFormat(poll.participant) == toCusFormat(me.id) ||
toCusFormat(poll.participant) == toCusFormat(me.lid)
) {
return {
id: poll.id,
remoteJid: vote.remoteJid,
fromMe: true,
participant: me.id,
};
}
// Other
return poll;
}
+24 -2
View File
@@ -16,7 +16,6 @@ import makeWASocket, {
jidNormalizedUser,
makeCacheableSignalKeyStore,
MiscMessageGenerationOptions,
NewsletterMetadata,
normalizeMessageContent,
PresenceData,
proto,
@@ -121,6 +120,7 @@ import {
SendSeenRequest,
WANumberExistResult,
} from '@waha/structures/chatting.dto';
import { SendListRequest } from '@waha/structures/chatting.list.dto';
import {
ContactQuery,
ContactRequest,
@@ -735,6 +735,7 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
return {
id: toCusFormat(meId),
pushName: me.name,
lid: me.lid,
};
}
@@ -952,15 +953,21 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
);
}
sendList(request: SendListRequest): Promise<any> {
throw new AvailableInPlusVersion();
}
async sendLocation(request: MessageLocationRequest) {
const chatId = toJID(this.ensureSuffix(request.chatId));
const msg = {
location: {
name: request.title || null,
degreesLatitude: request.latitude,
degreesLongitude: request.longitude,
},
};
const options = await this.getMessageOptions(request);
return await this.sock.sendMessage(request.chatId, msg, options);
return await this.sock.sendMessage(chatId, msg, options);
}
async forwardMessage(request: MessageForwardRequest): Promise<WAMessage> {
@@ -2702,5 +2709,20 @@ export function extractBody(message): string | null {
if (!body) {
body = content.buttonsResponseMessage?.selectedDisplayText;
}
// List message
if (!body) {
const type = getContentType(content);
if (type == 'listMessage') {
const list = content.listMessage;
const parts = [list.title, list.description, list.footerText];
body = parts.filter(Boolean).join('\n');
} else if (type === 'listResponseMessage') {
const response = content.listResponseMessage;
const parts = [response.title, response.description];
body = parts.filter(Boolean).join('\n');
}
}
return body;
}
+5 -2
View File
@@ -2,12 +2,15 @@ import {
NotImplementedException,
UnprocessableEntityException,
} from '@nestjs/common';
import { getEngineName } from '@waha/config';
export const DOCS_URL = 'https://waha.devlike.pro/';
const engine = getEngineName();
export class NotImplementedByEngineError extends NotImplementedException {
constructor(msg = '') {
let error = 'The method is not implemented by the engine.';
let error = `The method is not implemented by '${engine}' engine. Check the docs and try another engine: ${DOCS_URL}`;
if (msg) {
error = `${msg} ${error}`;
}
@@ -18,7 +21,7 @@ export class NotImplementedByEngineError extends NotImplementedException {
export class AvailableInPlusVersion extends UnprocessableEntityException {
constructor(feature: string = 'The feature') {
super(
`${feature} is available only in Plus version. Check this out: ${DOCS_URL}`,
`${feature} is available only in Plus version for '${engine}' engine. Check this out: ${DOCS_URL}`,
);
}
}
+6 -1
View File
@@ -18,7 +18,12 @@ export function serializeError(err: unknown) {
const properties = Object.getOwnPropertyNames(err);
// getOwnPropertyNames does not get 'name', like "ReferenceError"
properties.push('name');
return JSON.parse(JSON.stringify(err, properties));
const result = JSON.parse(JSON.stringify(err, properties));
// Put the "stack" field at the end, too wordy
const stack = result.stack;
delete result.stack;
result.stack = stack;
return result;
}
@Catch()
+33
View File
@@ -8,8 +8,10 @@ import { IsFileType } from '@waha/nestjs/validation/IsFileType';
import { GetChatMessagesQuery } from '@waha/structures/chats.dto';
import { plainToInstance, Transform, Type } from 'class-transformer';
import {
IsArray,
IsBoolean,
IsNotEmpty,
IsNumber,
IsOptional,
IsString,
IsUrl,
@@ -456,3 +458,34 @@ export class NewMessageIDResponse {
})
id: string;
}
export class MessagePollVoteRequest extends ChatRequest {
@ApiProperty({
description:
'The ID of the poll message. Format: {fromMe}_{chatID}_{messageId}[_{participant}] or just ID for GOWS',
example: 'false_11111111111@c.us_AAAAAAAAAAAAAAAAAAAA',
required: true,
})
@IsString()
@IsNotEmpty()
pollMessageId: string;
@ApiProperty({
description:
'Only for Channels - server message id (if known); if omitted, API may look it up in the storage',
required: false,
example: null,
})
@IsOptional()
@IsNumber()
pollServerId?: number;
@ApiProperty({
description: 'Poll options you are voting for',
example: 'Awesome!',
isArray: true,
})
@IsArray()
@IsString({ each: true })
votes: string[];
}
+141
View File
@@ -0,0 +1,141 @@
import { ApiExtraModels, ApiProperty } from '@nestjs/swagger';
import { ChatRequest } from '@waha/structures/chatting.dto';
import { ChatIdProperty } from '@waha/structures/properties.dto';
import { Type } from 'class-transformer';
import {
ArrayMinSize,
IsArray,
IsNotEmpty,
IsOptional,
IsString,
ValidateNested,
} from 'class-validator';
class Row {
@ApiProperty({ example: 'Option 1' })
@IsString()
@IsNotEmpty()
title: string;
@ApiProperty({ example: 'Description of option 1', required: false })
@IsOptional()
@IsString()
description?: string;
@ApiProperty({ example: 'option1' })
@IsString()
@IsNotEmpty()
rowId: string;
}
class Section {
@ApiProperty({ example: 'Menu' })
@IsString()
@IsNotEmpty()
title: string;
@ValidateNested({ each: true })
@Type(() => Row)
@IsArray()
@ArrayMinSize(1)
@ApiProperty({
example: [
{ title: 'Option 1', rowId: 'option1', description: 'First option' },
{ title: 'Option 2', rowId: 'option2', description: 'Second option' },
],
})
rows: Row[];
}
export class SendListMessage {
@ApiProperty({ example: 'Example List' })
@IsString()
@IsNotEmpty()
title: string;
@ApiProperty({ example: 'Choose one of the options', required: false })
@IsOptional()
@IsString()
description?: string;
@ApiProperty({ example: 'Footer note', required: false })
@IsOptional()
@IsString()
footer?: string;
@ApiProperty({ example: 'Select' })
@IsString()
@IsNotEmpty()
button: string;
@ValidateNested({ each: true })
@Type(() => Section)
@IsArray()
@ArrayMinSize(1)
@ApiProperty({
example: [
{
title: 'Section 1',
rows: [
{ title: 'Option 1', rowId: 'option1', description: 'Description 1' },
{ title: 'Option 2', rowId: 'option2', description: 'Description 2' },
],
},
],
})
sections: Section[];
}
@ApiExtraModels(SendListMessage)
export class SendListRequest extends ChatRequest {
@ChatIdProperty()
@IsString()
chatId: string;
@ValidateNested()
@Type(() => SendListMessage)
@ApiProperty({
type: SendListMessage,
example: {
title: 'Simple Menu',
description: 'Please choose an option',
footer: 'Thank you!',
button: 'Choose',
sections: [
{
title: 'Main',
rows: [
{
title: 'Option 1',
rowId: 'option1',
description: null,
},
{
title: 'Option 2',
rowId: 'option2',
description: null,
},
{
title: 'Option 3',
rowId: 'option3',
description: null,
},
],
},
],
},
})
message: SendListMessage;
@ApiProperty({
description:
'The ID of the message to reply to - false_11111111111@c.us_AAAAAAAAAAAAAAAAAAAA',
example: null,
required: false,
})
@IsOptional()
@IsString()
reply_to?: string;
}
export { Row, Section };
+5
View File
@@ -155,6 +155,11 @@ export class MeInfo {
@ChatIdProperty()
id: string;
@ApiProperty({
example: '123123@lid',
})
lid?: string;
pushName: string;
}
+5 -6
View File
@@ -1,4 +1,6 @@
// eslint-disable-next-line @typescript-eslint/no-var-requires
import { getEngineName } from '@waha/config';
import { getBrowserExecutablePath } from './core/abc/session.abc';
import { WAHAEngine } from './structures/enums.dto';
import { WAHAEnvironment } from './structures/environment.dto';
@@ -26,14 +28,9 @@ export function getWAHAVersion(): WAHAVersion {
return WAHAVersion.CORE;
}
export function getEngineName(): string {
// Load engine name from WHATSAPP_DEFAULT_ENGINE environment variable
// If not set - use WEBJS
return process.env.WHATSAPP_DEFAULT_ENGINE || WAHAEngine.WEBJS;
}
export const VERSION: WAHAEnvironment = {
version: '2025.8.1',
version: '2025.8.2',
engine: getEngineName(),
tier: getWAHAVersion(),
browser:
@@ -41,3 +38,5 @@ export const VERSION: WAHAEnvironment = {
};
export const IsChrome = VERSION.browser?.includes('chrome');
export { getEngineName };
+2 -2
View File
@@ -2,11 +2,11 @@
"waha": {
"gows": {
"repo": "devlikeapro/gows",
"ref": "v1.0.11"
"ref": "v1.0.12"
},
"dashboard": {
"repo": "devlikeapro/dashboard",
"ref": "59ebfe14fc89b42f77dd9da173d3e7e2983beb21"
"ref": "c6bf800fec183644ad820343f97e0caf8c71ed98"
}
}
}
+2 -2
View File
@@ -7,7 +7,7 @@ __metadata:
"@adiwajshing/baileys@github:devlikeapro/Baileys#fork-master-2025-07-04":
version: 6.7.18
resolution: "@adiwajshing/baileys@https://github.com/devlikeapro/Baileys.git#commit=bca2147eee9de863f5fd58673027a7286e559bed"
resolution: "@adiwajshing/baileys@https://github.com/devlikeapro/Baileys.git#commit=51fd53fdf4a4a99c80ae97016716872b4e5f0734"
dependencies:
"@cacheable/node-cache": ^1.4.0
"@hapi/boom": ^9.1.3
@@ -33,7 +33,7 @@ __metadata:
optional: true
sharp:
optional: true
checksum: b20db4943ed6b228e5ec10a6916f4be0f68aee150337c94267e623ab0c6c396191b0d7c82c83bf8efd55f17d8a57c2e339d12fc47c0a8152b45e4c9aaf8f996f
checksum: 20b2d70bbaa7840392d8b0d7eee65da72eaa9fe56e0bdbb61ffddc3f8778d85c1e3018edf67ae1078508828f1884a5af9875ae71a0dbf19f62065602e7c962cd
languageName: node
linkType: hard