@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/document.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* the emitted rich message — a rendered dialect string plus the
|
|
3
|
+
* `InputRichMessage` flags, settable fluently (`.rtl()`, `.noEntityDetection()`).
|
|
4
|
+
*
|
|
5
|
+
* `sendRichMessage`/`RichMessageDraft` accept it directly, and `toJSON()`
|
|
6
|
+
* delegates to `toInputRichMessage()`, so even a raw `api.call` payload holding
|
|
7
|
+
* a `RichDocument` serializes into the correct `rich_message` shape.
|
|
8
|
+
*/
|
|
9
|
+
export class RichDocument {
|
|
10
|
+
dialect;
|
|
11
|
+
content;
|
|
12
|
+
#isRtl;
|
|
13
|
+
#skipEntityDetection;
|
|
14
|
+
constructor(dialect, content) {
|
|
15
|
+
this.dialect = dialect;
|
|
16
|
+
this.content = content;
|
|
17
|
+
}
|
|
18
|
+
/** `InputRichMessage.is_rtl` — show the message right-to-left. */
|
|
19
|
+
rtl(value = true) {
|
|
20
|
+
this.#isRtl = value;
|
|
21
|
+
return this;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* `InputRichMessage.skip_entity_detection` — turn off auto-detection of urls,
|
|
25
|
+
* emails, `@mentions`, `#hashtags`, `$cashtags`, `/bot_commands`, and phone
|
|
26
|
+
* numbers in plain text.
|
|
27
|
+
*/
|
|
28
|
+
noEntityDetection(value = true) {
|
|
29
|
+
this.#skipEntityDetection = value;
|
|
30
|
+
return this;
|
|
31
|
+
}
|
|
32
|
+
/** the `rich_message` method argument: `{ [dialect]: content, is_rtl?, skip_entity_detection? }`. */
|
|
33
|
+
toInputRichMessage() {
|
|
34
|
+
return {
|
|
35
|
+
[this.dialect]: this.content,
|
|
36
|
+
...(this.#isRtl !== undefined ? { is_rtl: this.#isRtl } : {}),
|
|
37
|
+
...(this.#skipEntityDetection !== undefined
|
|
38
|
+
? { skip_entity_detection: this.#skipEntityDetection }
|
|
39
|
+
: {}),
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
toJSON() {
|
|
43
|
+
return this.toInputRichMessage();
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=document.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"document.js","sourceRoot":"","sources":["../src/document.ts"],"names":[],"mappings":"AAGA;;;;;;;GAOG;AACH,MAAM,OAAO,YAAY;IAKd;IACA;IALV,MAAM,CAAW;IACjB,oBAAoB,CAAW;IAE/B,YACU,OAAgB,EAChB,OAAe;QADf,YAAO,GAAP,OAAO,CAAS;QAChB,YAAO,GAAP,OAAO,CAAQ;IACtB,CAAC;IAEJ,kEAAkE;IAClE,GAAG,CAAC,KAAK,GAAG,IAAI;QACf,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC;QACpB,OAAO,IAAI,CAAC;IACb,CAAC;IAED;;;;OAIG;IACH,iBAAiB,CAAC,KAAK,GAAG,IAAI;QAC7B,IAAI,CAAC,oBAAoB,GAAG,KAAK,CAAC;QAClC,OAAO,IAAI,CAAC;IACb,CAAC;IAED,qGAAqG;IACrG,kBAAkB;QACjB,OAAO;YACN,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,IAAI,CAAC,OAAO;YAC5B,GAAG,CAAC,IAAI,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC7D,GAAG,CAAC,IAAI,CAAC,oBAAoB,KAAK,SAAS;gBAC1C,CAAC,CAAC,EAAE,qBAAqB,EAAE,IAAI,CAAC,oBAAoB,EAAE;gBACtD,CAAC,CAAC,EAAE,CAAC;SACN,CAAC;IACH,CAAC;IAED,MAAM;QACL,OAAO,IAAI,CAAC,kBAAkB,EAAE,CAAC;IAClC,CAAC;CACD"}
|
package/lib/draft.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Api, Message } from "@yaebal/core";
|
|
2
2
|
import type { InputRichMessage } from "@yaebal/types";
|
|
3
|
+
import { RichDocument } from "./document.js";
|
|
3
4
|
export interface RichMessageDraftOptions {
|
|
4
5
|
/**
|
|
5
6
|
* how often to re-push the last draft to keep it alive, in ms. telegram drops
|
|
@@ -10,7 +11,17 @@ export interface RichMessageDraftOptions {
|
|
|
10
11
|
keepAliveMs?: number;
|
|
11
12
|
/** called when a background keep-alive push fails (e.g. network blip). */
|
|
12
13
|
onError?: (error: unknown) => void;
|
|
14
|
+
/**
|
|
15
|
+
* the forum topic to stream/send into. sendRichMessageDraft has no
|
|
16
|
+
* business_connection_id param (drafts aren't supported in business chats), so only
|
|
17
|
+
* the thread id carries through the keep-alive pushes; `send()` additionally accepts
|
|
18
|
+
* `businessConnectionId` for the final, non-ephemeral sendRichMessage call.
|
|
19
|
+
*/
|
|
20
|
+
messageThreadId?: number;
|
|
21
|
+
/** routed into the final `send()` only — see {@link messageThreadId}. */
|
|
22
|
+
businessConnectionId?: string;
|
|
13
23
|
}
|
|
24
|
+
type DraftSource = RichDocument | InputRichMessage | string;
|
|
14
25
|
/**
|
|
15
26
|
* a `sendRichMessageDraft` streaming session — the operationally hard part of the
|
|
16
27
|
* rich-message api. telegram's draft is ephemeral: it vanishes 30s after the last
|
|
@@ -20,25 +31,51 @@ export interface RichMessageDraftOptions {
|
|
|
20
31
|
*
|
|
21
32
|
* - re-pushes the latest draft on a timer so a slow generator (e.g. an LLM
|
|
22
33
|
* stream) doesn't lose the draft between tokens;
|
|
23
|
-
* - refuses to push after `
|
|
34
|
+
* - refuses to push after `send()`/`cancel()`, so a stray late call can't
|
|
24
35
|
* resurrect a closed draft;
|
|
25
|
-
* - requires an explicit `
|
|
36
|
+
* - requires an explicit `send()` — there is no implicit "last push wins".
|
|
37
|
+
*
|
|
38
|
+
* two ways to grow the draft: `rewrite()` replaces the whole thing (what you want
|
|
39
|
+
* for a token stream, where each chunk is a longer version of the *same* paragraph),
|
|
40
|
+
* `write()` appends to it via plain string concatenation (what you want to tack on
|
|
41
|
+
* a block — a footer, a divider — after content that's already there, without
|
|
42
|
+
* re-supplying it). a push boundary that lands mid-tag with `write()` is on the
|
|
43
|
+
* caller, same tradeoff raw concatenation always has.
|
|
44
|
+
*
|
|
45
|
+
* `send()` finalizes: with no argument it auto-assembles from the accumulated
|
|
46
|
+
* `rewrite()`/`write()` calls, so you don't have to re-render the final content —
|
|
47
|
+
* pass an explicit override when you want the persisted message to differ from the
|
|
48
|
+
* last draft snapshot.
|
|
26
49
|
*
|
|
27
50
|
* @example
|
|
28
51
|
* const draft = ctx.richMessageDraft(1);
|
|
29
|
-
* await draft.
|
|
30
|
-
* for await (const chunk of stream)
|
|
31
|
-
*
|
|
52
|
+
* await draft.rewrite(document([thinking("…")]));
|
|
53
|
+
* for await (const chunk of stream) {
|
|
54
|
+
* soFar += chunk;
|
|
55
|
+
* await draft.rewrite(document([paragraph(soFar)]));
|
|
56
|
+
* }
|
|
57
|
+
* await draft.send();
|
|
32
58
|
*/
|
|
33
59
|
export declare class RichMessageDraft {
|
|
34
60
|
#private;
|
|
35
61
|
constructor(api: Api, chatId: number, draftId: number, options?: RichMessageDraftOptions);
|
|
36
62
|
get closed(): boolean;
|
|
37
|
-
/**
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
|
|
63
|
+
/** replace the whole draft with new content. */
|
|
64
|
+
rewrite(input: DraftSource): Promise<void>;
|
|
65
|
+
/**
|
|
66
|
+
* append to the current draft (plain string concatenation of the resolved text).
|
|
67
|
+
* throws if `input`'s dialect doesn't match the draft's, or if nothing has been
|
|
68
|
+
* written yet — call `rewrite()` first.
|
|
69
|
+
*/
|
|
70
|
+
write(input: DraftSource): Promise<void>;
|
|
71
|
+
/**
|
|
72
|
+
* finalize: persist the real message and stop the keep-alive. with no `override`,
|
|
73
|
+
* auto-assembles from the accumulated `rewrite()`/`write()` calls. always call this
|
|
74
|
+
* or `cancel()`.
|
|
75
|
+
*/
|
|
76
|
+
send(override?: DraftSource, extra?: Record<string, unknown>): Promise<Message>;
|
|
41
77
|
/** abandon the draft without persisting anything — it expires within 30s on its own. */
|
|
42
78
|
cancel(): void;
|
|
43
79
|
}
|
|
80
|
+
export {};
|
|
44
81
|
//# sourceMappingURL=draft.d.ts.map
|
package/lib/draft.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"draft.d.ts","sourceRoot":"","sources":["../src/draft.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACjD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"draft.d.ts","sourceRoot":"","sources":["../src/draft.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACjD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACtD,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAG7C,MAAM,WAAW,uBAAuB;IACvC;;;;;OAKG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,0EAA0E;IAC1E,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;IACnC;;;;;OAKG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,yEAAyE;IACzE,oBAAoB,CAAC,EAAE,MAAM,CAAC;CAC9B;AASD,KAAK,WAAW,GAAG,YAAY,GAAG,gBAAgB,GAAG,MAAM,CAAC;AAqC5D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,qBAAa,gBAAgB;;gBAahB,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,GAAE,uBAA4B;IAU5F,IAAI,MAAM,IAAI,OAAO,CAEpB;IAED,gDAAgD;IAC1C,OAAO,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAQhD;;;;OAIG;IACG,KAAK,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAoD9C;;;;OAIG;IACG,IAAI,CAAC,QAAQ,CAAC,EAAE,WAAW,EAAE,KAAK,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAuBzF,wFAAwF;IACxF,MAAM,IAAI,IAAI;CAgBd"}
|
package/lib/draft.js
CHANGED
|
@@ -1,3 +1,36 @@
|
|
|
1
|
+
import { RichDocument } from "./document.js";
|
|
2
|
+
function resolve(input, fallbackDialect) {
|
|
3
|
+
if (typeof input === "string")
|
|
4
|
+
return { dialect: fallbackDialect, text: input };
|
|
5
|
+
if (input instanceof RichDocument)
|
|
6
|
+
return resolve(input.toInputRichMessage(), fallbackDialect);
|
|
7
|
+
if (input.html !== undefined) {
|
|
8
|
+
return {
|
|
9
|
+
dialect: "html",
|
|
10
|
+
text: input.html,
|
|
11
|
+
isRtl: input.is_rtl,
|
|
12
|
+
skipEntityDetection: input.skip_entity_detection,
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
if (input.markdown !== undefined) {
|
|
16
|
+
return {
|
|
17
|
+
dialect: "markdown",
|
|
18
|
+
text: input.markdown,
|
|
19
|
+
isRtl: input.is_rtl,
|
|
20
|
+
skipEntityDetection: input.skip_entity_detection,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
throw new Error("RichMessageDraft: input must have an `html` or `markdown` field");
|
|
24
|
+
}
|
|
25
|
+
function toInputRichMessage(state) {
|
|
26
|
+
return {
|
|
27
|
+
[state.dialect]: state.text,
|
|
28
|
+
...(state.isRtl !== undefined ? { is_rtl: state.isRtl } : {}),
|
|
29
|
+
...(state.skipEntityDetection !== undefined
|
|
30
|
+
? { skip_entity_detection: state.skipEntityDetection }
|
|
31
|
+
: {}),
|
|
32
|
+
};
|
|
33
|
+
}
|
|
1
34
|
/**
|
|
2
35
|
* a `sendRichMessageDraft` streaming session — the operationally hard part of the
|
|
3
36
|
* rich-message api. telegram's draft is ephemeral: it vanishes 30s after the last
|
|
@@ -7,15 +40,30 @@
|
|
|
7
40
|
*
|
|
8
41
|
* - re-pushes the latest draft on a timer so a slow generator (e.g. an LLM
|
|
9
42
|
* stream) doesn't lose the draft between tokens;
|
|
10
|
-
* - refuses to push after `
|
|
43
|
+
* - refuses to push after `send()`/`cancel()`, so a stray late call can't
|
|
11
44
|
* resurrect a closed draft;
|
|
12
|
-
* - requires an explicit `
|
|
45
|
+
* - requires an explicit `send()` — there is no implicit "last push wins".
|
|
46
|
+
*
|
|
47
|
+
* two ways to grow the draft: `rewrite()` replaces the whole thing (what you want
|
|
48
|
+
* for a token stream, where each chunk is a longer version of the *same* paragraph),
|
|
49
|
+
* `write()` appends to it via plain string concatenation (what you want to tack on
|
|
50
|
+
* a block — a footer, a divider — after content that's already there, without
|
|
51
|
+
* re-supplying it). a push boundary that lands mid-tag with `write()` is on the
|
|
52
|
+
* caller, same tradeoff raw concatenation always has.
|
|
53
|
+
*
|
|
54
|
+
* `send()` finalizes: with no argument it auto-assembles from the accumulated
|
|
55
|
+
* `rewrite()`/`write()` calls, so you don't have to re-render the final content —
|
|
56
|
+
* pass an explicit override when you want the persisted message to differ from the
|
|
57
|
+
* last draft snapshot.
|
|
13
58
|
*
|
|
14
59
|
* @example
|
|
15
60
|
* const draft = ctx.richMessageDraft(1);
|
|
16
|
-
* await draft.
|
|
17
|
-
* for await (const chunk of stream)
|
|
18
|
-
*
|
|
61
|
+
* await draft.rewrite(document([thinking("…")]));
|
|
62
|
+
* for await (const chunk of stream) {
|
|
63
|
+
* soFar += chunk;
|
|
64
|
+
* await draft.rewrite(document([paragraph(soFar)]));
|
|
65
|
+
* }
|
|
66
|
+
* await draft.send();
|
|
19
67
|
*/
|
|
20
68
|
export class RichMessageDraft {
|
|
21
69
|
#api;
|
|
@@ -23,8 +71,10 @@ export class RichMessageDraft {
|
|
|
23
71
|
#draftId;
|
|
24
72
|
#keepAliveMs;
|
|
25
73
|
#onError;
|
|
74
|
+
#messageThreadId;
|
|
75
|
+
#businessConnectionId;
|
|
26
76
|
#timer;
|
|
27
|
-
#
|
|
77
|
+
#state;
|
|
28
78
|
#closed = false;
|
|
29
79
|
constructor(api, chatId, draftId, options = {}) {
|
|
30
80
|
this.#api = api;
|
|
@@ -32,42 +82,83 @@ export class RichMessageDraft {
|
|
|
32
82
|
this.#draftId = draftId;
|
|
33
83
|
this.#keepAliveMs = options.keepAliveMs ?? 20_000;
|
|
34
84
|
this.#onError = options.onError;
|
|
85
|
+
this.#messageThreadId = options.messageThreadId;
|
|
86
|
+
this.#businessConnectionId = options.businessConnectionId;
|
|
35
87
|
}
|
|
36
88
|
get closed() {
|
|
37
89
|
return this.#closed;
|
|
38
90
|
}
|
|
39
|
-
/**
|
|
40
|
-
async
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
this.#
|
|
45
|
-
|
|
91
|
+
/** replace the whole draft with new content. */
|
|
92
|
+
async rewrite(input) {
|
|
93
|
+
this.#assertOpen();
|
|
94
|
+
this.#state = resolve(input, this.#state?.dialect ?? "html");
|
|
95
|
+
await this.#pushCurrent();
|
|
96
|
+
this.#arm();
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* append to the current draft (plain string concatenation of the resolved text).
|
|
100
|
+
* throws if `input`'s dialect doesn't match the draft's, or if nothing has been
|
|
101
|
+
* written yet — call `rewrite()` first.
|
|
102
|
+
*/
|
|
103
|
+
async write(input) {
|
|
104
|
+
this.#assertOpen();
|
|
105
|
+
if (!this.#state) {
|
|
106
|
+
throw new Error("RichMessageDraft: write() before the first rewrite()");
|
|
107
|
+
}
|
|
108
|
+
const next = resolve(input, this.#state.dialect);
|
|
109
|
+
if (next.dialect !== this.#state.dialect) {
|
|
110
|
+
throw new Error(`RichMessageDraft: write() dialect "${next.dialect}" doesn't match the draft's "${this.#state.dialect}"`);
|
|
111
|
+
}
|
|
112
|
+
this.#state = {
|
|
113
|
+
dialect: this.#state.dialect,
|
|
114
|
+
text: this.#state.text + next.text,
|
|
115
|
+
isRtl: next.isRtl ?? this.#state.isRtl,
|
|
116
|
+
skipEntityDetection: next.skipEntityDetection ?? this.#state.skipEntityDetection,
|
|
117
|
+
};
|
|
118
|
+
await this.#pushCurrent();
|
|
46
119
|
this.#arm();
|
|
47
120
|
}
|
|
121
|
+
#pushCurrent() {
|
|
122
|
+
const state = this.#state;
|
|
123
|
+
if (!state)
|
|
124
|
+
throw new Error("unreachable: #pushCurrent() with no state");
|
|
125
|
+
return this.#push(toInputRichMessage(state));
|
|
126
|
+
}
|
|
48
127
|
#push(input) {
|
|
49
128
|
return this.#api.call("sendRichMessageDraft", {
|
|
50
129
|
chat_id: this.#chatId,
|
|
51
130
|
draft_id: this.#draftId,
|
|
52
131
|
rich_message: input,
|
|
132
|
+
...(this.#messageThreadId === undefined ? {} : { message_thread_id: this.#messageThreadId }),
|
|
53
133
|
});
|
|
54
134
|
}
|
|
55
135
|
#arm() {
|
|
56
136
|
clearInterval(this.#timer);
|
|
57
137
|
this.#timer = setInterval(() => {
|
|
58
|
-
if (!this.#
|
|
138
|
+
if (!this.#state)
|
|
59
139
|
return;
|
|
60
|
-
this.#push(this.#
|
|
140
|
+
this.#push(toInputRichMessage(this.#state)).catch((error) => this.#onError?.(error));
|
|
61
141
|
}, this.#keepAliveMs);
|
|
62
142
|
this.#timer.unref?.();
|
|
63
143
|
}
|
|
64
|
-
/**
|
|
65
|
-
|
|
144
|
+
/**
|
|
145
|
+
* finalize: persist the real message and stop the keep-alive. with no `override`,
|
|
146
|
+
* auto-assembles from the accumulated `rewrite()`/`write()` calls. always call this
|
|
147
|
+
* or `cancel()`.
|
|
148
|
+
*/
|
|
149
|
+
async send(override, extra = {}) {
|
|
66
150
|
this.#stop();
|
|
67
|
-
const
|
|
151
|
+
const state = override !== undefined ? resolve(override, this.#state?.dialect ?? "html") : this.#state;
|
|
152
|
+
if (!state) {
|
|
153
|
+
throw new Error("RichMessageDraft: send() with nothing written — call rewrite()/write() first or pass an override");
|
|
154
|
+
}
|
|
68
155
|
return this.#api.call("sendRichMessage", {
|
|
69
156
|
chat_id: this.#chatId,
|
|
70
|
-
rich_message:
|
|
157
|
+
rich_message: toInputRichMessage(state),
|
|
158
|
+
...(this.#messageThreadId === undefined ? {} : { message_thread_id: this.#messageThreadId }),
|
|
159
|
+
...(this.#businessConnectionId === undefined
|
|
160
|
+
? {}
|
|
161
|
+
: { business_connection_id: this.#businessConnectionId }),
|
|
71
162
|
...extra,
|
|
72
163
|
});
|
|
73
164
|
}
|
|
@@ -81,5 +172,10 @@ export class RichMessageDraft {
|
|
|
81
172
|
this.#closed = true;
|
|
82
173
|
clearInterval(this.#timer);
|
|
83
174
|
}
|
|
175
|
+
#assertOpen() {
|
|
176
|
+
if (this.#closed) {
|
|
177
|
+
throw new Error("RichMessageDraft: rewrite()/write() after send()/cancel()");
|
|
178
|
+
}
|
|
179
|
+
}
|
|
84
180
|
}
|
|
85
181
|
//# sourceMappingURL=draft.js.map
|
package/lib/draft.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"draft.js","sourceRoot":"","sources":["../src/draft.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"draft.js","sourceRoot":"","sources":["../src/draft.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"draft.test.d.ts","sourceRoot":"","sources":["../src/draft.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
import { mockApi } from "@yaebal/test";
|
|
4
|
+
import { RichMessageDraft } from "./draft.js";
|
|
5
|
+
test("rewrite() pushes a full sendRichMessageDraft snapshot and re-arms the keep-alive timer", async () => {
|
|
6
|
+
const { api, calls } = mockApi();
|
|
7
|
+
const draft = new RichMessageDraft(api, 1, 7, { keepAliveMs: 60_000 });
|
|
8
|
+
await draft.rewrite("<tg-thinking>…</tg-thinking>");
|
|
9
|
+
assert.deepEqual(calls.map((c) => ({ method: c.method, params: c.params })), [
|
|
10
|
+
{
|
|
11
|
+
method: "sendRichMessageDraft",
|
|
12
|
+
params: { chat_id: 1, draft_id: 7, rich_message: { html: "<tg-thinking>…</tg-thinking>" } },
|
|
13
|
+
},
|
|
14
|
+
]);
|
|
15
|
+
});
|
|
16
|
+
test("rewrite() replaces the whole draft — a second call drops the first's content", async () => {
|
|
17
|
+
const { api, calls } = mockApi();
|
|
18
|
+
const draft = new RichMessageDraft(api, 1, 1, { keepAliveMs: 60_000 });
|
|
19
|
+
await draft.rewrite("<p>one</p>");
|
|
20
|
+
await draft.rewrite("<p>two</p>");
|
|
21
|
+
assert.equal(calls.length, 2);
|
|
22
|
+
assert.deepEqual(calls[1]?.params?.rich_message, { html: "<p>two</p>" });
|
|
23
|
+
});
|
|
24
|
+
test("write() appends via string concatenation onto the current draft", async () => {
|
|
25
|
+
const { api, calls } = mockApi();
|
|
26
|
+
const draft = new RichMessageDraft(api, 1, 1, { keepAliveMs: 60_000 });
|
|
27
|
+
await draft.rewrite({ html: "<p>hello</p>" });
|
|
28
|
+
await draft.write({ html: "<hr/>" });
|
|
29
|
+
assert.deepEqual(calls[1]?.params?.rich_message, { html: "<p>hello</p><hr/>" });
|
|
30
|
+
});
|
|
31
|
+
test("write() before the first rewrite() throws", async () => {
|
|
32
|
+
const { api } = mockApi();
|
|
33
|
+
const draft = new RichMessageDraft(api, 1, 1);
|
|
34
|
+
await assert.rejects(() => draft.write("x"), /write\(\) before the first rewrite\(\)/);
|
|
35
|
+
});
|
|
36
|
+
test("write() with a dialect that doesn't match the draft's throws", async () => {
|
|
37
|
+
const { api } = mockApi();
|
|
38
|
+
const draft = new RichMessageDraft(api, 1, 1, { keepAliveMs: 60_000 });
|
|
39
|
+
await draft.rewrite({ html: "<p>hi</p>" });
|
|
40
|
+
await assert.rejects(() => draft.write({ markdown: "hi" }), /dialect "markdown" doesn't match the draft's "html"/);
|
|
41
|
+
});
|
|
42
|
+
test("send() with no override auto-assembles from the accumulated rewrite()/write() calls", async () => {
|
|
43
|
+
const { api, calls } = mockApi();
|
|
44
|
+
const draft = new RichMessageDraft(api, 1, 1, { keepAliveMs: 60_000 });
|
|
45
|
+
await draft.rewrite({ html: "<p>hello</p>" });
|
|
46
|
+
await draft.write({ html: "<hr/>" });
|
|
47
|
+
await draft.send();
|
|
48
|
+
assert.equal(calls[2]?.method, "sendRichMessage");
|
|
49
|
+
assert.deepEqual(calls[2]?.params, { chat_id: 1, rich_message: { html: "<p>hello</p><hr/>" } });
|
|
50
|
+
assert.equal(draft.closed, true);
|
|
51
|
+
});
|
|
52
|
+
test("send(override) persists the override instead of the accumulated draft", async () => {
|
|
53
|
+
const { api, calls } = mockApi();
|
|
54
|
+
const draft = new RichMessageDraft(api, 1, 1, { keepAliveMs: 60_000 });
|
|
55
|
+
await draft.rewrite({ html: "<p>draft text</p>" });
|
|
56
|
+
await draft.send({ html: "<p>final text</p>" }, { reply_markup: { x: 1 } });
|
|
57
|
+
assert.equal(calls[1]?.method, "sendRichMessage");
|
|
58
|
+
assert.deepEqual(calls[1]?.params, {
|
|
59
|
+
chat_id: 1,
|
|
60
|
+
rich_message: { html: "<p>final text</p>" },
|
|
61
|
+
reply_markup: { x: 1 },
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
test("send() with nothing written and no override throws", async () => {
|
|
65
|
+
const { api } = mockApi();
|
|
66
|
+
const draft = new RichMessageDraft(api, 1, 1);
|
|
67
|
+
await assert.rejects(() => draft.send(), /send\(\) with nothing written/);
|
|
68
|
+
});
|
|
69
|
+
test("cancel() closes without persisting anything", async () => {
|
|
70
|
+
const { api, calls } = mockApi();
|
|
71
|
+
const draft = new RichMessageDraft(api, 1, 2, { keepAliveMs: 60_000 });
|
|
72
|
+
await draft.rewrite("draft text");
|
|
73
|
+
draft.cancel();
|
|
74
|
+
assert.equal(draft.closed, true);
|
|
75
|
+
assert.equal(calls.some((c) => c.method === "sendRichMessage"), false);
|
|
76
|
+
});
|
|
77
|
+
test("rewrite()/write() after send()/cancel() throws", async () => {
|
|
78
|
+
const { api } = mockApi();
|
|
79
|
+
const draft = new RichMessageDraft(api, 1, 1, { keepAliveMs: 60_000 });
|
|
80
|
+
await draft.rewrite("x");
|
|
81
|
+
await draft.send();
|
|
82
|
+
await assert.rejects(() => draft.rewrite("late"), /after send\(\)\/cancel\(\)/);
|
|
83
|
+
await assert.rejects(() => draft.write("late"), /after send\(\)\/cancel\(\)/);
|
|
84
|
+
});
|
|
85
|
+
test("messageThreadId routes both the keep-alive pushes and the final send", async () => {
|
|
86
|
+
const { api, calls } = mockApi();
|
|
87
|
+
const draft = new RichMessageDraft(api, 1, 1, { keepAliveMs: 60_000, messageThreadId: 42 });
|
|
88
|
+
await draft.rewrite("<p>hi</p>");
|
|
89
|
+
await draft.send();
|
|
90
|
+
assert.equal(calls[0]?.method, "sendRichMessageDraft");
|
|
91
|
+
assert.deepEqual(calls[0]?.params, {
|
|
92
|
+
chat_id: 1,
|
|
93
|
+
draft_id: 1,
|
|
94
|
+
rich_message: { html: "<p>hi</p>" },
|
|
95
|
+
message_thread_id: 42,
|
|
96
|
+
});
|
|
97
|
+
assert.equal(calls[1]?.method, "sendRichMessage");
|
|
98
|
+
assert.deepEqual(calls[1]?.params, {
|
|
99
|
+
chat_id: 1,
|
|
100
|
+
rich_message: { html: "<p>hi</p>" },
|
|
101
|
+
message_thread_id: 42,
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
test("businessConnectionId routes only the final send (sendRichMessageDraft has no such param)", async () => {
|
|
105
|
+
const { api, calls } = mockApi();
|
|
106
|
+
const draft = new RichMessageDraft(api, 1, 1, {
|
|
107
|
+
keepAliveMs: 60_000,
|
|
108
|
+
businessConnectionId: "bc1",
|
|
109
|
+
});
|
|
110
|
+
await draft.rewrite("<p>hi</p>");
|
|
111
|
+
await draft.send();
|
|
112
|
+
assert.equal(calls[0]?.params?.business_connection_id, undefined);
|
|
113
|
+
assert.equal(calls[1]?.params?.business_connection_id, "bc1");
|
|
114
|
+
});
|
|
115
|
+
test("preserves is_rtl/skip_entity_detection across write() unless the new push overrides them", async () => {
|
|
116
|
+
const { api, calls } = mockApi();
|
|
117
|
+
const draft = new RichMessageDraft(api, 1, 1, { keepAliveMs: 60_000 });
|
|
118
|
+
await draft.rewrite({ html: "<p>a</p>", is_rtl: true });
|
|
119
|
+
await draft.write({ html: "<p>b</p>" });
|
|
120
|
+
assert.deepEqual(calls[1]?.params?.rich_message, { html: "<p>a</p><p>b</p>", is_rtl: true });
|
|
121
|
+
});
|
|
122
|
+
//# sourceMappingURL=draft.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"draft.test.js","sourceRoot":"","sources":["../src/draft.test.ts"],"names":[],"mappings":"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"}
|
package/lib/escape.d.ts
CHANGED
|
@@ -2,4 +2,12 @@
|
|
|
2
2
|
export declare function escapeText(value: string): string;
|
|
3
3
|
/** escape a double-quoted attribute value. */
|
|
4
4
|
export declare function escapeAttr(value: string): string;
|
|
5
|
+
/** escape text so it can never be re-parsed as rich-markdown syntax. */
|
|
6
|
+
export declare function escapeMarkdown(value: string): string;
|
|
7
|
+
/**
|
|
8
|
+
* escape a url used as a markdown link destination. `[text](url)` is terminated by `)`
|
|
9
|
+
* or whitespace, so an attacker-controlled url can't break out of the link — escape the
|
|
10
|
+
* parens/backslash and percent-encode whitespace.
|
|
11
|
+
*/
|
|
12
|
+
export declare function escapeMarkdownUrl(value: string): string;
|
|
5
13
|
//# sourceMappingURL=escape.d.ts.map
|
package/lib/escape.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"escape.d.ts","sourceRoot":"","sources":["../src/escape.ts"],"names":[],"mappings":"AAAA,mEAAmE;AACnE,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEhD;AAED,8CAA8C;AAC9C,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEhD"}
|
|
1
|
+
{"version":3,"file":"escape.d.ts","sourceRoot":"","sources":["../src/escape.ts"],"names":[],"mappings":"AAAA,mEAAmE;AACnE,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEhD;AAED,8CAA8C;AAC9C,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEhD;AAQD,wEAAwE;AACxE,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEpD;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEvD"}
|
package/lib/escape.js
CHANGED
|
@@ -6,4 +6,21 @@ export function escapeText(value) {
|
|
|
6
6
|
export function escapeAttr(value) {
|
|
7
7
|
return escapeText(value).replace(/"/g, """);
|
|
8
8
|
}
|
|
9
|
+
// rich-markdown specials. `&`/`<`/`>` become numeric entities (rich-markdown renders
|
|
10
|
+
// `\<` with the backslash showing, but does accept entities — the same trick telegram's
|
|
11
|
+
// classic parse_mode markdown uses); everything else backslash-escapes cleanly.
|
|
12
|
+
const MARKDOWN_SPECIALS = /[\\`*_~=|[\]()#!+\-&<>]/g;
|
|
13
|
+
const MARKDOWN_ENTITY = { "&": "&", "<": "<", ">": ">" };
|
|
14
|
+
/** escape text so it can never be re-parsed as rich-markdown syntax. */
|
|
15
|
+
export function escapeMarkdown(value) {
|
|
16
|
+
return value.replace(MARKDOWN_SPECIALS, (ch) => MARKDOWN_ENTITY[ch] ?? `\\${ch}`);
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* escape a url used as a markdown link destination. `[text](url)` is terminated by `)`
|
|
20
|
+
* or whitespace, so an attacker-controlled url can't break out of the link — escape the
|
|
21
|
+
* parens/backslash and percent-encode whitespace.
|
|
22
|
+
*/
|
|
23
|
+
export function escapeMarkdownUrl(value) {
|
|
24
|
+
return value.replace(/[\\()]/g, "\\$&").replace(/\s/g, (ch) => encodeURIComponent(ch));
|
|
25
|
+
}
|
|
9
26
|
//# sourceMappingURL=escape.js.map
|
package/lib/escape.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"escape.js","sourceRoot":"","sources":["../src/escape.ts"],"names":[],"mappings":"AAAA,mEAAmE;AACnE,MAAM,UAAU,UAAU,CAAC,KAAa;IACvC,OAAO,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;AACjF,CAAC;AAED,8CAA8C;AAC9C,MAAM,UAAU,UAAU,CAAC,KAAa;IACvC,OAAO,UAAU,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;AAClD,CAAC"}
|
|
1
|
+
{"version":3,"file":"escape.js","sourceRoot":"","sources":["../src/escape.ts"],"names":[],"mappings":"AAAA,mEAAmE;AACnE,MAAM,UAAU,UAAU,CAAC,KAAa;IACvC,OAAO,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;AACjF,CAAC;AAED,8CAA8C;AAC9C,MAAM,UAAU,UAAU,CAAC,KAAa;IACvC,OAAO,UAAU,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;AAClD,CAAC;AAED,qFAAqF;AACrF,wFAAwF;AACxF,gFAAgF;AAChF,MAAM,iBAAiB,GAAG,0BAA0B,CAAC;AACrD,MAAM,eAAe,GAA2B,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,CAAC;AAE7F,wEAAwE;AACxE,MAAM,UAAU,cAAc,CAAC,KAAa;IAC3C,OAAO,KAAK,CAAC,OAAO,CAAC,iBAAiB,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,eAAe,CAAC,EAAE,CAAC,IAAI,KAAK,EAAE,EAAE,CAAC,CAAC;AACnF,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAAC,KAAa;IAC9C,OAAO,KAAK,CAAC,OAAO,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,kBAAkB,CAAC,EAAE,CAAC,CAAC,CAAC;AACxF,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"escape.test.d.ts","sourceRoot":"","sources":["../src/escape.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
import { escapeAttr, escapeMarkdown, escapeMarkdownUrl, escapeText } from "./escape.js";
|
|
4
|
+
test("escapeText escapes &, <, > with named entities", () => {
|
|
5
|
+
assert.equal(escapeText("a & b < c > d"), "a & b < c > d");
|
|
6
|
+
});
|
|
7
|
+
test("escapeText leaves other characters (including quotes) untouched", () => {
|
|
8
|
+
assert.equal(escapeText(`"hi" 'there'`), `"hi" 'there'`);
|
|
9
|
+
});
|
|
10
|
+
test("escapeAttr additionally escapes double quotes", () => {
|
|
11
|
+
assert.equal(escapeAttr(`say "hi" & bye`), "say "hi" & bye");
|
|
12
|
+
});
|
|
13
|
+
test("escapeMarkdown backslash-escapes rich-markdown specials", () => {
|
|
14
|
+
assert.equal(escapeMarkdown("a*b_c~d=e|f[g]h(i)j#k!l+m-n`o"), "a\\*b\\_c\\~d\\=e\\|f\\[g\\]h\\(i\\)j\\#k\\!l\\+m\\-n\\`o");
|
|
15
|
+
});
|
|
16
|
+
test("escapeMarkdown numeric-entity-escapes &, <, >", () => {
|
|
17
|
+
assert.equal(escapeMarkdown("a & b < c > d"), "a & b < c > d");
|
|
18
|
+
});
|
|
19
|
+
test("escapeMarkdown leaves plain text untouched", () => {
|
|
20
|
+
assert.equal(escapeMarkdown("hello world 123"), "hello world 123");
|
|
21
|
+
});
|
|
22
|
+
test("escapeMarkdownUrl escapes parens/backslash so a url can't break out of [text](url)", () => {
|
|
23
|
+
assert.equal(escapeMarkdownUrl("https://x.test/a(b)c\\d"), "https://x.test/a\\(b\\)c\\\\d");
|
|
24
|
+
});
|
|
25
|
+
test("escapeMarkdownUrl percent-encodes whitespace", () => {
|
|
26
|
+
assert.equal(escapeMarkdownUrl("https://x.test/a b"), "https://x.test/a%20b");
|
|
27
|
+
});
|
|
28
|
+
//# sourceMappingURL=escape.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"escape.test.js","sourceRoot":"","sources":["../src/escape.test.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,oBAAoB,CAAC;AACxC,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,iBAAiB,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAExF,IAAI,CAAC,gDAAgD,EAAE,GAAG,EAAE;IAC3D,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,eAAe,CAAC,EAAE,yBAAyB,CAAC,CAAC;AACtE,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,iEAAiE,EAAE,GAAG,EAAE;IAC5E,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,cAAc,CAAC,EAAE,cAAc,CAAC,CAAC;AAC1D,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,+CAA+C,EAAE,GAAG,EAAE;IAC1D,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,gBAAgB,CAAC,EAAE,8BAA8B,CAAC,CAAC;AAC5E,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,yDAAyD,EAAE,GAAG,EAAE;IACpE,MAAM,CAAC,KAAK,CACX,cAAc,CAAC,+BAA+B,CAAC,EAC/C,2DAA2D,CAC3D,CAAC;AACH,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,+CAA+C,EAAE,GAAG,EAAE;IAC1D,MAAM,CAAC,KAAK,CAAC,cAAc,CAAC,eAAe,CAAC,EAAE,2BAA2B,CAAC,CAAC;AAC5E,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,4CAA4C,EAAE,GAAG,EAAE;IACvD,MAAM,CAAC,KAAK,CAAC,cAAc,CAAC,iBAAiB,CAAC,EAAE,iBAAiB,CAAC,CAAC;AACpE,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,oFAAoF,EAAE,GAAG,EAAE;IAC/F,MAAM,CAAC,KAAK,CAAC,iBAAiB,CAAC,yBAAyB,CAAC,EAAE,+BAA+B,CAAC,CAAC;AAC7F,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,8CAA8C,EAAE,GAAG,EAAE;IACzD,MAAM,CAAC,KAAK,CAAC,iBAAiB,CAAC,oBAAoB,CAAC,EAAE,sBAAsB,CAAC,CAAC;AAC/E,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"guards.test.d.ts","sourceRoot":"","sources":["../src/guards.test.ts"],"names":[],"mappings":""}
|