@xmanrui/dsh-im 3.0.2 → 3.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +2 -2
- package/README.md +2 -2
- package/lib/client.js +27 -18
- package/lib/index.js +205 -204
- package/package.json +1 -1
- package/plugin-src/client/channels/whatsapp/api.js +5 -0
- package/plugin-src/client/channels/whatsapp/index.js +30 -9
- package/plugin-src/client/i18n.js +3 -0
- package/plugin-src/client/styles.js +0 -6
- package/plugin-src/host/channels/whatsapp/rpc.mjs +2 -2
- package/src/channels/wecom/wecom-bridge.mjs +48 -11
- package/src/channels/whatsapp/config-store.mjs +1 -0
- package/src/channels/whatsapp/whatsapp-controller.mjs +1 -0
- package/src/channels/whatsapp/whatsapp-runtime.mjs +42 -6
package/package.json
CHANGED
|
@@ -97,6 +97,11 @@ function normalizeBot(value) {
|
|
|
97
97
|
typeof entry === 'string' && /^[1-9]\d{4,14}$/.test(entry)
|
|
98
98
|
)))]
|
|
99
99
|
: [],
|
|
100
|
+
groupAllowedNumbers: Array.isArray(value.accessPolicy?.groupAllowedNumbers)
|
|
101
|
+
? [...new Set(value.accessPolicy.groupAllowedNumbers.filter((entry) => (
|
|
102
|
+
typeof entry === 'string' && /^[1-9]\d{4,14}$/.test(entry)
|
|
103
|
+
)))]
|
|
104
|
+
: [],
|
|
100
105
|
},
|
|
101
106
|
bot: {
|
|
102
107
|
name: text(value.bot?.name, 'WhatsApp机器人', 100),
|
|
@@ -37,6 +37,8 @@ function accessPolicyFor(account) {
|
|
|
37
37
|
accessMode,
|
|
38
38
|
allowedNumbers: Array.isArray(account?.accessPolicy?.allowedNumbers)
|
|
39
39
|
? account.accessPolicy.allowedNumbers : [],
|
|
40
|
+
groupAllowedNumbers: Array.isArray(account?.accessPolicy?.groupAllowedNumbers)
|
|
41
|
+
? account.accessPolicy.groupAllowedNumbers : [],
|
|
40
42
|
};
|
|
41
43
|
}
|
|
42
44
|
|
|
@@ -52,30 +54,39 @@ function allowedNumbersFromText(value) {
|
|
|
52
54
|
export function WhatsappAccessSettings({ account, busy = false, onSave }) {
|
|
53
55
|
const policy = accessPolicyFor(account);
|
|
54
56
|
const sourceNumbers = policy.allowedNumbers.join('\n');
|
|
57
|
+
const sourceGroupNumbers = policy.groupAllowedNumbers.join('\n');
|
|
55
58
|
const helpId = React.useId();
|
|
56
59
|
const [accessMode, setAccessMode] = React.useState(policy.accessMode);
|
|
57
60
|
const [allowedNumbers, setAllowedNumbers] = React.useState(sourceNumbers);
|
|
61
|
+
const [groupAllowedNumbers, setGroupAllowedNumbers] = React.useState(sourceGroupNumbers);
|
|
58
62
|
const [error, setError] = React.useState(null);
|
|
59
63
|
|
|
60
64
|
React.useEffect(() => {
|
|
61
65
|
setAccessMode(policy.accessMode);
|
|
62
66
|
setAllowedNumbers(sourceNumbers);
|
|
67
|
+
setGroupAllowedNumbers(sourceGroupNumbers);
|
|
63
68
|
setError(null);
|
|
64
|
-
}, [policy.accessMode, sourceNumbers]);
|
|
69
|
+
}, [policy.accessMode, sourceNumbers, sourceGroupNumbers]);
|
|
65
70
|
|
|
66
71
|
const save = async (event) => {
|
|
67
72
|
event.preventDefault();
|
|
68
73
|
setError(null);
|
|
69
74
|
try {
|
|
70
75
|
const normalized = allowedNumbersFromText(allowedNumbers);
|
|
76
|
+
const normalizedGroup = allowedNumbersFromText(groupAllowedNumbers);
|
|
71
77
|
if (typeof onSave !== 'function') throw new Error('WhatsApp 访问设置暂不可用。');
|
|
72
|
-
await onSave({
|
|
78
|
+
await onSave({
|
|
79
|
+
accessMode,
|
|
80
|
+
allowedNumbers: normalized,
|
|
81
|
+
groupAllowedNumbers: normalizedGroup,
|
|
82
|
+
});
|
|
73
83
|
} catch (caught) {
|
|
74
84
|
setError(caught?.message ?? 'WhatsApp 访问设置保存失败。');
|
|
75
85
|
}
|
|
76
86
|
};
|
|
77
87
|
|
|
78
88
|
const allowlistEnabled = accessMode === 'private-allowlist';
|
|
89
|
+
const groupAllowlistEnabled = accessMode === 'open';
|
|
79
90
|
const labels = {
|
|
80
91
|
'self-only': '仅自己模式',
|
|
81
92
|
'private-allowlist': '指定联系人模式',
|
|
@@ -103,7 +114,7 @@ export function WhatsappAccessSettings({ account, busy = false, onSave }) {
|
|
|
103
114
|
h('span', null, '响应自聊和白名单联系人的私聊,忽略群聊。')),
|
|
104
115
|
h('span', { className: 'dwa-accessTooltipItem' },
|
|
105
116
|
h('strong', null, '开放响应模式'),
|
|
106
|
-
h('span', null, '
|
|
117
|
+
h('span', null, '响应所有私聊、已绑定账号自己发出的群聊消息,以及允许成员的提及或回复;群聊号码列表留空时允许所有群成员。')))))),
|
|
107
118
|
h('label', { className: 'dwa-accessField' },
|
|
108
119
|
h('span', null, '模式'),
|
|
109
120
|
h('select', {
|
|
@@ -115,18 +126,28 @@ export function WhatsappAccessSettings({ account, busy = false, onSave }) {
|
|
|
115
126
|
h('option', { value: 'self-only' }, '仅自己模式(默认)'),
|
|
116
127
|
h('option', { value: 'private-allowlist' }, '指定联系人模式'),
|
|
117
128
|
h('option', { value: 'open' }, '开放响应模式'))),
|
|
118
|
-
allowlistEnabled
|
|
129
|
+
(allowlistEnabled || groupAllowlistEnabled)
|
|
119
130
|
? h('label', { className: 'dwa-accessField' },
|
|
120
|
-
h('span', null,
|
|
131
|
+
h('span', null, allowlistEnabled
|
|
132
|
+
? '允许私聊的 WhatsApp 电话号码'
|
|
133
|
+
: '允许在群聊中呼叫机器人的 WhatsApp 电话号码'),
|
|
121
134
|
h('textarea', {
|
|
122
|
-
value: allowedNumbers,
|
|
135
|
+
value: allowlistEnabled ? allowedNumbers : groupAllowedNumbers,
|
|
123
136
|
disabled: busy,
|
|
124
137
|
rows: 3,
|
|
125
138
|
placeholder: '每行一个含国家或地区代码的号码',
|
|
126
|
-
'aria-label':
|
|
127
|
-
|
|
139
|
+
'aria-label': allowlistEnabled
|
|
140
|
+
? '允许私聊的 WhatsApp 电话号码'
|
|
141
|
+
: '允许在群聊中呼叫机器人的 WhatsApp 电话号码',
|
|
142
|
+
onChange: (event) => {
|
|
143
|
+
if (allowlistEnabled) setAllowedNumbers(event.target.value);
|
|
144
|
+
else setGroupAllowedNumbers(event.target.value);
|
|
145
|
+
setError(null);
|
|
146
|
+
},
|
|
128
147
|
}),
|
|
129
|
-
h('small', null,
|
|
148
|
+
h('small', null, allowlistEnabled
|
|
149
|
+
? '可以包含开头的 +,保存时会自动移除。'
|
|
150
|
+
: '留空表示所有群成员都可以通过提及或回复呼叫机器人。'))
|
|
130
151
|
: null,
|
|
131
152
|
allowlistEnabled && allowedNumbers.trim() === ''
|
|
132
153
|
? h('p', { className: 'dwa-accessWarning', role: 'status' },
|
|
@@ -380,9 +380,12 @@ const EN = Object.freeze({
|
|
|
380
380
|
'只响应已绑定 WhatsApp 账号的自聊消息。': 'Only respond to self-chat messages from the linked WhatsApp account.',
|
|
381
381
|
'响应自聊和白名单联系人的私聊,忽略群聊。': 'Respond to self-chat and allowlisted direct messages; ignore group messages.',
|
|
382
382
|
'响应所有私聊、已绑定账号自己发出的群聊消息,以及其他群成员的提及或回复。': 'Respond to all direct messages, group messages sent by the linked account, and mentions or replies from other group members.',
|
|
383
|
+
'响应所有私聊、已绑定账号自己发出的群聊消息,以及允许成员的提及或回复;群聊号码列表留空时允许所有群成员。': 'Respond to all direct messages, group messages sent by the linked account, and mentions or replies from allowed members; leaving the group number list empty allows every group member.',
|
|
383
384
|
'允许私聊的 WhatsApp 电话号码': 'WhatsApp phone numbers allowed to send direct messages',
|
|
385
|
+
'允许在群聊中呼叫机器人的 WhatsApp 电话号码': 'WhatsApp phone numbers allowed to call the bot in group chats',
|
|
384
386
|
'每行一个含国家或地区代码的号码': 'One number with country or region code per line',
|
|
385
387
|
'可以包含开头的 +,保存时会自动移除。': 'A leading + is allowed and removed when saved.',
|
|
388
|
+
'留空表示所有群成员都可以通过提及或回复呼叫机器人。': 'Leave empty to let every group member call the bot by mentioning it or replying to it.',
|
|
386
389
|
'仅指定联系人模式使用白名单,切换模式时会保留。': 'Only Selected contacts uses the allowlist; it is retained when modes change.',
|
|
387
390
|
'白名单为空;保存后将只接受自聊消息。': 'The allowlist is empty; only self-chat messages will be accepted after saving.',
|
|
388
391
|
'电话号码必须包含国家或地区代码,每行一个。': 'Each phone number must include a country or region code on its own line.',
|
|
@@ -245,8 +245,6 @@ const CSS = String.raw`
|
|
|
245
245
|
.dim-panel .ddt-qrFrame, .dim-panel .ddt-countdown { width: min(270px, 100%); }
|
|
246
246
|
@container (max-width: 680px) {
|
|
247
247
|
.dim-panel .bxf-headingTools, .dim-panel .dxw-tools, .dim-panel .ddt-tools { gap: 6px; }
|
|
248
|
-
.dim-panel .dim-botCardTop { flex-direction: column; align-items: stretch; }
|
|
249
|
-
.dim-panel .dim-botHealthGroup { justify-items: start; }
|
|
250
248
|
.dim-panel .dim-bindActions { gap: 6px; }
|
|
251
249
|
.dim-panel .bxf-headingTools .dim-scanButton, .dim-panel .dxw-tools .dim-scanButton, .dim-panel .ddt-tools .dim-scanButton, .dim-panel .dim-credentialButton { gap: 5px; padding-inline: 8px; font-size: 12px; }
|
|
252
250
|
.dim-panel .dim-actionIcon { width: 13px; height: 13px; flex-basis: 13px; }
|
|
@@ -270,10 +268,6 @@ const CSS = String.raw`
|
|
|
270
268
|
.dim-rail { max-height: none; overflow: visible; padding-right: 1px; }
|
|
271
269
|
.dim-channel { min-height: 48px; }
|
|
272
270
|
}
|
|
273
|
-
@media (max-width: 720px) {
|
|
274
|
-
.dim-panel .dim-botCardTop { flex-direction: column; align-items: stretch; }
|
|
275
|
-
.dim-panel .dim-botHealthGroup { justify-items: start; }
|
|
276
|
-
}
|
|
277
271
|
@media (max-width: 560px) {
|
|
278
272
|
.dim-title { flex-direction: column; gap: 10px; }
|
|
279
273
|
.dim-title p { white-space: normal; }
|
|
@@ -53,8 +53,8 @@ function payloadFailure(endpoint, payload) {
|
|
|
53
53
|
&& payload.confirm === true ? null : 'bot.delete requires a botId and confirm=true.';
|
|
54
54
|
}
|
|
55
55
|
if (endpoint === WHATSAPP_ENDPOINTS.setAccessPolicy) {
|
|
56
|
-
if (!exactKeys(payload, ['botId', 'accessMode', 'allowedNumbers'])
|
|
57
|
-
|| Object.keys(payload).length !==
|
|
56
|
+
if (!exactKeys(payload, ['botId', 'accessMode', 'allowedNumbers', 'groupAllowedNumbers'])
|
|
57
|
+
|| Object.keys(payload).length !== 4
|
|
58
58
|
|| !validId(payload.botId)) return '请输入有效的 WhatsApp 访问模式和电话号码。';
|
|
59
59
|
try {
|
|
60
60
|
normalizeWhatsappAccessPolicy(payload);
|
|
@@ -296,12 +296,23 @@ function splitUtf8(text, maxBytes = MAX_REPLY_BYTES) {
|
|
|
296
296
|
return chunks;
|
|
297
297
|
}
|
|
298
298
|
|
|
299
|
-
function
|
|
300
|
-
if (update?.type === 'text') return update.text;
|
|
299
|
+
function thinkingProgressText(update) {
|
|
301
300
|
if (update?.type === 'tool') return t('正在使用{name}…', { name: update.name });
|
|
302
301
|
return update?.text;
|
|
303
302
|
}
|
|
304
303
|
|
|
304
|
+
function streamContent(thinkingText, answerText = '', { finish = false } = {}) {
|
|
305
|
+
const thinking = String(thinkingText ?? '')
|
|
306
|
+
.replace(/<\/?think>/gi, '')
|
|
307
|
+
.trim();
|
|
308
|
+
const answer = String(answerText ?? '').trim();
|
|
309
|
+
if (!thinking) return answer;
|
|
310
|
+
const thinkBlock = finish || answer
|
|
311
|
+
? `<think>${thinking}</think>`
|
|
312
|
+
: `<think>${thinking}`;
|
|
313
|
+
return answer ? `${thinkBlock}\n${answer}` : thinkBlock;
|
|
314
|
+
}
|
|
315
|
+
|
|
305
316
|
function artifactFailureText(fileName, error) {
|
|
306
317
|
const name = String(fileName ?? t('结果文件')).replace(/[\r\n]+/g, ' ').trim()
|
|
307
318
|
|| t('结果文件');
|
|
@@ -867,6 +878,8 @@ export class WecomHarnessBridge {
|
|
|
867
878
|
const key = conversationKey(frame);
|
|
868
879
|
let streamId = null;
|
|
869
880
|
let streamStarted = false;
|
|
881
|
+
let streamThinkingText = t('正在思考中…');
|
|
882
|
+
let streamAnswerText = '';
|
|
870
883
|
let batchSettled = batchSubmission === null;
|
|
871
884
|
let promptRecorded = false;
|
|
872
885
|
try {
|
|
@@ -920,7 +933,12 @@ export class WecomHarnessBridge {
|
|
|
920
933
|
|
|
921
934
|
streamId = this.#generateReqId('stream');
|
|
922
935
|
try {
|
|
923
|
-
await this.#client.replyStream(
|
|
936
|
+
await this.#client.replyStream(
|
|
937
|
+
frame,
|
|
938
|
+
streamId,
|
|
939
|
+
streamContent(streamThinkingText),
|
|
940
|
+
false,
|
|
941
|
+
);
|
|
924
942
|
streamStarted = true;
|
|
925
943
|
} catch (error) {
|
|
926
944
|
this.#logger.warn?.('[dsh-im:wecom] unable to start a stream; using an active reply:', error);
|
|
@@ -945,8 +963,15 @@ export class WecomHarnessBridge {
|
|
|
945
963
|
control: { owner: this, key },
|
|
946
964
|
onUpdate: streamStarted && typeof this.#client.replyStreamNonBlocking === 'function'
|
|
947
965
|
? async (update) => {
|
|
948
|
-
|
|
949
|
-
|
|
966
|
+
if (update?.type === 'text') {
|
|
967
|
+
streamAnswerText = update.text;
|
|
968
|
+
} else {
|
|
969
|
+
streamThinkingText = thinkingProgressText(update) || streamThinkingText;
|
|
970
|
+
}
|
|
971
|
+
const preview = splitUtf8(
|
|
972
|
+
streamContent(streamThinkingText, streamAnswerText),
|
|
973
|
+
)[0];
|
|
974
|
+
if (preview) await this.#client.replyStreamNonBlocking(frame, streamId, preview, false);
|
|
950
975
|
}
|
|
951
976
|
: undefined,
|
|
952
977
|
onInteraction: (interaction) => this.#handleInteraction(interaction, {
|
|
@@ -966,18 +991,20 @@ export class WecomHarnessBridge {
|
|
|
966
991
|
|
|
967
992
|
this.#signal?.throwIfAborted();
|
|
968
993
|
const displayAnswer = answerTextForDelivery(answer, artifacts);
|
|
969
|
-
const
|
|
994
|
+
const streamChunks = splitUtf8(
|
|
995
|
+
streamContent(streamThinkingText, displayAnswer, { finish: true }),
|
|
996
|
+
);
|
|
970
997
|
let finalSent = false;
|
|
971
998
|
let textReceipt = null;
|
|
972
999
|
let textSendError = null;
|
|
973
1000
|
try {
|
|
974
|
-
if (streamStarted &&
|
|
1001
|
+
if (streamStarted && streamChunks.length > 0) {
|
|
975
1002
|
try {
|
|
976
1003
|
const providerMessageIds = [];
|
|
977
|
-
const streamed = await this.#client.replyStream(frame, streamId,
|
|
1004
|
+
const streamed = await this.#client.replyStream(frame, streamId, streamChunks[0], true);
|
|
978
1005
|
const streamedMessageId = providerMessageId(streamed);
|
|
979
1006
|
if (streamedMessageId) providerMessageIds.push(streamedMessageId);
|
|
980
|
-
for (const chunk of
|
|
1007
|
+
for (const chunk of streamChunks.slice(1)) {
|
|
981
1008
|
const sent = await this.#client.sendMessage(
|
|
982
1009
|
chatId,
|
|
983
1010
|
{ msgtype: 'markdown', markdown: { content: chunk } },
|
|
@@ -1040,7 +1067,12 @@ export class WecomHarnessBridge {
|
|
|
1040
1067
|
}
|
|
1041
1068
|
if (error?.code === 'turn-stopped') {
|
|
1042
1069
|
if (streamStarted && streamId) {
|
|
1043
|
-
await this.#client.replyStream(
|
|
1070
|
+
await this.#client.replyStream(
|
|
1071
|
+
frame,
|
|
1072
|
+
streamId,
|
|
1073
|
+
streamContent(streamThinkingText, t('已停止。'), { finish: true }),
|
|
1074
|
+
true,
|
|
1075
|
+
)
|
|
1044
1076
|
.catch(() => undefined);
|
|
1045
1077
|
}
|
|
1046
1078
|
if (!promptRecorded) await this.#state.markSeen(messageId);
|
|
@@ -1063,7 +1095,12 @@ export class WecomHarnessBridge {
|
|
|
1063
1095
|
: errorText;
|
|
1064
1096
|
try {
|
|
1065
1097
|
if (streamStarted && streamId) {
|
|
1066
|
-
await this.#client.replyStream(
|
|
1098
|
+
await this.#client.replyStream(
|
|
1099
|
+
frame,
|
|
1100
|
+
streamId,
|
|
1101
|
+
streamContent(streamThinkingText, visibleError, { finish: true }),
|
|
1102
|
+
true,
|
|
1103
|
+
);
|
|
1067
1104
|
} else {
|
|
1068
1105
|
await this.#sendImmediate(frame, chatId, visibleError);
|
|
1069
1106
|
}
|
|
@@ -63,6 +63,7 @@ export function normalizeWhatsappAccessPolicy(value = {}) {
|
|
|
63
63
|
return Object.freeze({
|
|
64
64
|
accessMode,
|
|
65
65
|
allowedNumbers: normalizeWhatsappAllowedNumbers(value.allowedNumbers),
|
|
66
|
+
groupAllowedNumbers: normalizeWhatsappAllowedNumbers(value.groupAllowedNumbers),
|
|
66
67
|
});
|
|
67
68
|
}
|
|
68
69
|
|
|
@@ -334,6 +334,7 @@ export class WhatsappController {
|
|
|
334
334
|
connectedAt: new Date().toISOString(),
|
|
335
335
|
accessMode: previous?.accessMode,
|
|
336
336
|
allowedNumbers: previous?.allowedNumbers,
|
|
337
|
+
groupAllowedNumbers: previous?.groupAllowedNumbers,
|
|
337
338
|
};
|
|
338
339
|
try {
|
|
339
340
|
if (record.controller.signal.aborted || this.#closed) throw Object.assign(new Error(), { name: 'AbortError' });
|
|
@@ -3,6 +3,7 @@ import { createHash, randomBytes } from 'node:crypto';
|
|
|
3
3
|
import {
|
|
4
4
|
areJidsSameUser,
|
|
5
5
|
downloadMediaMessage,
|
|
6
|
+
jidNormalizedUser,
|
|
6
7
|
normalizeMessageContent,
|
|
7
8
|
} from '@whiskeysockets/baileys';
|
|
8
9
|
|
|
@@ -13,6 +14,7 @@ import { trackOutboundArtifactProviderPromise } from '../shared/semantic/artifac
|
|
|
13
14
|
import { createWhatsappBridgeStatus, WhatsappHarnessBridge } from './whatsapp-bridge.mjs';
|
|
14
15
|
import {
|
|
15
16
|
WHATSAPP_ACCESS_MODES,
|
|
17
|
+
normalizeWhatsappAccountJid,
|
|
16
18
|
normalizeWhatsappAccessPolicy,
|
|
17
19
|
} from './config-store.mjs';
|
|
18
20
|
import { createWhatsappWebSession } from './whatsapp-web-session.mjs';
|
|
@@ -204,8 +206,21 @@ export function createWhatsappMediaDownloader({
|
|
|
204
206
|
});
|
|
205
207
|
}
|
|
206
208
|
|
|
209
|
+
function whatsappAccountMatcher(accountJid, aliases) {
|
|
210
|
+
const accountJids = new Set(
|
|
211
|
+
[accountJid, ...(Array.isArray(aliases) ? aliases : [])]
|
|
212
|
+
.map((jid) => normalizeWhatsappAccountJid(jidNormalizedUser(jid)))
|
|
213
|
+
.filter(Boolean),
|
|
214
|
+
);
|
|
215
|
+
return (jid) => {
|
|
216
|
+
const normalized = normalizeWhatsappAccountJid(jidNormalizedUser(jid));
|
|
217
|
+
return normalized !== null && accountJids.has(normalized);
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
|
|
207
221
|
export function normalizeWhatsappMessage(message, accountJid, {
|
|
208
222
|
download = downloadMediaMessage,
|
|
223
|
+
accountAliases = [],
|
|
209
224
|
} = {}) {
|
|
210
225
|
const remoteJid = typeof message?.key?.remoteJid === 'string' ? message.key.remoteJid : '';
|
|
211
226
|
const alternateRemoteJid = typeof message?.key?.remoteJidAlt === 'string'
|
|
@@ -215,8 +230,9 @@ export function normalizeWhatsappMessage(message, accountJid, {
|
|
|
215
230
|
|| remoteJid.endsWith('@newsletter')) return null;
|
|
216
231
|
const group = remoteJid.endsWith('@g.us');
|
|
217
232
|
const fromMe = message.key.fromMe === true;
|
|
233
|
+
const matchesAccount = whatsappAccountMatcher(accountJid, accountAliases);
|
|
218
234
|
const selfChat = fromMe && !group
|
|
219
|
-
&& [remoteJid, alternateRemoteJid].some(
|
|
235
|
+
&& [remoteJid, alternateRemoteJid].some(matchesAccount);
|
|
220
236
|
if (fromMe && !selfChat && !group) return null;
|
|
221
237
|
const senderJid = fromMe ? accountJid : group ? message.key.participant : remoteJid;
|
|
222
238
|
const senderAlternateJid = group && !fromMe ? message.key.participantAlt : alternateRemoteJid;
|
|
@@ -225,9 +241,9 @@ export function normalizeWhatsappMessage(message, accountJid, {
|
|
|
225
241
|
const content = normalizeMessageContent(message.message);
|
|
226
242
|
const context = messageContext(content);
|
|
227
243
|
const mentioned = Array.isArray(context?.mentionedJid)
|
|
228
|
-
&& context.mentionedJid.some(
|
|
244
|
+
&& context.mentionedJid.some(matchesAccount);
|
|
229
245
|
const replyToSelf = typeof context?.participant === 'string'
|
|
230
|
-
&&
|
|
246
|
+
&& matchesAccount(context.participant);
|
|
231
247
|
const image = whatsappImageSource(message, content, download, { viewOnce });
|
|
232
248
|
const file = whatsappFileSource(message, content, download);
|
|
233
249
|
return {
|
|
@@ -235,6 +251,7 @@ export function normalizeWhatsappMessage(message, accountJid, {
|
|
|
235
251
|
providerMessageId: messageId,
|
|
236
252
|
senderId: senderJid,
|
|
237
253
|
senderAlternateId: typeof senderAlternateJid === 'string' ? senderAlternateJid : '',
|
|
254
|
+
senderIsSelf: fromMe,
|
|
238
255
|
senderIsBot: false,
|
|
239
256
|
kind: group ? 'group' : 'direct',
|
|
240
257
|
conversationId: remoteJid,
|
|
@@ -253,12 +270,22 @@ export function normalizeWhatsappMessage(message, accountJid, {
|
|
|
253
270
|
export function whatsappInboundAllowed(message, {
|
|
254
271
|
accessMode = WHATSAPP_ACCESS_MODES.selfOnly,
|
|
255
272
|
allowedNumbers = new Set(),
|
|
273
|
+
groupAllowedNumbers = new Set(),
|
|
256
274
|
} = {}) {
|
|
257
|
-
if (accessMode === WHATSAPP_ACCESS_MODES.open)
|
|
275
|
+
if (accessMode === WHATSAPP_ACCESS_MODES.open) {
|
|
276
|
+
if (message?.kind !== 'group' || message.senderIsSelf === true) return true;
|
|
277
|
+
if (!(groupAllowedNumbers instanceof Set)) return false;
|
|
278
|
+
if (groupAllowedNumbers.size === 0) return true;
|
|
279
|
+
const senderJids = [message.senderId, message.senderAlternateId]
|
|
280
|
+
.filter((jid) => typeof jid === 'string' && jid.endsWith('@s.whatsapp.net'));
|
|
281
|
+
return [...groupAllowedNumbers].some((number) => senderJids.some((jid) => (
|
|
282
|
+
areJidsSameUser(jid, `${number}@s.whatsapp.net`)
|
|
283
|
+
)));
|
|
284
|
+
}
|
|
258
285
|
if (message?.kind !== 'direct') return false;
|
|
259
286
|
if (message.selfChat === true) return true;
|
|
260
|
-
if (accessMode !== WHATSAPP_ACCESS_MODES.privateAllowlist
|
|
261
|
-
|
|
287
|
+
if (accessMode !== WHATSAPP_ACCESS_MODES.privateAllowlist) return false;
|
|
288
|
+
if (!(allowedNumbers instanceof Set)) return false;
|
|
262
289
|
const senderJids = [message.senderId, message.senderAlternateId]
|
|
263
290
|
.filter((jid) => typeof jid === 'string' && jid.endsWith('@s.whatsapp.net'));
|
|
264
291
|
return [...allowedNumbers].some((number) => senderJids.some((jid) => (
|
|
@@ -542,6 +569,7 @@ export class WhatsappRuntime {
|
|
|
542
569
|
#mediaUploadTimeoutMs;
|
|
543
570
|
#accessMode;
|
|
544
571
|
#allowedPrivateNumbers;
|
|
572
|
+
#allowedGroupNumbers;
|
|
545
573
|
#createSession;
|
|
546
574
|
#status = createWhatsappRuntimeStatus();
|
|
547
575
|
#abortController = null;
|
|
@@ -590,6 +618,7 @@ export class WhatsappRuntime {
|
|
|
590
618
|
const policy = normalizeWhatsappAccessPolicy(value);
|
|
591
619
|
this.#accessMode = policy.accessMode;
|
|
592
620
|
this.#allowedPrivateNumbers = new Set(policy.allowedNumbers);
|
|
621
|
+
this.#allowedGroupNumbers = new Set(policy.groupAllowedNumbers);
|
|
593
622
|
this.#config = { ...this.#config, ...policy };
|
|
594
623
|
return policy;
|
|
595
624
|
}
|
|
@@ -624,7 +653,13 @@ export class WhatsappRuntime {
|
|
|
624
653
|
{ code: 'relink-required' },
|
|
625
654
|
)),
|
|
626
655
|
onMessage: async (raw, context) => {
|
|
656
|
+
const linkedAccount = context?.socket?.user;
|
|
627
657
|
const message = normalizeWhatsappMessage(raw, this.#config.accountJid, {
|
|
658
|
+
accountAliases: [
|
|
659
|
+
linkedAccount?.id,
|
|
660
|
+
linkedAccount?.lid,
|
|
661
|
+
linkedAccount?.phoneNumber,
|
|
662
|
+
],
|
|
628
663
|
download: createWhatsappMediaDownloader({
|
|
629
664
|
socket: context?.socket,
|
|
630
665
|
logger: this.#logger,
|
|
@@ -635,6 +670,7 @@ export class WhatsappRuntime {
|
|
|
635
670
|
if (!whatsappInboundAllowed(message, {
|
|
636
671
|
accessMode: this.#accessMode,
|
|
637
672
|
allowedNumbers: this.#allowedPrivateNumbers,
|
|
673
|
+
groupAllowedNumbers: this.#allowedGroupNumbers,
|
|
638
674
|
})) {
|
|
639
675
|
this.#status.messagesRejected += 1;
|
|
640
676
|
this.#status.lastRejectedAt = new Date().toISOString();
|