@mtkruto/node 0.70.1 → 0.71.1

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.
Files changed (28) hide show
  1. package/README.md +3 -4
  2. package/esm/0_deps.d.ts +2 -2
  3. package/esm/0_deps.d.ts.map +1 -1
  4. package/esm/0_deps.js +2 -2
  5. package/esm/client/0_html.d.ts +2 -1
  6. package/esm/client/0_html.d.ts.map +1 -1
  7. package/esm/client/0_html.js +420 -70
  8. package/esm/client/0_html_test.d.ts.map +1 -0
  9. package/esm/deps/jsr.io/@std/assert/1.0.14/instance_of.d.ts +23 -0
  10. package/esm/deps/jsr.io/@std/assert/1.0.14/instance_of.d.ts.map +1 -0
  11. package/esm/deps/jsr.io/@std/assert/1.0.14/instance_of.js +52 -0
  12. package/{script/deps/jsr.io/@std/streams/1.0.11 → esm/deps/jsr.io/@std/streams/1.0.12}/to_array_buffer.d.ts.map +1 -1
  13. package/package.json +1 -2
  14. package/script/0_deps.d.ts +2 -2
  15. package/script/0_deps.d.ts.map +1 -1
  16. package/script/0_deps.js +4 -4
  17. package/script/client/0_html.d.ts +2 -1
  18. package/script/client/0_html.d.ts.map +1 -1
  19. package/script/client/0_html.js +421 -70
  20. package/script/client/0_html_test.d.ts.map +1 -0
  21. package/script/deps/jsr.io/@std/assert/1.0.14/instance_of.d.ts +23 -0
  22. package/script/deps/jsr.io/@std/assert/1.0.14/instance_of.d.ts.map +1 -0
  23. package/script/deps/jsr.io/@std/assert/1.0.14/instance_of.js +55 -0
  24. package/{esm/deps/jsr.io/@std/streams/1.0.11 → script/deps/jsr.io/@std/streams/1.0.12}/to_array_buffer.d.ts.map +1 -1
  25. /package/esm/deps/jsr.io/@std/streams/{1.0.11 → 1.0.12}/to_array_buffer.d.ts +0 -0
  26. /package/esm/deps/jsr.io/@std/streams/{1.0.11 → 1.0.12}/to_array_buffer.js +0 -0
  27. /package/script/deps/jsr.io/@std/streams/{1.0.11 → 1.0.12}/to_array_buffer.d.ts +0 -0
  28. /package/script/deps/jsr.io/@std/streams/{1.0.11 → 1.0.12}/to_array_buffer.js +0 -0
package/README.md CHANGED
@@ -10,12 +10,11 @@ Cross-runtime JavaScript library for building Telegram clients
10
10
 
11
11
  ### Key Features
12
12
 
