@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.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
3
  "name": "telegram",
4
- "version": "0.1.0-rc.4",
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.4",
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(lead, body, limit = TEXT_LIMIT) {
509
+ function fitHtml(lead2, body, limit = TEXT_LIMIT) {
414
510
  const sep = "\n\n";
415
- if (lead === "") return truncateHtml(body, limit);
416
- if (body === "") return truncateHtml(lead, limit);
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(lead, room)}${sep}${body}`;
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
- const last = lines[lines.length - 1];
503
- if (lines.length > 1 && last?.endsWith(url)) {
504
- lines.pop();
505
- const label = markdownToTelegramHtml(last.slice(0, last.length - url.length));
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 && questionRenderableInline(card.form) && "enum" in f.schema) {
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 lead = text === "" || dup ? "" : markdownToTelegramHtml(message.text);
1049
- const fit = (p, leading) => ({
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: fit(p.withoutUrlButtons, leading) }
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) => fit(p, i === 0 ? lead : ""));
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.4",
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@capybara-hq/plugin-telegram",
3
- "version": "0.1.0-rc.4",
3
+ "version": "0.1.0-rc.6",
4
4
  "type": "module",
5
5
  "license": "Apache-2.0",
6
6
  "description": "Telegram (channel@1 bot over the Bot API: long polling or webhook, inline keyboard cards)",