Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
84cd350c42 | ||
|
|
100bf89074 | ||
|
|
d66c80f95a | ||
|
|
26f04bbd94 | ||
|
|
6582c03823 | ||
|
|
abacaf2e02 | ||
|
|
c65d8b9856 | ||
|
|
8f94cf06de | ||
|
|
94c1c16de8 | ||
|
|
e69d992725 | ||
|
|
e6c5dd6e22 | ||
|
|
5486d4874a | ||
|
|
9d9d8e03fe | ||
|
|
6ee50243a2 | ||
|
|
3ec078f9c1 | ||
|
|
4c3209be6d | ||
|
|
7fe8f9a8dc | ||
|
|
7fa2b29418 | ||
|
|
dd4edd5a85 | ||
|
|
b85df5a0d3 | ||
|
|
1a7bec3d4e | ||
|
|
727c4b454f | ||
|
|
4ba86b34a9 | ||
|
|
277a35aed9 | ||
|
|
28c1ffa09f | ||
|
|
10595680a9 | ||
|
|
577298dbc5 | ||
|
|
49ca4dfbf7 | ||
|
|
89ec47deec | ||
|
|
a043a46457 | ||
|
|
eb8e3a0fd1 | ||
|
|
fc25df8104 | ||
|
|
d8ffd3e543 | ||
|
|
ea4ec9310d | ||
|
|
479ec85e0d | ||
|
|
3e4b450260 | ||
|
|
f6a81949d0 | ||
|
|
6896962606 | ||
|
|
9b6d6ab4b0 | ||
|
|
d5f399f7d5 | ||
|
|
dbd3bd024c | ||
|
|
9ba7e19da3 | ||
|
|
5f6d1412cf | ||
|
|
0b5af3145e |
No files matched your search
@@ -1,2 +1,3 @@
|
||||
src/core/engines/gows/grpc/*
|
||||
src/core/engines/gows/proto/*
|
||||
src/core/engines/webjs/_lodash.js
|
||||
@@ -1,36 +0,0 @@
|
||||
module.exports = {
|
||||
parser: '@typescript-eslint/parser',
|
||||
parserOptions: {
|
||||
project: 'tsconfig.json',
|
||||
sourceType: 'module',
|
||||
},
|
||||
plugins: ['@typescript-eslint/eslint-plugin', 'simple-import-sort'],
|
||||
extends: [
|
||||
'plugin:@typescript-eslint/eslint-recommended',
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'prettier',
|
||||
'prettier/@typescript-eslint',
|
||||
],
|
||||
root: true,
|
||||
env: {
|
||||
node: true,
|
||||
jest: true,
|
||||
},
|
||||
rules: {
|
||||
'simple-import-sort/imports': 'error',
|
||||
'simple-import-sort/exports': 'error',
|
||||
'@typescript-eslint/interface-name-prefix': 'off',
|
||||
'@typescript-eslint/explicit-function-return-type': 'off',
|
||||
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
||||
'@typescript-eslint/no-inferrable-types': 'off',
|
||||
'@typescript-eslint/no-explicit-any': 'off',
|
||||
'@typescript-eslint/ban-ts-ignore': 'off',
|
||||
'@typescript-eslint/ban-ts-comment': 'off',
|
||||
'@typescript-eslint/no-unused-vars': 'off',
|
||||
'lines-between-class-members': [
|
||||
'error',
|
||||
'always',
|
||||
{ exceptAfterSingleLine: true },
|
||||
],
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"$schema": "./node_modules/oxlint/configuration_schema.json",
|
||||
"plugins": ["typescript"],
|
||||
"env": {
|
||||
"builtin": true
|
||||
},
|
||||
"rules": {
|
||||
"@typescript-eslint/explicit-function-return-type": "off",
|
||||
"@typescript-eslint/explicit-module-boundary-types": "off",
|
||||
"@typescript-eslint/no-inferrable-types": "off",
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"@typescript-eslint/ban-ts-comment": "off",
|
||||
"no-unused-vars": "off"
|
||||
}
|
||||
}
|
||||
+5
-11
@@ -17,18 +17,12 @@ repos:
|
||||
language: python
|
||||
stages: [commit-msg]
|
||||
|
||||
- repo: local
|
||||
hooks:
|
||||
- id: lint
|
||||
stages: [pre-commit]
|
||||
name: lint
|
||||
- id: oxlint
|
||||
name: oxlint
|
||||
entry: npx oxlint --fix --deny-warnings
|
||||
language: system
|
||||
entry: bash -c '$HOME/.nvm/nvm-exec npm run lint-cache'
|
||||
pass_filenames: false
|
||||
files: |
|
||||
(?x)^(
|
||||
src/.*|
|
||||
)$
|
||||
pass_filenames: true
|
||||
files: \.(ts|tsx|js|jsx)$
|
||||
|
||||
- repo: https://github.com/pre-commit/mirrors-prettier
|
||||
rev: 'v3.1.0'
|
||||
|
||||
+7
-8
@@ -14,8 +14,8 @@
|
||||
"start:debug": "nest start --debug --watch",
|
||||
"start:prod": "node dist/main",
|
||||
"start:prod-exit": "node dist/main",
|
||||
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix --max-warnings=0",
|
||||
"lint-cache": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix --cache --max-warnings=0",
|
||||
"lint": "oxlint --deny-warnings",
|
||||
"lint-fix": "oxlint --fix --deny-warnings",
|
||||
"test": "jest",
|
||||
"test:watch": "jest --watch",
|
||||
"test:cov": "jest --coverage",
|
||||
@@ -51,6 +51,7 @@
|
||||
"@types/lodash": "^4.14.194",
|
||||
"@types/mustache": "^4.2.5",
|
||||
"@types/passport": "^1.0.17",
|
||||
"@types/sqlite3": "^5.1.0",
|
||||
"@types/ws": "^8.5.4",
|
||||
"adm-zip": "0.5.10",
|
||||
"agentkeepalive": "^4.5.0",
|
||||
@@ -96,6 +97,7 @@
|
||||
"rimraf": "^4.3.0",
|
||||
"rxjs": "^7.8.1",
|
||||
"sharp": "^0.33.4",
|
||||
"sqlite3": "^5.1.7",
|
||||
"swagger-ui-express": "^4.1.4",
|
||||
"ulid": "^2.3.0",
|
||||
"whatsapp-web.js": "github:devlikeapro/whatsapp-web.js#fork-main-2025-06-09",
|
||||
@@ -122,15 +124,12 @@
|
||||
"@types/express": "^4.17.3",
|
||||
"@types/jest": "26.0.10",
|
||||
"@types/node": "^18.14.2",
|
||||
"@types/node-fetch": "^2.6.13",
|
||||
"@types/semver": "^7.7.0",
|
||||
"@types/supertest": "^2.0.8",
|
||||
"@typescript-eslint/eslint-plugin": "3.9.1",
|
||||
"@typescript-eslint/parser": "3.9.1",
|
||||
"eslint": "7.7.0",
|
||||
"eslint-config-prettier": "^6.10.0",
|
||||
"eslint-plugin-import": "^2.20.1",
|
||||
"eslint-plugin-simple-import-sort": "^10.0.0",
|
||||
"grpc-tools": "^1.13.0",
|
||||
"jest": "^29.7.0",
|
||||
"oxlint": "^1.13.0",
|
||||
"prettier": "^1.19.1",
|
||||
"protoc-gen-ts": "^0.8.7",
|
||||
"supertest": "^4.0.2",
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,4 +1,5 @@
|
||||
# Arabic translations for ChatWoot integration
|
||||
LOCALE_NAME: '🇦🇪 العربية (Arabic)'
|
||||
APP_INBOX_CONTACT_NAME: 'تكامل واتساب (WAHA)'
|
||||
|
||||
JOB_ERROR_REPORT: |
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
# Bengali translations for ChatWoot integration
|
||||
LOCALE_NAME: '🇧🇩 বাংলা (Bengali)'
|
||||
APP_INBOX_CONTACT_NAME: 'হোয়াটসঅ্যাপ ইন্টিগ্রেশন (WAHA)'
|
||||
|
||||
JOB_ERROR_REPORT: |
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
# German translations for ChatWoot integration
|
||||
LOCALE_NAME: '🇩🇪 Deutsch (German)'
|
||||
APP_INBOX_CONTACT_NAME: 'WhatsApp Integration (WAHA)'
|
||||
|
||||
JOB_ERROR_REPORT: |
|
||||
|
||||
@@ -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,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,4 +1,5 @@
|
||||
# Persian translations for ChatWoot integration
|
||||
LOCALE_NAME: '🇮🇷 فارسی (Persian)'
|
||||
APP_INBOX_CONTACT_NAME: 'ادغام واتساپ (WAHA)'
|
||||
|
||||
JOB_ERROR_REPORT: |
|
||||
|
||||
@@ -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,4 +1,5 @@
|
||||
# Hebrew translations for ChatWoot integration
|
||||
LOCALE_NAME: '🇮🇱 עברית (Hebrew)'
|
||||
APP_INBOX_CONTACT_NAME: 'אינטגרציית וואטסאפ (WAHA)'
|
||||
|
||||
JOB_ERROR_REPORT: |
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
# Hindi translations for ChatWoot integration
|
||||
LOCALE_NAME: '🇮🇳 हिंदी (Hindi)'
|
||||
APP_INBOX_CONTACT_NAME: 'व्हाट्सएप इंटीग्रेशन (WAHA)'
|
||||
|
||||
JOB_ERROR_REPORT: |
|
||||
|
||||
@@ -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,4 +1,5 @@
|
||||
# Punjabi translations for ChatWoot integration
|
||||
LOCALE_NAME: '🇵🇰 ਪੰਜਾਬੀ (Punjabi)'
|
||||
APP_INBOX_CONTACT_NAME: 'ਵਟਸਐਪ ਇੰਟੀਗ੍ਰੇਸ਼ਨ (WAHA)'
|
||||
|
||||
JOB_ERROR_REPORT: |
|
||||
|
||||
@@ -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,4 +1,5 @@
|
||||
# Russian translations for ChatWoot integration
|
||||
LOCALE_NAME: '🇷🇺 Русский (Russian)'
|
||||
APP_INBOX_CONTACT_NAME: 'WhatsApp Интеграция (WAHA)'
|
||||
|
||||
JOB_ERROR_REPORT: |
|
||||
|
||||
@@ -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,4 +1,5 @@
|
||||
# Ukrainian translations for ChatWoot integration
|
||||
LOCALE_NAME: '🇺🇦 Українська (Ukrainian)'
|
||||
APP_INBOX_CONTACT_NAME: 'Інтеграція WhatsApp (WAHA)'
|
||||
|
||||
JOB_ERROR_REPORT: |
|
||||
|
||||
@@ -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}}})
|
||||
@@ -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
@@ -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);
|
||||
|
||||
@@ -20,7 +20,7 @@ export function MarkdownToWhatsApp(text: string): string {
|
||||
// Links: [text](url) → text (url)
|
||||
.replace(/\[([^\]]+)\]\((https?:\/\/[^\s)]+)\)/g, '$1 ($2)')
|
||||
// Lists: -, +, * → * item
|
||||
.replace(/^[\-\+\*] (.*)/gm, '* $1')
|
||||
.replace(/^[-+*] (.*)/gm, '* $1')
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Injectable, Logger, OnApplicationBootstrap } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { GlobalWebhookConfigConfig } from '@waha/core/config/GlobalWebhookConfig';
|
||||
import { IgnoreJidConfig } from '@waha/core/utils/jids';
|
||||
|
||||
import { parseBool } from './helpers';
|
||||
import { WebhookConfig } from './structures/webhooks.config.dto';
|
||||
@@ -178,6 +179,23 @@ export class WhatsappConfigService implements OnApplicationBootstrap {
|
||||
return parseBool(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Global default "ignore settings" for chats.
|
||||
* If not defined, defaults to false (do not ignore anything).
|
||||
*/
|
||||
getIgnoreChatsConfig(): IgnoreJidConfig {
|
||||
const status = parseBool(
|
||||
this.configService.get('WAHA_SESSION_CONFIG_IGNORE_STATUS', 'false'),
|
||||
);
|
||||
const groups = parseBool(
|
||||
this.configService.get('WAHA_SESSION_CONFIG_IGNORE_GROUPS', 'false'),
|
||||
);
|
||||
const channels = parseBool(
|
||||
this.configService.get('WAHA_SESSION_CONFIG_IGNORE_CHANNELS', 'false'),
|
||||
);
|
||||
return { status, groups, channels };
|
||||
}
|
||||
|
||||
onApplicationBootstrap() {
|
||||
const error = this.webhookConfig.validateConfig();
|
||||
if (error) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -14,9 +14,11 @@ import { GowsEngineConfigService } from '@waha/core/config/GowsEngineConfigServi
|
||||
import { GowsBootstrap } from '@waha/core/engines/gows/GowsBootstrap';
|
||||
import { ISessionMeRepository } from '@waha/core/storage/ISessionMeRepository';
|
||||
import { ISessionWorkerRepository } from '@waha/core/storage/ISessionWorkerRepository';
|
||||
import { IgnoreJidConfig } from '@waha/core/utils/jids';
|
||||
import { WAHAWebhook } from '@waha/structures/webhooks.dto';
|
||||
import { waitUntil } from '@waha/utils/promiseTimeout';
|
||||
import { VERSION } from '@waha/version';
|
||||
import * as lodash from 'lodash';
|
||||
import { PinoLogger } from 'nestjs-pino';
|
||||
import { merge, Observable, of } from 'rxjs';
|
||||
|
||||
@@ -235,6 +237,12 @@ export abstract class SessionManager
|
||||
}
|
||||
return new NoopEngineBootstrap();
|
||||
}
|
||||
|
||||
protected ignoreChatsConfig(config: SessionConfig) {
|
||||
const ignore: IgnoreJidConfig = this.config.getIgnoreChatsConfig();
|
||||
// Given the default, overwrite from the config if any
|
||||
return lodash.defaults({}, config?.ignore, ignore);
|
||||
}
|
||||
}
|
||||
|
||||
export function populateSessionInfo(
|
||||
|
||||
@@ -4,7 +4,11 @@ import {
|
||||
IMediaConverter,
|
||||
} from '@waha/core/media/IConverter';
|
||||
import { MessagesForRead } from '@waha/core/utils/convertors';
|
||||
import { isJidNewsletter } from '@waha/core/utils/jids';
|
||||
import {
|
||||
IgnoreJidConfig,
|
||||
isJidNewsletter,
|
||||
JidFilter,
|
||||
} from '@waha/core/utils/jids';
|
||||
import {
|
||||
Channel,
|
||||
ChannelListResult,
|
||||
@@ -25,6 +29,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 +74,7 @@ import {
|
||||
MessageLinkPreviewRequest,
|
||||
MessageLocationRequest,
|
||||
MessagePollRequest,
|
||||
MessagePollVoteRequest,
|
||||
MessageReactionRequest,
|
||||
MessageReplyRequest,
|
||||
MessageStarRequest,
|
||||
@@ -88,10 +94,7 @@ import {
|
||||
WAHAPresenceStatus,
|
||||
WAHASessionStatus,
|
||||
} from '../../structures/enums.dto';
|
||||
import {
|
||||
EventCancelRequest,
|
||||
EventMessageRequest,
|
||||
} from '../../structures/events.dto';
|
||||
import { EventMessageRequest } from '../../structures/events.dto';
|
||||
import {
|
||||
CreateGroupRequest,
|
||||
GroupField,
|
||||
@@ -151,8 +154,11 @@ export interface SessionParams {
|
||||
loggerBuilder: LoggerBuilder;
|
||||
sessionStore: DataStore;
|
||||
proxyConfig?: ProxyConfig;
|
||||
// Raw unchanged SessionConfig
|
||||
sessionConfig?: SessionConfig;
|
||||
engineConfig?: any;
|
||||
// Ignore settings
|
||||
ignore: IgnoreJidConfig;
|
||||
}
|
||||
|
||||
export abstract class WhatsappSession {
|
||||
@@ -167,6 +173,7 @@ export abstract class WhatsappSession {
|
||||
public sessionConfig?: SessionConfig;
|
||||
protected engineConfig?: any;
|
||||
protected unpairing: boolean = false;
|
||||
protected jids: JidFilter;
|
||||
|
||||
private _status: WAHASessionStatus;
|
||||
private shouldPrintQR: boolean;
|
||||
@@ -193,6 +200,7 @@ export abstract class WhatsappSession {
|
||||
mediaManager,
|
||||
sessionConfig,
|
||||
engineConfig,
|
||||
ignore,
|
||||
}: SessionParams) {
|
||||
this.status$ = new BehaviorSubject(null);
|
||||
|
||||
@@ -258,6 +266,11 @@ export abstract class WhatsappSession {
|
||||
this.sessionConfig = sessionConfig;
|
||||
this.engineConfig = engineConfig;
|
||||
this.shouldPrintQR = printQR;
|
||||
this.logger.info(
|
||||
{ ignore: ignore },
|
||||
'The session ignores the following chat ids',
|
||||
);
|
||||
this.jids = new JidFilter(ignore);
|
||||
}
|
||||
|
||||
public getEventObservable(event: WAHAEvents) {
|
||||
@@ -451,6 +464,10 @@ export abstract class WhatsappSession {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
sendPollVote(request: MessagePollVoteRequest) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
abstract sendLocation(request: MessageLocationRequest);
|
||||
|
||||
sendLinkPreview(request: MessageLinkPreviewRequest) {
|
||||
@@ -479,6 +496,10 @@ export abstract class WhatsappSession {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
sendList(request: SendListRequest): Promise<any> {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
sendButtonsReply(request: MessageButtonReply) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
@@ -32,9 +32,7 @@ export class HashAuth implements IApiKeyAuth {
|
||||
constructor(
|
||||
private hash: string,
|
||||
private algorithm: string,
|
||||
) {
|
||||
this.algorithm = algorithm;
|
||||
}
|
||||
) {}
|
||||
|
||||
isValid(plain: string): boolean {
|
||||
if (!plain) {
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -48,6 +48,17 @@ function deserialize_messages_ChatPresenceRequest(buffer_arg) {
|
||||
return gows_pb.ChatPresenceRequest.deserializeBinary(new Uint8Array(buffer_arg));
|
||||
}
|
||||
|
||||
function serialize_messages_ChatUnreadRequest(arg) {
|
||||
if (!(arg instanceof gows_pb.ChatUnreadRequest)) {
|
||||
throw new Error('Expected argument of type messages.ChatUnreadRequest');
|
||||
}
|
||||
return Buffer.from(arg.serializeBinary());
|
||||
}
|
||||
|
||||
function deserialize_messages_ChatUnreadRequest(buffer_arg) {
|
||||
return gows_pb.ChatUnreadRequest.deserializeBinary(new Uint8Array(buffer_arg));
|
||||
}
|
||||
|
||||
function serialize_messages_CheckPhonesRequest(arg) {
|
||||
if (!(arg instanceof gows_pb.CheckPhonesRequest)) {
|
||||
throw new Error('Expected argument of type messages.CheckPhonesRequest');
|
||||
@@ -1053,6 +1064,17 @@ getProfilePicture: {
|
||||
responseSerialize: serialize_messages_CheckPhonesResponse,
|
||||
responseDeserialize: deserialize_messages_CheckPhonesResponse,
|
||||
},
|
||||
markChatUnread: {
|
||||
path: '/messages.MessageService/MarkChatUnread',
|
||||
requestStream: false,
|
||||
responseStream: false,
|
||||
requestType: gows_pb.ChatUnreadRequest,
|
||||
responseType: gows_pb.Empty,
|
||||
requestSerialize: serialize_messages_ChatUnreadRequest,
|
||||
requestDeserialize: deserialize_messages_ChatUnreadRequest,
|
||||
responseSerialize: serialize_messages_Empty,
|
||||
responseDeserialize: deserialize_messages_Empty,
|
||||
},
|
||||
//
|
||||
// Message
|
||||
//
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -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',
|
||||
@@ -251,6 +254,11 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
|
||||
proxy: new messages.SessionProxyConfig({
|
||||
url: this.getProxyUrl(this.proxyConfig),
|
||||
}),
|
||||
ignore: new messages.SessionIgnoreJidsConfig({
|
||||
status: this.jids.ignore.status,
|
||||
groups: this.jids.ignore.groups,
|
||||
newsletters: this.jids.ignore.channels,
|
||||
}),
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -307,6 +315,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;
|
||||
@@ -387,7 +396,11 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
|
||||
const all$ = this.all$;
|
||||
this.events2.get(WAHAEvents.ENGINE_EVENT).switch(all$);
|
||||
|
||||
const messages$ = all$.pipe(onlyEvent(WhatsMeowEvent.MESSAGE));
|
||||
const messages$ = all$.pipe(
|
||||
onlyEvent(WhatsMeowEvent.MESSAGE),
|
||||
filter((msg: any) => this.jids.include(msg?.Info?.Chat)),
|
||||
share(),
|
||||
);
|
||||
|
||||
let [messagesFromMe$, messagesFromOthers$] = partition(messages$, isMine);
|
||||
messagesFromMe$ = messagesFromMe$.pipe(
|
||||
@@ -444,7 +457,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 {
|
||||
@@ -457,7 +470,10 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
|
||||
);
|
||||
this.events2.get(WAHAEvents.MESSAGE_EDITED).switch(messagesEdited$);
|
||||
|
||||
const receipt$ = all$.pipe(onlyEvent(WhatsMeowEvent.RECEIPT));
|
||||
const receipt$ = all$.pipe(
|
||||
onlyEvent(WhatsMeowEvent.RECEIPT),
|
||||
filter((r: any) => this.jids.include(r?.Chat)),
|
||||
);
|
||||
const messageAck$ = receipt$.pipe(
|
||||
mergeMap(this.receiptToMessageAck.bind(this)),
|
||||
DistinctAck(),
|
||||
@@ -472,9 +488,13 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
|
||||
|
||||
const presence$ = all$.pipe(
|
||||
onlyEvent(WhatsMeowEvent.PRESENCE),
|
||||
filter((event: any) => this.jids.include(event?.From)),
|
||||
filter((event) => !isJidGroup(event.From)),
|
||||
);
|
||||
const chatPresence$ = all$.pipe(onlyEvent(WhatsMeowEvent.CHAT_PRESENCE));
|
||||
const chatPresence$ = all$.pipe(
|
||||
onlyEvent(WhatsMeowEvent.CHAT_PRESENCE),
|
||||
filter((event: any) => this.jids.include(event?.Chat)),
|
||||
);
|
||||
const presenceUpdates$ = merge(presence$, chatPresence$).pipe(
|
||||
map((event) => this.toWahaPresences(event.From || event.Chat, [event])),
|
||||
);
|
||||
@@ -534,8 +554,10 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
|
||||
//
|
||||
const pollVoteEvent$ = all$.pipe(
|
||||
onlyEvent(WhatsMeowEvent.POLL_VOTE_EVENT),
|
||||
filter((event: any) => this.jids.include(event?.Info?.Chat)),
|
||||
map(this.toPollVotePayload.bind(this)),
|
||||
filter(Boolean),
|
||||
share(),
|
||||
);
|
||||
|
||||
// Split into successful and failed responses
|
||||
@@ -552,6 +574,7 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
|
||||
//
|
||||
const eventMessageResponse$ = all$.pipe(
|
||||
onlyEvent(WhatsMeowEvent.EVENT_MESSAGE_RESPONSE),
|
||||
filter((event: any) => this.jids.include(event?.Info?.Chat)),
|
||||
map(this.toEventResponsePayload.bind(this)),
|
||||
filter(Boolean),
|
||||
);
|
||||
@@ -792,6 +815,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 +912,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> {
|
||||
@@ -1815,6 +1859,17 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
|
||||
return labels.map(this.toLabel);
|
||||
}
|
||||
|
||||
public async chatsUnreadChat(chatId: string): Promise<any> {
|
||||
const jid = toJID(this.ensureSuffix(chatId));
|
||||
const request = new messages.ChatUnreadRequest({
|
||||
session: this.session,
|
||||
jid: jid,
|
||||
read: false,
|
||||
});
|
||||
await promisify(this.client.MarkChatUnread)(request);
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
public async putLabelsToChat(chatId: string, labels: LabelID[]) {
|
||||
const jid = toJID(chatId);
|
||||
const labelsIds = labels.map((label) => label.id);
|
||||
@@ -1844,7 +1899,7 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
|
||||
// END - Methods for API
|
||||
//
|
||||
|
||||
protected async processIncomingMessage(message, downloadMedia = true) {
|
||||
protected shouldProcessIncomingMessage(message): boolean {
|
||||
// if there is no text or media message
|
||||
if (!message) return;
|
||||
if (!message.Message) return;
|
||||
@@ -1863,27 +1918,48 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
|
||||
// Ignore key distribution messages
|
||||
if (message?.Message?.senderKeyDistributionMessage) return;
|
||||
}
|
||||
|
||||
if (downloadMedia) {
|
||||
try {
|
||||
message = await this.downloadMedia(message);
|
||||
} catch (e) {
|
||||
this.logger.error('Failed when tried to download media for a message');
|
||||
this.logger.error(e, e.stack);
|
||||
}
|
||||
}
|
||||
return this.toWAMessage(message);
|
||||
return true;
|
||||
}
|
||||
|
||||
protected downloadMedia(message) {
|
||||
protected async processIncomingMessage(message, downloadMedia = true) {
|
||||
// Filter
|
||||
if (!this.shouldProcessIncomingMessage(message)) {
|
||||
return null;
|
||||
}
|
||||
// Convert
|
||||
const wamessage = this.toWAMessage(message);
|
||||
// Media
|
||||
if (downloadMedia) {
|
||||
const media = await this.downloadMediaSafe(message);
|
||||
wamessage.media = media;
|
||||
}
|
||||
return wamessage;
|
||||
}
|
||||
|
||||
protected async downloadMediaSafe(message) {
|
||||
try {
|
||||
return await this.downloadMedia(message);
|
||||
} catch (e) {
|
||||
this.logger.error('Failed when tried to download media for a message');
|
||||
this.logger.error(e, e.stack);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
protected async downloadMedia(message) {
|
||||
const processor = new GOWSEngineMediaProcessor(this);
|
||||
return this.mediaManager.processMedia(processor, message, this.name);
|
||||
const media = await this.mediaManager.processMedia(
|
||||
processor,
|
||||
message,
|
||||
this.name,
|
||||
);
|
||||
return media;
|
||||
}
|
||||
|
||||
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) {
|
||||
@@ -1905,7 +1981,7 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
|
||||
participant: toCusFormat(fromToParticipant.participant),
|
||||
// Media
|
||||
hasMedia: Boolean(mediaContent),
|
||||
media: message.media || null,
|
||||
media: null,
|
||||
mediaUrl: message.media?.url,
|
||||
// @ts-ignore
|
||||
ack: ack,
|
||||
@@ -1919,12 +1995,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 +2067,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 +2077,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 +2259,7 @@ function getFromToParticipant(message) {
|
||||
from: info.Chat,
|
||||
to: info.IsGroup ? info.Sender : null,
|
||||
participant: info.IsGroup ? info.Sender : null,
|
||||
fromMe: info.IsFromMe,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -2243,3 +2312,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;
|
||||
}
|
||||
@@ -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,
|
||||
@@ -148,6 +148,7 @@ import {
|
||||
LabelID,
|
||||
} from '@waha/structures/labels.dto';
|
||||
import { LidToPhoneNumber } from '@waha/structures/lids.dto';
|
||||
import { WAMedia } from '@waha/structures/media.dto';
|
||||
import { ReplyToMessage } from '@waha/structures/message.dto';
|
||||
import { PaginationParams } from '@waha/structures/pagination.dto';
|
||||
import {
|
||||
@@ -384,6 +385,7 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
|
||||
this.store = new NowebPersistentStore(
|
||||
this.loggerBuilder.child({ name: NowebPersistentStore.name }),
|
||||
storage,
|
||||
this.jids,
|
||||
);
|
||||
await this.store.init();
|
||||
}
|
||||
@@ -735,6 +737,7 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
|
||||
return {
|
||||
id: toCusFormat(meId),
|
||||
pushName: me.name,
|
||||
lid: me.lid,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -952,15 +955,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 +1777,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);
|
||||
@@ -1835,6 +1847,7 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
|
||||
const messagesUpsert$ = fromEvent(this.sock.ev, 'messages.upsert').pipe(
|
||||
map((event: BaileysEventMap['messages.upsert']) => event.messages),
|
||||
mergeAll(),
|
||||
filter((msg) => this.jids.include(msg.key.remoteJid)),
|
||||
share(),
|
||||
);
|
||||
let [messagesFromMe$, messagesFromOthers$] = partition(
|
||||
@@ -1845,6 +1858,9 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
|
||||
mergeMap((msg) => this.processIncomingMessage(msg, true)),
|
||||
share(), // share it so we don't process twice in message.any
|
||||
);
|
||||
messagesFromMe$ = messagesFromMe$.pipe(
|
||||
filter((msg) => this.shouldProcessIncomingMessage(msg)),
|
||||
);
|
||||
messagesFromOthers$ = messagesFromOthers$.pipe(
|
||||
mergeMap((msg) => this.processIncomingMessage(msg, true)),
|
||||
share(), // share it so we don't process twice in message.any
|
||||
@@ -1861,7 +1877,7 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
|
||||
proto.Message.ProtocolMessage.Type.REVOKE,
|
||||
),
|
||||
mergeMap(async (message): Promise<WAMessageRevokedBody> => {
|
||||
const afterMessage = await this.toWAMessage(message);
|
||||
const afterMessage = this.toWAMessage(message);
|
||||
// Extract the revoked message ID from protocolMessage.key
|
||||
const revokedMessageId = message.message.protocolMessage.key?.id;
|
||||
return {
|
||||
@@ -1885,7 +1901,7 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
|
||||
message.message?.protocolMessage?.editedMessage !== undefined,
|
||||
),
|
||||
mergeMap(async (message): Promise<WAMessageEditedBody> => {
|
||||
const waMessage = await this.toWAMessage(message);
|
||||
const waMessage = this.toWAMessage(message);
|
||||
// Extract the body from editedMessage using extractBody function
|
||||
const body =
|
||||
extractBody(message.message.protocolMessage.editedMessage) || '';
|
||||
@@ -1919,6 +1935,7 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
|
||||
).pipe(
|
||||
// @ts-ignore
|
||||
mergeAll(),
|
||||
filter((update) => this.jids.include(update.key.remoteJid)),
|
||||
share(),
|
||||
);
|
||||
const messageAckDirect$ = messageUpdates$.pipe(
|
||||
@@ -1930,6 +1947,7 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
|
||||
fromEvent(this.sock.ev, 'message-receipt.update').pipe(
|
||||
// @ts-ignore
|
||||
mergeAll(),
|
||||
filter((update) => this.jids.include(update.key.remoteJid)),
|
||||
share(),
|
||||
);
|
||||
|
||||
@@ -1997,6 +2015,7 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
|
||||
|
||||
this.events2.get(WAHAEvents.PRESENCE_UPDATE).switch(
|
||||
fromEvent(this.sock.ev, 'presence.update').pipe(
|
||||
filter((presence: any) => this.jids.include(presence.id)),
|
||||
map((data: any) => this.toWahaPresences(data.id, data.presences)),
|
||||
share(),
|
||||
),
|
||||
@@ -2027,7 +2046,13 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
|
||||
//
|
||||
// @ts-ignore
|
||||
const calls$: Observable<WACallEvent[]> = fromEvent(this.sock.ev, 'call');
|
||||
const call$ = calls$.pipe(mergeMap(identity), share());
|
||||
const call$ = calls$.pipe(
|
||||
mergeMap(identity),
|
||||
filter((call: WACallEvent) =>
|
||||
this.jids.include(call.groupJid || call.chatId),
|
||||
),
|
||||
share(),
|
||||
);
|
||||
this.events2.get(WAHAEvents.CALL_RECEIVED).switch(
|
||||
call$.pipe(
|
||||
filter((call: WACallEvent) => call.status === 'offer'),
|
||||
@@ -2160,7 +2185,7 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
|
||||
return reaction;
|
||||
}
|
||||
|
||||
protected async processIncomingMessage(message, downloadMedia = true) {
|
||||
shouldProcessIncomingMessage(message): boolean {
|
||||
// if there is no text or media message
|
||||
if (!message) return;
|
||||
if (!message.message) return;
|
||||
@@ -2194,27 +2219,41 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
|
||||
// Ignore key distribution messages
|
||||
if (message?.message?.senderKeyDistributionMessage) return;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (downloadMedia) {
|
||||
try {
|
||||
message = await this.downloadMedia(message);
|
||||
} catch (e) {
|
||||
this.logger.error('Failed when tried to download media for a message');
|
||||
this.logger.error(e, e.stack);
|
||||
}
|
||||
protected async processIncomingMessage(
|
||||
message,
|
||||
downloadMedia: boolean,
|
||||
): Promise<WAMessage | null> {
|
||||
// Filter
|
||||
if (!this.shouldProcessIncomingMessage(message)) {
|
||||
return null;
|
||||
}
|
||||
// Convert
|
||||
const wamessage = this.toWAMessageSafe(message);
|
||||
if (!wamessage) {
|
||||
return null;
|
||||
}
|
||||
// Media
|
||||
if (downloadMedia) {
|
||||
const media = await this.downloadMediaSafe(message);
|
||||
wamessage.media = media;
|
||||
}
|
||||
return wamessage;
|
||||
}
|
||||
|
||||
protected toWAMessageSafe(message): WAMessage | null {
|
||||
try {
|
||||
return await this.toWAMessage(message);
|
||||
return this.toWAMessage(message);
|
||||
} catch (error) {
|
||||
this.logger.error('Failed to process incoming message');
|
||||
this.logger.error(error);
|
||||
console.trace(error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
protected toWAMessage(message): Promise<WAMessage> {
|
||||
protected toWAMessage(message): WAMessage {
|
||||
const fromToParticipant = getFromToParticipant(message.key);
|
||||
const id = buildMessageId(message.key);
|
||||
const body = extractBody(message.message);
|
||||
@@ -2222,7 +2261,7 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
|
||||
const ack = message.ack || StatusToAck(message.status);
|
||||
const mediaContent = extractMediaContent(message.message);
|
||||
const source = this.getMessageSource(message.key.id);
|
||||
return Promise.resolve({
|
||||
return {
|
||||
id: id,
|
||||
timestamp: ensureNumber(message.messageTimestamp),
|
||||
from: toCusFormat(fromToParticipant.from),
|
||||
@@ -2233,7 +2272,7 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
|
||||
participant: toCusFormat(fromToParticipant.participant),
|
||||
// Media
|
||||
hasMedia: Boolean(mediaContent),
|
||||
media: message.media || null,
|
||||
media: null,
|
||||
mediaUrl: message.media?.url,
|
||||
// @ts-ignore
|
||||
ack: ack,
|
||||
@@ -2243,7 +2282,7 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
|
||||
vCards: message.vCards,
|
||||
replyTo: replyTo,
|
||||
_data: message,
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
protected extractReplyTo(message): ReplyToMessage | null {
|
||||
@@ -2435,7 +2474,17 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
|
||||
return { id: chatId, presences: presences };
|
||||
}
|
||||
|
||||
protected downloadMedia(message) {
|
||||
protected async downloadMediaSafe(message): Promise<WAMedia | null> {
|
||||
try {
|
||||
return await this.downloadMedia(message);
|
||||
} catch (e) {
|
||||
this.logger.error('Failed when tried to download media for a message');
|
||||
this.logger.error(e, e.stack);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
protected async downloadMedia(message): Promise<WAMedia | null> {
|
||||
const processor = new NOWEBEngineMediaProcessor(this, this.loggerBuilder);
|
||||
return this.mediaManager.processMedia(processor, message, this.name);
|
||||
}
|
||||
@@ -2699,5 +2748,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;
|
||||
}
|
||||
@@ -14,6 +14,7 @@ import makeWASocket, {
|
||||
updateMessageWithReceipt,
|
||||
WAMessage,
|
||||
} from '@adiwajshing/baileys';
|
||||
import { WACallEvent } from '@adiwajshing/baileys/lib/Types/Call';
|
||||
import { GroupMetadata } from '@adiwajshing/baileys/lib/Types/GroupMetadata';
|
||||
import { Label } from '@adiwajshing/baileys/lib/Types/Label';
|
||||
import {
|
||||
@@ -24,6 +25,7 @@ import { isLidUser } from '@adiwajshing/baileys/lib/WABinary/jid-utils';
|
||||
import { IGroupRepository } from '@waha/core/engines/noweb/store/IGroupRepository';
|
||||
import { ILabelAssociationRepository } from '@waha/core/engines/noweb/store/ILabelAssociationsRepository';
|
||||
import { ILabelsRepository } from '@waha/core/engines/noweb/store/ILabelsRepository';
|
||||
import { JidFilter } from '@waha/core/utils/jids';
|
||||
import {
|
||||
GetChatMessagesFilter,
|
||||
OverviewFilter,
|
||||
@@ -39,6 +41,7 @@ import { waitUntil } from '@waha/utils/promiseTimeout';
|
||||
import * as lodash from 'lodash';
|
||||
import { toNumber } from 'lodash';
|
||||
import { Logger } from 'pino';
|
||||
import { filter } from 'rxjs';
|
||||
|
||||
import { IChatRepository } from './IChatRepository';
|
||||
import { IContactRepository } from './IContactRepository';
|
||||
@@ -82,6 +85,7 @@ export class NowebPersistentStore implements INowebStore {
|
||||
constructor(
|
||||
private logger: Logger,
|
||||
public storage: INowebStorage,
|
||||
private jids: JidFilter,
|
||||
) {
|
||||
this.socket = null;
|
||||
this.chatRepo = storage.getChatRepository();
|
||||
@@ -247,6 +251,7 @@ export class NowebPersistentStore implements INowebStore {
|
||||
|
||||
private async syncMessagesHistory(messages) {
|
||||
const realMessages = messages.filter(isRealMessage);
|
||||
messages = messages.filter((msg) => this.jids.include(msg.key.remoteJid));
|
||||
await this.messagesRepo.upsert(realMessages);
|
||||
this.logger.info(
|
||||
`history sync - '${messages.length}' got messages, '${realMessages.length}' real messages`,
|
||||
@@ -254,11 +259,13 @@ export class NowebPersistentStore implements INowebStore {
|
||||
}
|
||||
|
||||
private async onMessagesUpsert(update) {
|
||||
const { messages, type } = update;
|
||||
const type = update.type;
|
||||
if (type !== 'notify' && type !== 'append') {
|
||||
this.logger.debug(`unexpected type for messages.upsert: '${type}'`);
|
||||
return;
|
||||
}
|
||||
let messages = update.messages;
|
||||
messages = messages.filter((msg) => this.jids.include(msg.key.remoteJid));
|
||||
const realMessages = messages.filter(isRealMessage);
|
||||
await this.messagesRepo.upsert(realMessages);
|
||||
this.logger.debug(
|
||||
@@ -270,6 +277,9 @@ export class NowebPersistentStore implements INowebStore {
|
||||
for (const update of updates) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const jid = jidNormalizedUser(update.key.remoteJid!);
|
||||
if (!this.jids.include(jid)) {
|
||||
continue;
|
||||
}
|
||||
if (!update.key.id) {
|
||||
continue;
|
||||
}
|
||||
@@ -333,12 +343,16 @@ export class NowebPersistentStore implements INowebStore {
|
||||
delete chat['messages'];
|
||||
chat.conversationTimestamp = toNumber(chat.conversationTimestamp) || null;
|
||||
}
|
||||
chats = chats.filter((chat) => this.jids.include(chat.id));
|
||||
await this.chatRepo.upsertMany(chats);
|
||||
this.logger.info(`store sync - '${chats.length}' synced chats`);
|
||||
}
|
||||
|
||||
private async onGroupUpsert(groups: GroupMetadata[]) {
|
||||
for (const group of groups) {
|
||||
if (!this.jids.include(group.id)) {
|
||||
continue;
|
||||
}
|
||||
await this.groupRepo.save(group);
|
||||
}
|
||||
this.logger.info(`store sync - '${groups.length}' synced groups`);
|
||||
@@ -346,6 +360,9 @@ export class NowebPersistentStore implements INowebStore {
|
||||
|
||||
private async onGroupUpdate(groups: Partial<GroupMetadata>[]) {
|
||||
for (const update of groups) {
|
||||
if (!this.jids.include(update.id)) {
|
||||
continue;
|
||||
}
|
||||
let group = await this.groupRepo.getById(update.id);
|
||||
group = Object.assign(group || {}, update) as GroupMetadata;
|
||||
await this.groupRepo.save(group);
|
||||
@@ -356,6 +373,9 @@ export class NowebPersistentStore implements INowebStore {
|
||||
|
||||
private async onGroupParticipantsUpdate(data) {
|
||||
const id: string = data.id;
|
||||
if (!this.jids.include(id)) {
|
||||
return;
|
||||
}
|
||||
const participants: string[] = data.participants;
|
||||
const action: ParticipantAction = data.action;
|
||||
|
||||
@@ -419,6 +439,9 @@ export class NowebPersistentStore implements INowebStore {
|
||||
|
||||
private async onChatUpdate(updates: ChatUpdate[]) {
|
||||
for (const update of updates) {
|
||||
if (!this.jids.include(update.id)) {
|
||||
continue;
|
||||
}
|
||||
const chat = (await this.chatRepo.getById(update.id)) || ({} as Chat);
|
||||
Object.assign(chat, update);
|
||||
chat.conversationTimestamp = toNumber(chat.conversationTimestamp) || null;
|
||||
@@ -447,6 +470,9 @@ export class NowebPersistentStore implements INowebStore {
|
||||
const ids = contacts.map((c) => c.id);
|
||||
const contactById = await this.contactRepo.getEntitiesByIds(ids);
|
||||
for (const update of contacts) {
|
||||
if (!this.jids.include(update.id)) {
|
||||
continue;
|
||||
}
|
||||
const contact = contactById.get(update.id) || {};
|
||||
// remove undefined from data
|
||||
Object.keys(update).forEach(
|
||||
@@ -460,6 +486,9 @@ export class NowebPersistentStore implements INowebStore {
|
||||
|
||||
private async onContactUpdate(updates: Partial<Contact>[]) {
|
||||
for (const update of updates) {
|
||||
if (!this.jids.include(update.id)) {
|
||||
continue;
|
||||
}
|
||||
let contact = await this.contactRepo.getById(update.id);
|
||||
|
||||
if (!contact) {
|
||||
@@ -493,6 +522,9 @@ export class NowebPersistentStore implements INowebStore {
|
||||
|
||||
private async onMessageReaction(reactions) {
|
||||
for (const { key, reaction } of reactions) {
|
||||
if (!this.jids.include(key.remoteJid)) {
|
||||
continue;
|
||||
}
|
||||
const msg = await this.messagesRepo.getByJidById(key.remoteJid, key.id);
|
||||
if (!msg) {
|
||||
this.logger.warn(
|
||||
@@ -509,6 +541,9 @@ export class NowebPersistentStore implements INowebStore {
|
||||
|
||||
private async onMessageReceiptUpdate(updates) {
|
||||
for (const { key, receipt } of updates) {
|
||||
if (!this.jids.include(key.remoteJid)) {
|
||||
continue;
|
||||
}
|
||||
const msg = await this.messagesRepo.getByJidById(key.remoteJid, key.id);
|
||||
if (!msg) {
|
||||
this.logger.warn(
|
||||
@@ -544,6 +579,9 @@ export class NowebPersistentStore implements INowebStore {
|
||||
}
|
||||
|
||||
private async onPresenceUpdate({ id, presences: update }) {
|
||||
if (!this.jids.include(id)) {
|
||||
return;
|
||||
}
|
||||
this.presences[id] = this.presences[id] || {};
|
||||
Object.assign(this.presences[id], update);
|
||||
}
|
||||
|
||||
@@ -446,7 +446,7 @@ export default (config: BaileysInMemoryStoreConfig) => {
|
||||
) => {
|
||||
const list = assertMessageList(jid);
|
||||
const mode = !cursor || 'before' in cursor ? 'before' : 'after';
|
||||
const cursorKey = !!cursor
|
||||
const cursorKey = cursor
|
||||
? 'before' in cursor
|
||||
? cursor.before
|
||||
: cursor.after
|
||||
|
||||
@@ -22,7 +22,7 @@ export class Sqlite3Storage extends INowebStorage {
|
||||
constructor(filePath: string) {
|
||||
super();
|
||||
this.knex = Knex({
|
||||
client: 'better-sqlite3',
|
||||
client: 'sqlite3',
|
||||
connection: { filename: filePath },
|
||||
useNullAsDefault: true,
|
||||
});
|
||||
|
||||
@@ -30,7 +30,10 @@ export class WebjsClientCore extends Client {
|
||||
public events = new EventEmitter();
|
||||
private wpage: WPage = null;
|
||||
|
||||
constructor(options) {
|
||||
constructor(
|
||||
options,
|
||||
protected tags: boolean,
|
||||
) {
|
||||
super(options);
|
||||
// Wait until it's READY and inject more utils
|
||||
this.on(Events.READY, async () => {
|
||||
@@ -86,6 +89,32 @@ export class WebjsClientCore extends Client {
|
||||
return;
|
||||
},
|
||||
);
|
||||
if (this.tags) {
|
||||
await this.attachTagsEvents();
|
||||
}
|
||||
}
|
||||
|
||||
async attachTagsEvents() {
|
||||
await this.pupPage.evaluate(() => {
|
||||
// @ts-ignore
|
||||
if (window.decodeStanzaBack) {
|
||||
return;
|
||||
}
|
||||
|
||||
const tags = ['receipt', 'presence', 'chatstate'];
|
||||
// @ts-ignore
|
||||
window.decodeStanzaBack = window.Store.SocketWap.decodeStanza;
|
||||
// @ts-ignore
|
||||
window.Store.SocketWap.decodeStanza = async (...args) => {
|
||||
// @ts-ignore
|
||||
const result = await window.decodeStanzaBack(...args);
|
||||
if (tags.includes(result?.tag)) {
|
||||
// @ts-ignore
|
||||
setTimeout(() => window.onTag(result), 0);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
async destroy() {
|
||||
|
||||
@@ -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[] = [];
|
||||
|
||||
@@ -103,6 +103,7 @@ import {
|
||||
} from '@waha/structures/groups.dto';
|
||||
import { Label, LabelDTO, LabelID } from '@waha/structures/labels.dto';
|
||||
import { LidToPhoneNumber } from '@waha/structures/lids.dto';
|
||||
import { WAMedia } from '@waha/structures/media.dto';
|
||||
import { ReplyToMessage } from '@waha/structures/message.dto';
|
||||
import { PaginationParams, SortOrder } from '@waha/structures/pagination.dto';
|
||||
import {
|
||||
@@ -235,7 +236,14 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
|
||||
rmMaxRetries: undefined,
|
||||
});
|
||||
this.addProxyConfig(clientOptions);
|
||||
return new WebjsClientCore(clientOptions);
|
||||
return new WebjsClientCore(clientOptions, this.getWebjsTagsFlag());
|
||||
}
|
||||
|
||||
protected getWebjsTagsFlag() {
|
||||
// Emit 'tag:*' events only when explicitly enabled in session config.
|
||||
// This flag is required for presence.update and message.ack events.
|
||||
// Disabled by default for performance and stability reasons.
|
||||
return !!this.sessionConfig?.webjs?.tagsEventsOn;
|
||||
}
|
||||
|
||||
private restartClient() {
|
||||
@@ -783,7 +791,7 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
|
||||
chat.id._serialized,
|
||||
false,
|
||||
);
|
||||
const lastMessage = !!chat.lastMessage
|
||||
const lastMessage = chat.lastMessage
|
||||
? this.toWAMessage(chat.lastMessage)
|
||||
: null;
|
||||
return {
|
||||
@@ -1439,6 +1447,7 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
|
||||
//
|
||||
const messageReceived$ = fromEvent(this.whatsapp, Events.MESSAGE_RECEIVED);
|
||||
const messagesFromOthers$ = messageReceived$.pipe(
|
||||
filter((msg: Message) => this.jids.include(msg?.id?.remote)),
|
||||
mergeMap((msg: any) => this.processIncomingMessage(msg, true)),
|
||||
share(),
|
||||
);
|
||||
@@ -1446,6 +1455,7 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
|
||||
|
||||
const messageCreate$ = fromEvent(this.whatsapp, Events.MESSAGE_CREATE);
|
||||
const messagesFromAll$ = messageCreate$.pipe(
|
||||
filter((msg: Message) => this.jids.include(msg?.id?.remote)),
|
||||
mergeMap((msg: any) => this.processIncomingMessage(msg, true)),
|
||||
share(),
|
||||
);
|
||||
@@ -1456,6 +1466,7 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
|
||||
Events.MESSAGE_CIPHERTEXT,
|
||||
);
|
||||
const messagesWaiting$ = messageCiphertext$.pipe(
|
||||
filter((msg: Message) => this.jids.include(msg?.id?.remote)),
|
||||
mergeMap((msg: any) => this.processIncomingMessage(msg, false)),
|
||||
share(),
|
||||
);
|
||||
@@ -1469,6 +1480,9 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
|
||||
},
|
||||
);
|
||||
const messagesRevoked$ = messageRevoked$.pipe(
|
||||
filter((evt: any) =>
|
||||
this.jids.include(evt?.after?.id?.remote || evt?.before?.id?.remote),
|
||||
),
|
||||
map((event): WAMessageRevokedBody => {
|
||||
const afterMessage = event.after ? this.toWAMessage(event.after) : null;
|
||||
const beforeMessage = event.before
|
||||
@@ -1487,6 +1501,7 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
|
||||
|
||||
const messageReaction$ = fromEvent(this.whatsapp, 'message_reaction');
|
||||
const messagesReaction$ = messageReaction$.pipe(
|
||||
filter((reaction: Reaction) => this.jids.include(reaction?.id?.remote)),
|
||||
map(this.processMessageReaction.bind(this)),
|
||||
filter(Boolean),
|
||||
);
|
||||
@@ -1500,6 +1515,7 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
|
||||
},
|
||||
);
|
||||
const messagesEdit$ = messageEdit$.pipe(
|
||||
filter((event: any) => this.jids.include(event?.message?.id?.remote)),
|
||||
map((event): WAMessageEditedBody => {
|
||||
const message = this.toWAMessage(event.message);
|
||||
return {
|
||||
@@ -1523,6 +1539,7 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
|
||||
map((event) => event.message),
|
||||
map<any, WAMessage>(this.toWAMessage.bind(this)),
|
||||
filter((ack) => !isJidGroup(ack.to) && !isJidStatusBroadcast(ack.to)),
|
||||
filter((ack) => this.jids.include(ack.to)),
|
||||
);
|
||||
const tagReceiptNode$ = fromEvent(this.whatsapp, Events.TAG_RECEIPT);
|
||||
const messageAckGroups$ = tagReceiptNode$.pipe(
|
||||
@@ -1532,6 +1549,7 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
|
||||
filter(Boolean),
|
||||
mergeMap(this.TagReceiptToMessageAck.bind(this)),
|
||||
filter((ack) => isJidGroup(ack.to) || isJidStatusBroadcast(ack.to)),
|
||||
filter((ack) => this.jids.include(ack.to)),
|
||||
);
|
||||
|
||||
const messageAckAll$ = merge(messagesAckDM$, messageAckGroups$);
|
||||
@@ -1552,11 +1570,13 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
|
||||
const presences$ = tagPresenceNode$.pipe(
|
||||
map(TagPresenceToPresence),
|
||||
filter(Boolean),
|
||||
filter((presence: any) => this.jids.include(presence.id)),
|
||||
);
|
||||
const tagChatstateNode$ = fromEvent(this.whatsapp, 'tag:chatstate');
|
||||
const chatstatePresences$ = tagChatstateNode$.pipe(
|
||||
map(TagChatstateToPresence),
|
||||
filter(Boolean),
|
||||
filter((presence: any) => this.jids.include(presence.id)),
|
||||
);
|
||||
const presenceUpdate$ = merge(presences$, chatstatePresences$);
|
||||
this.events2.get(WAHAEvents.PRESENCE_UPDATE).switch(presenceUpdate$);
|
||||
@@ -1625,6 +1645,7 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
|
||||
},
|
||||
);
|
||||
const chatsArchived$ = chatArchived$.pipe(
|
||||
filter((event: any) => this.jids.include(event?.chat?.id?._serialized)),
|
||||
map((event) => {
|
||||
return {
|
||||
id: event.chat.id._serialized,
|
||||
@@ -1640,6 +1661,7 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
|
||||
//
|
||||
const call$ = fromEvent(this.whatsapp, 'call');
|
||||
const calls$ = call$.pipe(
|
||||
filter((call: Call) => this.jids.include((call as any)?.from)),
|
||||
map((call: Call) => {
|
||||
return {
|
||||
id: call.id,
|
||||
@@ -1657,15 +1679,14 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
|
||||
message: Message,
|
||||
downloadMedia = true,
|
||||
) {
|
||||
// Convert
|
||||
const wamessage = this.toWAMessage(message);
|
||||
// Media
|
||||
if (downloadMedia) {
|
||||
try {
|
||||
message = await this.downloadMedia(message);
|
||||
} catch (e) {
|
||||
this.logger.error('Failed when tried to download media for a message');
|
||||
this.logger.error(e, e.stack);
|
||||
}
|
||||
const media = await this.downloadMediaSafe(message);
|
||||
wamessage.media = media;
|
||||
}
|
||||
return this.toWAMessage(message);
|
||||
return wamessage;
|
||||
}
|
||||
|
||||
private processMessageReaction(reaction: Reaction): WAMessageReaction {
|
||||
@@ -1737,8 +1758,7 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
|
||||
// Media
|
||||
// @ts-ignore
|
||||
hasMedia: Boolean(message.hasMedia),
|
||||
// @ts-ignore
|
||||
media: message.media || null,
|
||||
media: null,
|
||||
// @ts-ignore
|
||||
mediaUrl: message.media?.url,
|
||||
// @ts-ignore
|
||||
@@ -1781,9 +1801,24 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
|
||||
return contact;
|
||||
}
|
||||
|
||||
protected downloadMedia(message: Message) {
|
||||
protected async downloadMediaSafe(message): Promise<WAMedia | null> {
|
||||
try {
|
||||
return await this.downloadMedia(message);
|
||||
} catch (e) {
|
||||
this.logger.error('Failed when tried to download media for a message');
|
||||
this.logger.error(e, e.stack);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
protected async downloadMedia(message: Message) {
|
||||
const processor = new WEBJSEngineMediaProcessor();
|
||||
return this.mediaManager.processMedia(processor, message, this.name);
|
||||
const media = await this.mediaManager.processMedia(
|
||||
processor,
|
||||
message,
|
||||
this.name,
|
||||
);
|
||||
return media;
|
||||
}
|
||||
|
||||
protected getMessageOptions(request: any): any {
|
||||
|
||||
@@ -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}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -200,6 +200,7 @@ export class SessionManagerCore extends SessionManager implements OnModuleInit {
|
||||
sessionStore: this.store,
|
||||
proxyConfig: proxyConfig,
|
||||
sessionConfig: this.sessionConfig,
|
||||
ignore: this.ignoreChatsConfig(this.sessionConfig),
|
||||
};
|
||||
if (this.EngineClass === WhatsappSessionWebJSCore) {
|
||||
sessionConfig.engineConfig = this.webjsEngineConfigService.getConfig();
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { IMediaEngineProcessor } from '@waha/core/media/IMediaEngineProcessor';
|
||||
import { WAMedia } from '@waha/structures/media.dto';
|
||||
|
||||
/**
|
||||
* General interface for MediaManager - one that handles the logic
|
||||
@@ -9,7 +10,7 @@ interface IMediaManager {
|
||||
processor: IMediaEngineProcessor<Message>,
|
||||
message: Message,
|
||||
session: string,
|
||||
): Promise<Message>;
|
||||
): Promise<WAMedia | null>;
|
||||
close(): void;
|
||||
}
|
||||
|
||||
|
||||
@@ -102,7 +102,7 @@ export class MediaManager implements IMediaManager {
|
||||
processor: IMediaEngineProcessor<Message>,
|
||||
message: Message,
|
||||
session: string,
|
||||
): Promise<Message> {
|
||||
): Promise<WAMedia | null> {
|
||||
let messageId: string;
|
||||
try {
|
||||
messageId = processor.getMessageId(message);
|
||||
@@ -134,9 +134,7 @@ export class MediaManager implements IMediaManager {
|
||||
// @ts-ignore
|
||||
media.error.details = `${err.stack}`;
|
||||
}
|
||||
// @ts-ignore
|
||||
message.media = media;
|
||||
return message;
|
||||
return media;
|
||||
}
|
||||
|
||||
private async fetchMedia(
|
||||
|
||||
@@ -3,7 +3,7 @@ import { SessionConfig } from '../../structures/sessions.dto';
|
||||
export abstract class ISessionConfigRepository {
|
||||
abstract saveConfig(
|
||||
sessionName: string,
|
||||
config: SessionConfig | null,
|
||||
config: SessionConfig,
|
||||
): Promise<void>;
|
||||
|
||||
abstract getConfig(sessionName: string): Promise<SessionConfig | null>;
|
||||
|
||||
@@ -48,13 +48,13 @@ export class LocalSessionConfigRepository extends ISessionConfigRepository {
|
||||
return JSON.parse(content);
|
||||
}
|
||||
|
||||
async saveConfig(sessionName: string, config: SessionConfig | null) {
|
||||
// Create folder if not exist
|
||||
async saveConfig(sessionName: string, config: SessionConfig) {
|
||||
// Create a folder if not exist
|
||||
const folder = this.store.getSessionDirectory(sessionName);
|
||||
await fs.mkdir(folder, { recursive: true });
|
||||
// Save config
|
||||
const filepath = this.getFilePath(sessionName);
|
||||
const content = JSON.stringify(config || null);
|
||||
const content = JSON.stringify(config || {});
|
||||
await writeFileAtomic(filepath, content);
|
||||
}
|
||||
|
||||
|
||||
@@ -68,7 +68,7 @@ export class LocalStoreCore extends LocalStore {
|
||||
const engineDir = this.getEngineDirectory();
|
||||
const database = path.join(engineDir, 'waha.sqlite3');
|
||||
return Knex({
|
||||
client: 'better-sqlite3',
|
||||
client: 'sqlite3',
|
||||
connection: { filename: database },
|
||||
useNullAsDefault: true,
|
||||
});
|
||||
|
||||
+27
-2
@@ -1,11 +1,15 @@
|
||||
import { isJidGroup, isJidMetaIa } from '@adiwajshing/baileys';
|
||||
import {
|
||||
isJidGroup,
|
||||
isJidMetaIa,
|
||||
isJidStatusBroadcast,
|
||||
} from '@adiwajshing/baileys';
|
||||
import {
|
||||
isJidBroadcast,
|
||||
isLidUser,
|
||||
} from '@adiwajshing/baileys/lib/WABinary/jid-utils';
|
||||
|
||||
export function isJidNewsletter(jid: string) {
|
||||
return jid.endsWith('@newsletter');
|
||||
return jid?.endsWith('@newsletter');
|
||||
}
|
||||
|
||||
export function isJidCus(jid: string) {
|
||||
@@ -35,3 +39,24 @@ export function toJID(chatId) {
|
||||
const number = chatId.split('@')[0];
|
||||
return number + '@s.whatsapp.net';
|
||||
}
|
||||
|
||||
export interface IgnoreJidConfig {
|
||||
status: boolean;
|
||||
groups: boolean;
|
||||
channels: boolean;
|
||||
}
|
||||
|
||||
export class JidFilter {
|
||||
constructor(public ignore: IgnoreJidConfig) {}
|
||||
|
||||
include(jid: string): boolean {
|
||||
if (this.ignore.status && isJidStatusBroadcast(jid)) {
|
||||
return false;
|
||||
} else if (this.ignore.groups && isJidGroup(jid)) {
|
||||
return false;
|
||||
} else if (this.ignore.channels && isJidNewsletter(jid)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
|
||||
@@ -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[];
|
||||
}
|
||||
@@ -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 };
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
IsBoolean,
|
||||
IsOptional,
|
||||
IsString,
|
||||
Matches,
|
||||
MaxLength,
|
||||
ValidateNested,
|
||||
} from 'class-validator';
|
||||
@@ -88,6 +89,42 @@ export class NowebConfig {
|
||||
markOnline: boolean = true;
|
||||
}
|
||||
|
||||
export class WebjsConfig {
|
||||
@ApiProperty({
|
||||
description:
|
||||
"Enable emission of special 'tag:*' engine events required for presence.update and message.ack.\n" +
|
||||
'WARNING: Enabling this may have performance and stability impact. Disabled by default.',
|
||||
required: false,
|
||||
default: false,
|
||||
})
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
tagsEventsOn?: boolean = false;
|
||||
}
|
||||
|
||||
export class IgnoreConfig {
|
||||
@ApiProperty({
|
||||
description: 'Ignore a status@broadcast (stories) events',
|
||||
})
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
status?: boolean;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'Ignore groups events',
|
||||
})
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
groups?: boolean;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'Ignore channels events',
|
||||
})
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
channels?: boolean;
|
||||
}
|
||||
|
||||
export class SessionConfig {
|
||||
@ValidateNested({ each: true })
|
||||
@Type(() => WebhookConfig)
|
||||
@@ -122,7 +159,20 @@ export class SessionConfig {
|
||||
})
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
debug: boolean;
|
||||
debug?: boolean;
|
||||
|
||||
@ApiProperty({
|
||||
example: {
|
||||
status: null,
|
||||
groups: null,
|
||||
channels: null,
|
||||
},
|
||||
description: 'Ignore some events related to specific chats',
|
||||
})
|
||||
@ValidateNested()
|
||||
@Type(() => IgnoreConfig)
|
||||
@IsOptional()
|
||||
ignore?: IgnoreConfig;
|
||||
|
||||
@ApiProperty({
|
||||
example: {
|
||||
@@ -136,6 +186,15 @@ export class SessionConfig {
|
||||
@Type(() => NowebConfig)
|
||||
@IsOptional()
|
||||
noweb?: NowebConfig;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'WebJS-specific settings.',
|
||||
required: false,
|
||||
})
|
||||
@ValidateNested()
|
||||
@Type(() => WebjsConfig)
|
||||
@IsOptional()
|
||||
webjs?: WebjsConfig;
|
||||
}
|
||||
|
||||
export class SessionDTO {
|
||||
@@ -154,6 +213,11 @@ export class MeInfo {
|
||||
@ChatIdProperty()
|
||||
id: string;
|
||||
|
||||
@ApiProperty({
|
||||
example: '123123@lid',
|
||||
})
|
||||
lid?: string;
|
||||
|
||||
pushName: string;
|
||||
}
|
||||
|
||||
@@ -179,6 +243,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
@@ -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.9',
|
||||
version: '2025.9.1',
|
||||
engine: getEngineName(),
|
||||
tier: getWAHAVersion(),
|
||||
browser:
|
||||
@@ -41,3 +38,5 @@ export const VERSION: WAHAEnvironment = {
|
||||
};
|
||||
|
||||
export const IsChrome = VERSION.browser?.includes('chrome');
|
||||
|
||||
export { getEngineName };
|
||||
+2
-2
@@ -2,11 +2,11 @@
|
||||
"waha": {
|
||||
"gows": {
|
||||
"repo": "devlikeapro/gows",
|
||||
"ref": "v1.0.10"
|
||||
"ref": "v1.0.13"
|
||||
},
|
||||
"dashboard": {
|
||||
"repo": "devlikeapro/dashboard",
|
||||
"ref": "0000e1ffee9bc40b4d3e0ee6eff825ab5bc19f30"
|
||||
"ref": "a29267de044e439535f99bff42568aec5df7b462"
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user