13
- - **Easy-to-use.** Provides high-level [methods](https://mtkruto.deno.dev/methods) and [types](https://mtkruto.deno.dev/types) for convenience.
14
13
  - **Cross-runtime.** Supports Node.js, Deno, browsers, and Bun.
15
14
  - **Type-safe.** Written in TypeScript with accurate typings.
16
- - **Made for the Web.** Leverages Web APIs.
17
- - **Unopinionated.** No hidden behaviors.
18
- - **Extensible.** Highly customizable.
15
+ - **Prioritizes the Web.** Prefers Web APIs over runtime-specific APIs.
16
+ - **Easy-to-use.** Provides its own high-level API on top of the Telegram API.
17
+ - **Extensible.** Its middleware system lets you integrate external code.
19
18
 
20
19
  > Note: MTKruto has not reached version 1.0.0 yet. While it can run in production, we currently do not recommend depending on it for critical projects.
21
20
 
package/esm/0_deps.d.ts CHANGED
@@ -21,6 +21,7 @@ export { assert } from "./deps/jsr.io/@std/assert/1.0.14/assert.js";
21
21
  export { assertFalse } from "./deps/jsr.io/@std/assert/1.0.14/false.js";
22
22
  export { assertEquals } from "./deps/jsr.io/@std/assert/1.0.14/equals.js";
23
23
  export { unreachable } from "./deps/jsr.io/@std/assert/1.0.14/unreachable.js";
24
+ export { assertInstanceOf } from "./deps/jsr.io/@std/assert/1.0.14/instance_of.js";
24
25
  export { AssertionError } from "./deps/jsr.io/@std/assert/1.0.14/assertion_error.js";
25
26
  export { join } from "./deps/jsr.io/@std/path/1.1.2/join.js";
26
27
  export { extname } from "./deps/jsr.io/@std/path/1.1.2/extname.js";
@@ -33,12 +34,11 @@ export { LruCache } from "./deps/jsr.io/@std/cache/0.2.0/lru_cache.js";
33
34
  export { iterateReader } from "./deps/jsr.io/@std/io/0.225.2/iterate_reader.js";
34
35
  export { format } from "./deps/jsr.io/@std/datetime/0.225.5/format.js";
35
36
  export { MINUTE, SECOND } from "./deps/jsr.io/@std/datetime/0.225.5/constants.js";
36
- export { toArrayBuffer } from "./deps/jsr.io/@std/streams/1.0.11/to_array_buffer.js";
37
+ export { toArrayBuffer } from "./deps/jsr.io/@std/streams/1.0.12/to_array_buffer.js";
37
38
  export { decodeBase64, encodeBase64 } from "./deps/jsr.io/@std/encoding/1.0.7/base64.js";
38
39
  export { encodeHex } from "./deps/jsr.io/@std/encoding/1.0.7/hex.js";
39
40
  import { contentType as contentType_ } from "./deps/jsr.io/@std/media-types/1.1.0/content_type.js";
40
41
  export declare const contentType: typeof contentType_;
41
42
  export declare function extension(mimeType: string): string;
42
43
  export { ige256Decrypt, ige256Encrypt, init as initTgCrypto } from "./deps/jsr.io/@roj/tgcrypto/1.0.1/dist/mod.js";
43
- export { Parser } from "htmlparser2";
44
44
  //# sourceMappingURL=0_deps.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"0_deps.d.ts","sourceRoot":"","sources":["../src/0_deps.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,4CAA4C,CAAC;AACpE,OAAO,EAAE,WAAW,EAAE,MAAM,2CAA2C,CAAC;AACxE,OAAO,EAAE,YAAY,EAAE,MAAM,4CAA4C,CAAC;AAC1E,OAAO,EAAE,WAAW,EAAE,MAAM,iDAAiD,CAAC;AAC9E,OAAO,EAAE,cAAc,EAAE,MAAM,qDAAqD,CAAC;AAErF,OAAO,EAAE,IAAI,EAAE,MAAM,uCAAuC,CAAC;AAC7D,OAAO,EAAE,OAAO,EAAE,MAAM,0CAA0C,CAAC;AACnE,OAAO,EAAE,QAAQ,EAAE,MAAM,2CAA2C,CAAC;AACrE,OAAO,EAAE,SAAS,EAAE,MAAM,8CAA8C,CAAC;AACzE,OAAO,EAAE,UAAU,EAAE,MAAM,8CAA8C,CAAC;AAE1E,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,wCAAwC,CAAC;AAE1E,OAAO,EAAE,MAAM,EAAE,MAAM,0CAA0C,CAAC;AAElE,OAAO,EAAE,QAAQ,EAAE,MAAM,6CAA6C,CAAC;AAEvE,OAAO,EAAE,aAAa,EAAE,MAAM,iDAAiD,CAAC;AAEhF,OAAO,EAAE,MAAM,EAAE,MAAM,+CAA+C,CAAC;AAEvE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,kDAAkD,CAAC;AAElF,OAAO,EAAE,aAAa,EAAE,MAAM,sDAAsD,CAAC;AAErF,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,6CAA6C,CAAC;AAEzF,OAAO,EAAE,SAAS,EAAE,MAAM,0CAA0C,CAAC;AAErE,OAAO,EAAE,WAAW,IAAI,YAAY,EAAE,MAAM,sDAAsD,CAAC;AACnG,eAAO,MAAM,WAAW,EAAE,OAAO,YAMhC,CAAC;AAEF,wBAAgB,SAAS,CAAC,QAAQ,EAAE,MAAM,UAMzC;AAED,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,IAAI,IAAI,YAAY,EAAE,MAAM,+CAA+C,CAAC;AAEnH,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC"}
1
+ {"version":3,"file":"0_deps.d.ts","sourceRoot":"","sources":["../src/0_deps.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,4CAA4C,CAAC;AACpE,OAAO,EAAE,WAAW,EAAE,MAAM,2CAA2C,CAAC;AACxE,OAAO,EAAE,YAAY,EAAE,MAAM,4CAA4C,CAAC;AAC1E,OAAO,EAAE,WAAW,EAAE,MAAM,iDAAiD,CAAC;AAC9E,OAAO,EAAE,gBAAgB,EAAE,MAAM,iDAAiD,CAAC;AACnF,OAAO,EAAE,cAAc,EAAE,MAAM,qDAAqD,CAAC;AAErF,OAAO,EAAE,IAAI,EAAE,MAAM,uCAAuC,CAAC;AAC7D,OAAO,EAAE,OAAO,EAAE,MAAM,0CAA0C,CAAC;AACnE,OAAO,EAAE,QAAQ,EAAE,MAAM,2CAA2C,CAAC;AACrE,OAAO,EAAE,SAAS,EAAE,MAAM,8CAA8C,CAAC;AACzE,OAAO,EAAE,UAAU,EAAE,MAAM,8CAA8C,CAAC;AAE1E,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,wCAAwC,CAAC;AAE1E,OAAO,EAAE,MAAM,EAAE,MAAM,0CAA0C,CAAC;AAElE,OAAO,EAAE,QAAQ,EAAE,MAAM,6CAA6C,CAAC;AAEvE,OAAO,EAAE,aAAa,EAAE,MAAM,iDAAiD,CAAC;AAEhF,OAAO,EAAE,MAAM,EAAE,MAAM,+CAA+C,CAAC;AAEvE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,kDAAkD,CAAC;AAElF,OAAO,EAAE,aAAa,EAAE,MAAM,sDAAsD,CAAC;AAErF,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,6CAA6C,CAAC;AAEzF,OAAO,EAAE,SAAS,EAAE,MAAM,0CAA0C,CAAC;AAErE,OAAO,EAAE,WAAW,IAAI,YAAY,EAAE,MAAM,sDAAsD,CAAC;AACnG,eAAO,MAAM,WAAW,EAAE,OAAO,YAMhC,CAAC;AAEF,wBAAgB,SAAS,CAAC,QAAQ,EAAE,MAAM,UAMzC;AAED,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,IAAI,IAAI,YAAY,EAAE,MAAM,+CAA+C,CAAC"}
package/esm/0_deps.js CHANGED
@@ -21,6 +21,7 @@ export { assert } from "./deps/jsr.io/@std/assert/1.0.14/assert.js";
21
21
  export { assertFalse } from "./deps/jsr.io/@std/assert/1.0.14/false.js";
22
22
  export { assertEquals } from "./deps/jsr.io/@std/assert/1.0.14/equals.js";
23
23
  export { unreachable } from "./deps/jsr.io/@std/assert/1.0.14/unreachable.js";
24
+ export { assertInstanceOf } from "./deps/jsr.io/@std/assert/1.0.14/instance_of.js";
24
25
  export { AssertionError } from "./deps/jsr.io/@std/assert/1.0.14/assertion_error.js";
25
26
  export { join } from "./deps/jsr.io/@std/path/1.1.2/join.js";
26
27
  export { extname } from "./deps/jsr.io/@std/path/1.1.2/extname.js";
@@ -33,7 +34,7 @@ export { LruCache } from "./deps/jsr.io/@std/cache/0.2.0/lru_cache.js";
33
34
  export { iterateReader } from "./deps/jsr.io/@std/io/0.225.2/iterate_reader.js";
34
35
  export { format } from "./deps/jsr.io/@std/datetime/0.225.5/format.js";
35
36
  export { MINUTE, SECOND } from "./deps/jsr.io/@std/datetime/0.225.5/constants.js";
36
- export { toArrayBuffer } from "./deps/jsr.io/@std/streams/1.0.11/to_array_buffer.js";
37
+ export { toArrayBuffer } from "./deps/jsr.io/@std/streams/1.0.12/to_array_buffer.js";
37
38
  export { decodeBase64, encodeBase64 } from "./deps/jsr.io/@std/encoding/1.0.7/base64.js";
38
39
  export { encodeHex } from "./deps/jsr.io/@std/encoding/1.0.7/hex.js";
39
40
  import { contentType as contentType_ } from "./deps/jsr.io/@std/media-types/1.1.0/content_type.js";
@@ -55,4 +56,3 @@ export function extension(mimeType) {
55
56
  }
56
57
  }
57
58
  export { ige256Decrypt, ige256Encrypt, init as initTgCrypto } from "./deps/jsr.io/@roj/tgcrypto/1.0.1/dist/mod.js";
58
- export { Parser } from "htmlparser2";
@@ -18,5 +18,6 @@
18
18
  * along with this program. If not, see <https://www.gnu.org/licenses/>.
19
19
  */
20
20
  import type { MessageEntity } from "../3_types.js";
21
- export declare function parseHtml(html: string): readonly [string, MessageEntity[]];
21
+ export declare function parseHtml(html_: string): [string, MessageEntity[]];
22
+ export declare function parseAttributes(attributes: string[]): Record<string, string>;
22
23
  //# sourceMappingURL=0_html.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"0_html.d.ts","sourceRoot":"","sources":["../../src/client/0_html.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAIH,OAAO,KAAK,EAAE,aAAa,EAA2B,MAAM,eAAe,CAAC;AAE5E,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,sCAmFrC"}
1
+ {"version":3,"file":"0_html.d.ts","sourceRoot":"","sources":["../../src/client/0_html.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAGH,OAAO,KAAK,EAAE,aAAa,EAA2B,MAAM,eAAe,CAAC;AAuC5E,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,CAAC,MAAM,EAAE,aAAa,EAAE,CAAC,CA6NlE;AACD,wBAAgB,eAAe,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAyD5E"}
@@ -17,86 +17,436 @@
17
17
  * You should have received a copy of the GNU Lesser General Public License
18
18
  * along with this program. If not, see <https://www.gnu.org/licenses/>.
19
19
  */
20
- import { Parser } from "../0_deps.js";
21
20
  import { InputError } from "../0_errors.js";
22
- export function parseHtml(html) {
23
- html = html.trim();
21
+ const TAG_NAMES = [
22
+ "a",
23
+ "b",
24
+ "strong",
25
+ "i",
26
+ "em",
27
+ "s",
28
+ "strike",
29
+ "del",
30
+ "u",
31
+ "ins",
32
+ "tg-spoiler",
33
+ "tg-emoji",
34
+ "span",
35
+ "pre",
36
+ "code",
37
+ "blockquote",
38
+ ];
39
+ const ENTITY_TYPES = [
40
+ "textLink",
41
+ "bold",
42
+ "bold",
43
+ "italic",
44
+ "italic",
45
+ "strikethrough",
46
+ "strikethrough",
47
+ "strikethrough",
48
+ "underline",
49
+ "underline",
50
+ "spoiler",
51
+ "customEmoji",
52
+ "spoiler",
53
+ "code",
54
+ "code",
55
+ "blockquote",
56
+ ];
57
+ export function parseHtml(html_) {
58
+ html_ = html_.trim();
24
59
  let text = "";
25
- const entities = new Array();
26
- const stack = new Array();
27
- const parser = new Parser({
28
- onopentag(name, attribs) {
29
- switch (name) {
30
- case "b":
31
- case "strong":
32
- stack.push({ type: "bold", offset: text.length, length: 0 });
33
- break;
34
- case "em":
35
- case "i":
36
- stack.push({ type: "italic", offset: text.length, length: 0 });
37
- break;
38
- case "code":
39
- stack.push({ type: "code", offset: text.length, length: 0 });
40
- break;
41
- case "pre": {
42
- const language = attribs.language ?? "";
43
- stack.push({ type: "pre", offset: text.length, length: 0, language });
44
- break;
45
- }
46
- case "a": {
47
- const url = attribs.href;
48
- if (!url) {
49
- throw new InputError("Missing attribute: href");
60
+ let entities = new Array();
61
+ const entityTags = new Array();
62
+ const tagStack = new Array();
63
+ const html = Array.from(html_);
64
+ for (let i = 0; i < html.length; ++i) {
65
+ const c = html[i];
66
+ if (c === "&") {
67
+ const result = decodeEntity(html, i);
68
+ [text, i] = [text + result[0], i + result[1]];
69
+ continue;
70
+ }
71
+ else if (c !== "<") {
72
+ text += c;
73
+ continue;
74
+ }
75
+ ++i;
76
+ let closing = false;
77
+ let end = i;
78
+ if (html[end] && html[end] === "/") {
79
+ closing = true;
80
+ ++i;
81
+ ++end;
82
+ }
83
+ while (html[end] && html[end] !== ">") {
84
+ ++end;
85
+ }
86
+ const tagName_ = html.slice(i, end);
87
+ let tagName = tagName_.join("");
88
+ let attributes;
89
+ if (closing) {
90
+ tagName = tagName.trim().toLowerCase();
91
+ }
92
+ else {
93
+ const index = tagName_.indexOf(" ");
94
+ if (index !== -1) {
95
+ attributes = parseAttributes(tagName_.slice(index));
96
+ tagName = tagName.slice(0, index).toLowerCase();
97
+ }
98
+ else {
99
+ tagName = tagName.toLowerCase();
100
+ }
101
+ }
102
+ if (closing && !tagName.length && tagStack.length) {
103
+ tagName = tagStack[tagStack.length - 1].tagName;
104
+ }
105
+ if (!tagName.length) {
106
+ throw new InputError(`Invalid tag at offset ${i}.`);
107
+ }
108
+ else if (!TAG_NAMES.includes(tagName)) {
109
+ throw new InputError(`Unsupported HTML tag at offset ${i}: ${tagName}`);
110
+ }
111
+ else if (closing) {
112
+ const openTag = tagStack.pop();
113
+ if (openTag?.tagName !== tagName) {
114
+ throw new InputError(`Unexpected closing tag at offset ${i}.`);
115
+ }
116
+ const offset = openTag.openedAt;
117
+ const length = text.length - openTag.openedAt;
118
+ if (tagName === "a") {
119
+ let url = openTag.url;
120
+ if (!url) {
121
+ const text_ = text.slice(offset, offset + length);
122
+ try {
123
+ url = new URL(text_).href;
124
+ }
125
+ catch {
126
+ if (!Array.from(text).some(isSpace)) {
127
+ try {
128
+ url = new URL(`http://${text_}`).href;
129
+ }
130
+ catch {
131
+ //
132
+ }
133
+ }
50
134
  }
51
- stack.push({ type: "textLink", offset: text.length, length: 0, url });
52
- break;
53
135
  }
54
- case "ins":
55
- case "u":
56
- stack.push({ type: "underline", offset: text.length, length: 0 });
57
- break;
58
- case "del":
59
- case "strike":
60
- stack.push({ type: "strikethrough", offset: text.length, length: 0 });
61
- break;
62
- case "span":
63
- if (attribs.class !== "tg-spoiler") {
64
- throw new InputError('The class attribute must be "tg-spoiler."');
136
+ if (url) {
137
+ entities.push({
138
+ type: "textLink",
139
+ offset,
140
+ length,
141
+ url,
142
+ });
143
+ entityTags.push(openTag);
144
+ }
145
+ }
146
+ else if (tagName === "pre") {
147
+ const lastEntity = entities[entities.length - 1];
148
+ const entityTag = entityTags[entityTags.length - 1];
149
+ if (lastEntity && lastEntity.type === "code" && lastEntity.offset === offset && lastEntity.length === length && entityTag.language) {
150
+ entities[entities.length - 1] = {
151
+ type: "pre",
152
+ offset: lastEntity.offset,
153
+ length: lastEntity.length,
154
+ language: entityTag.language,
155
+ };
156
+ }
157
+ else {
158
+ entities.push({
159
+ type: "pre",
160
+ offset,
161
+ length,
162
+ language: "",
163
+ });
164
+ entityTags.push(openTag);
165
+ }
166
+ }
167
+ else if (tagName === "code") {
168
+ const lastEntity = entities[entities.length - 1];
169
+ if (lastEntity && lastEntity.type === "pre" && lastEntity.offset === offset && lastEntity.length === length && openTag.language) {
170
+ lastEntity.language = openTag.language;
171
+ }
172
+ else {
173
+ entities.push({
174
+ type: "code",
175
+ offset,
176
+ length,
177
+ });
178
+ entityTags.push(openTag);
179
+ }
180
+ }
181
+ else if (openTag.userId !== undefined) {
182
+ entities.push({
183
+ type: "textMention",
184
+ offset,
185
+ length,
186
+ userId: openTag.userId,
187
+ });
188
+ entityTags.push(openTag);
189
+ }
190
+ else if (tagName === "blockquote") {
191
+ const entity = {
192
+ type: "blockquote",
193
+ offset,
194
+ length,
195
+ };
196
+ if (openTag.collapsible) {
197
+ entity.collapsible = true;
198
+ }
199
+ entities.push(entity);
200
+ entityTags.push(openTag);
201
+ if (openTag.collapsible) {
202
+ entities[entities.length - 1].collapsible = true;
203
+ }
204
+ }
205
+ else if (openTag.customEmojiId !== undefined) {
206
+ entities.push({
207
+ type: "customEmoji",
208
+ offset,
209
+ length,
210
+ customEmojiId: openTag.customEmojiId,
211
+ });
212
+ entityTags.push(openTag);
213
+ }
214
+ else {
215
+ entities.push({
216
+ type: ENTITY_TYPES[TAG_NAMES.indexOf(openTag.tagName)],
217
+ offset,
218
+ length,
219
+ });
220
+ entityTags.push(openTag);
221
+ }
222
+ }
223
+ else if (tagName === "span" && attributes?.class !== "tg-spoiler") {
224
+ throw new InputError(`Invalid or missing attribute class for tag span at offset ${i}.`);
225
+ }
226
+ else {
227
+ let url;
228
+ let language;
229
+ let userId;
230
+ let collapsible;
231
+ let customEmojiId;
232
+ if (tagName === "a") {
233
+ url = attributes?.href;
234
+ if (url) {
235
+ let url_;
236
+ try {
237
+ url_ = new URL(url);
238
+ }
239
+ catch {
240
+ try {
241
+ url_ = new URL(`http://${url}`);
242
+ url = url_.href;
243
+ }
244
+ catch {
245
+ throw new InputError(`Invalid URL specified for tag a at offset ${i}.`);
246
+ }
247
+ }
248
+ if (url_.protocol !== "tg:" && url_.protocol !== "ton:" && url_.protocol !== "http:" && url_.protocol !== "https:") {
249
+ throw new InputError(`Unsupported URL protocol at offset ${i}: ${url_.protocol}`);
250
+ }
251
+ if (url_.protocol === "tg:" && url_.hostname === "user") {
252
+ const id = Number(url_.searchParams.get("id"));
253
+ if (!isNaN(id) && id > 0) {
254
+ userId = id;
255
+ url = undefined;
256
+ }
257
+ else {
258
+ throw new InputError(`Invalid ID specified for mention at offset ${i}.`);
259
+ }
65
260
  }
66
- /* falls through */
67
- case "tg-spoiler":
68
- stack.push({ type: "spoiler", offset: text.length, length: 0 });
69
- break;
70
- case "tg-emoji":
71
- if (!attribs["emoji-id"]) {
72
- throw new InputError("Missing attribute: emoji-id");
261
+ }
262
+ }
263
+ else if (tagName === "code") {
264
+ language = attributes?.class?.replace("language-", "");
265
+ }
266
+ else if (tagName === "blockquote" && attributes?.expandable !== undefined) {
267
+ collapsible = true;
268
+ }
269
+ else if (tagName === "tg-emoji") {
270
+ customEmojiId = attributes?.["emoji-id"];
271
+ let valid;
272
+ try {
273
+ valid = BigInt(customEmojiId ?? "") !== 0n;
274
+ }
275
+ catch {
276
+ valid = false;
277
+ }
278
+ if (!valid) {
279
+ throw new InputError(`Invalid emoji-id specified for tag tg-emoji at offset ${i}.`);
280
+ }
281
+ }
282
+ tagStack.push({ tagName, openedAt: text.length, url, language, userId, collapsible, customEmojiId });
283
+ }
284
+ i += end - i;
285
+ }
286
+ entities = entities.filter((v) => v.length);
287
+ entities = sortEntities(entities);
288
+ return [text, entities];
289
+ }
290
+ export function parseAttributes(attributes) {
291
+ const parsed = {};
292
+ for (let i = 0; i < attributes.length; ++i) {
293
+ const c = attributes[i];
294
+ if (isSpace(c)) {
295
+ continue;
296
+ }
297
+ let end = i;
298
+ while (attributes[end] && !isSpace(attributes[end]) && attributes[end] !== "=") {
299
+ ++end;
300
+ }
301
+ const name = attributes.slice(i, end);
302
+ while (attributes[end] && isSpace(attributes[end])) {
303
+ ++end;
304
+ }
305
+ let value = new Array();
306
+ if (attributes[end] === "=") {
307
+ ++end;
308
+ while (attributes[end] && isSpace(attributes[end])) {
309
+ ++end;
310
+ }
311
+ if (attributes[end]) {
312
+ const isQuote = attributes[end] === '"' || attributes[end] === "'";
313
+ if (!isQuote) {
314
+ const valueStart = end;
315
+ ++end;
316
+ while (attributes[end] && !isSpace(attributes[end])) {
317
+ ++end;
73
318
  }
74
- stack.push({ type: "spoiler", offset: text.length, length: 0 });
75
- break;
76
- case "blockquote":
77
- stack.push({ type: "blockquote", offset: text.length, length: 0 });
78
- if (attribs.collapsible) {
79
- stack[stack.length - 1].collapsible = true;
319
+ value = attributes.slice(valueStart, end);
320
+ }
321
+ else {
322
+ const quote = attributes[end];
323
+ ++end;
324
+ const valueStart = end;
325
+ while (attributes[end] && attributes[end] !== quote) {
326
+ ++end;
80
327
  }
328
+ value = attributes.slice(valueStart, end);
329
+ }
330
+ }
331
+ }
332
+ let value_ = "";
333
+ for (let i = 0; i < value.length; ++i) {
334
+ const c = value[i];
335
+ if (c === "&") {
336
+ const result = decodeEntity(value, i);
337
+ [value_, i] = [value_ + result[0], i + result[1]];
81
338
  }
82
- },
83
- ontext(data) {
84
- if (!text.length) {
85
- data = data.trimStart();
339
+ else {
340
+ value_ += c;
86
341
  }
87
- text += data;
88
- for (const item of stack) {
89
- item.length += data.length;
342
+ }
343
+ parsed[name.join("")] = value_;
344
+ i += end - i;
345
+ }
346
+ return parsed;
347
+ }
348
+ function decodeEntity(html, i) {
349
+ let text = "";
350
+ let end = i + 1;
351
+ let fallback = false;
352
+ if (html[end] === "#") {
353
+ ++end;
354
+ let code;
355
+ if (html[end] === "x") {
356
+ ++end;
357
+ while (isHex(html[end])) {
358
+ ++end;
90
359
  }
91
- },
92
- onclosetag() {
93
- const lastItem = stack.pop();
94
- if (lastItem) {
95
- entities.push(lastItem);
360
+ const hex = html.slice(i + 3, end).join("");
361
+ code = parseInt(hex, 16);
362
+ }
363
+ else {
364
+ while (isDigit(html[end])) {
365
+ ++end;
96
366
  }
97
- },
367
+ const code_ = html.slice(i + 2, end).join("");
368
+ code = parseInt(code_);
369
+ }
370
+ if (!isNaN(code) && code !== 0 && code < 0x10ffff && end - i < 10) {
371
+ text = String.fromCharCode(code);
372
+ }
373
+ else {
374
+ fallback = true;
375
+ }
376
+ }
377
+ else {
378
+ while (isAlpha(html[end])) {
379
+ ++end;
380
+ }
381
+ const name = html.slice(i + 1, end).join("");
382
+ switch (name) {
383
+ case "amp":
384
+ text = "&";
385
+ break;
386
+ case "quot":
387
+ text = '"';
388
+ break;
389
+ case "lt":
390
+ text = "<";
391
+ break;
392
+ case "gt":
393
+ text = ">";
394
+ break;
395
+ default:
396
+ fallback = true;
397
+ }
398
+ }
399
+ if (html[end] === ";") {
400
+ ++end;
401
+ }
402
+ if (fallback) {
403
+ text = html.slice(i, end).join("");
404
+ }
405
+ return [text, end - i - 1];
406
+ }
407
+ function isAlpha(string) {
408
+ return "a" <= string && string <= "z";
409
+ }
410
+ function isDigit(string) {
411
+ return "0" <= string && string <= "9";
412
+ }
413
+ function isHex(string) {
414
+ return isDigit(string) || ("a" <= string && string <= "f");
415
+ }
416
+ function isSpace(string) {
417
+ return string === " " || string === "\t" || string === "\r" || string === "\n" || string === "\0" || string === "\v";
418
+ }
419
+ function sortEntities(entities) {
420
+ return entities.sort(({ offset, type, length }, other) => {
421
+ if (offset !== other.offset) {
422
+ return offset < other.offset ? -1 : 1;
423
+ }
424
+ if (length !== other.length) {
425
+ return length > other.length ? -1 : 1;
426
+ }
427
+ const priority = ENTITY_TYPE_PRIORITIES[type];
428
+ const otherPriority = ENTITY_TYPE_PRIORITIES[other.type];
429
+ return priority < otherPriority ? -1 : 1;
98
430
  });
99
- parser.write(html);
100
- parser.end();
101
- return [text, entities];
102
431
  }
432
+ const ENTITY_TYPE_PRIORITIES = {
433
+ "mention": 50,
434
+ "hashtag": 50,
435
+ "botCommand": 50,
436
+ "url": 50,
437
+ "email": 50,
438
+ "bold": 90,
439
+ "italic": 91,
440
+ "code": 20,
441
+ "pre": 11,
442
+ "textLink": 49,
443
+ "textMention": 49,
444
+ "cashtag": 50,
445
+ "phoneNumber": 50,
446
+ "underline": 92,
447
+ "strikethrough": 93,
448
+ "blockquote": 0,
449
+ "bankCard": 50,
450
+ "spoiler": 94,
451
+ "customEmoji": 99,
452
+ };
@@ -0,0 +1 @@
1
+ {"version":3,"file":"0_html_test.d.ts","sourceRoot":"","sources":["../../src/client/0_html_test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,OAAO,2BAA2B,CAAC"}
@@ -0,0 +1,23 @@
1
+ /** Any constructor */
2
+ export type AnyConstructor = new (...args: any[]) => any;
3
+ /** Gets constructor type */
4
+ export type GetConstructorType<T extends AnyConstructor> = InstanceType<T>;
5
+ /**
6
+ * Make an assertion that `obj` is an instance of `type`.
7
+ * If not then throw.
8
+ *
9
+ * @example Usage
10
+ * ```ts ignore
11
+ * import { assertInstanceOf } from "@std/assert";
12
+ *
13
+ * assertInstanceOf(new Date(), Date); // Doesn't throw
14
+ * assertInstanceOf(new Date(), Number); // Throws
15
+ * ```
16
+ *
17
+ * @typeParam T The expected type of the object.
18
+ * @param actual The object to check.
19
+ * @param expectedType The expected class constructor.
20
+ * @param msg The optional message to display if the assertion fails.
21
+ */
22
+ export declare function assertInstanceOf<T extends abstract new (...args: any[]) => any>(actual: unknown, expectedType: T, msg?: string): asserts actual is InstanceType<T>;
23
+ //# sourceMappingURL=instance_of.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"instance_of.d.ts","sourceRoot":"","sources":["../../../../../../src/deps/jsr.io/@std/assert/1.0.14/instance_of.ts"],"names":[],"mappings":"AAIA,sBAAsB;AAEtB,MAAM,MAAM,cAAc,GAAG,KAAK,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,GAAG,CAAC;AACzD,4BAA4B;AAC5B,MAAM,MAAM,kBAAkB,CAAC,CAAC,SAAS,cAAc,IAAI,YAAY,CAAC,CAAC,CAAC,CAAC;AAE3E;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,gBAAgB,CAE9B,CAAC,SAAS,QAAQ,MAAM,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,GAAG,EAE9C,MAAM,EAAE,OAAO,EACf,YAAY,EAAE,CAAC,EACf,GAAG,SAAK,GACP,OAAO,CAAC,MAAM,IAAI,YAAY,CAAC,CAAC,CAAC,CA6BnC"}