@xmanrui/dsh-im 1.4.0 → 2.0.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 -6
- package/README.md +18 -6
- package/lib/index.js +206 -176
- package/package.json +1 -1
- package/plugin-src/host/index.mjs +2 -0
- package/src/channels/dingtalk/config-store.mjs +5 -3
- package/src/channels/dingtalk/dingtalk-api.mjs +105 -82
- package/src/channels/dingtalk/dingtalk-bridge.mjs +79 -110
- package/src/channels/dingtalk/dingtalk-card-stream.mjs +3 -1
- package/src/channels/dingtalk/dingtalk-controller.mjs +24 -23
- package/src/channels/dingtalk/dingtalk-runtime.mjs +4 -3
- package/src/channels/dingtalk/state-store.mjs +3 -1
- package/src/channels/discord/config-store.mjs +2 -1
- package/src/channels/discord/discord-api.mjs +25 -1
- package/src/channels/discord/discord-runtime.mjs +237 -11
- package/src/channels/feishu/bridge.mjs +169 -180
- package/src/channels/feishu/feishu-cards.mjs +62 -54
- package/src/channels/feishu/feishu-channel.mjs +57 -21
- package/src/channels/feishu/feishu-runtime.mjs +10 -9
- package/src/channels/feishu/message-utils.mjs +4 -3
- package/src/channels/office/office-job-executor.mjs +15 -14
- package/src/channels/office/office-runtime.mjs +6 -5
- package/src/channels/qq/config-store.mjs +3 -1
- package/src/channels/qq/markdown-reply.mjs +4 -2
- package/src/channels/qq/qq-bridge.mjs +149 -130
- package/src/channels/qq/qq-controller.mjs +17 -16
- package/src/channels/qq/qq-runtime.mjs +3 -2
- package/src/channels/shared/agent-preset.mjs +3 -1
- package/src/channels/shared/compact-command.mjs +15 -14
- package/src/channels/shared/connection-test.mjs +6 -4
- package/src/channels/shared/control-command.mjs +13 -11
- package/src/channels/shared/editable-message-stream.mjs +4 -2
- package/src/channels/shared/harness-approval.mjs +23 -21
- package/src/channels/shared/harness-client.mjs +3 -2
- package/src/channels/shared/harness-question.mjs +8 -6
- package/src/channels/shared/i18n-en/dingtalk.mjs +52 -0
- package/src/channels/shared/i18n-en/discord.mjs +8 -0
- package/src/channels/shared/i18n-en/feishu.mjs +217 -0
- package/src/channels/shared/i18n-en/office.mjs +23 -0
- package/src/channels/shared/i18n-en/qq.mjs +54 -0
- package/src/channels/shared/i18n-en/shared-a.mjs +91 -0
- package/src/channels/shared/i18n-en/shared-b.mjs +215 -0
- package/src/channels/shared/i18n-en/shared-c.mjs +99 -0
- package/src/channels/shared/i18n-en/slack.mjs +27 -0
- package/src/channels/shared/i18n-en/telegram.mjs +21 -0
- package/src/channels/shared/i18n-en/wecom.mjs +38 -0
- package/src/channels/shared/i18n-en/weixin.mjs +80 -0
- package/src/channels/shared/i18n-en/whatsapp.mjs +17 -0
- package/src/channels/shared/i18n-en.mjs +35 -0
- package/src/channels/shared/i18n.mjs +35 -0
- package/src/channels/shared/image-prompt.mjs +17 -15
- package/src/channels/shared/inbound-file.mjs +5 -3
- package/src/channels/shared/model-command.mjs +35 -31
- package/src/channels/shared/preset-command.mjs +41 -38
- package/src/channels/shared/semantic/artifact-delivery.mjs +170 -0
- package/src/channels/shared/semantic/artifact.mjs +2 -2
- package/src/channels/shared/semantic/delivery.mjs +40 -0
- package/src/channels/shared/text-harness-bridge.mjs +154 -136
- package/src/channels/shared/token-bot-controller.mjs +32 -11
- package/src/channels/shared/workspace-command.mjs +69 -58
- package/src/channels/slack/config-store.mjs +3 -1
- package/src/channels/slack/slack-api.mjs +6 -5
- package/src/channels/slack/slack-controller.mjs +10 -9
- package/src/channels/slack/slack-runtime.mjs +16 -4
- package/src/channels/telegram/config-store.mjs +2 -1
- package/src/channels/telegram/telegram-api.mjs +111 -19
- package/src/channels/telegram/telegram-rich-message.mjs +147 -0
- package/src/channels/telegram/telegram-runtime.mjs +383 -5
- package/src/channels/wecom/config-store.mjs +3 -1
- package/src/channels/wecom/wecom-bridge.mjs +154 -153
- package/src/channels/wecom/wecom-controller.mjs +20 -19
- package/src/channels/wecom/wecom-runtime.mjs +2 -1
- package/src/channels/weixin/config-store.mjs +2 -1
- package/src/channels/weixin/weixin-api.mjs +140 -107
- package/src/channels/weixin/weixin-bridge.mjs +86 -123
- package/src/channels/weixin/weixin-controller.mjs +22 -21
- package/src/channels/weixin/weixin-runtime.mjs +3 -2
- package/src/channels/whatsapp/config-store.mjs +3 -1
- package/src/channels/whatsapp/whatsapp-bridge.mjs +3 -1
- package/src/channels/whatsapp/whatsapp-controller.mjs +15 -14
- package/src/channels/whatsapp/whatsapp-runtime.mjs +47 -13
- package/src/channels/whatsapp/whatsapp-web-session.mjs +3 -1
|
@@ -1,6 +1,15 @@
|
|
|
1
|
+
import { randomInt } from 'node:crypto';
|
|
2
|
+
|
|
1
3
|
import { createEditableMessageStream, splitMessageText } from '../shared/editable-message-stream.mjs';
|
|
4
|
+
import { createTextDeliveryBlock } from '../shared/semantic/delivery.mjs';
|
|
5
|
+
import { t } from '../shared/i18n.mjs';
|
|
2
6
|
import { COMMANDS_MENU_BUTTON, TelegramApi } from './telegram-api.mjs';
|
|
3
7
|
import { createTelegramBridgeStatus, TelegramHarnessBridge } from './telegram-bridge.mjs';
|
|
8
|
+
import {
|
|
9
|
+
splitTelegramRegularText,
|
|
10
|
+
splitTelegramRichMarkdown,
|
|
11
|
+
toTelegramRichMarkdown,
|
|
12
|
+
} from './telegram-rich-message.mjs';
|
|
4
13
|
import {
|
|
5
14
|
TELEGRAM_ACCESS_MODES,
|
|
6
15
|
normalizeTelegramAccessPolicy,
|
|
@@ -23,6 +32,13 @@ export const TELEGRAM_COMMAND_MENU = Object.freeze([
|
|
|
23
32
|
{ command: 'help', description: '显示帮助' },
|
|
24
33
|
]);
|
|
25
34
|
|
|
35
|
+
export function telegramCommandMenu() {
|
|
36
|
+
return TELEGRAM_COMMAND_MENU.map((item) => ({
|
|
37
|
+
...item,
|
|
38
|
+
description: t(item.description),
|
|
39
|
+
}));
|
|
40
|
+
}
|
|
41
|
+
|
|
26
42
|
function escaped(value) {
|
|
27
43
|
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
28
44
|
}
|
|
@@ -149,6 +165,7 @@ export function normalizeTelegramUpdate(update, {
|
|
|
149
165
|
addressed,
|
|
150
166
|
replyTarget: {
|
|
151
167
|
chatId,
|
|
168
|
+
chatType: message.chat.type,
|
|
152
169
|
replyToMessageId: messageId,
|
|
153
170
|
messageThreadId,
|
|
154
171
|
},
|
|
@@ -166,17 +183,105 @@ export function telegramInboundAllowed(message, {
|
|
|
166
183
|
&& allowedPrivateUserIds.has(String(message.senderId));
|
|
167
184
|
}
|
|
168
185
|
|
|
186
|
+
function telegramMessageId(value) {
|
|
187
|
+
return Number.isSafeInteger(value?.message_id) ? String(value.message_id) : null;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function telegramFailure(error) {
|
|
191
|
+
const providerCode = Number(error?.providerCode);
|
|
192
|
+
const status = Number(error?.status);
|
|
193
|
+
const unknown = error?.deliveryOutcome === 'unknown'
|
|
194
|
+
|| providerCode >= 500
|
|
195
|
+
|| status >= 500
|
|
196
|
+
|| ['telegram-timeout', 'telegram-aborted-after-dispatch', 'telegram-transport-error',
|
|
197
|
+
'telegram-response-invalid'].includes(error?.code);
|
|
198
|
+
return {
|
|
199
|
+
outcome: unknown ? 'unknown' : 'failed',
|
|
200
|
+
reason: typeof error?.code === 'string' && error.code
|
|
201
|
+
? error.code
|
|
202
|
+
: unknown ? 'telegram-delivery-uncertain' : 'telegram-provider-rejected',
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function deliveryResult(presentation, providerMessageIds, deliveryOutcome = 'sent', reason) {
|
|
207
|
+
return {
|
|
208
|
+
presentation,
|
|
209
|
+
providerMessageIds: [...new Set(providerMessageIds)],
|
|
210
|
+
deliveryOutcome,
|
|
211
|
+
...(reason ? { reason } : {}),
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
class TelegramDeliveryStream {
|
|
216
|
+
#update;
|
|
217
|
+
#finish;
|
|
218
|
+
#fail;
|
|
219
|
+
#logger;
|
|
220
|
+
#providerMessageIds;
|
|
221
|
+
#closed = false;
|
|
222
|
+
#lastUpdate = null;
|
|
223
|
+
|
|
224
|
+
constructor({ update, finish, fail, providerMessageIds = [], presentation, logger }) {
|
|
225
|
+
this.#update = update;
|
|
226
|
+
this.#finish = finish;
|
|
227
|
+
this.#fail = fail;
|
|
228
|
+
this.#providerMessageIds = providerMessageIds;
|
|
229
|
+
this.presentation = presentation;
|
|
230
|
+
this.#logger = logger;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
get providerMessageIds() {
|
|
234
|
+
return [...this.#providerMessageIds];
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
async update(value) {
|
|
238
|
+
if (this.#closed) return undefined;
|
|
239
|
+
const block = createTextDeliveryBlock(value);
|
|
240
|
+
const key = `${block.format}:${block.text}`;
|
|
241
|
+
if (key === this.#lastUpdate) return undefined;
|
|
242
|
+
this.#lastUpdate = key;
|
|
243
|
+
try {
|
|
244
|
+
return await this.#update(block);
|
|
245
|
+
} catch (error) {
|
|
246
|
+
this.#logger.warn?.('[dsh-im:telegram] rich stream update failed:', error);
|
|
247
|
+
return undefined;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
async finish(value) {
|
|
252
|
+
if (this.#closed) throw new Error('Message stream is already closed');
|
|
253
|
+
this.#closed = true;
|
|
254
|
+
const result = await this.#finish(createTextDeliveryBlock(value));
|
|
255
|
+
this.#providerMessageIds.push(...(result?.providerMessageIds ?? []));
|
|
256
|
+
return result;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
async fail(text) {
|
|
260
|
+
if (this.#closed) return undefined;
|
|
261
|
+
this.#closed = true;
|
|
262
|
+
const result = await this.#fail(createTextDeliveryBlock(text, 'plain'));
|
|
263
|
+
this.#providerMessageIds.push(...(result?.providerMessageIds ?? []));
|
|
264
|
+
return result;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
cancel() {
|
|
268
|
+
this.#closed = true;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
169
272
|
export class TelegramBotClient {
|
|
170
273
|
#api;
|
|
171
274
|
#signal;
|
|
275
|
+
#logger;
|
|
172
276
|
|
|
173
|
-
constructor({ api, signal }) {
|
|
277
|
+
constructor({ api, signal, logger = console }) {
|
|
174
278
|
this.#api = api;
|
|
175
279
|
this.#signal = signal;
|
|
280
|
+
this.#logger = logger;
|
|
176
281
|
}
|
|
177
282
|
|
|
178
283
|
async sendText(target, text) {
|
|
179
|
-
const chunks =
|
|
284
|
+
const chunks = splitTelegramRegularText(text);
|
|
180
285
|
const providerMessageIds = [];
|
|
181
286
|
for (const [index, chunk] of chunks.entries()) {
|
|
182
287
|
const result = await this.#api.sendMessage({
|
|
@@ -211,6 +316,275 @@ export class TelegramBotClient {
|
|
|
211
316
|
});
|
|
212
317
|
}
|
|
213
318
|
|
|
319
|
+
sendImage(target, file) {
|
|
320
|
+
return this.#api.sendPhoto({
|
|
321
|
+
chatId: target.chatId,
|
|
322
|
+
file,
|
|
323
|
+
replyToMessageId: target.replyToMessageId,
|
|
324
|
+
messageThreadId: target.messageThreadId,
|
|
325
|
+
signal: this.#signal,
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
async #sendPlain(target, text, { placeholderMessageId } = {}) {
|
|
330
|
+
const chunks = splitTelegramRegularText(text);
|
|
331
|
+
const providerMessageIds = placeholderMessageId === undefined
|
|
332
|
+
? []
|
|
333
|
+
: [String(placeholderMessageId)];
|
|
334
|
+
let firstUnsent = 0;
|
|
335
|
+
if (placeholderMessageId !== undefined) {
|
|
336
|
+
try {
|
|
337
|
+
await this.#api.editMessageText({
|
|
338
|
+
chatId: target.chatId,
|
|
339
|
+
messageId: placeholderMessageId,
|
|
340
|
+
text: chunks[0],
|
|
341
|
+
signal: this.#signal,
|
|
342
|
+
});
|
|
343
|
+
firstUnsent = 1;
|
|
344
|
+
} catch (error) {
|
|
345
|
+
const failure = telegramFailure(error);
|
|
346
|
+
if (failure.outcome === 'unknown') {
|
|
347
|
+
return deliveryResult('text-fallback', providerMessageIds, 'unknown', failure.reason);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
const fallback = await this.#sendPlain(target, text);
|
|
351
|
+
const terminalText = fallback.deliveryOutcome === 'sent'
|
|
352
|
+
? '回复已发送。'
|
|
353
|
+
: fallback.deliveryOutcome === 'unknown'
|
|
354
|
+
? '回复发送结果未能确认。'
|
|
355
|
+
: '消息发送失败,请稍后重试。';
|
|
356
|
+
try {
|
|
357
|
+
await this.#api.editMessageText({
|
|
358
|
+
chatId: target.chatId,
|
|
359
|
+
messageId: placeholderMessageId,
|
|
360
|
+
text: terminalText,
|
|
361
|
+
signal: this.#signal,
|
|
362
|
+
});
|
|
363
|
+
} catch (terminalError) {
|
|
364
|
+
this.#logger.warn?.(
|
|
365
|
+
'[dsh-im:telegram] unable to replace the processing placeholder:',
|
|
366
|
+
terminalError,
|
|
367
|
+
);
|
|
368
|
+
}
|
|
369
|
+
return deliveryResult(
|
|
370
|
+
'text-fallback',
|
|
371
|
+
[...providerMessageIds, ...fallback.providerMessageIds],
|
|
372
|
+
fallback.deliveryOutcome,
|
|
373
|
+
fallback.reason,
|
|
374
|
+
);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
for (let index = firstUnsent; index < chunks.length; index += 1) {
|
|
379
|
+
try {
|
|
380
|
+
const result = await this.#api.sendMessage({
|
|
381
|
+
chatId: target.chatId,
|
|
382
|
+
text: chunks[index],
|
|
383
|
+
replyToMessageId: index === 0 ? target.replyToMessageId : undefined,
|
|
384
|
+
messageThreadId: target.messageThreadId,
|
|
385
|
+
signal: this.#signal,
|
|
386
|
+
});
|
|
387
|
+
const id = telegramMessageId(result);
|
|
388
|
+
if (id) providerMessageIds.push(id);
|
|
389
|
+
} catch (error) {
|
|
390
|
+
const failure = telegramFailure(error);
|
|
391
|
+
return deliveryResult(
|
|
392
|
+
'text-fallback',
|
|
393
|
+
providerMessageIds,
|
|
394
|
+
failure.outcome,
|
|
395
|
+
failure.reason,
|
|
396
|
+
);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
return deliveryResult('text-fallback', providerMessageIds);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
async #sendRich(target, block) {
|
|
403
|
+
if (block.format === 'plain') return this.#sendPlain(target, block.text);
|
|
404
|
+
|
|
405
|
+
let chunks;
|
|
406
|
+
try {
|
|
407
|
+
chunks = splitTelegramRichMarkdown(block.text);
|
|
408
|
+
} catch {
|
|
409
|
+
return this.#sendPlain(target, block.text);
|
|
410
|
+
}
|
|
411
|
+
const providerMessageIds = [];
|
|
412
|
+
for (const [index, chunk] of chunks.entries()) {
|
|
413
|
+
try {
|
|
414
|
+
const result = await this.#api.sendRichMessage({
|
|
415
|
+
chatId: target.chatId,
|
|
416
|
+
richMessage: { markdown: chunk.markdown },
|
|
417
|
+
replyToMessageId: index === 0 ? target.replyToMessageId : undefined,
|
|
418
|
+
messageThreadId: target.messageThreadId,
|
|
419
|
+
signal: this.#signal,
|
|
420
|
+
});
|
|
421
|
+
const id = telegramMessageId(result);
|
|
422
|
+
if (id) providerMessageIds.push(id);
|
|
423
|
+
} catch (error) {
|
|
424
|
+
const failure = telegramFailure(error);
|
|
425
|
+
if (failure.outcome === 'unknown') {
|
|
426
|
+
return deliveryResult(
|
|
427
|
+
'telegram-rich-final',
|
|
428
|
+
providerMessageIds,
|
|
429
|
+
'unknown',
|
|
430
|
+
failure.reason,
|
|
431
|
+
);
|
|
432
|
+
}
|
|
433
|
+
const remaining = chunks.slice(index).map((part) => part.source).join('');
|
|
434
|
+
const fallback = await this.#sendPlain({
|
|
435
|
+
...target,
|
|
436
|
+
replyToMessageId: index === 0 ? target.replyToMessageId : undefined,
|
|
437
|
+
}, remaining);
|
|
438
|
+
return deliveryResult(
|
|
439
|
+
fallback.presentation,
|
|
440
|
+
[...providerMessageIds, ...fallback.providerMessageIds],
|
|
441
|
+
fallback.deliveryOutcome,
|
|
442
|
+
fallback.reason,
|
|
443
|
+
);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
return deliveryResult('telegram-rich-final', providerMessageIds);
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
async #editRich(target, messageId, block) {
|
|
450
|
+
if (block.format === 'plain') {
|
|
451
|
+
return this.#sendPlain(target, block.text, {
|
|
452
|
+
placeholderMessageId: messageId,
|
|
453
|
+
});
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
let chunks;
|
|
457
|
+
try {
|
|
458
|
+
chunks = splitTelegramRichMarkdown(block.text);
|
|
459
|
+
} catch {
|
|
460
|
+
return this.#sendPlain(target, block.text, { placeholderMessageId: messageId });
|
|
461
|
+
}
|
|
462
|
+
const providerMessageIds = [String(messageId)];
|
|
463
|
+
try {
|
|
464
|
+
await this.#api.editMessageText({
|
|
465
|
+
chatId: target.chatId,
|
|
466
|
+
messageId,
|
|
467
|
+
richMessage: { markdown: chunks[0].markdown },
|
|
468
|
+
signal: this.#signal,
|
|
469
|
+
});
|
|
470
|
+
} catch (error) {
|
|
471
|
+
const failure = telegramFailure(error);
|
|
472
|
+
if (failure.outcome === 'unknown') {
|
|
473
|
+
return deliveryResult(
|
|
474
|
+
'telegram-rich-final',
|
|
475
|
+
providerMessageIds,
|
|
476
|
+
'unknown',
|
|
477
|
+
failure.reason,
|
|
478
|
+
);
|
|
479
|
+
}
|
|
480
|
+
return this.#sendPlain(target, block.text, { placeholderMessageId: messageId });
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
for (const [offset, chunk] of chunks.slice(1).entries()) {
|
|
484
|
+
try {
|
|
485
|
+
const result = await this.#api.sendRichMessage({
|
|
486
|
+
chatId: target.chatId,
|
|
487
|
+
richMessage: { markdown: chunk.markdown },
|
|
488
|
+
messageThreadId: target.messageThreadId,
|
|
489
|
+
signal: this.#signal,
|
|
490
|
+
});
|
|
491
|
+
const id = telegramMessageId(result);
|
|
492
|
+
if (id) providerMessageIds.push(id);
|
|
493
|
+
} catch (error) {
|
|
494
|
+
const failure = telegramFailure(error);
|
|
495
|
+
if (failure.outcome === 'unknown') {
|
|
496
|
+
return deliveryResult(
|
|
497
|
+
'telegram-rich-final',
|
|
498
|
+
providerMessageIds,
|
|
499
|
+
'unknown',
|
|
500
|
+
failure.reason,
|
|
501
|
+
);
|
|
502
|
+
}
|
|
503
|
+
const remaining = chunks.slice(offset + 1).map((part) => part.source).join('');
|
|
504
|
+
const fallback = await this.#sendPlain({
|
|
505
|
+
...target,
|
|
506
|
+
replyToMessageId: undefined,
|
|
507
|
+
}, remaining);
|
|
508
|
+
return deliveryResult(
|
|
509
|
+
fallback.presentation,
|
|
510
|
+
[...providerMessageIds, ...fallback.providerMessageIds],
|
|
511
|
+
fallback.deliveryOutcome,
|
|
512
|
+
fallback.reason,
|
|
513
|
+
);
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
return deliveryResult('telegram-rich-final', providerMessageIds);
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
sendDelivery(target, value) {
|
|
520
|
+
return this.#sendRich(target, createTextDeliveryBlock(value));
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
async openDeliveryStream(target) {
|
|
524
|
+
if (target.chatType === 'private') {
|
|
525
|
+
const draftId = randomInt(1, 2_147_483_647);
|
|
526
|
+
const updateDraft = async (block) => {
|
|
527
|
+
const richMessage = { markdown: toTelegramRichMarkdown(block.text) };
|
|
528
|
+
await this.#api.sendRichMessageDraft({
|
|
529
|
+
chatId: target.chatId,
|
|
530
|
+
draftId,
|
|
531
|
+
richMessage,
|
|
532
|
+
messageThreadId: target.messageThreadId,
|
|
533
|
+
signal: this.#signal,
|
|
534
|
+
});
|
|
535
|
+
return deliveryResult('telegram-rich-draft', []);
|
|
536
|
+
};
|
|
537
|
+
const stream = new TelegramDeliveryStream({
|
|
538
|
+
update: updateDraft,
|
|
539
|
+
finish: (block) => this.#sendRich(target, block),
|
|
540
|
+
fail: (block) => this.#sendPlain(target, block.text),
|
|
541
|
+
presentation: 'telegram-rich-draft',
|
|
542
|
+
logger: this.#logger,
|
|
543
|
+
});
|
|
544
|
+
await stream.update(createTextDeliveryBlock('正在处理…', 'plain'));
|
|
545
|
+
return stream;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
const placeholder = await this.#api.sendMessage({
|
|
549
|
+
chatId: target.chatId,
|
|
550
|
+
text: '正在处理…',
|
|
551
|
+
replyToMessageId: target.replyToMessageId,
|
|
552
|
+
messageThreadId: target.messageThreadId,
|
|
553
|
+
signal: this.#signal,
|
|
554
|
+
});
|
|
555
|
+
const messageId = placeholder?.message_id;
|
|
556
|
+
if (!Number.isSafeInteger(messageId)) {
|
|
557
|
+
throw new Error('Telegram did not return a placeholder message id');
|
|
558
|
+
}
|
|
559
|
+
return new TelegramDeliveryStream({
|
|
560
|
+
update: async (block) => {
|
|
561
|
+
if (block.format === 'plain') {
|
|
562
|
+
await this.#api.editMessageText({
|
|
563
|
+
chatId: target.chatId,
|
|
564
|
+
messageId,
|
|
565
|
+
text: block.text,
|
|
566
|
+
signal: this.#signal,
|
|
567
|
+
});
|
|
568
|
+
return deliveryResult('text-fallback', [String(messageId)]);
|
|
569
|
+
}
|
|
570
|
+
await this.#api.editMessageText({
|
|
571
|
+
chatId: target.chatId,
|
|
572
|
+
messageId,
|
|
573
|
+
richMessage: { markdown: toTelegramRichMarkdown(block.text) },
|
|
574
|
+
signal: this.#signal,
|
|
575
|
+
});
|
|
576
|
+
return deliveryResult('telegram-rich-draft', [String(messageId)]);
|
|
577
|
+
},
|
|
578
|
+
finish: (block) => this.#editRich(target, messageId, block),
|
|
579
|
+
fail: (block) => this.#sendPlain(target, block.text, {
|
|
580
|
+
placeholderMessageId: messageId,
|
|
581
|
+
}),
|
|
582
|
+
providerMessageIds: [String(messageId)],
|
|
583
|
+
presentation: 'telegram-regular',
|
|
584
|
+
logger: this.#logger,
|
|
585
|
+
});
|
|
586
|
+
}
|
|
587
|
+
|
|
214
588
|
async openStream(target) {
|
|
215
589
|
const stream = createEditableMessageStream({
|
|
216
590
|
limit: 4_000,
|
|
@@ -337,12 +711,12 @@ export class TelegramRuntime {
|
|
|
337
711
|
}
|
|
338
712
|
const webhook = await api.getWebhookInfo({ signal: controller.signal });
|
|
339
713
|
if (typeof webhook?.url === 'string' && webhook.url) {
|
|
340
|
-
const error = new Error('该 Telegram 机器人已配置 Webhook,请先在原服务中移除 Webhook 后重试。');
|
|
714
|
+
const error = new Error(t('该 Telegram 机器人已配置 Webhook,请先在原服务中移除 Webhook 后重试。'));
|
|
341
715
|
error.code = 'webhook-configured';
|
|
342
716
|
throw error;
|
|
343
717
|
}
|
|
344
718
|
try {
|
|
345
|
-
await api.setMyCommands({ commands:
|
|
719
|
+
await api.setMyCommands({ commands: telegramCommandMenu(), signal: controller.signal });
|
|
346
720
|
await api.setChatMenuButton({ menuButton: COMMANDS_MENU_BUTTON, signal: controller.signal });
|
|
347
721
|
} catch (error) {
|
|
348
722
|
this.#logger.warn?.(
|
|
@@ -350,7 +724,11 @@ export class TelegramRuntime {
|
|
|
350
724
|
error,
|
|
351
725
|
);
|
|
352
726
|
}
|
|
353
|
-
const client = new TelegramBotClient({
|
|
727
|
+
const client = new TelegramBotClient({
|
|
728
|
+
api,
|
|
729
|
+
signal: controller.signal,
|
|
730
|
+
logger: this.#logger,
|
|
731
|
+
});
|
|
354
732
|
this.#bridge = new TelegramHarnessBridge({
|
|
355
733
|
bot: client,
|
|
356
734
|
harness: this.#harness,
|
|
@@ -2,6 +2,8 @@ import { createHash } from 'node:crypto';
|
|
|
2
2
|
import { mkdir, readFile, rename, unlink, writeFile } from 'node:fs/promises';
|
|
3
3
|
import { dirname } from 'node:path';
|
|
4
4
|
|
|
5
|
+
import { t } from '../shared/i18n.mjs';
|
|
6
|
+
|
|
5
7
|
const EMPTY_DOCUMENT = Object.freeze({ version: 1, bots: Object.freeze([]) });
|
|
6
8
|
|
|
7
9
|
function cleanString(value) {
|
|
@@ -30,7 +32,7 @@ export function deriveWecomBotIdentity(remoteBotId) {
|
|
|
30
32
|
|
|
31
33
|
export function maskWecomBotId(remoteBotId) {
|
|
32
34
|
const value = cleanString(remoteBotId) ?? '';
|
|
33
|
-
if (!value) return '企业微信机器人';
|
|
35
|
+
if (!value) return t('企业微信机器人');
|
|
34
36
|
if (value.length <= 10) return `${value.slice(0, 3)}•••`;
|
|
35
37
|
return `${value.slice(0, 6)}••••${value.slice(-4)}`;
|
|
36
38
|
}
|