@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,5 +1,6 @@
|
|
|
1
1
|
import { fetchFileStream } from '../shared/file-download.mjs';
|
|
2
2
|
import { fetchImageBuffer } from '../shared/image-prompt.mjs';
|
|
3
|
+
import { t } from '../shared/i18n.mjs';
|
|
3
4
|
|
|
4
5
|
const DEFAULT_BASE_URL = 'https://api.telegram.org/';
|
|
5
6
|
const TELEGRAM_FILE_HOSTS = Object.freeze(['api.telegram.org']);
|
|
@@ -35,33 +36,52 @@ function preserveProviderMetadata(target, source) {
|
|
|
35
36
|
return target;
|
|
36
37
|
}
|
|
37
38
|
|
|
38
|
-
function
|
|
39
|
+
function inputRichMessage(value) {
|
|
40
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
41
|
+
throw new TypeError('A Telegram rich message is required');
|
|
42
|
+
}
|
|
43
|
+
const formats = ['markdown', 'html', 'blocks'].filter((key) => value[key] !== undefined);
|
|
44
|
+
if (formats.length !== 1) {
|
|
45
|
+
throw new TypeError('A Telegram rich message requires exactly one format');
|
|
46
|
+
}
|
|
47
|
+
const selected = formats[0];
|
|
48
|
+
if ((selected === 'markdown' || selected === 'html')
|
|
49
|
+
&& (typeof value[selected] !== 'string' || !value[selected].trim())) {
|
|
50
|
+
throw new TypeError('Telegram rich message text is invalid');
|
|
51
|
+
}
|
|
52
|
+
if (selected === 'blocks' && !Array.isArray(value.blocks)) {
|
|
53
|
+
throw new TypeError('Telegram rich message blocks are invalid');
|
|
54
|
+
}
|
|
55
|
+
return value;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function telegramArtifactProviderError(cause, mediaLabel = 'document') {
|
|
39
59
|
const providerCode = Number(cause?.providerCode);
|
|
40
60
|
const status = Number(cause?.status);
|
|
41
61
|
const message = cleanString(cause?.message) ?? '';
|
|
42
62
|
let code = 'artifact-provider-rejected';
|
|
43
|
-
let summary =
|
|
63
|
+
let summary = `Telegram rejected the ${mediaLabel}.`;
|
|
44
64
|
if (providerCode === 401 || providerCode === 403 || status === 401 || status === 403) {
|
|
45
65
|
code = 'artifact-permission-required';
|
|
46
|
-
summary =
|
|
66
|
+
summary = `Telegram denied permission to send the ${mediaLabel}.`;
|
|
47
67
|
} else if (providerCode === 413 || status === 413
|
|
48
68
|
|| /(?:file|request|entity).{0,20}(?:too (?:big|large)|size limit)|too (?:big|large)/i.test(message)) {
|
|
49
69
|
code = 'artifact-too-large';
|
|
50
|
-
summary =
|
|
70
|
+
summary = `The ${mediaLabel} exceeds Telegram's size limit.`;
|
|
51
71
|
} else if (providerCode === 429 || status === 429) {
|
|
52
72
|
code = 'artifact-rate-limited';
|
|
53
|
-
summary =
|
|
73
|
+
summary = `Telegram rate-limited ${mediaLabel} delivery.`;
|
|
54
74
|
} else if (providerCode >= 500 || status >= 500) {
|
|
55
75
|
code = 'artifact-delivery-uncertain';
|
|
56
|
-
summary =
|
|
76
|
+
summary = `Telegram ${mediaLabel} delivery result is uncertain.`;
|
|
57
77
|
}
|
|
58
78
|
const error = new Error(summary, { cause });
|
|
59
79
|
error.code = code;
|
|
60
80
|
return preserveProviderMetadata(error, cause);
|
|
61
81
|
}
|
|
62
82
|
|
|
63
|
-
function uncertainTelegramDelivery(cause) {
|
|
64
|
-
const error = new Error(
|
|
83
|
+
function uncertainTelegramDelivery(cause, mediaLabel = 'document') {
|
|
84
|
+
const error = new Error(`Telegram ${mediaLabel} delivery result is uncertain`, { cause });
|
|
65
85
|
error.code = 'artifact-delivery-uncertain';
|
|
66
86
|
return preserveProviderMetadata(error, cause);
|
|
67
87
|
}
|
|
@@ -183,16 +203,71 @@ export class TelegramApi {
|
|
|
183
203
|
}, { signal });
|
|
184
204
|
}
|
|
185
205
|
|
|
206
|
+
async sendRichMessage({
|
|
207
|
+
chatId,
|
|
208
|
+
richMessage,
|
|
209
|
+
replyToMessageId,
|
|
210
|
+
messageThreadId,
|
|
211
|
+
signal,
|
|
212
|
+
}) {
|
|
213
|
+
return this.#call('sendRichMessage', {
|
|
214
|
+
chat_id: chatId,
|
|
215
|
+
rich_message: inputRichMessage(richMessage),
|
|
216
|
+
...(replyToMessageId ? {
|
|
217
|
+
reply_parameters: { message_id: replyToMessageId, allow_sending_without_reply: true },
|
|
218
|
+
} : {}),
|
|
219
|
+
...(messageThreadId ? { message_thread_id: messageThreadId } : {}),
|
|
220
|
+
}, { signal });
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
async sendRichMessageDraft({ chatId, draftId, richMessage, messageThreadId, signal }) {
|
|
224
|
+
if (!Number.isSafeInteger(draftId) || draftId === 0) {
|
|
225
|
+
throw new TypeError('Telegram rich message draft id must be a non-zero integer');
|
|
226
|
+
}
|
|
227
|
+
return this.#call('sendRichMessageDraft', {
|
|
228
|
+
chat_id: chatId,
|
|
229
|
+
draft_id: draftId,
|
|
230
|
+
rich_message: inputRichMessage(richMessage),
|
|
231
|
+
...(messageThreadId ? { message_thread_id: messageThreadId } : {}),
|
|
232
|
+
}, { signal });
|
|
233
|
+
}
|
|
234
|
+
|
|
186
235
|
async sendDocument({ chatId, file, replyToMessageId, messageThreadId, signal }) {
|
|
236
|
+
return this.#sendArtifact('sendDocument', 'document', 'document', {
|
|
237
|
+
chatId,
|
|
238
|
+
file,
|
|
239
|
+
replyToMessageId,
|
|
240
|
+
messageThreadId,
|
|
241
|
+
signal,
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
async sendPhoto({ chatId, file, replyToMessageId, messageThreadId, signal }) {
|
|
246
|
+
return this.#sendArtifact('sendPhoto', 'photo', 'photo', {
|
|
247
|
+
chatId,
|
|
248
|
+
file,
|
|
249
|
+
replyToMessageId,
|
|
250
|
+
messageThreadId,
|
|
251
|
+
signal,
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
async #sendArtifact(method, fieldName, mediaLabel, {
|
|
256
|
+
chatId,
|
|
257
|
+
file,
|
|
258
|
+
replyToMessageId,
|
|
259
|
+
messageThreadId,
|
|
260
|
+
signal,
|
|
261
|
+
}) {
|
|
187
262
|
if (!file || typeof file !== 'object'
|
|
188
263
|
|| typeof file.fileName !== 'string' || !file.fileName
|
|
189
264
|
|| !Buffer.isBuffer(file.bytes)) {
|
|
190
|
-
throw new TypeError(
|
|
265
|
+
throw new TypeError(`A Telegram ${mediaLabel} is required`);
|
|
191
266
|
}
|
|
192
267
|
const payload = new FormData();
|
|
193
268
|
payload.append('chat_id', String(chatId));
|
|
194
269
|
payload.append(
|
|
195
|
-
|
|
270
|
+
fieldName,
|
|
196
271
|
new Blob([file.bytes], { type: file.mediaType ?? 'application/octet-stream' }),
|
|
197
272
|
file.fileName,
|
|
198
273
|
);
|
|
@@ -206,26 +281,34 @@ export class TelegramApi {
|
|
|
206
281
|
if (signal?.aborted) throw abortReason(signal);
|
|
207
282
|
const uploadSignal = requestSignal(signal, this.#fileUploadTimeoutMs);
|
|
208
283
|
try {
|
|
209
|
-
return await this.#call(
|
|
284
|
+
return await this.#call(method, payload, {
|
|
210
285
|
signal: uploadSignal,
|
|
211
286
|
timeoutMs: this.#fileUploadTimeoutMs,
|
|
212
287
|
multipart: true,
|
|
213
288
|
});
|
|
214
289
|
} catch (error) {
|
|
215
290
|
if (signal?.aborted) throw abortReason(signal);
|
|
291
|
+
if (error?.deliveryOutcome === 'unknown') {
|
|
292
|
+
throw uncertainTelegramDelivery(error?.cause ?? error, mediaLabel);
|
|
293
|
+
}
|
|
216
294
|
if (error?.code?.startsWith?.('telegram-')) {
|
|
217
|
-
throw telegramArtifactProviderError(error);
|
|
295
|
+
throw telegramArtifactProviderError(error, mediaLabel);
|
|
218
296
|
}
|
|
219
|
-
throw uncertainTelegramDelivery(error);
|
|
297
|
+
throw uncertainTelegramDelivery(error, mediaLabel);
|
|
220
298
|
}
|
|
221
299
|
}
|
|
222
300
|
|
|
223
|
-
async editMessageText({ chatId, messageId, text, signal }) {
|
|
301
|
+
async editMessageText({ chatId, messageId, text, richMessage, signal }) {
|
|
302
|
+
if ((text === undefined) === (richMessage === undefined)) {
|
|
303
|
+
throw new TypeError('Telegram message edit requires exactly one of text or richMessage');
|
|
304
|
+
}
|
|
224
305
|
return this.#call('editMessageText', {
|
|
225
306
|
chat_id: chatId,
|
|
226
307
|
message_id: messageId,
|
|
227
|
-
text
|
|
228
|
-
|
|
308
|
+
...(text === undefined ? { rich_message: inputRichMessage(richMessage) } : {
|
|
309
|
+
text,
|
|
310
|
+
link_preview_options: { is_disabled: true },
|
|
311
|
+
}),
|
|
229
312
|
}, { signal });
|
|
230
313
|
}
|
|
231
314
|
|
|
@@ -257,6 +340,7 @@ export class TelegramApi {
|
|
|
257
340
|
}
|
|
258
341
|
|
|
259
342
|
async #call(method, payload, { signal, timeoutMs = 15_000, multipart = false } = {}) {
|
|
343
|
+
if (signal?.aborted) throw abortReason(signal);
|
|
260
344
|
const url = new URL(this.#baseUrl);
|
|
261
345
|
url.pathname = `${url.pathname.replace(/\/$/, '')}/bot${this.#token}/${method}`;
|
|
262
346
|
let response;
|
|
@@ -269,8 +353,14 @@ export class TelegramApi {
|
|
|
269
353
|
redirect: 'error',
|
|
270
354
|
});
|
|
271
355
|
} catch (error) {
|
|
272
|
-
|
|
273
|
-
|
|
356
|
+
const transport = new Error(`Telegram ${method} transport failed`, { cause: error });
|
|
357
|
+
transport.code = error?.name === 'TimeoutError'
|
|
358
|
+
? 'telegram-timeout'
|
|
359
|
+
: error?.name === 'AbortError'
|
|
360
|
+
? 'telegram-aborted-after-dispatch'
|
|
361
|
+
: 'telegram-transport-error';
|
|
362
|
+
transport.deliveryOutcome = 'unknown';
|
|
363
|
+
throw transport;
|
|
274
364
|
}
|
|
275
365
|
let body;
|
|
276
366
|
try {
|
|
@@ -278,6 +368,8 @@ export class TelegramApi {
|
|
|
278
368
|
} catch {
|
|
279
369
|
const error = new Error(`Telegram ${method} returned invalid JSON`);
|
|
280
370
|
error.status = response?.status;
|
|
371
|
+
error.code = 'telegram-response-invalid';
|
|
372
|
+
error.deliveryOutcome = 'unknown';
|
|
281
373
|
throw error;
|
|
282
374
|
}
|
|
283
375
|
if (!response.ok || body?.ok !== true) {
|
|
@@ -303,7 +395,7 @@ export async function inspectTelegramToken(token, options = {}) {
|
|
|
303
395
|
if (!bot?.id || bot?.is_bot !== true) throw new Error('Telegram token does not belong to a bot');
|
|
304
396
|
return {
|
|
305
397
|
platformId: String(bot.id),
|
|
306
|
-
name: cleanString([bot.first_name, bot.last_name].filter(Boolean).join(' ')) ?? 'Telegram机器人',
|
|
398
|
+
name: cleanString([bot.first_name, bot.last_name].filter(Boolean).join(' ')) ?? t('Telegram机器人'),
|
|
307
399
|
username: cleanString(bot.username),
|
|
308
400
|
};
|
|
309
401
|
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
export const TELEGRAM_RICH_TEXT_LIMIT = 30_000;
|
|
2
|
+
export const TELEGRAM_REGULAR_TEXT_LIMIT = 4_000;
|
|
3
|
+
|
|
4
|
+
function textValue(value) {
|
|
5
|
+
if (typeof value !== 'string' || !value.trim()) {
|
|
6
|
+
throw new TypeError('Telegram message text must be a non-empty string');
|
|
7
|
+
}
|
|
8
|
+
return value;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function escapedCharacterLength(value) {
|
|
12
|
+
if (value === '&') return 5;
|
|
13
|
+
if (value === '<' || value === '>') return 4;
|
|
14
|
+
return 1;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function escapedLength(value) {
|
|
18
|
+
return Array.from(value).reduce((total, character) => (
|
|
19
|
+
total + escapedCharacterLength(character)
|
|
20
|
+
), 0);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function preferredCut(points, offset, hardEnd, limit) {
|
|
24
|
+
const minimum = offset + Math.floor(limit * 0.5);
|
|
25
|
+
for (let index = hardEnd - 1; index >= minimum; index -= 1) {
|
|
26
|
+
if (points[index] === '\n' || points[index] === ' ') return index + 1;
|
|
27
|
+
}
|
|
28
|
+
return hardEnd;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function splitText(value, limit, characterLength = () => 1) {
|
|
32
|
+
if (typeof value !== 'string') throw new TypeError('Telegram message text must be a string');
|
|
33
|
+
if (!value) return [];
|
|
34
|
+
const points = Array.from(value);
|
|
35
|
+
const chunks = [];
|
|
36
|
+
let offset = 0;
|
|
37
|
+
while (offset < points.length) {
|
|
38
|
+
let end = offset;
|
|
39
|
+
let length = 0;
|
|
40
|
+
while (end < points.length) {
|
|
41
|
+
const nextLength = characterLength(points[end]);
|
|
42
|
+
if (length + nextLength > limit) break;
|
|
43
|
+
length += nextLength;
|
|
44
|
+
end += 1;
|
|
45
|
+
}
|
|
46
|
+
if (end === offset) throw new Error('Telegram message character exceeds the chunk limit');
|
|
47
|
+
if (end < points.length) end = preferredCut(points, offset, end, limit);
|
|
48
|
+
chunks.push(points.slice(offset, end).join(''));
|
|
49
|
+
offset = end;
|
|
50
|
+
}
|
|
51
|
+
return chunks;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function fenceMarkers(markdown) {
|
|
55
|
+
return [...markdown.matchAll(/^```[^\n]*(?:\n|$)/gm)];
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function assertCompleteFences(markdown) {
|
|
59
|
+
const markers = fenceMarkers(markdown);
|
|
60
|
+
if (markers.length % 2 !== 0) {
|
|
61
|
+
throw new Error('Telegram Rich Markdown contains an unfinished code fence');
|
|
62
|
+
}
|
|
63
|
+
return markers;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function escapedMarkdown(value) {
|
|
67
|
+
return value
|
|
68
|
+
.replaceAll('&', '&')
|
|
69
|
+
.replaceAll('<', '<')
|
|
70
|
+
.replaceAll('>', '>');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function plainRichChunks(source, limit) {
|
|
74
|
+
return splitText(source, limit, escapedCharacterLength).map((chunk) => ({
|
|
75
|
+
source: chunk,
|
|
76
|
+
markdown: escapedMarkdown(chunk),
|
|
77
|
+
}));
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function fencedRichChunks(source, opening, closing, limit) {
|
|
81
|
+
const body = source.slice(opening.length, source.length - closing.length);
|
|
82
|
+
const wrapperLength = escapedLength(opening)
|
|
83
|
+
+ Math.max(escapedLength(closing), escapedLength('```'))
|
|
84
|
+
+ 1;
|
|
85
|
+
const bodyLimit = limit - wrapperLength;
|
|
86
|
+
if (bodyLimit < 1) throw new Error('Telegram Rich Markdown code fence exceeds the limit');
|
|
87
|
+
const bodyChunks = splitText(body, bodyLimit, escapedCharacterLength);
|
|
88
|
+
return bodyChunks.map((bodyChunk, index) => {
|
|
89
|
+
const last = index === bodyChunks.length - 1;
|
|
90
|
+
const separator = bodyChunk.endsWith('\n') ? '' : '\n';
|
|
91
|
+
const renderedClosing = last ? closing : '```';
|
|
92
|
+
const sourceChunk = `${index === 0 ? opening : ''}${bodyChunk}${last ? closing : ''}`;
|
|
93
|
+
const markdown = escapedMarkdown(`${opening}${bodyChunk}${separator}${renderedClosing}`);
|
|
94
|
+
if (Array.from(markdown).length > limit) {
|
|
95
|
+
throw new Error('Telegram Rich Markdown code block exceeds the limit');
|
|
96
|
+
}
|
|
97
|
+
return { source: sourceChunk, markdown };
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function toTelegramRichMarkdown(value) {
|
|
102
|
+
const markdown = textValue(value);
|
|
103
|
+
assertCompleteFences(markdown);
|
|
104
|
+
// Rich Markdown also accepts HTML tags. Escape entities first so encoded or
|
|
105
|
+
// raw model output cannot be interpreted as Telegram-specific markup.
|
|
106
|
+
return escapedMarkdown(markdown);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function splitTelegramRichMarkdown(value, limit = TELEGRAM_RICH_TEXT_LIMIT) {
|
|
110
|
+
if (!Number.isInteger(limit) || limit < 128 || limit > 32_768) {
|
|
111
|
+
throw new TypeError('Telegram Rich Markdown limit is invalid');
|
|
112
|
+
}
|
|
113
|
+
const source = textValue(value);
|
|
114
|
+
const markers = assertCompleteFences(source);
|
|
115
|
+
const complete = escapedMarkdown(source);
|
|
116
|
+
if (Array.from(complete).length <= limit) return [{ source, markdown: complete }];
|
|
117
|
+
|
|
118
|
+
const chunks = [];
|
|
119
|
+
let cursor = 0;
|
|
120
|
+
for (let index = 0; index < markers.length; index += 2) {
|
|
121
|
+
const opening = markers[index];
|
|
122
|
+
const closing = markers[index + 1];
|
|
123
|
+
if (opening.index > cursor) {
|
|
124
|
+
chunks.push(...plainRichChunks(source.slice(cursor, opening.index), limit));
|
|
125
|
+
}
|
|
126
|
+
const end = closing.index + closing[0].length;
|
|
127
|
+
const fenced = source.slice(opening.index, end);
|
|
128
|
+
const rendered = escapedMarkdown(fenced);
|
|
129
|
+
if (Array.from(rendered).length <= limit) {
|
|
130
|
+
chunks.push({ source: fenced, markdown: rendered });
|
|
131
|
+
} else {
|
|
132
|
+
chunks.push(...fencedRichChunks(fenced, opening[0], closing[0], limit));
|
|
133
|
+
}
|
|
134
|
+
cursor = end;
|
|
135
|
+
}
|
|
136
|
+
if (cursor < source.length) chunks.push(...plainRichChunks(source.slice(cursor), limit));
|
|
137
|
+
return chunks;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export function splitTelegramRegularText(value, limit = TELEGRAM_REGULAR_TEXT_LIMIT) {
|
|
141
|
+
if (!Number.isInteger(limit) || limit < 1 || limit > 4_096) {
|
|
142
|
+
throw new TypeError('Telegram regular message limit is invalid');
|
|
143
|
+
}
|
|
144
|
+
// Keep Telegram's established 4000 UTF-16-unit boundary without ever cutting
|
|
145
|
+
// a surrogate pair in half.
|
|
146
|
+
return splitText(textValue(value), limit, (character) => character.length);
|
|
147
|
+
}
|