@yaebal/rich 0.0.1 → 0.0.3
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.md +109 -35
- package/lib/blocks.d.ts +93 -32
- package/lib/blocks.d.ts.map +1 -1
- package/lib/blocks.js +227 -71
- package/lib/blocks.js.map +1 -1
- package/lib/blocks.test.d.ts +2 -0
- package/lib/blocks.test.d.ts.map +1 -0
- package/lib/blocks.test.js +142 -0
- package/lib/blocks.test.js.map +1 -0
- package/lib/document.d.ts +28 -0
- package/lib/document.d.ts.map +1 -0
- package/lib/document.js +46 -0
- package/lib/document.js.map +1 -0
- package/lib/draft.d.ts +46 -9
- package/lib/draft.d.ts.map +1 -1
- package/lib/draft.js +115 -19
- package/lib/draft.js.map +1 -1
- package/lib/draft.test.d.ts +2 -0
- package/lib/draft.test.d.ts.map +1 -0
- package/lib/draft.test.js +122 -0
- package/lib/draft.test.js.map +1 -0
- package/lib/escape.d.ts +8 -0
- package/lib/escape.d.ts.map +1 -1
- package/lib/escape.js +17 -0
- package/lib/escape.js.map +1 -1
- package/lib/escape.test.d.ts +2 -0
- package/lib/escape.test.d.ts.map +1 -0
- package/lib/escape.test.js +28 -0
- package/lib/escape.test.js.map +1 -0
- package/lib/guards.test.d.ts +2 -0
- package/lib/guards.test.d.ts.map +1 -0
- package/lib/guards.test.js +85 -0
- package/lib/guards.test.js.map +1 -0
- package/lib/index.d.ts +18 -9
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +17 -8
- package/lib/index.js.map +1 -1
- package/lib/index.test.js +73 -103
- package/lib/index.test.js.map +1 -1
- package/lib/inline.d.ts +48 -59
- package/lib/inline.d.ts.map +1 -1
- package/lib/inline.js +87 -82
- package/lib/inline.js.map +1 -1
- package/lib/inline.test.d.ts +2 -0
- package/lib/inline.test.d.ts.map +1 -0
- package/lib/inline.test.js +84 -0
- package/lib/inline.test.js.map +1 -0
- package/lib/node.d.ts +25 -0
- package/lib/node.d.ts.map +1 -0
- package/lib/node.js +21 -0
- package/lib/node.js.map +1 -0
- package/lib/plaintext.test.d.ts +2 -0
- package/lib/plaintext.test.d.ts.map +1 -0
- package/lib/plaintext.test.js +100 -0
- package/lib/plaintext.test.js.map +1 -0
- package/lib/render.d.ts +17 -0
- package/lib/render.d.ts.map +1 -0
- package/lib/render.js +30 -0
- package/lib/render.js.map +1 -0
- package/lib/send.d.ts +6 -3
- package/lib/send.d.ts.map +1 -1
- package/lib/send.js +12 -3
- package/lib/send.js.map +1 -1
- package/lib/template.d.ts +46 -0
- package/lib/template.d.ts.map +1 -0
- package/lib/template.js +82 -0
- package/lib/template.js.map +1 -0
- package/lib/template.test.d.ts +2 -0
- package/lib/template.test.d.ts.map +1 -0
- package/lib/template.test.js +78 -0
- package/lib/template.test.js.map +1 -0
- package/package.json +5 -4
- package/src/blocks.test.ts +233 -0
- package/src/blocks.ts +304 -86
- package/src/document.ts +51 -0
- package/src/draft.test.ts +167 -0
- package/src/draft.ts +148 -21
- package/src/escape.test.ts +38 -0
- package/src/escape.ts +20 -0
- package/src/guards.test.ts +138 -0
- package/src/index.test.ts +79 -140
- package/src/index.ts +26 -11
- package/src/inline.test.ts +125 -0
- package/src/inline.ts +131 -97
- package/src/node.ts +43 -0
- package/src/plaintext.test.ts +141 -0
- package/src/render.ts +54 -0
- package/src/send.ts +17 -10
- package/src/template.test.ts +100 -0
- package/src/template.ts +115 -0
- package/lib/message.d.ts +0 -26
- package/lib/message.d.ts.map +0 -1
- package/lib/message.js +0 -31
- package/lib/message.js.map +0 -1
- package/src/message.ts +0 -45
package/lib/inline.js
CHANGED
|
@@ -1,78 +1,77 @@
|
|
|
1
|
-
import { escapeAttr,
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import { escapeAttr, escapeMarkdownUrl } from "./escape.js";
|
|
2
|
+
import { makeNode } from "./node.js";
|
|
3
|
+
import { escapeFor, render } from "./render.js";
|
|
4
|
+
// every builder here returns a dialect-agnostic `RichNode` — the html/markdown
|
|
5
|
+
// choice happens once, at the `html`/`md`/`document()` boundary. children render
|
|
6
|
+
// lazily, so a node built from user input is safe in either dialect.
|
|
7
|
+
function children(items, dialect) {
|
|
8
|
+
return items.map((item) => render(item, dialect)).join("");
|
|
4
9
|
}
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
return value.html;
|
|
9
|
-
return value == null ? "" : escapeText(String(value));
|
|
10
|
-
}
|
|
11
|
-
function join(children) {
|
|
12
|
-
return children.map(toHtml).join("");
|
|
13
|
-
}
|
|
14
|
-
function wrap(tag) {
|
|
15
|
-
return (...children) => ({ html: `<${tag}>${join(children)}</${tag}>` });
|
|
16
|
-
}
|
|
17
|
-
/**
|
|
18
|
-
* an extended-html template: interpolations are auto-escaped (never re-parsed as
|
|
19
|
-
* markup), and a nested `RichNode` (from these builders, or another `html`
|
|
20
|
-
* template) is spliced in raw. mirrors `@yaebal/fmt`'s `html` tag, but for the
|
|
21
|
-
* rich-message dialect instead of the classic entity one.
|
|
22
|
-
*/
|
|
23
|
-
export function html(strings, ...subs) {
|
|
24
|
-
let out = "";
|
|
25
|
-
for (let i = 0; i < strings.length; i++) {
|
|
26
|
-
out += strings[i] ?? "";
|
|
27
|
-
if (i < subs.length)
|
|
28
|
-
out += toHtml(subs[i]);
|
|
29
|
-
}
|
|
30
|
-
return { html: out };
|
|
10
|
+
// a wrapper mark: same shape in both dialects, differing only in the surrounding tokens.
|
|
11
|
+
function wrap(md, html) {
|
|
12
|
+
return (...items) => makeNode("inline", (d) => (d === "markdown" ? md : html)(children(items, d)));
|
|
31
13
|
}
|
|
32
14
|
// --- inline marks — confirmed tags (same dialect as classic parse_mode html) ---
|
|
33
|
-
/** `RichTextBold
|
|
34
|
-
export const bold = wrap(
|
|
35
|
-
/** `RichTextItalic
|
|
36
|
-
export const italic = wrap(
|
|
37
|
-
/** `RichTextUnderline
|
|
38
|
-
export const underline = wrap(
|
|
39
|
-
/** `RichTextStrikethrough
|
|
40
|
-
export const strikethrough = wrap(
|
|
41
|
-
/** `RichTextSpoiler
|
|
42
|
-
export const spoiler = wrap(
|
|
43
|
-
/** `RichTextCode
|
|
44
|
-
export const code = wrap(
|
|
15
|
+
/** `RichTextBold` — `<b>` / `**x**`. */
|
|
16
|
+
export const bold = wrap((x) => `**${x}**`, (x) => `<b>${x}</b>`);
|
|
17
|
+
/** `RichTextItalic` — `<i>` / `*x*`. */
|
|
18
|
+
export const italic = wrap((x) => `*${x}*`, (x) => `<i>${x}</i>`);
|
|
19
|
+
/** `RichTextUnderline` — `<u>`; no markdown token, the raw tag is embedded there too. */
|
|
20
|
+
export const underline = wrap((x) => `<u>${x}</u>`, (x) => `<u>${x}</u>`);
|
|
21
|
+
/** `RichTextStrikethrough` — `<s>` / `~~x~~`. */
|
|
22
|
+
export const strikethrough = wrap((x) => `~~${x}~~`, (x) => `<s>${x}</s>`);
|
|
23
|
+
/** `RichTextSpoiler` — `<tg-spoiler>` / `||x||`. */
|
|
24
|
+
export const spoiler = wrap((x) => `||${x}||`, (x) => `<tg-spoiler>${x}</tg-spoiler>`);
|
|
25
|
+
/** `RichTextCode` — `<code>` / `` `x` ``. */
|
|
26
|
+
export const code = wrap((x) => `\`${x}\``, (x) => `<code>${x}</code>`);
|
|
27
|
+
/** line break — hard newline in markdown, `<br/>` in html. */
|
|
28
|
+
export function br() {
|
|
29
|
+
return makeNode("inline", (d) => (d === "markdown" ? "\n" : "<br/>"));
|
|
30
|
+
}
|
|
45
31
|
/**
|
|
46
|
-
* `RichTextCustomEmoji
|
|
47
|
-
* `parse_mode: "HTML"` uses
|
|
48
|
-
*
|
|
32
|
+
* `RichTextCustomEmoji` — `<tg-emoji emoji-id="…">`, the same custom tag classic
|
|
33
|
+
* `parse_mode: "HTML"` uses (telegram documents it as reused verbatim here);
|
|
34
|
+
* best-effort `` in markdown. `fallback` is the
|
|
35
|
+
* plain emoji shown where custom emoji can't render.
|
|
49
36
|
*/
|
|
50
37
|
export function customEmoji(emojiId, fallback) {
|
|
51
|
-
return
|
|
38
|
+
return makeNode("inline", (d) => d === "markdown"
|
|
39
|
+
? `})`
|
|
40
|
+
: `<tg-emoji emoji-id="${escapeAttr(emojiId)}">${escapeFor(fallback, d)}</tg-emoji>`);
|
|
41
|
+
}
|
|
42
|
+
// a markdown link destination: `[text](url)` is terminated by `)` or whitespace,
|
|
43
|
+
// so the url must be escaped to keep an attacker-controlled value inside the link.
|
|
44
|
+
function mdLink(text, url) {
|
|
45
|
+
return `[${text}](${escapeMarkdownUrl(url)})`;
|
|
46
|
+
}
|
|
47
|
+
/** `RichTextUrl`, an explicit link. for a bare auto-linked url, just write it as plain text. */
|
|
48
|
+
export function link(url, ...items) {
|
|
49
|
+
return makeNode("inline", (d) => d === "markdown"
|
|
50
|
+
? mdLink(children(items, d), url)
|
|
51
|
+
: `<a href="${escapeAttr(url)}">${children(items, d)}</a>`);
|
|
52
52
|
}
|
|
53
53
|
/**
|
|
54
|
-
* `RichTextTextMention`, a mention of a user who may have no `@username` —
|
|
55
|
-
* `tg://user?id=…` link telegram's classic
|
|
54
|
+
* `RichTextTextMention`, a mention of a user who may have no `@username` — the
|
|
55
|
+
* same `tg://user?id=…` link telegram's classic dialects use for `text_mention`.
|
|
56
56
|
* for `@username` mentions (`RichTextMention`), just write `@username` as plain
|
|
57
|
-
* text — the schema lists it as auto-detected (see `
|
|
57
|
+
* text — the schema lists it as auto-detected (see `noEntityDetection`).
|
|
58
58
|
*/
|
|
59
|
-
export function textMention(user, ...
|
|
60
|
-
return
|
|
61
|
-
}
|
|
62
|
-
/** `RichTextUrl`, an explicit link. for a bare auto-linked url, just write it as plain text. */
|
|
63
|
-
export function link(url, ...children) {
|
|
64
|
-
return { html: `<a href="${escapeAttr(url)}">${join(children)}</a>` };
|
|
59
|
+
export function textMention(user, ...items) {
|
|
60
|
+
return link(`tg://user?id=${user.id}`, ...items);
|
|
65
61
|
}
|
|
66
|
-
/** `RichTextAnchor` (inline form) — a named jump target, `<a name="…"
|
|
62
|
+
/** `RichTextAnchor` (inline form) — a named jump target, `<a name="…">` in both dialects. */
|
|
67
63
|
export function anchor(name) {
|
|
68
|
-
return
|
|
64
|
+
return makeNode("inline", () => `<a name="${escapeAttr(name)}"></a>`);
|
|
69
65
|
}
|
|
70
66
|
/**
|
|
71
67
|
* `RichTextAnchorLink`, a link to an `anchor()` elsewhere in the message —
|
|
72
|
-
* `<a href="#name"
|
|
68
|
+
* `<a href="#name">` / `[text](#name)`. an empty `name` jumps back to the top
|
|
69
|
+
* (per the schema).
|
|
73
70
|
*/
|
|
74
|
-
export function anchorLink(name, ...
|
|
75
|
-
return
|
|
71
|
+
export function anchorLink(name, ...items) {
|
|
72
|
+
return makeNode("inline", (d) => d === "markdown"
|
|
73
|
+
? mdLink(children(items, d), `#${name}`)
|
|
74
|
+
: `<a href="#${escapeAttr(name)}">${children(items, d)}</a>`);
|
|
76
75
|
}
|
|
77
76
|
// --- inline marks — best-effort tags ---
|
|
78
77
|
//
|
|
@@ -82,39 +81,45 @@ export function anchorLink(name, ...children) {
|
|
|
82
81
|
// reusing standard html5 semantics (sub/sup/mark/time) or its `tg-*` custom-tag
|
|
83
82
|
// convention — verify against the live "rich message formatting options" docs
|
|
84
83
|
// before depending on the exact tag/attribute spelling in production.
|
|
85
|
-
/** `RichTextMarked`, best-effort `<mark>`
|
|
86
|
-
export const marked = wrap(
|
|
87
|
-
/** `RichTextSubscript
|
|
88
|
-
export const subscript = wrap(
|
|
89
|
-
/** `RichTextSuperscript
|
|
90
|
-
export const superscript = wrap(
|
|
84
|
+
/** `RichTextMarked`, highlighted text — best-effort `<mark>` / `==x==`. */
|
|
85
|
+
export const marked = wrap((x) => `==${x}==`, (x) => `<mark>${x}</mark>`);
|
|
86
|
+
/** `RichTextSubscript` — best-effort `<sub>`; no markdown token, raw tag embedded there too. */
|
|
87
|
+
export const subscript = wrap((x) => `<sub>${x}</sub>`, (x) => `<sub>${x}</sub>`);
|
|
88
|
+
/** `RichTextSuperscript` — best-effort `<sup>`; no markdown token, raw tag embedded there too. */
|
|
89
|
+
export const superscript = wrap((x) => `<sup>${x}</sup>`, (x) => `<sup>${x}</sup>`);
|
|
91
90
|
/**
|
|
92
|
-
* `RichTextMathematicalExpression` (inline)
|
|
93
|
-
* form is confirmed as `<tg-math-block>` — see `mathBlock` in
|
|
91
|
+
* `RichTextMathematicalExpression` (inline) — best-effort `<tg-math>` / `$x$`
|
|
92
|
+
* (the block form is confirmed as `<tg-math-block>` — see `mathBlock` in
|
|
93
|
+
* blocks.ts). `expression` is raw LaTeX — not markdown-escaped.
|
|
94
94
|
*/
|
|
95
95
|
export function math(expression) {
|
|
96
|
-
return {
|
|
96
|
+
return makeNode("inline", (d) => d === "markdown" ? `$${expression}$` : `<tg-math>${escapeFor(expression, "html")}</tg-math>`);
|
|
97
97
|
}
|
|
98
98
|
/**
|
|
99
|
-
* `RichTextDateTime`, best-effort `<time
|
|
100
|
-
*
|
|
99
|
+
* `RichTextDateTime`, auto-formatted date-time — best-effort `<time>` (attribute
|
|
100
|
+
* name `data-format` is a guess) / ``.
|
|
101
|
+
* `format` is telegram's date-time entity format string.
|
|
101
102
|
*/
|
|
102
|
-
export function dateTime(unixTime, format, ...
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
103
|
+
export function dateTime(unixTime, format, ...items) {
|
|
104
|
+
const query = `tg://time?unix=${unixTime}${format ? `&format=${format}` : ""}`;
|
|
105
|
+
return makeNode("inline", (d) => d === "markdown"
|
|
106
|
+
? `})`
|
|
107
|
+
: `<time datetime="${unixTime}" data-format="${escapeAttr(format)}">${children(items, d)}</time>`);
|
|
106
108
|
}
|
|
107
109
|
/**
|
|
108
110
|
* `RichTextReference`, a footnote definition. no tag is documented anywhere in
|
|
109
|
-
* the scraped schema —
|
|
111
|
+
* the scraped schema — `<tg-reference>` is a from-scratch `tg-*`-style guess,
|
|
112
|
+
* `[^name]: …` the standard markdown footnote line (place it at line start).
|
|
110
113
|
*/
|
|
111
|
-
export function reference(name, ...
|
|
112
|
-
return
|
|
114
|
+
export function reference(name, ...items) {
|
|
115
|
+
return makeNode("inline", (d) => d === "markdown"
|
|
116
|
+
? `[^${name}]: ${children(items, d)}`
|
|
117
|
+
: `<tg-reference name="${escapeAttr(name)}">${children(items, d)}</tg-reference>`);
|
|
113
118
|
}
|
|
114
|
-
/** `RichTextReferenceLink`, a link to a `reference()
|
|
115
|
-
export function referenceLink(name, ...
|
|
116
|
-
return
|
|
117
|
-
|
|
118
|
-
|
|
119
|
+
/** `RichTextReferenceLink`, a link to a `reference()` — same caveat as `reference`. */
|
|
120
|
+
export function referenceLink(name, ...items) {
|
|
121
|
+
return makeNode("inline", (d) => d === "markdown"
|
|
122
|
+
? `[^${name}]`
|
|
123
|
+
: `<tg-reference-link name="${escapeAttr(name)}">${children(items, d)}</tg-reference-link>`);
|
|
119
124
|
}
|
|
120
125
|
//# sourceMappingURL=inline.js.map
|
package/lib/inline.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"inline.js","sourceRoot":"","sources":["../src/inline.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,
|
|
1
|
+
{"version":3,"file":"inline.js","sourceRoot":"","sources":["../src/inline.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inline.test.d.ts","sourceRoot":"","sources":["../src/inline.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
import { anchor, anchorLink, bold, br, code, customEmoji, dateTime, italic, link, marked, math, reference, referenceLink, spoiler, strikethrough, subscript, superscript, textMention, underline, } from "./inline.js";
|
|
4
|
+
// every inline builder is dual-dialect: one node, two renders.
|
|
5
|
+
function both(node) {
|
|
6
|
+
return [node.render("html"), node.render("markdown")];
|
|
7
|
+
}
|
|
8
|
+
test("wrapper marks render their token pair in each dialect", () => {
|
|
9
|
+
assert.deepEqual(both(bold("x")), ["<b>x</b>", "**x**"]);
|
|
10
|
+
assert.deepEqual(both(italic("x")), ["<i>x</i>", "*x*"]);
|
|
11
|
+
assert.deepEqual(both(strikethrough("x")), ["<s>x</s>", "~~x~~"]);
|
|
12
|
+
assert.deepEqual(both(spoiler("x")), ["<tg-spoiler>x</tg-spoiler>", "||x||"]);
|
|
13
|
+
assert.deepEqual(both(code("x")), ["<code>x</code>", "`x`"]);
|
|
14
|
+
assert.deepEqual(both(marked("x")), ["<mark>x</mark>", "==x=="]);
|
|
15
|
+
});
|
|
16
|
+
test("marks with no markdown token embed the raw html tag in both dialects", () => {
|
|
17
|
+
assert.deepEqual(both(underline("x")), ["<u>x</u>", "<u>x</u>"]);
|
|
18
|
+
assert.deepEqual(both(subscript("x")), ["<sub>x</sub>", "<sub>x</sub>"]);
|
|
19
|
+
assert.deepEqual(both(superscript("x")), ["<sup>x</sup>", "<sup>x</sup>"]);
|
|
20
|
+
});
|
|
21
|
+
test("string children are escaped per dialect, so user input cannot inject markup", () => {
|
|
22
|
+
assert.equal(bold("<i>&</i>").render("html"), "<b><i>&</i></b>");
|
|
23
|
+
assert.equal(bold("a*b_c").render("markdown"), "**a\\*b\\_c**");
|
|
24
|
+
// html-significant chars become numeric entities in markdown (a backslash would show).
|
|
25
|
+
assert.equal(bold("<&>").render("markdown"), "**<&>**");
|
|
26
|
+
});
|
|
27
|
+
test("nested nodes render into the chosen dialect; siblings concatenate", () => {
|
|
28
|
+
const node = bold("a ", italic("b"), " c");
|
|
29
|
+
assert.equal(node.render("html"), "<b>a <i>b</i> c</b>");
|
|
30
|
+
assert.equal(node.render("markdown"), "**a *b* c**");
|
|
31
|
+
});
|
|
32
|
+
test("null/undefined/false children render as nothing — `cond && node` composes", () => {
|
|
33
|
+
const cond = false;
|
|
34
|
+
assert.equal(bold("a", null, undefined, cond && italic("b")).render("html"), "<b>a</b>");
|
|
35
|
+
});
|
|
36
|
+
test("array children are flattened and concatenated", () => {
|
|
37
|
+
assert.equal(bold(["a", ["b", italic("c")]]).render("html"), "<b>ab<i>c</i></b>");
|
|
38
|
+
});
|
|
39
|
+
test("number and bigint children are stringified then escaped", () => {
|
|
40
|
+
assert.equal(bold(42, "|", 10n).render("markdown"), "**42\\|10**");
|
|
41
|
+
});
|
|
42
|
+
test("br() is a hard newline in markdown, <br/> in html", () => {
|
|
43
|
+
assert.deepEqual(both(br()), ["<br/>", "\n"]);
|
|
44
|
+
});
|
|
45
|
+
test("link() escapes the url per dialect so a hostile url cannot break out", () => {
|
|
46
|
+
assert.equal(link('https://x.dev/?a=1&b="2"', "t").render("html"), '<a href="https://x.dev/?a=1&b="2"">t</a>');
|
|
47
|
+
assert.equal(link("https://x.dev/a) b", "t").render("markdown"), "[t](https://x.dev/a\\)%20b)");
|
|
48
|
+
});
|
|
49
|
+
test("textMention() links tg://user?id=… in both dialects", () => {
|
|
50
|
+
assert.deepEqual(both(textMention({ id: 7 }, "dave")), [
|
|
51
|
+
'<a href="tg://user?id=7">dave</a>',
|
|
52
|
+
"[dave](tg://user?id=7)",
|
|
53
|
+
]);
|
|
54
|
+
});
|
|
55
|
+
test("anchor()/anchorLink() — jump target and #link", () => {
|
|
56
|
+
assert.deepEqual(both(anchor("top")), ['<a name="top"></a>', '<a name="top"></a>']);
|
|
57
|
+
assert.deepEqual(both(anchorLink("top", "up")), ['<a href="#top">up</a>', "[up](#top)"]);
|
|
58
|
+
});
|
|
59
|
+
test("customEmoji() — tg-emoji tag / tg://emoji image link", () => {
|
|
60
|
+
assert.deepEqual(both(customEmoji("5368324170671202286", "👍")), [
|
|
61
|
+
'<tg-emoji emoji-id="5368324170671202286">👍</tg-emoji>',
|
|
62
|
+
"",
|
|
63
|
+
]);
|
|
64
|
+
});
|
|
65
|
+
test("math() keeps LaTeX raw in markdown, html-escapes it in html", () => {
|
|
66
|
+
assert.deepEqual(both(math("a_1 < b")), ["<tg-math>a_1 < b</tg-math>", "$a_1 < b$"]);
|
|
67
|
+
});
|
|
68
|
+
test("dateTime() — best-effort <time> / tg://time image link", () => {
|
|
69
|
+
assert.deepEqual(both(dateTime(1735689600, "R", "soon")), [
|
|
70
|
+
'<time datetime="1735689600" data-format="R">soon</time>',
|
|
71
|
+
"",
|
|
72
|
+
]);
|
|
73
|
+
});
|
|
74
|
+
test("reference()/referenceLink() — tg-reference tags / markdown footnote syntax", () => {
|
|
75
|
+
assert.deepEqual(both(reference("1", "the fine print")), [
|
|
76
|
+
'<tg-reference name="1">the fine print</tg-reference>',
|
|
77
|
+
"[^1]: the fine print",
|
|
78
|
+
]);
|
|
79
|
+
assert.deepEqual(both(referenceLink("1", "see note")), [
|
|
80
|
+
'<tg-reference-link name="1">see note</tg-reference-link>',
|
|
81
|
+
"[^1]",
|
|
82
|
+
]);
|
|
83
|
+
});
|
|
84
|
+
//# sourceMappingURL=inline.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inline.test.js","sourceRoot":"","sources":["../src/inline.test.ts"],"names":[],"mappings":"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"}
|
package/lib/node.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/** the two wire dialects of `InputRichMessage` — `html` and `markdown`. */
|
|
2
|
+
export type Dialect = "html" | "markdown";
|
|
3
|
+
/** whether a node is an inline mark (bold, link, …) or a top-level block (paragraph, table, …). */
|
|
4
|
+
export type Level = "inline" | "block";
|
|
5
|
+
declare const RICH_NODE: unique symbol;
|
|
6
|
+
/**
|
|
7
|
+
* one builder result — a dialect-agnostic fragment of a rich message. every
|
|
8
|
+
* builder (`bold`, `paragraph`, `table`, …) returns one of these; nothing is
|
|
9
|
+
* serialized until the node lands in an `html`/`md` template or a `document()`,
|
|
10
|
+
* which picks the dialect and calls `render` once.
|
|
11
|
+
*/
|
|
12
|
+
export interface RichNode {
|
|
13
|
+
readonly [RICH_NODE]: true;
|
|
14
|
+
readonly level: Level;
|
|
15
|
+
render(dialect: Dialect): string;
|
|
16
|
+
}
|
|
17
|
+
/** construct a `RichNode` from a level + per-dialect renderer. */
|
|
18
|
+
export declare function makeNode(level: Level, render: (dialect: Dialect) => string): RichNode;
|
|
19
|
+
export declare function isRichNode(value: unknown): value is RichNode;
|
|
20
|
+
/** thrown on misuse — a dialect-mismatched interpolation, unsupported content. */
|
|
21
|
+
export declare class RichError extends Error {
|
|
22
|
+
constructor(message: string);
|
|
23
|
+
}
|
|
24
|
+
export {};
|
|
25
|
+
//# sourceMappingURL=node.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"node.d.ts","sourceRoot":"","sources":["../src/node.ts"],"names":[],"mappings":"AAAA,2EAA2E;AAC3E,MAAM,MAAM,OAAO,GAAG,MAAM,GAAG,UAAU,CAAC;AAE1C,mGAAmG;AACnG,MAAM,MAAM,KAAK,GAAG,QAAQ,GAAG,OAAO,CAAC;AAKvC,QAAA,MAAM,SAAS,eAAiC,CAAC;AAEjD;;;;;GAKG;AACH,MAAM,WAAW,QAAQ;IACxB,QAAQ,CAAC,CAAC,SAAS,CAAC,EAAE,IAAI,CAAC;IAC3B,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC;IACtB,MAAM,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,CAAC;CACjC;AAED,kEAAkE;AAClE,wBAAgB,QAAQ,CAAC,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,MAAM,GAAG,QAAQ,CAErF;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,QAAQ,CAM5D;AAED,kFAAkF;AAClF,qBAAa,SAAU,SAAQ,KAAK;gBACvB,OAAO,EAAE,MAAM;CAI3B"}
|
package/lib/node.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// `Symbol.for` so two copies of the package in one node_modules tree still
|
|
2
|
+
// recognise each other's nodes (a duck-typed `.html` check would false-positive
|
|
3
|
+
// on any object that happens to carry an `html: string` field).
|
|
4
|
+
const RICH_NODE = Symbol.for("yaebal.rich.node");
|
|
5
|
+
/** construct a `RichNode` from a level + per-dialect renderer. */
|
|
6
|
+
export function makeNode(level, render) {
|
|
7
|
+
return { [RICH_NODE]: true, level, render };
|
|
8
|
+
}
|
|
9
|
+
export function isRichNode(value) {
|
|
10
|
+
return (typeof value === "object" &&
|
|
11
|
+
value !== null &&
|
|
12
|
+
value[RICH_NODE] === true);
|
|
13
|
+
}
|
|
14
|
+
/** thrown on misuse — a dialect-mismatched interpolation, unsupported content. */
|
|
15
|
+
export class RichError extends Error {
|
|
16
|
+
constructor(message) {
|
|
17
|
+
super(message);
|
|
18
|
+
this.name = "RichError";
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=node.js.map
|
package/lib/node.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"node.js","sourceRoot":"","sources":["../src/node.ts"],"names":[],"mappings":"AAMA,2EAA2E;AAC3E,gFAAgF;AAChF,gEAAgE;AAChE,MAAM,SAAS,GAAG,MAAM,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC;AAcjD,kEAAkE;AAClE,MAAM,UAAU,QAAQ,CAAC,KAAY,EAAE,MAAoC;IAC1E,OAAO,EAAE,CAAC,SAAS,CAAC,EAAE,IAAa,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC;AACtD,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,KAAc;IACxC,OAAO,CACN,OAAO,KAAK,KAAK,QAAQ;QACzB,KAAK,KAAK,IAAI;QACb,KAAiC,CAAC,SAAS,CAAC,KAAK,IAAI,CACtD,CAAC;AACH,CAAC;AAED,kFAAkF;AAClF,MAAM,OAAO,SAAU,SAAQ,KAAK;IACnC,YAAY,OAAe;QAC1B,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,WAAW,CAAC;IACzB,CAAC;CACD"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plaintext.test.d.ts","sourceRoot":"","sources":["../src/plaintext.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
import { richBlockToPlainText, richMessageToPlainText, richTextToPlainText } from "./plaintext.js";
|
|
4
|
+
test("richTextToPlainText flattens a plain string, nested marks, and arrays", () => {
|
|
5
|
+
assert.equal(richTextToPlainText("hi"), "hi");
|
|
6
|
+
assert.equal(richTextToPlainText({ type: "bold", text: "hi" }), "hi");
|
|
7
|
+
assert.equal(richTextToPlainText([{ type: "bold", text: "a" }, " ", { type: "italic", text: "b" }]), "a b");
|
|
8
|
+
});
|
|
9
|
+
test("richTextToPlainText special-cases custom_emoji/mathematical_expression/anchor", () => {
|
|
10
|
+
assert.equal(richTextToPlainText({ type: "custom_emoji", alternative_text: "😀" }), "😀");
|
|
11
|
+
assert.equal(richTextToPlainText({ type: "mathematical_expression", expression: "a^2" }), "a^2");
|
|
12
|
+
assert.equal(richTextToPlainText({ type: "anchor", name: "top" }), "");
|
|
13
|
+
});
|
|
14
|
+
test("richTextToPlainText returns empty string for undefined", () => {
|
|
15
|
+
assert.equal(richTextToPlainText(undefined), "");
|
|
16
|
+
});
|
|
17
|
+
test("richBlockToPlainText flattens simple text-bearing blocks with a trailing newline", () => {
|
|
18
|
+
assert.equal(richBlockToPlainText({ type: "paragraph", text: "hi" }), "hi\n");
|
|
19
|
+
assert.equal(richBlockToPlainText({ type: "heading", text: "title", size: 1 }), "title\n");
|
|
20
|
+
assert.equal(richBlockToPlainText({ type: "pre", text: "code" }), "code\n");
|
|
21
|
+
assert.equal(richBlockToPlainText({ type: "footer", text: "f" }), "f\n");
|
|
22
|
+
assert.equal(richBlockToPlainText({ type: "thinking", text: "…" }), "…\n");
|
|
23
|
+
assert.equal(richBlockToPlainText({ type: "divider" }), "---\n");
|
|
24
|
+
assert.equal(richBlockToPlainText({ type: "anchor", name: "top" }), "");
|
|
25
|
+
assert.equal(richBlockToPlainText({ type: "mathematical_expression", expression: "a^2" }), "a^2\n");
|
|
26
|
+
});
|
|
27
|
+
test("richBlockToPlainText flattens a list, one `- ` item per line", () => {
|
|
28
|
+
const block = {
|
|
29
|
+
type: "list",
|
|
30
|
+
items: [
|
|
31
|
+
{ blocks: [{ type: "paragraph", text: "one" }] },
|
|
32
|
+
{ blocks: [{ type: "paragraph", text: "two" }] },
|
|
33
|
+
],
|
|
34
|
+
};
|
|
35
|
+
assert.equal(richBlockToPlainText(block), "- one\n- two\n");
|
|
36
|
+
});
|
|
37
|
+
test("richBlockToPlainText flattens blockquote/pullquote with an optional ' — credit' suffix", () => {
|
|
38
|
+
assert.equal(richBlockToPlainText({
|
|
39
|
+
type: "blockquote",
|
|
40
|
+
blocks: [{ type: "paragraph", text: "x" }],
|
|
41
|
+
}), "x\n\n");
|
|
42
|
+
assert.equal(richBlockToPlainText({
|
|
43
|
+
type: "blockquote",
|
|
44
|
+
blocks: [{ type: "paragraph", text: "x" }],
|
|
45
|
+
credit: "author",
|
|
46
|
+
}), "x\n — author\n");
|
|
47
|
+
assert.equal(richBlockToPlainText({ type: "pullquote", text: "x" }), "x\n");
|
|
48
|
+
assert.equal(richBlockToPlainText({ type: "pullquote", text: "x", credit: "author" }), "x — author\n");
|
|
49
|
+
});
|
|
50
|
+
test("richBlockToPlainText flattens collage/slideshow blocks with a captioned trailer", () => {
|
|
51
|
+
const block = {
|
|
52
|
+
type: "collage",
|
|
53
|
+
blocks: [{ type: "photo", photo: [], caption: undefined }],
|
|
54
|
+
caption: { text: "a cat", credit: "photographer" },
|
|
55
|
+
};
|
|
56
|
+
assert.equal(richBlockToPlainText(block), "a cat (photographer)\n");
|
|
57
|
+
});
|
|
58
|
+
test("richBlockToPlainText flattens a table, ` | `-joined cells, one row per line", () => {
|
|
59
|
+
const block = {
|
|
60
|
+
type: "table",
|
|
61
|
+
cells: [
|
|
62
|
+
[{ text: "a" }, { text: "b" }],
|
|
63
|
+
[{ text: "c" }, { text: "d" }],
|
|
64
|
+
],
|
|
65
|
+
};
|
|
66
|
+
assert.equal(richBlockToPlainText(block), "a | b\nc | d\n");
|
|
67
|
+
});
|
|
68
|
+
test("richBlockToPlainText flattens details as its summary followed by its blocks", () => {
|
|
69
|
+
const block = {
|
|
70
|
+
type: "details",
|
|
71
|
+
summary: "more",
|
|
72
|
+
blocks: [{ type: "paragraph", text: "hidden" }],
|
|
73
|
+
};
|
|
74
|
+
assert.equal(richBlockToPlainText(block), "more\nhidden\n");
|
|
75
|
+
});
|
|
76
|
+
test("richBlockToPlainText flattens map/media blocks down to just their caption", () => {
|
|
77
|
+
assert.equal(richBlockToPlainText({ type: "map", caption: { text: "here" } }), "here\n");
|
|
78
|
+
assert.equal(richBlockToPlainText({ type: "photo", photo: [], caption: undefined }), "");
|
|
79
|
+
for (const type of ["animation", "audio", "photo", "video", "voice_note"]) {
|
|
80
|
+
assert.equal(richBlockToPlainText({ type, caption: { text: "cap" } }), "cap\n");
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
test("richBlockToPlainText falls back to empty string for an unrecognized block type", () => {
|
|
84
|
+
assert.equal(richBlockToPlainText({ type: "not-a-real-type" }), "");
|
|
85
|
+
});
|
|
86
|
+
test("richMessageToPlainText flattens a full block tree, joined and trimmed", () => {
|
|
87
|
+
const message = {
|
|
88
|
+
blocks: [
|
|
89
|
+
{ type: "heading", text: "title", size: 1 },
|
|
90
|
+
{ type: "paragraph", text: [{ type: "bold", text: "hi" }, " there"] },
|
|
91
|
+
{ type: "divider" },
|
|
92
|
+
{
|
|
93
|
+
type: "list",
|
|
94
|
+
items: [{ label: "1", blocks: [{ type: "paragraph", text: "one" }] }],
|
|
95
|
+
},
|
|
96
|
+
],
|
|
97
|
+
};
|
|
98
|
+
assert.equal(richMessageToPlainText(message), "title\nhi there\n---\n- one");
|
|
99
|
+
});
|
|
100
|
+
//# sourceMappingURL=plaintext.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plaintext.test.js","sourceRoot":"","sources":["../src/plaintext.test.ts"],"names":[],"mappings":"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"}
|
package/lib/render.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { RichDocument } from "./document.js";
|
|
2
|
+
import { type Dialect, type RichNode } from "./node.js";
|
|
3
|
+
/**
|
|
4
|
+
* anything a builder or template accepts as content:
|
|
5
|
+
*
|
|
6
|
+
* - `string` / `number` / `bigint` — escaped for the target dialect, renders literally;
|
|
7
|
+
* - a `RichNode` (builder result) — rendered into the target dialect;
|
|
8
|
+
* - a `RichDocument` — inlined as-is (its dialect must match, else `RichError`);
|
|
9
|
+
* - `null` / `undefined` / `false` — empty string, so `cond && bold(…)` composes;
|
|
10
|
+
* - an array — each item rendered and concatenated.
|
|
11
|
+
*/
|
|
12
|
+
export type Insertable = RichNode | RichDocument | string | number | bigint | null | undefined | false | readonly Insertable[];
|
|
13
|
+
/** escape plain text for a dialect so it can never be re-parsed as markup. */
|
|
14
|
+
export declare function escapeFor(text: string, dialect: Dialect): string;
|
|
15
|
+
/** resolve one piece of content into a dialect string (text escaped, nodes rendered). */
|
|
16
|
+
export declare function render(value: Insertable, dialect: Dialect): string;
|
|
17
|
+
//# sourceMappingURL=render.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../src/render.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAE7C,OAAO,EAAE,KAAK,OAAO,EAAyB,KAAK,QAAQ,EAAE,MAAM,WAAW,CAAC;AAE/E;;;;;;;;GAQG;AACH,MAAM,MAAM,UAAU,GACnB,QAAQ,GACR,YAAY,GACZ,MAAM,GACN,MAAM,GACN,MAAM,GACN,IAAI,GACJ,SAAS,GACT,KAAK,GACL,SAAS,UAAU,EAAE,CAAC;AAEzB,8EAA8E;AAC9E,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,MAAM,CAEhE;AAED,yFAAyF;AACzF,wBAAgB,MAAM,CAAC,KAAK,EAAE,UAAU,EAAE,OAAO,EAAE,OAAO,GAAG,MAAM,CAuBlE"}
|
package/lib/render.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { RichDocument } from "./document.js";
|
|
2
|
+
import { escapeMarkdown, escapeText } from "./escape.js";
|
|
3
|
+
import { isRichNode, RichError } from "./node.js";
|
|
4
|
+
/** escape plain text for a dialect so it can never be re-parsed as markup. */
|
|
5
|
+
export function escapeFor(text, dialect) {
|
|
6
|
+
return dialect === "html" ? escapeText(text) : escapeMarkdown(text);
|
|
7
|
+
}
|
|
8
|
+
/** resolve one piece of content into a dialect string (text escaped, nodes rendered). */
|
|
9
|
+
export function render(value, dialect) {
|
|
10
|
+
if (value === null || value === undefined || value === false)
|
|
11
|
+
return "";
|
|
12
|
+
if (typeof value === "string")
|
|
13
|
+
return escapeFor(value, dialect);
|
|
14
|
+
if (typeof value === "number" || typeof value === "bigint") {
|
|
15
|
+
return escapeFor(String(value), dialect);
|
|
16
|
+
}
|
|
17
|
+
if (Array.isArray(value)) {
|
|
18
|
+
return value.map((item) => render(item, dialect)).join("");
|
|
19
|
+
}
|
|
20
|
+
if (value instanceof RichDocument) {
|
|
21
|
+
if (value.dialect !== dialect) {
|
|
22
|
+
throw new RichError(`cannot inline a ${value.dialect} document into a ${dialect} template`);
|
|
23
|
+
}
|
|
24
|
+
return value.content;
|
|
25
|
+
}
|
|
26
|
+
if (isRichNode(value))
|
|
27
|
+
return value.render(dialect);
|
|
28
|
+
throw new RichError(`unsupported rich content: ${typeof value}`);
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=render.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"render.js","sourceRoot":"","sources":["../src/render.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAC7C,OAAO,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzD,OAAO,EAAgB,UAAU,EAAE,SAAS,EAAiB,MAAM,WAAW,CAAC;AAsB/E,8EAA8E;AAC9E,MAAM,UAAU,SAAS,CAAC,IAAY,EAAE,OAAgB;IACvD,OAAO,OAAO,KAAK,MAAM,CAAC,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC;AACrE,CAAC;AAED,yFAAyF;AACzF,MAAM,UAAU,MAAM,CAAC,KAAiB,EAAE,OAAgB;IACzD,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,KAAK;QAAE,OAAO,EAAE,CAAC;IAExE,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,SAAS,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;IAChE,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC5D,OAAO,SAAS,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC,CAAC;IAC1C,CAAC;IAED,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QAC1B,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,MAAM,CAAC,IAAkB,EAAE,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAC1E,CAAC;IAED,IAAI,KAAK,YAAY,YAAY,EAAE,CAAC;QACnC,IAAI,KAAK,CAAC,OAAO,KAAK,OAAO,EAAE,CAAC;YAC/B,MAAM,IAAI,SAAS,CAAC,mBAAmB,KAAK,CAAC,OAAO,oBAAoB,OAAO,WAAW,CAAC,CAAC;QAC7F,CAAC;QAED,OAAO,KAAK,CAAC,OAAO,CAAC;IACtB,CAAC;IAED,IAAI,UAAU,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IAEpD,MAAM,IAAI,SAAS,CAAC,6BAA6B,OAAO,KAAK,EAAE,CAAC,CAAC;AAClE,CAAC"}
|
package/lib/send.d.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import type { Api, Context, Message, Plugin } from "@yaebal/core";
|
|
2
2
|
import type { InputRichMessage } from "@yaebal/types";
|
|
3
|
+
import { RichDocument } from "./document.js";
|
|
3
4
|
import { RichMessageDraft, type RichMessageDraftOptions } from "./draft.js";
|
|
5
|
+
/** everything the send/draft surface accepts: a template/`document()` result, a raw payload, or an html string. */
|
|
6
|
+
export type RichSource = RichDocument | InputRichMessage | string;
|
|
4
7
|
/**
|
|
5
8
|
* `sendRichMessage` — standalone, no plugin install required (the `@yaebal/fmt` /
|
|
6
9
|
* `@yaebal/keyboard` "pure helper" style). `extra` covers the rest of
|
|
@@ -9,16 +12,16 @@ import { RichMessageDraft, type RichMessageDraftOptions } from "./draft.js";
|
|
|
9
12
|
* `business_connection_id`, `message_thread_id`, `direct_messages_topic_id`,
|
|
10
13
|
* `allow_paid_broadcast`, `suggested_post_parameters`.
|
|
11
14
|
*/
|
|
12
|
-
export declare function sendRichMessage(api: Api, chatId: number | string, input:
|
|
15
|
+
export declare function sendRichMessage(api: Api, chatId: number | string, input: RichSource, extra?: Record<string, unknown>): Promise<Message>;
|
|
13
16
|
/**
|
|
14
17
|
* `sendRichMessageDraft` — a single ephemeral (30s-ttl) push. private chats
|
|
15
18
|
* only. most callers want `RichMessageDraft` (draft.ts) instead, which keeps the
|
|
16
19
|
* ttl alive across a stream and forces a final `sendRichMessage` commit.
|
|
17
20
|
*/
|
|
18
|
-
export declare function sendRichMessageDraft(api: Api, chatId: number, draftId: number, input:
|
|
21
|
+
export declare function sendRichMessageDraft(api: Api, chatId: number, draftId: number, input: RichSource, extra?: Record<string, unknown>): Promise<boolean>;
|
|
19
22
|
export interface RichContext {
|
|
20
23
|
/** `ctx.send`-flavored `sendRichMessage`, bound to the current chat. */
|
|
21
|
-
sendRichMessage(input:
|
|
24
|
+
sendRichMessage(input: RichSource, extra?: Record<string, unknown>): Promise<Message>;
|
|
22
25
|
/** open a `RichMessageDraft` streaming session bound to the current chat. */
|
|
23
26
|
richMessageDraft(draftId: number, options?: RichMessageDraftOptions): RichMessageDraft;
|
|
24
27
|
}
|