Compare commits

...
26 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
devlikepro 89ec47deec [core] 2025.8.1
Release / WEBJS - chrome - amd64 - chrome (push) Waiting to run
Release / WEBJS - chromium - amd64 - latest (push) Waiting to run
Release / WEBJS - chromium - linux/arm64 - arm (push) Waiting to run
Release / GOWS - none - amd64 - gows (push) Waiting to run
Release / GOWS - none - linux/arm64 - gows-arm (push) Waiting to run
Release / NOWEB - none - amd64 - noweb (push) Waiting to run
Release / NOWEB - none - linux/arm64 - noweb-arm (push) Waiting to run
2025-08-05 15:53:42 +07:00
devlikepro a043a46457 [core] API - limited characters in the session name
fix #1163
2025-08-05 15:53:42 +07:00
devlikepro eb8e3a0fd1 [core] WEBJS - fix create group
fix #1145
2025-08-05 15:53:41 +07:00
devlikepro fc25df8104 [core] NOWEB - fix channel list
fix #1177
2025-08-05 15:53:41 +07:00
devlikepro d8ffd3e543 [core] 2025.7.11
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-08-03 13:59:23 +07:00
devlikepro ea4ec9310d [core] WEBJS - fix Caught error, dropping value from, event: 'message.ack'
fix #1178
2025-08-03 13:59:22 +07:00
devlikepro 479ec85e0d [core] Dashboard - i18n 2025-08-03 13:59:22 +07:00
devlikepro 3e4b450260 [core] ChatWoot - find chat id - jid, lid, chat id 2025-08-03 13:59:22 +07:00
devlikepro f6a81949d0 [core] ChatWoot - Urdu 2025-08-03 13:59:21 +07:00
devlikepro 6896962606 [core] Always show list of chatwoot languages
fix #1138
2025-08-03 13:59:21 +07:00
devlikepro 9b6d6ab4b0 [core] Inline LOCALE_NAME in yaml 2025-08-03 13:59:21 +07:00
40 changed files with 2958 additions and 104 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);
+2 -1
View File
@@ -9,6 +9,7 @@ import { BullAuthMiddleware } from '@waha/apps/app_sdk/auth';
import { AppsDisabledService } from '@waha/apps/app_sdk/services/AppsDisabledService';
import { AppsEnabledService } from '@waha/apps/app_sdk/services/AppsEnabledService';
import { AppsService } from '@waha/apps/app_sdk/services/IAppsService';
import { ChatwootLocalesController } from '@waha/apps/chatwoot/api/chatwoot.locales.controller';
import { ChatWootExports } from '@waha/apps/chatwoot/chatwoot.module';
import { parseBool } from '@waha/helpers';
import { RMutexModule } from '@waha/modules/rmutex';
@@ -88,7 +89,7 @@ const AppsDisabled = {
},
],
imports: [],
controllers: [AppsController],
controllers: [AppsController, ChatwootLocalesController],
};
function checkApiKey() {
@@ -17,9 +17,9 @@ export class ChatwootLocalesController {
description: 'Get available languages for Chatwoot app',
})
getLanguages(): LanguageResponse[] {
return Object.entries(LOCALE_NAMES).map(([locale, name]) => ({
name: name,
locale: locale,
return Array.from(LOCALE_NAMES.entries()).map(([locale, name]) => ({
name,
locale,
}));
}
}
@@ -1,6 +1,6 @@
import { Body, Controller, Param, Post } from '@nestjs/common';
import { ApiOperation, ApiTags } from '@nestjs/swagger';
import { GetChatID } from '@waha/apps/chatwoot/client/ids';
import { FindChatID } from '@waha/apps/chatwoot/client/ids';
import { EventName, MessageType } from '@waha/apps/chatwoot/client/types';
import { INBOX_CONTACT_CHAT_ID } from '@waha/apps/chatwoot/const';
import { InboxData } from '@waha/apps/chatwoot/consumers/types';
@@ -43,7 +43,7 @@ export class ChatwootWebhookController {
// Check if it's a command message (sent to the special inbox contact)
const sender = body?.conversation?.meta?.sender;
const chatId = GetChatID(sender);
const chatId = FindChatID(sender);
const isCommandsChat = chatId === INBOX_CONTACT_CHAT_ID;
// Check if it's a deleted message
+13
View File
@@ -15,6 +15,19 @@ export function GetChatID(contact: any): string | null {
return contact?.custom_attributes?.[AttributeKey.WA_CHAT_ID];
}
export function FindChatID(contact: any): string | null {
if (GetJID(contact)) {
return GetJID(contact);
}
if (GetLID(contact)) {
return GetLID(contact);
}
if (GetChatID(contact)) {
return GetChatID(contact);
}
return null;
}
export function SerializeWhatsAppKey(message: WhatsAppMessage): string {
const key = {
id: message.message_id,
+2 -2
View File
@@ -1,7 +1,7 @@
import { AppConsumer } from '@waha/apps/app_sdk/AppConsumer';
import { JobLoggerWrapper } from '@waha/apps/app_sdk/JobLoggerWrapper';
import { HasBeenRetried } from '@waha/apps/app_sdk/JobUtils';
import { GetChatID } from '@waha/apps/chatwoot/client/ids';
import { FindChatID } from '@waha/apps/chatwoot/client/ids';
import { EventName } from '@waha/apps/chatwoot/client/types';
import { ChatWootConversationKey } from '@waha/apps/chatwoot/consumers/mutex';
import { InboxData } from '@waha/apps/chatwoot/consumers/types';
@@ -124,7 +124,7 @@ export async function LookupAndCheckChatId(
body: any,
): Promise<string> {
const sender = body.conversation.meta.sender;
let chatId = GetChatID(sender);
let chatId = FindChatID(sender);
if (!chatId && sender.phone_number) {
const existResult = await session.contactCheckExists(sender.phone_number);
if (!existResult.numberExists) {
@@ -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);
+1
View File
@@ -1,4 +1,5 @@
# Arabic translations for ChatWoot integration
LOCALE_NAME: '🇦🇪 العربية (Arabic)'
APP_INBOX_CONTACT_NAME: 'تكامل واتساب (WAHA)'
JOB_ERROR_REPORT: |
+1
View File
@@ -1,4 +1,5 @@
# Bengali translations for ChatWoot integration
LOCALE_NAME: '🇧🇩 বাংলা (Bengali)'
APP_INBOX_CONTACT_NAME: 'হোয়াটসঅ্যাপ ইন্টিগ্রেশন (WAHA)'
JOB_ERROR_REPORT: |
+1
View File
@@ -1,4 +1,5 @@
# German translations for ChatWoot integration
LOCALE_NAME: '🇩🇪 Deutsch (German)'
APP_INBOX_CONTACT_NAME: 'WhatsApp Integration (WAHA)'
JOB_ERROR_REPORT: |
+1
View File
@@ -1,4 +1,5 @@
# English translations for ChatWoot integration
LOCALE_NAME: '🇺🇸 English (English)'
APP_INBOX_CONTACT_NAME: 'WhatsApp Integration (WAHA)'
APP_INBOX_CONTACT_AVATAR_URL: 'https://raw.githubusercontent.com/devlikeapro/waha/core/logo.png'
+1
View File
@@ -1,4 +1,5 @@
# Spanish translations for ChatWoot integration
LOCALE_NAME: '🇪🇸 Español (Spanish)'
APP_INBOX_CONTACT_NAME: 'Integración de WhatsApp (WAHA)'
JOB_ERROR_REPORT: |
+1
View File
@@ -1,4 +1,5 @@
# Persian translations for ChatWoot integration
LOCALE_NAME: '🇮🇷 فارسی (Persian)'
APP_INBOX_CONTACT_NAME: 'ادغام واتساپ (WAHA)'
JOB_ERROR_REPORT: |
+1
View File
@@ -1,4 +1,5 @@
# French translations for ChatWoot integration
LOCALE_NAME: '🇫🇷 Français (French)'
APP_INBOX_CONTACT_NAME: 'Intégration WhatsApp (WAHA)'
JOB_ERROR_REPORT: |
+1
View File
@@ -1,4 +1,5 @@
# Hebrew translations for ChatWoot integration
LOCALE_NAME: '🇮🇱 עברית (Hebrew)'
APP_INBOX_CONTACT_NAME: 'אינטגרציית וואטסאפ (WAHA)'
JOB_ERROR_REPORT: |
+1
View File
@@ -1,4 +1,5 @@
# Hindi translations for ChatWoot integration
LOCALE_NAME: '🇮🇳 हिंदी (Hindi)'
APP_INBOX_CONTACT_NAME: 'व्हाट्सएप इंटीग्रेशन (WAHA)'
JOB_ERROR_REPORT: |
+1
View File
@@ -1,4 +1,5 @@
# Indonesian translations for ChatWoot integration
LOCALE_NAME: '🇮🇩 Bahasa Indonesia (Indonesian)'
APP_INBOX_CONTACT_NAME: 'Integrasi WhatsApp (WAHA)'
JOB_ERROR_REPORT: |
+1
View File
@@ -1,4 +1,5 @@
# Punjabi translations for ChatWoot integration
LOCALE_NAME: '🇵🇰 ਪੰਜਾਬੀ (Punjabi)'
APP_INBOX_CONTACT_NAME: 'ਵਟਸਐਪ ਇੰਟੀਗ੍ਰੇਸ਼ਨ (WAHA)'
JOB_ERROR_REPORT: |
+1
View File
@@ -1,4 +1,5 @@
# Portuguese translations for ChatWoot integration
LOCALE_NAME: '🇧🇷🇵🇹 Português (Portuguese)'
APP_INBOX_CONTACT_NAME: 'Integração WhatsApp (WAHA)'
JOB_ERROR_REPORT: |
+1
View File
@@ -1,4 +1,5 @@
# Russian translations for ChatWoot integration
LOCALE_NAME: '🇷🇺 Русский (Russian)'
APP_INBOX_CONTACT_NAME: 'WhatsApp Интеграция (WAHA)'
JOB_ERROR_REPORT: |
+1
View File
@@ -1,4 +1,5 @@
# Turkish translations for ChatWoot integration
LOCALE_NAME: '🇹🇷 Türkçe (Turkish)'
APP_INBOX_CONTACT_NAME: 'WhatsApp Entegrasyonu (WAHA)'
JOB_ERROR_REPORT: |
+1
View File
@@ -1,4 +1,5 @@
# Ukrainian translations for ChatWoot integration
LOCALE_NAME: '🇺🇦 Українська (Ukrainian)'
APP_INBOX_CONTACT_NAME: 'Інтеграція WhatsApp (WAHA)'
JOB_ERROR_REPORT: |
+182
View File
@@ -0,0 +1,182 @@
# Urdu translations for ChatWoot integration
LOCALE_NAME: '🇵🇰 اردو (Urdu)'
APP_INBOX_CONTACT_NAME: 'واٹس ایپ انٹیگریشن (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_ERROR_RECEIVING_MESSAGE: واٹس ایپ سے پیغام موصول نہیں کر سکا
WHATSAPP_ERROR_REMOVING_MESSAGE: واٹس ایپ سے پیغام ہٹا نہیں سکا
APP_SCHEDULED_JOB_ERROR: شیڈول کردہ کام انجام دینے میں ناکام رہا
MESSAGE_FROM_WHATSAPP: |
{{{text}}}
📱 *واٹس ایپ سے بھیجا گیا*
MESSAGE_FROM_API: |
{{{text}}}
📱 *API کے ذریعے بھیجا گیا*
MESSAGE_REMOVED_IN_WHATSAPP: |
❌ *یہ پیغام واٹس ایپ میں حذف کر دیا گیا تھا*
MESSAGE_EDITED_IN_WHATSAPP: |
{{{text}}}
✏️ *واٹس ایپ میں ترمیم کی گئی*
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: |
🔗 **منسلک: واٹس ایپ سیشن ChatWoot انبکس سے**
✅ واٹس ایپ سیشن **{{{name}}}** کامیابی سے اس ChatWoot انبکس سے منسلک ہو گیا ہے۔
یہ انبکس اب آپ کے **واٹس ایپ اکاؤنٹ** سے منسلک ہے۔ آپ یہاں سے براہ راست پیغامات بھیج اور موصول کر سکتے ہیں — بالکل واٹس ایپ استعمال کرنے کی طرح، لیکن ChatWoot کے اندر۔
آپ اس انبکس سے منسلک واٹس ایپ سیشن کا انتظام بھی کر سکتے ہیں۔ دستیاب اقدامات دیکھنے کے لیے، اس چیٹ میں **help** بھیجیں۔
📋 عام اقدامات جو آپ کر سکتے ہیں:
- چیک کریں کہ آیا واٹس ایپ سیشن فعال ہے
- واٹس ایپ سیشن کو دوبارہ شروع کریں یا روکیں
- لاگ آؤٹ کریں اور نیا QR کوڈ اسکین کر کے دوبارہ منسلک ہوں
اگر سب کچھ کام کر رہا ہے، تو آپ آزادانہ طور پر چیٹ کر سکیں گے۔ اگر آپ کو انتباہات نظر آتے ہیں، تو آپ کو اپنے واٹس ایپ اکاؤنٹ کو دوبارہ منسلک کرنے کی ضرورت ہو سکتی ہے۔
آئیے شروع کریں! 🚀
APP_DISCONNECTED_MESSAGE: |
🔌 **منقطع: واٹس ایپ سیشن ChatWoot انبکس سے**
❌ واٹس ایپ سیشن **{{{name}}}** اس ChatWoot انبکس سے منقطع ہو گیا ہے۔
آپ اب واٹس ایپ سے پیغامات بھیجنے یا موصول کرنے کے قابل نہیں ہوں گے۔
دوبارہ منسلک ہونے کے لیے، براہ کرم اپنے منتظم سے رابطہ کریں۔
APP_UPDATED_MESSAGE: |
🔄 **اپڈیٹ شدہ: واٹس ایپ سیشن کی ترتیبات**
✅ واٹس ایپ سیشن **{{{name}}}** کی ترتیبات اپڈیٹ کر دی گئی ہیں۔
APP_COMMANDS_LIST: |
آپ درج ذیل کمانڈز میں سے ایک بھیج کر **واٹس ایپ انٹیگریشن** کا انتظام کر سکتے ہیں:
🖥️ سیشنز:
1. **status** - موجودہ سیشن کی حالت حاصل کریں۔
2. **restart** - سیشن کو دوبارہ شروع کریں - یہ آپ کے واٹس ایپ اکاؤنٹ کو دوبارہ منسلک کرنے کی کوشش کرے گا۔
3. **start** - سیشن شروع کریں۔
4. **stop** - سیشن روکیں۔
5. **logout** - سیشن سے لاگ آؤٹ کریں، تاکہ آپ QR کوڈ اسکین کر سکیں اور سیشن کی مرمت کر سکیں۔
6. **qr** - سیشن کے لیے 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}}}** میں ہے
**واٹس ایپ اکاؤنٹ:**
📱 **{{{name}}} ({{{id}}})**
APP_DASHBOARD_LINK: |
مزید تفصیلات کے لیے [WAHA ڈیش بورڈ]({{{url}}}) ملاحظہ کریں۔
APP_SESSION_STATUS_WORKING: |
✅ یہ ChatWoot انبکس **واٹس ایپ اکاؤنٹ** سے منسلک ہے اور پیغامات بھیجنے اور موصول کرنے کے لیے تیار ہے!
📱 **{{{name}}} ({{{id}}})**
APP_SESSION_STATUS_ERROR: |
⚠️ **یہ ChatWoot انبکس واٹس ایپ اکاؤنٹ سے منسلک ہے لیکن پیغامات بھیجنے اور موصول کرنے کے لیے تیار نہیں ہے!**
براہ کرم سیشن کی حالت چیک کریں اور اسے دوبارہ شروع کرنے کی کوشش کریں یا لاگ آؤٹ کریں اور QR کوڈ دوبارہ اسکین کریں۔
APP_SESSION_SCAN_QR_CODE: |
⚠️ **اس ChatWoot انبکس کے ساتھ واٹس ایپ اکاؤنٹ کو منسلک کرنے کے لیے اپنے فون پر QR کوڈ اسکین کریں!**
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}}})
+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}}})
+22 -50
View File
@@ -4,59 +4,26 @@ 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',
}
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,
'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 LOCALE_NAMES: Record<LocaleCode, string> = {
[LocaleCode.EN_US]: '🇺🇸 English (English)',
[LocaleCode.AR_AE]: '🇦🇪 العربية (Arabic)',
[LocaleCode.BN_BD]: '🇧🇩 বাংলা (Bengali)',
[LocaleCode.PT_BR]: '🇧🇷🇵🇹 Português (Portuguese)',
[LocaleCode.DE_DE]: '🇩🇪 Deutsch (German)',
[LocaleCode.ES_ES]: '🇪🇸 Español (Spanish)',
[LocaleCode.FR_FR]: '🇫🇷 Français (French)',
[LocaleCode.ID_ID]: '🇮🇩 Bahasa Indonesia (Indonesian)',
[LocaleCode.HE_IL]: '🇮🇱 עברית (Hebrew)',
[LocaleCode.HI_IN]: '🇮🇳 हिंदी (Hindi)',
[LocaleCode.FA_IR]: '🇮🇷 فارسی (Persian)',
[LocaleCode.PA_PK]: '🇵🇰 ਪੰਜਾਬੀ (Punjabi)',
[LocaleCode.RU_RU]: '🇷🇺 Русский (Russian)',
[LocaleCode.TR_TR]: '🇹🇷 Türkçe (Turkish)',
[LocaleCode.UK_UA]: '🇺🇦 Українська (Ukrainian)',
};
export const DEFAULT_LOCALE = 'en-US';
function loadTemplateStrings(locale: string) {
@@ -82,8 +49,13 @@ function loadLocales(locales: string[]) {
}
let locales: Record<string, Record<string, string>> = {};
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, strings['LOCALE_NAME']);
}
} catch (error) {
console.error('Error loading locales:', error);
process.exit(1);
+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;
}
+28 -3
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> {
@@ -1768,7 +1775,10 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
public async channelsList(query: ListChannelsQuery): Promise<Channel[]> {
const newsletters = await this.sock.newsletterSubscribed();
let channels = newsletters.map(toNewsletterMetadata).map(this.toChannel);
let channels = newsletters
.map(toNewsletterMetadata)
.filter(Boolean)
.map(this.toChannel);
if (query.role) {
// @ts-ignore
channels = channels.filter((channel) => channel.role === query.role);
@@ -2699,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;
}
+3 -3
View File
@@ -49,7 +49,7 @@ export function TagReceiptNodeToReceiptEvent(
const { attrs, content } = node;
const status = getStatusFromReceiptType(attrs.type);
if (status == null) {
return null;
return [];
}
const from = jidNormalizedUser(jid(attrs.from));
@@ -118,14 +118,14 @@ function handleGroupedReceipts(
fromMe: boolean,
isLid: boolean,
me: Me,
): ReceiptEvent[] | null {
): ReceiptEvent[] {
const { content } = node;
if (!Array.isArray(content)) {
return [];
}
const participantsTags = content.filter((c) => c.tag === 'participants');
if (participantsTags.length === 0) {
return null;
return [];
}
const receiptEvents: ReceiptEvent[] = [];
+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 };
+10
View File
@@ -7,6 +7,7 @@ import {
IsBoolean,
IsOptional,
IsString,
Matches,
MaxLength,
ValidateNested,
} from 'class-validator';
@@ -154,6 +155,11 @@ export class MeInfo {
@ChatIdProperty()
id: string;
@ApiProperty({
example: '123123@lid',
})
lid?: string;
pushName: string;
}
@@ -179,6 +185,10 @@ export class SessionCreateRequest {
@IsString()
@IsOptional()
@MaxLength(DB_NAME_LIMIT - DB_NAME_MAX_PREFIX_LEN)
@Matches(/^[a-zA-Z0-9_-]*$/, {
message:
'Session name can only contain alphanumeric characters, hyphens, and underscores (a-z, A-Z, 0-9, -, _) or be empty',
})
name: string | undefined;
@ValidateNested()
+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.7.10',
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": "0000e1ffee9bc40b4d3e0ee6eff825ab5bc19f30"
"ref": "c6bf800fec183644ad820343f97e0caf8c71ed98"
}
}
}
+4 -4
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
@@ -13861,7 +13861,7 @@ __metadata:
"whatsapp-web.js@github:devlikeapro/whatsapp-web.js#fork-main-2025-06-09":
version: 1.30.1-alpha.2
resolution: "whatsapp-web.js@https://github.com/devlikeapro/whatsapp-web.js.git#commit=3db9c9d867a0cb5be8525552ad2dc7de9ba42580"
resolution: "whatsapp-web.js@https://github.com/devlikeapro/whatsapp-web.js.git#commit=55581239a70ddb76e6fe32381d9f991557cf9df3"
dependencies:
"@pedroslopez/moduleraid": ^5.0.2
archiver: ^5.3.1
@@ -13879,7 +13879,7 @@ __metadata:
optional: true
unzipper:
optional: true
checksum: 6aeee54f021f48fcd73df7b3b26f785fe9ae376715b76de53d7a491dec60ff281b149754fab20be01192ff9bf7aa246a18475b14a69fb6e006ae23985b95ebcb
checksum: 2af8df50bf3f6225222417d3d21e63750cc520cf19636c5ffdd3e828b26c132b9f5acd36fc43a4d041a245ce1f75b3b080550f844cbf6142a322ce4ca73734c4
languageName: node
linkType: hard