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 |
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'
|
||||
|
||||
+5
-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",
|
||||
@@ -124,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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -0,0 +1,182 @@
|
||||
# Simplified Chinese translations for ChatWoot integration
|
||||
LOCALE_NAME: '🇨🇳 中文(Simplified Chinese)'
|
||||
APP_INBOX_CONTACT_NAME: 'WhatsApp 集成(WAHA)'
|
||||
APP_INBOX_CONTACT_AVATAR_URL: 'https://raw.githubusercontent.com/devlikeapro/waha/core/logo.png'
|
||||
|
||||
JOB_ERROR_REPORT: |
|
||||
⚠️ **{{{header}}}**
|
||||
{{#attempts.nextDelay}}
|
||||
🔄 将在 {{attempts.nextDelay}} 秒后重试...
|
||||
{{/attempts.nextDelay}}
|
||||
{{^attempts.nextDelay}}
|
||||
❌ 没有更多重试机会。请稍后再试。
|
||||
{{/attempts.nextDelay}}
|
||||
尝试:{{{attempts.current}}} / {{{attempts.max}}}
|
||||
详情:[{{{details.text}}}]({{{details.url}}})
|
||||
{{#error}}
|
||||
```
|
||||
|
||||
{{{error}}}
|
||||
```
|
||||
{{/error}}
|
||||
|
||||
JOB_SUCCEEDED_REPORT: |
|
||||
✅ **成功**
|
||||
尝试:{{{attempts.current}}} / {{{attempts.max}}}
|
||||
详情:[{{{details.text}}}]({{{details.url}}})
|
||||
|
||||
WHATSAPP_ERROR_SENDING_MESSAGE: 无法在 WhatsApp 中发送消息。
|
||||
WHATSAPP_ERROR_RECEIVING_MESSAGE: 无法接收来自 WhatsApp 的消息
|
||||
WHATSAPP_ERROR_REMOVING_MESSAGE: 无法从 WhatsApp 中删除消息
|
||||
APP_SCHEDULED_JOB_ERROR: 计划任务执行失败
|
||||
|
||||
MESSAGE_FROM_WHATSAPP: |
|
||||
{{{text}}}
|
||||
|
||||
📱 *通过 WhatsApp 发送*
|
||||
|
||||
MESSAGE_FROM_API: |
|
||||
{{{text}}}
|
||||
|
||||
📱 *通过 API 发送*
|
||||
|
||||
MESSAGE_REMOVED_IN_WHATSAPP: |
|
||||
❌ *此消息已在 WhatsApp 中被删除*
|
||||
|
||||
MESSAGE_EDITED_IN_WHATSAPP: |
|
||||
{{{text}}}
|
||||
|
||||
✏️ *已在 WhatsApp 中编辑*
|
||||
|
||||
WHATSAPP_GROUP_MESSAGE: |
|
||||
{{{text}}}
|
||||
|
||||
👥 *{{{participant}}}*
|
||||
|
||||
WHATSAPP_REACTION_ADDED: |
|
||||
*已反应* {{{emoji}}}
|
||||
|
||||
WHATSAPP_REACTION_REMOVED: |
|
||||
*已移除反应*
|
||||
|
||||
WHATSAPP_CONTACT_GROUP_SUFFIX: '群组'
|
||||
WHATSAPP_CONTACT_CHANNEL_SUFFIX: '频道'
|
||||
# Aka Stories
|
||||
WHATSAPP_CONTACT_STATUS_NAME: '状态'
|
||||
|
||||
APP_CONNECTED_MESSAGE: |
|
||||
🔗 **已连接:WhatsApp 会话到 ChatWoot 收件箱**
|
||||
|
||||
✅ WhatsApp 会话 **{{{name}}}** 已成功连接到此 ChatWoot 收件箱。
|
||||
|
||||
此收件箱现已链接到你的**WhatsApp 账号**。你可以在这里直接收发消息——就像使用 WhatsApp 一样,但在 ChatWoot 内完成。
|
||||
|
||||
你也可以管理与此收件箱关联的 WhatsApp 会话。要查看可用操作,请向此对话发送 **help**。
|
||||
|
||||
📋 你可以执行的常见操作:
|
||||
- 检查 WhatsApp 会话是否活动
|
||||
- 重启或停止 WhatsApp 会话
|
||||
- 登出并通过扫描新的二维码重新连接
|
||||
|
||||
如果一切正常,你就可以自由聊天。如果你看到警告,可能需要重新连接你的 WhatsApp 账号。
|
||||
|
||||
开始吧!🚀
|
||||
|
||||
APP_DISCONNECTED_MESSAGE: |
|
||||
🔌 **已断开:从 ChatWoot 收件箱断开 WhatsApp 会话**
|
||||
|
||||
❌ WhatsApp 会话 **{{{name}}}** 已与此 ChatWoot 收件箱断开。
|
||||
|
||||
你将无法再从 WhatsApp 发送或接收消息。
|
||||
|
||||
如需重新连接,请联系你的管理员。
|
||||
|
||||
APP_UPDATED_MESSAGE: |
|
||||
🔄 **已更新:WhatsApp 会话设置**
|
||||
|
||||
✅ WhatsApp 会话 **{{{name}}}** 的设置已更新。
|
||||
|
||||
APP_COMMANDS_LIST: |
|
||||
你可以通过发送以下命令来管理**WhatsApp 集成**:
|
||||
|
||||
🖥️ 会话:
|
||||
1. **status** - 获取当前会话状态。
|
||||
2. **restart** - 重启会话——将尝试重新连接你的 WhatsApp 账号。
|
||||
3. **start** - 启动会话。
|
||||
4. **stop** - 停止会话。
|
||||
5. **logout** - 登出会话,以便你可以扫描二维码并修复会话。
|
||||
6. **qr** - 获取该会话的二维码。
|
||||
7. **screenshot** - 获取会话截图(仅在 **WEBJS** 引擎中可用)。
|
||||
8. **help** - 查看可用命令。
|
||||
|
||||
⚙️ 服务器:
|
||||
- **server status** - 获取服务器版本和状态信息。
|
||||
- **server reboot** - 优雅地重启服务器。
|
||||
- **server reboot force** - 强制重启服务器。
|
||||
|
||||
向此对话发送**命令**(不区分大小写)或**命令编号**。
|
||||
|
||||
APP_HELP_REMINDER: |
|
||||
发送 **help** 获取可用命令列表。
|
||||
|
||||
APP_SESSION_STATUS_CHANGE: |
|
||||
{{{emoji}}} "{{{session}}}" 会话已切换为 {{{status}}} 状态
|
||||
|
||||
APP_SESSION_CURRENT_STATUS: |
|
||||
{{{emoji}}} **{{{session}}}** 会话状态为 **{{{status}}}**
|
||||
**WhatsApp 账号:**
|
||||
|
||||
📱 **{{{name}}} ({{{id}}})**
|
||||
|
||||
APP_DASHBOARD_LINK: |
|
||||
访问 [WAHA 控制台]({{{url}}}) 以了解更多信息。
|
||||
|
||||
APP_SESSION_STATUS_WORKING: |
|
||||
✅ 此 ChatWoot 收件箱已链接到**WhatsApp 账号**,可以正常收发消息!
|
||||
|
||||
📱 **{{{name}}} ({{{id}}})**
|
||||
|
||||
APP_SESSION_STATUS_ERROR: |
|
||||
⚠️ **此 ChatWoot 收件箱已链接到 WhatsApp 账号,但尚未准备好收发消息!**
|
||||
|
||||
请检查会话状态,尝试重启,或登出后重新扫描二维码。
|
||||
|
||||
APP_SESSION_SCAN_QR_CODE: |
|
||||
⚠️ **请在手机上扫描二维码,将 WhatsApp 账号与此 ChatWoot 收件箱关联!**
|
||||
|
||||
APP_SERVER_VERSION_AND_STATUS: |
|
||||
**版本:**
|
||||
```
|
||||
{{{version}}}
|
||||
```
|
||||
|
||||
**状态:**
|
||||
```
|
||||
{{{status}}}
|
||||
```
|
||||
|
||||
APP_SERVER_REBOOT: |
|
||||
服务器正在优雅重启...
|
||||
|
||||
APP_SERVER_REBOOT_FORCE: |
|
||||
服务器正在强制重启...
|
||||
|
||||
APP_LOGOUT_SUCCESS: |
|
||||
✅ 账号已成功取消授权!
|
||||
|
||||
APP_NEW_VERSION_AVAILABLE: |
|
||||
🔄 **有新版本可用!**
|
||||
|
||||
当前版本:**{{{currentVersion}}}**
|
||||
新版本:**{{{newVersion}}}**
|
||||
|
||||
👉 查看[**🆕 更新日志**]({{{changelogUrl}}})
|
||||
|
||||
APP_CORE_VERSION_USED: |
|
||||
❤️ **支持 WAHA!**
|
||||
|
||||
你正在使用免费的 **WAHA Core** 版本。如果你觉得该项目有帮助,请考虑支持其开发。
|
||||
|
||||
你的支持是我们持续进行更新、修复和新增功能的唯一动力。
|
||||
|
||||
[**🎁 支持我们**]({{{supportUrl}}})
|
||||
+19
-37
@@ -4,42 +4,24 @@ import * as Mustache from 'mustache';
|
||||
import * as path from 'path';
|
||||
import * as yaml from 'yaml';
|
||||
|
||||
export enum LocaleCode {
|
||||
EN_US = 'en-US',
|
||||
AR_AE = 'ar-AE',
|
||||
BN_BD = 'bn-BD',
|
||||
PT_BR = 'pt-BR',
|
||||
DE_DE = 'de-DE',
|
||||
ES_ES = 'es-ES',
|
||||
FR_FR = 'fr-FR',
|
||||
ID_ID = 'id-ID',
|
||||
HE_IL = 'he-IL',
|
||||
HI_IN = 'hi-IN',
|
||||
FA_IR = 'fa-IR',
|
||||
PA_PK = 'pa-PK',
|
||||
RU_RU = 'ru-RU',
|
||||
TR_TR = 'tr-TR',
|
||||
UK_UA = 'uk-UA',
|
||||
UR_PK = 'ur-PK',
|
||||
}
|
||||
|
||||
export const LOCALES = [
|
||||
LocaleCode.EN_US,
|
||||
LocaleCode.AR_AE,
|
||||
LocaleCode.BN_BD,
|
||||
LocaleCode.PT_BR,
|
||||
LocaleCode.DE_DE,
|
||||
LocaleCode.ES_ES,
|
||||
LocaleCode.FR_FR,
|
||||
LocaleCode.ID_ID,
|
||||
LocaleCode.HE_IL,
|
||||
LocaleCode.HI_IN,
|
||||
LocaleCode.FA_IR,
|
||||
LocaleCode.PA_PK,
|
||||
LocaleCode.RU_RU,
|
||||
LocaleCode.TR_TR,
|
||||
LocaleCode.UK_UA,
|
||||
LocaleCode.UR_PK,
|
||||
'en-US',
|
||||
'ar-AE',
|
||||
'bn-BD',
|
||||
'pt-BR',
|
||||
'de-DE',
|
||||
'es-ES',
|
||||
'fr-FR',
|
||||
'id-ID',
|
||||
'he-IL',
|
||||
'hi-IN',
|
||||
'fa-IR',
|
||||
'pa-PK',
|
||||
'ru-RU',
|
||||
'tr-TR',
|
||||
'uk-UA',
|
||||
'ur-PK',
|
||||
'zh-CN',
|
||||
];
|
||||
|
||||
export const DEFAULT_LOCALE = 'en-US';
|
||||
@@ -67,12 +49,12 @@ function loadLocales(locales: string[]) {
|
||||
}
|
||||
|
||||
let locales: Record<string, Record<string, string>> = {};
|
||||
export const LOCALE_NAMES: Map<LocaleCode, string> = new Map();
|
||||
export const LOCALE_NAMES: Map<string, string> = new Map();
|
||||
|
||||
try {
|
||||
locales = loadLocales(LOCALES);
|
||||
for (const [locale, strings] of Object.entries(locales)) {
|
||||
LOCALE_NAMES.set(locale as LocaleCode, strings['LOCALE_NAME']);
|
||||
LOCALE_NAMES.set(locale, strings['LOCALE_NAME']);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading locales:', error);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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(
|
||||
|
||||
+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)
|
||||
@@ -124,6 +161,19 @@ export class SessionConfig {
|
||||
@IsOptional()
|
||||
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: {
|
||||
store: {
|
||||
@@ -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.11',
|
||||
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.11"
|
||||
"ref": "v1.0.13"
|
||||
},
|
||||
"dashboard": {
|
||||
"repo": "devlikeapro/dashboard",
|
||||
"ref": "59ebfe14fc89b42f77dd9da173d3e7e2983beb21"
|
||||
"ref": "a29267de044e439535f99bff42568aec5df7b462"
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user