@xmanrui/dsh-im 1.0.2 → 1.1.0
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 +18 -0
- package/README.md +18 -0
- package/assets/logo-dsh-im-chinese-readme-3x2.png +0 -0
- package/assets/logo_cn.png +0 -0
- package/lib/client.js +603 -560
- package/lib/index.js +163 -163
- package/package.json +1 -1
- package/plugin-src/client/agent-preset.js +15 -6
- package/plugin-src/client/channel-card-meta.js +48 -0
- package/plugin-src/client/channels/dingtalk/index.js +25 -19
- package/plugin-src/client/channels/dingtalk/styles.js +0 -6
- package/plugin-src/client/channels/feishu/index.js +41 -35
- package/plugin-src/client/channels/feishu/styles.js +0 -5
- package/plugin-src/client/channels/qq/index.js +24 -16
- package/plugin-src/client/channels/shared/token-channel.js +32 -24
- package/plugin-src/client/channels/wecom/index.js +24 -16
- package/plugin-src/client/channels/weixin/index.js +29 -23
- package/plugin-src/client/channels/weixin/styles.js +0 -5
- package/plugin-src/client/channels/whatsapp/index.js +27 -23
- package/plugin-src/client/i18n.js +2 -0
- package/plugin-src/client/styles.js +23 -8
- package/plugin-src/host/index.mjs +14 -1
- package/src/channels/dingtalk/dingtalk-api.mjs +215 -2
- package/src/channels/dingtalk/dingtalk-bridge.mjs +155 -4
- package/src/channels/discord/discord-api.mjs +134 -6
- package/src/channels/discord/discord-runtime.mjs +15 -4
- package/src/channels/feishu/bridge.mjs +223 -15
- package/src/channels/feishu/feishu-channel.mjs +227 -1
- package/src/channels/feishu/plugin-controller.mjs +1 -0
- package/src/channels/qq/qq-bridge.mjs +217 -10
- package/src/channels/shared/editable-message-stream.mjs +18 -1
- package/src/channels/shared/harness-client.mjs +99 -7
- package/src/channels/shared/semantic/artifact.mjs +748 -0
- package/src/channels/shared/semantic/delivery.mjs +153 -0
- package/src/channels/shared/text-harness-bridge.mjs +149 -3
- package/src/channels/shared/workspace-session.mjs +15 -1
- package/src/channels/slack/manifest.mjs +1 -0
- package/src/channels/slack/slack-api.mjs +167 -4
- package/src/channels/slack/slack-runtime.mjs +21 -5
- package/src/channels/telegram/telegram-api.mjs +111 -5
- package/src/channels/telegram/telegram-runtime.mjs +18 -4
- package/src/channels/wecom/wecom-bridge.mjs +260 -12
- package/src/channels/weixin/weixin-api.mjs +268 -2
- package/src/channels/weixin/weixin-bridge.mjs +134 -3
- package/src/channels/weixin/weixin-controller.mjs +5 -1
- package/src/channels/weixin/weixin-runtime.mjs +5 -1
- package/src/channels/whatsapp/whatsapp-runtime.mjs +108 -5
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
|
|
1
3
|
import {
|
|
2
4
|
areJidsSameUser,
|
|
3
5
|
downloadMediaMessage,
|
|
@@ -6,12 +8,14 @@ import {
|
|
|
6
8
|
|
|
7
9
|
import { splitMessageText } from '../shared/editable-message-stream.mjs';
|
|
8
10
|
import { ImagePromptError } from '../shared/image-prompt.mjs';
|
|
11
|
+
import { trackOutboundArtifactProviderPromise } from '../shared/semantic/artifact.mjs';
|
|
9
12
|
import { createWhatsappBridgeStatus, WhatsappHarnessBridge } from './whatsapp-bridge.mjs';
|
|
10
13
|
import { createWhatsappWebSession } from './whatsapp-web-session.mjs';
|
|
11
14
|
|
|
12
15
|
const IMAGE_MEDIA_TYPES = new Set(['image/jpeg', 'image/png', 'image/webp', 'image/gif']);
|
|
13
16
|
const DEFAULT_MAX_IMAGE_BYTES = 5 * 1024 * 1024;
|
|
14
17
|
const IMAGE_DOWNLOAD_TIMEOUT_MS = 15_000;
|
|
18
|
+
const WHATSAPP_MEDIA_UPLOAD_TIMEOUT_MS = 120_000;
|
|
15
19
|
const MESSAGE_WRAPPER_KEYS = [
|
|
16
20
|
'ephemeralMessage',
|
|
17
21
|
'viewOnceMessage',
|
|
@@ -225,27 +229,114 @@ class RecentWhatsappOutboundIds {
|
|
|
225
229
|
}
|
|
226
230
|
}
|
|
227
231
|
|
|
228
|
-
|
|
232
|
+
function abortReason(signal) {
|
|
233
|
+
return signal?.reason ?? new DOMException('The operation was aborted.', 'AbortError');
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function waitWithSignal(promise, signal) {
|
|
237
|
+
if (!signal) return promise;
|
|
238
|
+
signal.throwIfAborted();
|
|
239
|
+
return new Promise((resolve, reject) => {
|
|
240
|
+
let settled = false;
|
|
241
|
+
const finish = (callback, value) => {
|
|
242
|
+
if (settled) return;
|
|
243
|
+
settled = true;
|
|
244
|
+
signal.removeEventListener('abort', onAbort);
|
|
245
|
+
callback(value);
|
|
246
|
+
};
|
|
247
|
+
const onAbort = () => finish(reject, abortReason(signal));
|
|
248
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
249
|
+
Promise.resolve(promise).then(
|
|
250
|
+
(value) => finish(resolve, value),
|
|
251
|
+
(error) => finish(reject, error),
|
|
252
|
+
);
|
|
253
|
+
if (signal.aborted) onAbort();
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function uncertainArtifactDelivery(error) {
|
|
258
|
+
if (error?.code === 'artifact-delivery-uncertain') return error;
|
|
259
|
+
const uncertain = new Error('WhatsApp could not confirm result file delivery.');
|
|
260
|
+
uncertain.code = 'artifact-delivery-uncertain';
|
|
261
|
+
uncertain.cause = error;
|
|
262
|
+
return uncertain;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
export class WhatsappBotClient {
|
|
229
266
|
#socket;
|
|
230
267
|
#outboundIds;
|
|
268
|
+
#signal;
|
|
269
|
+
#mediaUploadTimeoutMs;
|
|
231
270
|
#typingTimers = new Map();
|
|
232
271
|
|
|
233
|
-
constructor(socket, outboundIds
|
|
272
|
+
constructor(socket, outboundIds, {
|
|
273
|
+
signal,
|
|
274
|
+
mediaUploadTimeoutMs = WHATSAPP_MEDIA_UPLOAD_TIMEOUT_MS,
|
|
275
|
+
} = {}) {
|
|
234
276
|
this.#socket = socket;
|
|
235
277
|
this.#outboundIds = outboundIds;
|
|
278
|
+
this.#signal = signal;
|
|
279
|
+
this.#mediaUploadTimeoutMs = mediaUploadTimeoutMs;
|
|
236
280
|
}
|
|
237
281
|
|
|
238
282
|
async sendText(target, text) {
|
|
239
283
|
await this.#stopTyping(target.jid);
|
|
240
|
-
|
|
284
|
+
const providerMessageIds = [];
|
|
241
285
|
for (const [index, chunk] of splitMessageText(text, 4_000).entries()) {
|
|
242
|
-
result = await this.#socket.sendMessage(
|
|
286
|
+
const result = await this.#socket.sendMessage(
|
|
243
287
|
target.jid,
|
|
244
288
|
{ text: chunk },
|
|
245
289
|
index === 0 && target.quoted ? { quoted: target.quoted } : undefined,
|
|
246
290
|
);
|
|
247
291
|
this.#outboundIds.remember(result?.key?.id);
|
|
292
|
+
if (typeof result?.key?.id === 'string' && result.key.id) {
|
|
293
|
+
providerMessageIds.push(result.key.id);
|
|
294
|
+
}
|
|
248
295
|
}
|
|
296
|
+
return { providerMessageIds };
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
async sendFile(target, file) {
|
|
300
|
+
this.#signal?.throwIfAborted();
|
|
301
|
+
await this.#stopTyping(target.jid);
|
|
302
|
+
this.#signal?.throwIfAborted();
|
|
303
|
+
const deliverySeed = typeof file.deliveryKey === 'string' && file.deliveryKey
|
|
304
|
+
? file.deliveryKey
|
|
305
|
+
: file.artifactId;
|
|
306
|
+
const messageId = typeof deliverySeed === 'string' && deliverySeed
|
|
307
|
+
? createHash('sha256').update(deliverySeed).digest('hex').slice(0, 20).toUpperCase()
|
|
308
|
+
: undefined;
|
|
309
|
+
const options = {
|
|
310
|
+
...(target.quoted ? { quoted: target.quoted } : {}),
|
|
311
|
+
...(messageId ? { messageId } : {}),
|
|
312
|
+
mediaUploadTimeoutMs: this.#mediaUploadTimeoutMs,
|
|
313
|
+
};
|
|
314
|
+
// Baileys can emit a self-chat echo before sendMessage settles. Reserve the
|
|
315
|
+
// deterministic id before dispatch so that echo cannot re-enter the bridge.
|
|
316
|
+
this.#outboundIds.remember(messageId);
|
|
317
|
+
let result;
|
|
318
|
+
try {
|
|
319
|
+
const pending = this.#socket.sendMessage(
|
|
320
|
+
target.jid,
|
|
321
|
+
{
|
|
322
|
+
document: file.bytes,
|
|
323
|
+
mimetype: file.mediaType ?? 'application/octet-stream',
|
|
324
|
+
fileName: file.fileName,
|
|
325
|
+
},
|
|
326
|
+
options,
|
|
327
|
+
);
|
|
328
|
+
trackOutboundArtifactProviderPromise(file, pending);
|
|
329
|
+
const timeout = AbortSignal.timeout(this.#mediaUploadTimeoutMs);
|
|
330
|
+
const waitSignal = this.#signal
|
|
331
|
+
? AbortSignal.any([this.#signal, timeout])
|
|
332
|
+
: timeout;
|
|
333
|
+
result = await waitWithSignal(pending, waitSignal);
|
|
334
|
+
} catch (error) {
|
|
335
|
+
if (this.#signal?.aborted) throw abortReason(this.#signal);
|
|
336
|
+
throw uncertainArtifactDelivery(error);
|
|
337
|
+
}
|
|
338
|
+
this.#signal?.throwIfAborted();
|
|
339
|
+
this.#outboundIds.remember(result?.key?.id);
|
|
249
340
|
return result;
|
|
250
341
|
}
|
|
251
342
|
|
|
@@ -298,6 +389,7 @@ export class WhatsappRuntime {
|
|
|
298
389
|
#logger;
|
|
299
390
|
#replyTimeoutMs;
|
|
300
391
|
#connectTimeoutMs;
|
|
392
|
+
#mediaUploadTimeoutMs;
|
|
301
393
|
#createSession;
|
|
302
394
|
#status = createWhatsappRuntimeStatus();
|
|
303
395
|
#abortController = null;
|
|
@@ -314,6 +406,7 @@ export class WhatsappRuntime {
|
|
|
314
406
|
logger = console,
|
|
315
407
|
replyTimeoutMs = 600_000,
|
|
316
408
|
connectTimeoutMs = 30_000,
|
|
409
|
+
mediaUploadTimeoutMs = WHATSAPP_MEDIA_UPLOAD_TIMEOUT_MS,
|
|
317
410
|
createSession = createWhatsappWebSession,
|
|
318
411
|
}) {
|
|
319
412
|
if (!config || !authDir || !harness || !state || typeof createSession !== 'function') {
|
|
@@ -326,6 +419,13 @@ export class WhatsappRuntime {
|
|
|
326
419
|
this.#logger = logger;
|
|
327
420
|
this.#replyTimeoutMs = replyTimeoutMs;
|
|
328
421
|
this.#connectTimeoutMs = connectTimeoutMs;
|
|
422
|
+
if (!Number.isSafeInteger(mediaUploadTimeoutMs) || mediaUploadTimeoutMs <= 0) {
|
|
423
|
+
throw new TypeError('mediaUploadTimeoutMs must be a positive safe integer');
|
|
424
|
+
}
|
|
425
|
+
this.#mediaUploadTimeoutMs = Math.min(
|
|
426
|
+
mediaUploadTimeoutMs,
|
|
427
|
+
WHATSAPP_MEDIA_UPLOAD_TIMEOUT_MS,
|
|
428
|
+
);
|
|
329
429
|
this.#createSession = createSession;
|
|
330
430
|
}
|
|
331
431
|
|
|
@@ -390,7 +490,10 @@ export class WhatsappRuntime {
|
|
|
390
490
|
if (!areJidsSameUser(identity.accountJid, this.#config.accountJid)) {
|
|
391
491
|
throw new Error('WhatsApp linked account does not match the saved bot');
|
|
392
492
|
}
|
|
393
|
-
const client = new WhatsappBotClient(session.socket, outboundIds
|
|
493
|
+
const client = new WhatsappBotClient(session.socket, outboundIds, {
|
|
494
|
+
signal: controller.signal,
|
|
495
|
+
mediaUploadTimeoutMs: this.#mediaUploadTimeoutMs,
|
|
496
|
+
});
|
|
394
497
|
this.#client = client;
|
|
395
498
|
this.#bridge = new WhatsappHarnessBridge({
|
|
396
499
|
bot: client,
|