@capybara-hq/plugin-telegram 0.1.0-rc.4 → 0.1.0-rc.6
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/.capybara-plugin/plugin.json +2 -2
- package/dist/index.mjs +119 -15
- package/package.json +1 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
3
|
"name": "telegram",
|
|
4
|
-
"version": "0.1.0-rc.
|
|
4
|
+
"version": "0.1.0-rc.6",
|
|
5
5
|
"publisher": "Capybara",
|
|
6
6
|
"description": {
|
|
7
7
|
"en": "Telegram (channel@1 bot over the Bot API: long polling or webhook, inline keyboard cards)",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"entry": "./dist/index.mjs",
|
|
18
18
|
"manifest": {
|
|
19
19
|
"name": "telegram",
|
|
20
|
-
"version": "0.1.0-rc.
|
|
20
|
+
"version": "0.1.0-rc.6",
|
|
21
21
|
"sdk": "^0.1.0-rc.4",
|
|
22
22
|
"provides": [
|
|
23
23
|
"channel@1"
|
package/dist/index.mjs
CHANGED
|
@@ -265,6 +265,102 @@ import {
|
|
|
265
265
|
renderCardAsText
|
|
266
266
|
} from "@capybara-hq/plugin-sdk";
|
|
267
267
|
|
|
268
|
+
// src/tables.ts
|
|
269
|
+
var FENCE_OPEN = /^\s*(?:```|~~~)/;
|
|
270
|
+
var FENCE_CLOSE = /^\s*(```|~~~)\s*$/;
|
|
271
|
+
var DELIM_CELL = /^:?-+:?$/;
|
|
272
|
+
function splitRow(line) {
|
|
273
|
+
let s = line.trim();
|
|
274
|
+
if (!/(^|[^\\])\|/.test(s)) return void 0;
|
|
275
|
+
if (s.startsWith("|")) s = s.slice(1);
|
|
276
|
+
if (s.endsWith("|") && !s.endsWith("\\|")) s = s.slice(0, -1);
|
|
277
|
+
const cells = [];
|
|
278
|
+
let cur = "";
|
|
279
|
+
for (let i = 0; i < s.length; i++) {
|
|
280
|
+
const c = s[i];
|
|
281
|
+
if (c === "\\" && s[i + 1] === "|") {
|
|
282
|
+
cur += "|";
|
|
283
|
+
i++;
|
|
284
|
+
} else if (c === "|") {
|
|
285
|
+
cells.push(cur.trim());
|
|
286
|
+
cur = "";
|
|
287
|
+
} else cur += c;
|
|
288
|
+
}
|
|
289
|
+
cells.push(cur.trim());
|
|
290
|
+
return cells;
|
|
291
|
+
}
|
|
292
|
+
var candidate = (line) => !/^\s{4,}/.test(line) && !/^\s*>/.test(line);
|
|
293
|
+
function delimiterRow(line, width) {
|
|
294
|
+
if (line === void 0 || !candidate(line)) return false;
|
|
295
|
+
const cells = splitRow(line);
|
|
296
|
+
return cells !== void 0 && cells.length === width && cells.every((c) => DELIM_CELL.test(c));
|
|
297
|
+
}
|
|
298
|
+
var fit = (cells, width) => Array.from({ length: width }, (_, i) => cells[i] ?? "");
|
|
299
|
+
function segmentTables(md) {
|
|
300
|
+
const lines = md.replace(/\r\n?/g, "\n").split("\n");
|
|
301
|
+
const out = [];
|
|
302
|
+
let text = [];
|
|
303
|
+
const flushText = () => {
|
|
304
|
+
if (text.length > 0) out.push({ kind: "text", lines: text });
|
|
305
|
+
text = [];
|
|
306
|
+
};
|
|
307
|
+
let inFence = false;
|
|
308
|
+
for (let i = 0; i < lines.length; i++) {
|
|
309
|
+
const line = lines[i];
|
|
310
|
+
if (inFence ? FENCE_CLOSE.test(line) : FENCE_OPEN.test(line)) {
|
|
311
|
+
inFence = !inFence;
|
|
312
|
+
text.push(line);
|
|
313
|
+
continue;
|
|
314
|
+
}
|
|
315
|
+
const header = !inFence && candidate(line) ? splitRow(line) : void 0;
|
|
316
|
+
if (!header || !delimiterRow(lines[i + 1], header.length)) {
|
|
317
|
+
text.push(line);
|
|
318
|
+
continue;
|
|
319
|
+
}
|
|
320
|
+
const rows = [];
|
|
321
|
+
let j = i + 2;
|
|
322
|
+
for (; j < lines.length; j++) {
|
|
323
|
+
const l = lines[j];
|
|
324
|
+
if (l.trim() === "" || FENCE_OPEN.test(l) || !candidate(l)) break;
|
|
325
|
+
const cells = splitRow(l);
|
|
326
|
+
if (!cells) break;
|
|
327
|
+
rows.push(fit(cells, header.length));
|
|
328
|
+
}
|
|
329
|
+
flushText();
|
|
330
|
+
out.push({ kind: "table", table: { header, rows } });
|
|
331
|
+
i = j - 1;
|
|
332
|
+
}
|
|
333
|
+
flushText();
|
|
334
|
+
return out;
|
|
335
|
+
}
|
|
336
|
+
var BR = /<br\s*\/?>/gi;
|
|
337
|
+
var CJK_END = /[⺀-鿿가-豈--]$/u;
|
|
338
|
+
var INDENT = " ";
|
|
339
|
+
var pair = (h, v) => h === "" ? v : `${h}${CJK_END.test(h) ? ":" : ": "}${v}`;
|
|
340
|
+
var lead = (cell) => /`|\*\*|__/.test(cell) ? cell : `**${cell}**`;
|
|
341
|
+
function tableToListMarkdown(t) {
|
|
342
|
+
const clean = (s) => s.replace(BR, " / ").trim();
|
|
343
|
+
const header = t.header.map(clean);
|
|
344
|
+
if (t.rows.length === 0) return [header.filter((h) => h !== "").join(" · ")];
|
|
345
|
+
const out = [];
|
|
346
|
+
for (const raw of t.rows) {
|
|
347
|
+
const [first = "", ...rest] = raw.map(clean);
|
|
348
|
+
const head = first === "" ? "" : lead(first);
|
|
349
|
+
const items = rest.flatMap((v, i) => v === "" ? [] : [pair(header[i + 1] ?? "", v)]);
|
|
350
|
+
if (head === "" && items.length === 0) continue;
|
|
351
|
+
if (header.length === 2) {
|
|
352
|
+
out.push(`- ${[head, rest[0] ?? ""].filter((s) => s !== "").join(" — ")}`);
|
|
353
|
+
continue;
|
|
354
|
+
}
|
|
355
|
+
out.push(`- ${head === "" ? items.shift() ?? "" : head}`);
|
|
356
|
+
for (const it of items) out.push(`${INDENT}${it}`);
|
|
357
|
+
}
|
|
358
|
+
return out;
|
|
359
|
+
}
|
|
360
|
+
function tablesToLists(md) {
|
|
361
|
+
return segmentTables(md).flatMap((s) => s.kind === "text" ? s.lines : tableToListMarkdown(s.table)).join("\n");
|
|
362
|
+
}
|
|
363
|
+
|
|
268
364
|
// src/html.ts
|
|
269
365
|
var TEXT_LIMIT = 4096;
|
|
270
366
|
function escapeHtml(s) {
|
|
@@ -347,7 +443,7 @@ function codeBlock(lang, lines) {
|
|
|
347
443
|
return l === "" ? `<pre>${body}</pre>` : `<pre><code class="language-${l}">${body}</code></pre>`;
|
|
348
444
|
}
|
|
349
445
|
function markdownToTelegramHtml(md) {
|
|
350
|
-
const lines = md.replace(/\r\n?/g, "\n").replaceAll("\0", "").split("\n");
|
|
446
|
+
const lines = tablesToLists(md.replace(/\r\n?/g, "\n").replaceAll("\0", "")).split("\n");
|
|
351
447
|
const out = [];
|
|
352
448
|
let fence;
|
|
353
449
|
let quote;
|
|
@@ -410,12 +506,12 @@ function truncateHtml(html, limit = TEXT_LIMIT) {
|
|
|
410
506
|
const closing = /(?:<\/[a-z-]+>)*$/.exec(head)?.[0] ?? "";
|
|
411
507
|
return `${head.slice(0, head.length - closing.length)}…${closing}`;
|
|
412
508
|
}
|
|
413
|
-
function fitHtml(
|
|
509
|
+
function fitHtml(lead2, body, limit = TEXT_LIMIT) {
|
|
414
510
|
const sep = "\n\n";
|
|
415
|
-
if (
|
|
416
|
-
if (body === "") return truncateHtml(
|
|
511
|
+
if (lead2 === "") return truncateHtml(body, limit);
|
|
512
|
+
if (body === "") return truncateHtml(lead2, limit);
|
|
417
513
|
const room = limit - visibleLength(body) - sep.length;
|
|
418
|
-
if (room >= 2) return `${truncateHtml(
|
|
514
|
+
if (room >= 2) return `${truncateHtml(lead2, room)}${sep}${body}`;
|
|
419
515
|
return truncateHtml(body, limit);
|
|
420
516
|
}
|
|
421
517
|
function splitHtml(html, limit = TEXT_LIMIT) {
|
|
@@ -426,6 +522,7 @@ function splitHtml(html, limit = TEXT_LIMIT) {
|
|
|
426
522
|
let stack = [];
|
|
427
523
|
let prefix = "";
|
|
428
524
|
let cand;
|
|
525
|
+
let nl;
|
|
429
526
|
const emit = (toks, count, at) => {
|
|
430
527
|
if (count > 0) parts.push(prefix + toks.map((t) => t.text).join("") + closers(at));
|
|
431
528
|
};
|
|
@@ -439,6 +536,7 @@ function splitHtml(html, limit = TEXT_LIMIT) {
|
|
|
439
536
|
continue;
|
|
440
537
|
}
|
|
441
538
|
if (vis + t.len > limit) {
|
|
539
|
+
if (nl && nl.vis >= limit / 2) cand = nl;
|
|
442
540
|
if (cand && cand.vis >= limit / 2) {
|
|
443
541
|
emit(cur.slice(0, cand.i), cand.vis, cand.stack);
|
|
444
542
|
const rest = cur.slice(cand.i + 1);
|
|
@@ -452,8 +550,10 @@ function splitHtml(html, limit = TEXT_LIMIT) {
|
|
|
452
550
|
cur = [];
|
|
453
551
|
}
|
|
454
552
|
cand = void 0;
|
|
553
|
+
nl = void 0;
|
|
455
554
|
}
|
|
456
555
|
if (/^\s$/.test(t.text)) cand = { i: cur.length, vis, stack: [...stack] };
|
|
556
|
+
if (t.text === "\n") nl = cand;
|
|
457
557
|
cur.push(t);
|
|
458
558
|
vis += t.len;
|
|
459
559
|
}
|
|
@@ -499,10 +599,12 @@ var urlButton = (text, url) => isHttp(url) ? [{ text, url }] : [];
|
|
|
499
599
|
function cardText(card, locale) {
|
|
500
600
|
const url = card.kind === "connect" || card.kind === "bind" ? card.url : card.webUrl;
|
|
501
601
|
const lines = renderCardAsText(card, locale).split("\n");
|
|
502
|
-
|
|
503
|
-
if (
|
|
504
|
-
|
|
505
|
-
|
|
602
|
+
let at = lines.length - 1;
|
|
603
|
+
if (card.kind === "question" && card.state === "pending" && card.textReply !== void 0) at--;
|
|
604
|
+
const line = lines[at];
|
|
605
|
+
if (at > 0 && line?.endsWith(url)) {
|
|
606
|
+
lines.splice(at, 1);
|
|
607
|
+
const label = markdownToTelegramHtml(line.slice(0, line.length - url.length));
|
|
506
608
|
return { body: markdownToTelegramHtml(lines.join("\n")), linkLine: `${label}${htmlLink(url)}` };
|
|
507
609
|
}
|
|
508
610
|
return { body: markdownToTelegramHtml(lines.join("\n")) };
|
|
@@ -549,7 +651,9 @@ async function keyboard(card, locale, tokens) {
|
|
|
549
651
|
case "question": {
|
|
550
652
|
const f = card.form.fields[0];
|
|
551
653
|
const rows = [];
|
|
552
|
-
if (card.state === "pending" && f && !f.multiSelect &&
|
|
654
|
+
if (card.state === "pending" && f && !f.multiSelect && // With textReply the Other path is a plain reply: a field with allowOther still gets its
|
|
655
|
+
// option buttons, never an Other button.
|
|
656
|
+
questionRenderableInline(card.form, { textReply: card.textReply !== void 0 }) && "enum" in f.schema) {
|
|
553
657
|
for (const option of f.schema.enum) {
|
|
554
658
|
rows.push(
|
|
555
659
|
await callbackButton(tokens, option, {
|
|
@@ -1045,14 +1149,14 @@ function createTelegramChannel(o) {
|
|
|
1045
1149
|
return text === "" ? [] : splitHtml(markdownToTelegramHtml(message.text)).map((html) => ({ html }));
|
|
1046
1150
|
}
|
|
1047
1151
|
const dup = cards.some((c) => cardHeadline(c, locale)?.trim() === text);
|
|
1048
|
-
const
|
|
1049
|
-
const
|
|
1152
|
+
const lead2 = text === "" || dup ? "" : markdownToTelegramHtml(message.text);
|
|
1153
|
+
const fit2 = (p, leading) => ({
|
|
1050
1154
|
...p,
|
|
1051
1155
|
html: fitHtml(leading, p.html),
|
|
1052
|
-
...p.withoutUrlButtons && { withoutUrlButtons:
|
|
1156
|
+
...p.withoutUrlButtons && { withoutUrlButtons: fit2(p.withoutUrlButtons, leading) }
|
|
1053
1157
|
});
|
|
1054
1158
|
const posts = await renderCards(cards, locale, cardTokenStore(o.kv, botId), o.log);
|
|
1055
|
-
return posts.map((p, i) =>
|
|
1159
|
+
return posts.map((p, i) => fit2(p, i === 0 ? lead2 : ""));
|
|
1056
1160
|
}
|
|
1057
1161
|
async function withUrlButtonFallback(post, attempt) {
|
|
1058
1162
|
try {
|
|
@@ -1242,7 +1346,7 @@ function createTelegramChannel(o) {
|
|
|
1242
1346
|
// src/index.ts
|
|
1243
1347
|
var index_default = defineExtension({
|
|
1244
1348
|
name: "telegram",
|
|
1245
|
-
version: "0.1.0-rc.
|
|
1349
|
+
version: "0.1.0-rc.6",
|
|
1246
1350
|
sdk: "^0.1.0-rc.4",
|
|
1247
1351
|
provides: [CHANNEL_EXTENSION_POINT],
|
|
1248
1352
|
permissions: { network: ["api.telegram.org"] },
|
package/package.json
CHANGED