@mks2508/telegram-message-builder 0.2.0 → 0.3.0
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/dist/index.cjs +1057 -12
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1022 -13
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/builder/builder.d.ts +55 -0
- package/src/builder/builder.d.ts.map +1 -1
- package/src/builder/builder.ts +145 -10
- package/src/builder/index.d.ts +2 -1
- package/src/builder/index.d.ts.map +1 -1
- package/src/builder/index.ts +2 -1
- package/src/builder/media.d.ts +352 -0
- package/src/builder/media.d.ts.map +1 -0
- package/src/builder/media.test.ts +664 -0
- package/src/builder/media.ts +484 -0
- package/src/builder.test.ts +465 -0
- package/src/escaping.test.ts +2 -2
- package/src/formatters/index.d.ts +47 -0
- package/src/formatters/index.d.ts.map +1 -1
- package/src/formatters/index.ts +92 -1
- package/src/formatters/markdown.d.ts +179 -0
- package/src/formatters/markdown.d.ts.map +1 -0
- package/src/formatters/markdown.test.ts +417 -0
- package/src/formatters/markdown.ts +220 -0
- package/src/formatters/markdownv2.d.ts +184 -0
- package/src/formatters/markdownv2.d.ts.map +1 -0
- package/src/formatters/markdownv2.ts +235 -0
- package/src/formatters.test.ts +17 -7
- package/src/index.d.ts +2 -0
- package/src/index.d.ts.map +1 -1
- package/src/index.ts +12 -0
- package/src/integration.test.ts +523 -0
- package/src/media-integration.test.ts +384 -0
- package/src/types/index.d.ts +1 -0
- package/src/types/index.d.ts.map +1 -1
- package/src/types/index.ts +1 -0
- package/src/types/media.types.d.ts +158 -0
- package/src/types/media.types.d.ts.map +1 -0
- package/src/types/media.types.ts +178 -0
- package/src/types.test.ts +539 -0
- package/src/utils/index.d.ts +1 -1
- package/src/utils/index.ts +0 -5
|
@@ -0,0 +1,465 @@
|
|
|
1
|
+
import { describe, it, expect } from "bun:test";
|
|
2
|
+
import { TelegramMessageBuilder } from "./builder";
|
|
3
|
+
|
|
4
|
+
describe("TelegramMessageBuilder", () => {
|
|
5
|
+
describe("Initialization", () => {
|
|
6
|
+
it("should create builder with HTML default", () => {
|
|
7
|
+
const builder = TelegramMessageBuilder.text();
|
|
8
|
+
expect(builder.getParseMode()).toBe("html");
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it("should allow setting parse mode to markdown", () => {
|
|
12
|
+
const builder = TelegramMessageBuilder.text().setParseMode("markdown");
|
|
13
|
+
expect(builder.getParseMode()).toBe("markdown");
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it("should allow setting parse mode to markdownv2", () => {
|
|
17
|
+
const builder = TelegramMessageBuilder.text().setParseMode("markdownv2");
|
|
18
|
+
expect(builder.getParseMode()).toBe("markdownv2");
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
describe("Formatting Methods - HTML Mode", () => {
|
|
23
|
+
it("should format title with bold", () => {
|
|
24
|
+
const message = TelegramMessageBuilder.text().title("Test Title").build();
|
|
25
|
+
expect(message.text).toContain("<b>Test Title</b>");
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("should format section with underline", () => {
|
|
29
|
+
const message = TelegramMessageBuilder.text()
|
|
30
|
+
.section("Test Section")
|
|
31
|
+
.build();
|
|
32
|
+
expect(message.text).toContain("<u>Test Section</u>");
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it("should format line with key and value", () => {
|
|
36
|
+
const message = TelegramMessageBuilder.text()
|
|
37
|
+
.line("Status", "Active")
|
|
38
|
+
.build();
|
|
39
|
+
expect(message.text).toContain("Status: Active");
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("should format line with bold option", () => {
|
|
43
|
+
const message = TelegramMessageBuilder.text()
|
|
44
|
+
.line("Status", "Active", { bold: true })
|
|
45
|
+
.build();
|
|
46
|
+
expect(message.text).toContain("Status: <b>Active</b>");
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it("should format line with italic option", () => {
|
|
50
|
+
const message = TelegramMessageBuilder.text()
|
|
51
|
+
.line("Status", "Active", { italic: true })
|
|
52
|
+
.build();
|
|
53
|
+
expect(message.text).toContain("Status: <i>Active</i>");
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it("should format line with code option", () => {
|
|
57
|
+
const message = TelegramMessageBuilder.text()
|
|
58
|
+
.line("Command", "npm install", { code: true })
|
|
59
|
+
.build();
|
|
60
|
+
expect(message.text).toContain("Command: <code>npm install</code>");
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it("should format line with underline option", () => {
|
|
64
|
+
const message = TelegramMessageBuilder.text()
|
|
65
|
+
.line("Note", "Important", { underline: true })
|
|
66
|
+
.build();
|
|
67
|
+
expect(message.text).toContain("Note: <u>Important</u>");
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it("should format code block", () => {
|
|
71
|
+
const message = TelegramMessageBuilder.text()
|
|
72
|
+
.codeBlock("const x = 1;")
|
|
73
|
+
.build();
|
|
74
|
+
expect(message.text).toContain("<pre><code>const x = 1;</code></pre>");
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it("should format code block with language", () => {
|
|
78
|
+
const message = TelegramMessageBuilder.text()
|
|
79
|
+
.codeBlock("const x = 1;", "javascript")
|
|
80
|
+
.build();
|
|
81
|
+
expect(message.text).toContain(
|
|
82
|
+
'<pre><code class="language-javascript">const x = 1;</code></pre>',
|
|
83
|
+
);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it("should format list item", () => {
|
|
87
|
+
const message = TelegramMessageBuilder.text().listItem("Item 1").build();
|
|
88
|
+
expect(message.text).toContain("• Item 1");
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it("should add separator", () => {
|
|
92
|
+
const message = TelegramMessageBuilder.text().separator().build();
|
|
93
|
+
expect(message.text).toContain("---");
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it("should add raw text with escaping", () => {
|
|
97
|
+
const message = TelegramMessageBuilder.text()
|
|
98
|
+
.text("<b>Hello</b>")
|
|
99
|
+
.build();
|
|
100
|
+
expect(message.text).toContain("<b>Hello</b>");
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it("should format link", () => {
|
|
104
|
+
const message = TelegramMessageBuilder.text()
|
|
105
|
+
.link("Click here", "https://example.com")
|
|
106
|
+
.build();
|
|
107
|
+
expect(message.text).toContain(
|
|
108
|
+
'<a href="https://example.com">Click here</a>',
|
|
109
|
+
);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it("should format mention with default name", () => {
|
|
113
|
+
const message = TelegramMessageBuilder.text().mention(123456).build();
|
|
114
|
+
expect(message.text).toContain(
|
|
115
|
+
'<a href="tg://user?id=123456">user123456</a>',
|
|
116
|
+
);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it("should format mention with custom name", () => {
|
|
120
|
+
const message = TelegramMessageBuilder.text()
|
|
121
|
+
.mention(123456, "John Doe")
|
|
122
|
+
.build();
|
|
123
|
+
expect(message.text).toContain(
|
|
124
|
+
'<a href="tg://user?id=123456">John Doe</a>',
|
|
125
|
+
);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it("should format hashtag", () => {
|
|
129
|
+
const message = TelegramMessageBuilder.text().hashtag("test").build();
|
|
130
|
+
expect(message.text).toContain(
|
|
131
|
+
'<a href="tg://hashtag?tag=test">#test</a>',
|
|
132
|
+
);
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
describe("Formatting Methods - Markdown Mode", () => {
|
|
137
|
+
it("should format title with bold markdown", () => {
|
|
138
|
+
const message = TelegramMessageBuilder.text()
|
|
139
|
+
.setParseMode("markdown")
|
|
140
|
+
.title("Test Title")
|
|
141
|
+
.build();
|
|
142
|
+
expect(message.text).toContain("*Test Title*");
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it("should format section with underline markdown (HTML fallback)", () => {
|
|
146
|
+
const message = TelegramMessageBuilder.text()
|
|
147
|
+
.setParseMode("markdown")
|
|
148
|
+
.section("Test Section")
|
|
149
|
+
.build();
|
|
150
|
+
expect(message.text).toContain("<u>Test Section</u>");
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it("should format line with bold option markdown", () => {
|
|
154
|
+
const message = TelegramMessageBuilder.text()
|
|
155
|
+
.setParseMode("markdown")
|
|
156
|
+
.line("Status", "Active", { bold: true })
|
|
157
|
+
.build();
|
|
158
|
+
expect(message.text).toContain("Status: *Active*");
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it("should format code block markdown", () => {
|
|
162
|
+
const message = TelegramMessageBuilder.text()
|
|
163
|
+
.setParseMode("markdown")
|
|
164
|
+
.codeBlock("const x = 1;")
|
|
165
|
+
.build();
|
|
166
|
+
expect(message.text).toContain("const x = 1;\n");
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it("should format code block with language markdown", () => {
|
|
170
|
+
const message = TelegramMessageBuilder.text()
|
|
171
|
+
.setParseMode("markdown")
|
|
172
|
+
.codeBlock("const x = 1;", "javascript")
|
|
173
|
+
.build();
|
|
174
|
+
expect(message.text).toContain("javascript\nconst x = 1;\n");
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it("should format link markdown", () => {
|
|
178
|
+
const message = TelegramMessageBuilder.text()
|
|
179
|
+
.setParseMode("markdown")
|
|
180
|
+
.link("Click here", "https://example.com")
|
|
181
|
+
.build();
|
|
182
|
+
expect(message.text).toContain("[Click here](https://example.com)");
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it("should format mention markdown", () => {
|
|
186
|
+
const message = TelegramMessageBuilder.text()
|
|
187
|
+
.setParseMode("markdown")
|
|
188
|
+
.mention(123456, "John")
|
|
189
|
+
.build();
|
|
190
|
+
expect(message.text).toContain("tg://user?id=123456");
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it("should format hashtag markdown", () => {
|
|
194
|
+
const message = TelegramMessageBuilder.text()
|
|
195
|
+
.setParseMode("markdown")
|
|
196
|
+
.hashtag("test")
|
|
197
|
+
.build();
|
|
198
|
+
expect(message.text).toContain("#test");
|
|
199
|
+
});
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
describe("Formatting Methods - MarkdownV2 Mode", () => {
|
|
203
|
+
it("should format title with bold markdownv2", () => {
|
|
204
|
+
const message = TelegramMessageBuilder.text()
|
|
205
|
+
.setParseMode("markdownv2")
|
|
206
|
+
.title("Test Title")
|
|
207
|
+
.build();
|
|
208
|
+
expect(message.text).toContain("*Test Title*");
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
it("should format section with underline markdownv2", () => {
|
|
212
|
+
const message = TelegramMessageBuilder.text()
|
|
213
|
+
.setParseMode("markdownv2")
|
|
214
|
+
.section("Test Section")
|
|
215
|
+
.build();
|
|
216
|
+
expect(message.text).toContain("__Test Section__");
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it("should format line with bold option markdownv2", () => {
|
|
220
|
+
const message = TelegramMessageBuilder.text()
|
|
221
|
+
.setParseMode("markdownv2")
|
|
222
|
+
.line("Status", "Active", { bold: true })
|
|
223
|
+
.build();
|
|
224
|
+
expect(message.text).toContain("Status: *Active*");
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
it("should format line with italic option markdownv2", () => {
|
|
228
|
+
const message = TelegramMessageBuilder.text()
|
|
229
|
+
.setParseMode("markdownv2")
|
|
230
|
+
.line("Status", "Active", { italic: true })
|
|
231
|
+
.build();
|
|
232
|
+
expect(message.text).toContain("Status: _Active_");
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
it("should format code block markdownv2", () => {
|
|
236
|
+
const message = TelegramMessageBuilder.text()
|
|
237
|
+
.setParseMode("markdownv2")
|
|
238
|
+
.codeBlock("const x = 1;")
|
|
239
|
+
.build();
|
|
240
|
+
expect(message.text).toContain("const x = 1;\n");
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
it("should format link markdownv2", () => {
|
|
244
|
+
const message = TelegramMessageBuilder.text()
|
|
245
|
+
.setParseMode("markdownv2")
|
|
246
|
+
.link("Click here", "https://example.com")
|
|
247
|
+
.build();
|
|
248
|
+
expect(message.text).toContain("[Click here](https://example.com)");
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
it("should format mention markdownv2", () => {
|
|
252
|
+
const message = TelegramMessageBuilder.text()
|
|
253
|
+
.setParseMode("markdownv2")
|
|
254
|
+
.mention(123456, "John")
|
|
255
|
+
.build();
|
|
256
|
+
expect(message.text).toContain("[John](tg://user?id=123456)");
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
it("should format hashtag markdownv2", () => {
|
|
260
|
+
const message = TelegramMessageBuilder.text()
|
|
261
|
+
.setParseMode("markdownv2")
|
|
262
|
+
.hashtag("test")
|
|
263
|
+
.build();
|
|
264
|
+
expect(message.text).toContain("#test");
|
|
265
|
+
});
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
describe("Layout Methods", () => {
|
|
269
|
+
it("should add single newline", () => {
|
|
270
|
+
const message = TelegramMessageBuilder.text()
|
|
271
|
+
.text("Line 1")
|
|
272
|
+
.newline()
|
|
273
|
+
.text("Line 2")
|
|
274
|
+
.build();
|
|
275
|
+
// Each newline adds "\n\n" to parts, and parts are joined with "\n"
|
|
276
|
+
// Result: "Line 1" + "\n" (join) + "\n\n" (newline) + "\n" (join) + "Line 2"
|
|
277
|
+
// = "Line 1\n\n\n\nLine 2"
|
|
278
|
+
expect(message.text).toContain("Line 1");
|
|
279
|
+
expect(message.text).toContain("Line 2");
|
|
280
|
+
expect(message.text).toMatch(/Line 1\n\n\n\nLine 2/);
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
it("should add multiple newlines", () => {
|
|
284
|
+
const message = TelegramMessageBuilder.text()
|
|
285
|
+
.text("Line 1")
|
|
286
|
+
.newline(3)
|
|
287
|
+
.text("Line 2")
|
|
288
|
+
.build();
|
|
289
|
+
// 3 newlines = each adds "\n\n", joined with "\n"
|
|
290
|
+
expect(message.text).toContain("Line 1");
|
|
291
|
+
expect(message.text).toContain("Line 2");
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
it("should add separator between content", () => {
|
|
295
|
+
const message = TelegramMessageBuilder.text()
|
|
296
|
+
.text("Before")
|
|
297
|
+
.separator()
|
|
298
|
+
.text("After")
|
|
299
|
+
.build();
|
|
300
|
+
expect(message.text).toContain("Before\n---\nAfter");
|
|
301
|
+
});
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
describe("Method Chaining", () => {
|
|
305
|
+
it("should support fluent API chaining", () => {
|
|
306
|
+
const message = TelegramMessageBuilder.text()
|
|
307
|
+
.title("Welcome")
|
|
308
|
+
.newline()
|
|
309
|
+
.section("User Info")
|
|
310
|
+
.line("Name", "John", { bold: true })
|
|
311
|
+
.line("Email", "john@example.com")
|
|
312
|
+
.separator()
|
|
313
|
+
.codeBlock("console.log('Hello');", "javascript")
|
|
314
|
+
.build();
|
|
315
|
+
|
|
316
|
+
expect(message.text).toContain("<b>Welcome</b>");
|
|
317
|
+
expect(message.text).toContain("Name: <b>John</b>");
|
|
318
|
+
expect(message.text).toContain("Email: john@example.com");
|
|
319
|
+
expect(message.text).toContain("---");
|
|
320
|
+
expect(message.text).toContain("language-javascript");
|
|
321
|
+
expect(message.parse_mode).toBe("html");
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
it("should work with markdown mode chaining", () => {
|
|
325
|
+
const message = TelegramMessageBuilder.text()
|
|
326
|
+
.setParseMode("markdown")
|
|
327
|
+
.title("Welcome")
|
|
328
|
+
.line("Status", "Active", { bold: true })
|
|
329
|
+
.link("Click", "https://example.com")
|
|
330
|
+
.build();
|
|
331
|
+
|
|
332
|
+
expect(message.text).toContain("*Welcome*");
|
|
333
|
+
expect(message.text).toContain("Status: *Active*");
|
|
334
|
+
expect(message.text).toContain("[Click](https://example.com)");
|
|
335
|
+
expect(message.parse_mode).toBe("markdown");
|
|
336
|
+
});
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
describe("Build Options", () => {
|
|
340
|
+
it("should build with default HTML parse mode", () => {
|
|
341
|
+
const message = TelegramMessageBuilder.text().title("Test").build();
|
|
342
|
+
|
|
343
|
+
expect(message.parse_mode).toBe("html");
|
|
344
|
+
expect(message.text).toContain("<b>Test</b>");
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
it("should build with custom option", () => {
|
|
348
|
+
const message = TelegramMessageBuilder.text()
|
|
349
|
+
.setOption("disable_web_page_preview", true)
|
|
350
|
+
.title("Test")
|
|
351
|
+
.build();
|
|
352
|
+
|
|
353
|
+
expect(message.disable_web_page_preview).toBe(true);
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
it("should build with multiple custom options", () => {
|
|
357
|
+
const message = TelegramMessageBuilder.text()
|
|
358
|
+
.setOptions({
|
|
359
|
+
disable_web_page_preview: true,
|
|
360
|
+
protect_content: true,
|
|
361
|
+
})
|
|
362
|
+
.title("Test")
|
|
363
|
+
.build();
|
|
364
|
+
|
|
365
|
+
expect(message.disable_web_page_preview).toBe(true);
|
|
366
|
+
expect(message.protect_content).toBe(true);
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
it("should return TelegramMessage interface structure", () => {
|
|
370
|
+
const message = TelegramMessageBuilder.text().title("Test").build();
|
|
371
|
+
|
|
372
|
+
expect(message).toHaveProperty("text");
|
|
373
|
+
expect(message).toHaveProperty("parse_mode");
|
|
374
|
+
expect(typeof message.text).toBe("string");
|
|
375
|
+
expect(typeof message.parse_mode).toBe("string");
|
|
376
|
+
});
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
describe("Complex Messages", () => {
|
|
380
|
+
it("should build complex message with all features", () => {
|
|
381
|
+
const message = TelegramMessageBuilder.text()
|
|
382
|
+
.title("User Profile")
|
|
383
|
+
.newline()
|
|
384
|
+
.section("Personal Information")
|
|
385
|
+
.line("Name", "John Doe", { bold: true })
|
|
386
|
+
.line("Age", "30", { code: true })
|
|
387
|
+
.line("Status", "Active", { italic: true })
|
|
388
|
+
.newline()
|
|
389
|
+
.section("Contact")
|
|
390
|
+
.line("Email", "john@example.com")
|
|
391
|
+
.line("Telegram", "@johndoe")
|
|
392
|
+
.separator()
|
|
393
|
+
.codeBlock("const user = { name: 'John', age: 30 };", "javascript")
|
|
394
|
+
.newline()
|
|
395
|
+
.listItem("Feature 1")
|
|
396
|
+
.listItem("Feature 2")
|
|
397
|
+
.listItem("Feature 3")
|
|
398
|
+
.newline()
|
|
399
|
+
.link("View Profile", "https://example.com/profile")
|
|
400
|
+
.mention(123456, "John Doe")
|
|
401
|
+
.hashtag("profile")
|
|
402
|
+
.setOption("disable_notification", true)
|
|
403
|
+
.build();
|
|
404
|
+
|
|
405
|
+
expect(message.text).toContain("<b>User Profile</b>");
|
|
406
|
+
expect(message.text).toContain("<u>Personal Information</u>");
|
|
407
|
+
expect(message.text).toContain("Name: <b>John Doe</b>");
|
|
408
|
+
expect(message.text).toContain("Age: <code>30</code>");
|
|
409
|
+
expect(message.text).toContain("Status: <i>Active</i>");
|
|
410
|
+
expect(message.text).toContain("---");
|
|
411
|
+
expect(message.text).toContain("language-javascript");
|
|
412
|
+
expect(message.text).toContain("• Feature 1");
|
|
413
|
+
expect(message.text).toContain("View Profile");
|
|
414
|
+
expect(message.text).toContain("tg://user?id=123456");
|
|
415
|
+
expect(message.text).toContain("#profile");
|
|
416
|
+
expect(message.disable_notification).toBe(true);
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
it("should handle empty message", () => {
|
|
420
|
+
const message = TelegramMessageBuilder.text().build();
|
|
421
|
+
expect(message.text).toBe("");
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
it("should escape HTML special characters in text", () => {
|
|
425
|
+
const message = TelegramMessageBuilder.text()
|
|
426
|
+
.text("<script>alert('xss')</script>")
|
|
427
|
+
.build();
|
|
428
|
+
expect(message.text).toContain("<script>");
|
|
429
|
+
expect(message.text).not.toContain("<script>");
|
|
430
|
+
});
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
describe("Cross-Mode Consistency", () => {
|
|
434
|
+
it("should build same message in HTML, Markdown, and MarkdownV2", () => {
|
|
435
|
+
const html = TelegramMessageBuilder.text()
|
|
436
|
+
.title("Test")
|
|
437
|
+
.line("Status", "Active", { bold: true })
|
|
438
|
+
.build();
|
|
439
|
+
|
|
440
|
+
const markdown = TelegramMessageBuilder.text()
|
|
441
|
+
.setParseMode("markdown")
|
|
442
|
+
.title("Test")
|
|
443
|
+
.line("Status", "Active", { bold: true })
|
|
444
|
+
.build();
|
|
445
|
+
|
|
446
|
+
const markdownv2 = TelegramMessageBuilder.text()
|
|
447
|
+
.setParseMode("markdownv2")
|
|
448
|
+
.title("Test")
|
|
449
|
+
.line("Status", "Active", { bold: true })
|
|
450
|
+
.build();
|
|
451
|
+
|
|
452
|
+
expect(html.text).toContain("<b>Test</b>");
|
|
453
|
+
expect(html.text).toContain("Status: <b>Active</b>");
|
|
454
|
+
expect(html.parse_mode).toBe("html");
|
|
455
|
+
|
|
456
|
+
expect(markdown.text).toContain("*Test*");
|
|
457
|
+
expect(markdown.text).toContain("Status: *Active*");
|
|
458
|
+
expect(markdown.parse_mode).toBe("markdown");
|
|
459
|
+
|
|
460
|
+
expect(markdownv2.text).toContain("*Test*");
|
|
461
|
+
expect(markdownv2.text).toContain("Status: *Active*");
|
|
462
|
+
expect(markdownv2.parse_mode).toBe("markdownv2");
|
|
463
|
+
});
|
|
464
|
+
});
|
|
465
|
+
});
|
package/src/escaping.test.ts
CHANGED
|
@@ -68,7 +68,7 @@ describe("Escaping", () => {
|
|
|
68
68
|
const reserved = "_*[]()~`>#+-=|{}.!";
|
|
69
69
|
const result = fmt.escapeMarkdownV2(reserved);
|
|
70
70
|
expect(result).toBe(
|
|
71
|
-
"\\_\\*\\[\\]\\
|
|
71
|
+
"\\_\\*\\[\\]\\(\\)\\~\\`\\>\\#\\+\\-\\=\\|\\{\\}\\.\\!",
|
|
72
72
|
);
|
|
73
73
|
});
|
|
74
74
|
|
|
@@ -122,7 +122,7 @@ describe("Escaping", () => {
|
|
|
122
122
|
it("should select MarkdownV2 mode", () => {
|
|
123
123
|
const text = "[]()~`>#+-=|{}.!";
|
|
124
124
|
const result = fmt.escape(text, "markdownv2");
|
|
125
|
-
expect(result).toBe("\\[\\]\\)\\~\\`\\>\\#\\+\\-\\=\\|\\{\\}
|
|
125
|
+
expect(result).toBe("\\[\\]\\(\\)\\~\\`\\>\\#\\+\\-\\=\\|\\{\\}\\.\\!");
|
|
126
126
|
});
|
|
127
127
|
|
|
128
128
|
it("should default to HTML mode", () => {
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import type { ParseMode } from "../types";
|
|
2
|
+
import { boldMD, italicMD, codeMD, codeBlockMD, linkMD, mentionMD, hashtagMD, underlineMD, strikethroughMD, spoilerMD, emailMD, urlMD, customEmojiMD, rawMD } from "./markdown";
|
|
3
|
+
import { boldMDv2, italicMDv2, underlineMDv2, strikethroughMDv2, spoilerMDv2, codeMDv2, codeBlockMDv2, linkMDv2, mentionMDv2, hashtagMDv2, emailMDv2, urlMDv2, customEmojiMDv2, rawMDv2, escapeMDv2 } from "./markdownv2";
|
|
4
|
+
export * from "./markdown";
|
|
5
|
+
export * from "./markdownv2";
|
|
2
6
|
export declare function escapeHTML(text: string): string;
|
|
3
7
|
export declare function escapeMarkdown(text: string): string;
|
|
4
8
|
export declare function escapeMarkdownV2(text: string): string;
|
|
@@ -16,6 +20,19 @@ export declare function hashtag(tag: string): string;
|
|
|
16
20
|
export declare function customEmoji(emojiId: string): string;
|
|
17
21
|
export declare function email(email: string): string;
|
|
18
22
|
export declare function url(link: string): string;
|
|
23
|
+
/**
|
|
24
|
+
* Raw HTML string - bypasses escaping for combining pre-formatted content
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* ```typescript
|
|
28
|
+
* // Instead of this (which escapes inner tags):
|
|
29
|
+
* fmt.bold(fmt.italic("Text")) // <b><i>Text</i></b>
|
|
30
|
+
*
|
|
31
|
+
* // Use this:
|
|
32
|
+
* fmt.bold(fmt.raw(fmt.italic("Text"))) // <b><i>Text</i></b>
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
export declare function raw(html: string): string;
|
|
19
36
|
export declare function escape(text: string, mode?: ParseMode): string;
|
|
20
37
|
export declare const fmt: {
|
|
21
38
|
bold: typeof bold;
|
|
@@ -36,5 +53,35 @@ export declare const fmt: {
|
|
|
36
53
|
escapeHTML: typeof escapeHTML;
|
|
37
54
|
escapeMarkdown: typeof escapeMarkdown;
|
|
38
55
|
escapeMarkdownV2: typeof escapeMarkdownV2;
|
|
56
|
+
raw: typeof raw;
|
|
57
|
+
boldMD: typeof boldMD;
|
|
58
|
+
italicMD: typeof italicMD;
|
|
59
|
+
codeMD: typeof codeMD;
|
|
60
|
+
codeBlockMD: typeof codeBlockMD;
|
|
61
|
+
linkMD: typeof linkMD;
|
|
62
|
+
mentionMD: typeof mentionMD;
|
|
63
|
+
hashtagMD: typeof hashtagMD;
|
|
64
|
+
underlineMD: typeof underlineMD;
|
|
65
|
+
strikethroughMD: typeof strikethroughMD;
|
|
66
|
+
spoilerMD: typeof spoilerMD;
|
|
67
|
+
emailMD: typeof emailMD;
|
|
68
|
+
urlMD: typeof urlMD;
|
|
69
|
+
customEmojiMD: typeof customEmojiMD;
|
|
70
|
+
rawMD: typeof rawMD;
|
|
71
|
+
boldMDv2: typeof boldMDv2;
|
|
72
|
+
italicMDv2: typeof italicMDv2;
|
|
73
|
+
underlineMDv2: typeof underlineMDv2;
|
|
74
|
+
strikethroughMDv2: typeof strikethroughMDv2;
|
|
75
|
+
spoilerMDv2: typeof spoilerMDv2;
|
|
76
|
+
codeMDv2: typeof codeMDv2;
|
|
77
|
+
codeBlockMDv2: typeof codeBlockMDv2;
|
|
78
|
+
linkMDv2: typeof linkMDv2;
|
|
79
|
+
mentionMDv2: typeof mentionMDv2;
|
|
80
|
+
hashtagMDv2: typeof hashtagMDv2;
|
|
81
|
+
emailMDv2: typeof emailMDv2;
|
|
82
|
+
urlMDv2: typeof urlMDv2;
|
|
83
|
+
customEmojiMDv2: typeof customEmojiMDv2;
|
|
84
|
+
rawMDv2: typeof rawMDv2;
|
|
85
|
+
escapeMDv2: typeof escapeMDv2;
|
|
39
86
|
};
|
|
40
87
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAG1C,OAAO,EACL,MAAM,EACN,QAAQ,EACR,MAAM,EACN,WAAW,EACX,MAAM,EACN,SAAS,EACT,SAAS,EACT,WAAW,EACX,eAAe,EACf,SAAS,EACT,OAAO,EACP,KAAK,EACL,aAAa,EACb,KAAK,EACN,MAAM,YAAY,CAAC;AAEpB,OAAO,EACL,QAAQ,EACR,UAAU,EACV,aAAa,EACb,iBAAiB,EACjB,WAAW,EACX,QAAQ,EACR,aAAa,EACb,QAAQ,EACR,WAAW,EACX,WAAW,EACX,SAAS,EACT,OAAO,EACP,eAAe,EACf,OAAO,EACP,UAAU,EACX,MAAM,cAAc,CAAC;AAGtB,cAAc,YAAY,CAAC;AAC3B,cAAc,cAAc,CAAC;AAE7B,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAM/C;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAMnD;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAOrD;AAED,wBAAgB,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEzC;AAED,wBAAgB,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE3C;AAED,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE9C;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAElD;AAED,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE5C;AAED,wBAAgB,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEzC;AAED,wBAAgB,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAExC;AAED,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAIjE;AAED,wBAAgB,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAEtD;AAED,wBAAgB,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAG7D;AAED,wBAAgB,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAE3C;AAED,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAEnD;AAED,wBAAgB,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAE3C;AAED,wBAAgB,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAExC;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAExC;AAED,wBAAgB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,GAAE,SAAkB,GAAG,MAAM,CASrE;AAED,eAAO,MAAM,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAsDf,CAAC"}
|
package/src/formatters/index.ts
CHANGED
|
@@ -1,5 +1,45 @@
|
|
|
1
1
|
import type { ParseMode } from "../types";
|
|
2
2
|
|
|
3
|
+
// Import Markdown and MarkdownV2 formatters for use in fmt object
|
|
4
|
+
import {
|
|
5
|
+
boldMD,
|
|
6
|
+
italicMD,
|
|
7
|
+
codeMD,
|
|
8
|
+
codeBlockMD,
|
|
9
|
+
linkMD,
|
|
10
|
+
mentionMD,
|
|
11
|
+
hashtagMD,
|
|
12
|
+
underlineMD,
|
|
13
|
+
strikethroughMD,
|
|
14
|
+
spoilerMD,
|
|
15
|
+
emailMD,
|
|
16
|
+
urlMD,
|
|
17
|
+
customEmojiMD,
|
|
18
|
+
rawMD,
|
|
19
|
+
} from "./markdown";
|
|
20
|
+
|
|
21
|
+
import {
|
|
22
|
+
boldMDv2,
|
|
23
|
+
italicMDv2,
|
|
24
|
+
underlineMDv2,
|
|
25
|
+
strikethroughMDv2,
|
|
26
|
+
spoilerMDv2,
|
|
27
|
+
codeMDv2,
|
|
28
|
+
codeBlockMDv2,
|
|
29
|
+
linkMDv2,
|
|
30
|
+
mentionMDv2,
|
|
31
|
+
hashtagMDv2,
|
|
32
|
+
emailMDv2,
|
|
33
|
+
urlMDv2,
|
|
34
|
+
customEmojiMDv2,
|
|
35
|
+
rawMDv2,
|
|
36
|
+
escapeMDv2,
|
|
37
|
+
} from "./markdownv2";
|
|
38
|
+
|
|
39
|
+
// Re-export all formatters for external use
|
|
40
|
+
export * from "./markdown";
|
|
41
|
+
export * from "./markdownv2";
|
|
42
|
+
|
|
3
43
|
export function escapeHTML(text: string): string {
|
|
4
44
|
return text
|
|
5
45
|
.replace(/&/g, "&")
|
|
@@ -56,7 +96,7 @@ export function pre(text: string): string {
|
|
|
56
96
|
export function codeBlock(text: string, language?: string): string {
|
|
57
97
|
return language
|
|
58
98
|
? `<pre><code class="language-${language}">${escapeHTML(text)}</code></pre>`
|
|
59
|
-
: `<pre>${escapeHTML(text)}</pre>`;
|
|
99
|
+
: `<pre><code>${escapeHTML(text)}</code></pre>`;
|
|
60
100
|
}
|
|
61
101
|
|
|
62
102
|
export function link(text: string, url: string): string {
|
|
@@ -84,6 +124,22 @@ export function url(link: string): string {
|
|
|
84
124
|
return `<a href="${escapeHTML(link)}">${escapeHTML(link)}</a>`;
|
|
85
125
|
}
|
|
86
126
|
|
|
127
|
+
/**
|
|
128
|
+
* Raw HTML string - bypasses escaping for combining pre-formatted content
|
|
129
|
+
*
|
|
130
|
+
* @example
|
|
131
|
+
* ```typescript
|
|
132
|
+
* // Instead of this (which escapes inner tags):
|
|
133
|
+
* fmt.bold(fmt.italic("Text")) // <b><i>Text</i></b>
|
|
134
|
+
*
|
|
135
|
+
* // Use this:
|
|
136
|
+
* fmt.bold(fmt.raw(fmt.italic("Text"))) // <b><i>Text</i></b>
|
|
137
|
+
* ```
|
|
138
|
+
*/
|
|
139
|
+
export function raw(html: string): string {
|
|
140
|
+
return html;
|
|
141
|
+
}
|
|
142
|
+
|
|
87
143
|
export function escape(text: string, mode: ParseMode = "html"): string {
|
|
88
144
|
switch (mode) {
|
|
89
145
|
case "html":
|
|
@@ -96,6 +152,7 @@ export function escape(text: string, mode: ParseMode = "html"): string {
|
|
|
96
152
|
}
|
|
97
153
|
|
|
98
154
|
export const fmt = {
|
|
155
|
+
// HTML formatters
|
|
99
156
|
bold,
|
|
100
157
|
italic,
|
|
101
158
|
underline,
|
|
@@ -114,4 +171,38 @@ export const fmt = {
|
|
|
114
171
|
escapeHTML,
|
|
115
172
|
escapeMarkdown,
|
|
116
173
|
escapeMarkdownV2,
|
|
174
|
+
raw,
|
|
175
|
+
|
|
176
|
+
// Markdown formatters
|
|
177
|
+
boldMD,
|
|
178
|
+
italicMD,
|
|
179
|
+
codeMD,
|
|
180
|
+
codeBlockMD,
|
|
181
|
+
linkMD,
|
|
182
|
+
mentionMD,
|
|
183
|
+
hashtagMD,
|
|
184
|
+
underlineMD,
|
|
185
|
+
strikethroughMD,
|
|
186
|
+
spoilerMD,
|
|
187
|
+
emailMD,
|
|
188
|
+
urlMD,
|
|
189
|
+
customEmojiMD,
|
|
190
|
+
rawMD,
|
|
191
|
+
|
|
192
|
+
// MarkdownV2 formatters
|
|
193
|
+
boldMDv2,
|
|
194
|
+
italicMDv2,
|
|
195
|
+
underlineMDv2,
|
|
196
|
+
strikethroughMDv2,
|
|
197
|
+
spoilerMDv2,
|
|
198
|
+
codeMDv2,
|
|
199
|
+
codeBlockMDv2,
|
|
200
|
+
linkMDv2,
|
|
201
|
+
mentionMDv2,
|
|
202
|
+
hashtagMDv2,
|
|
203
|
+
emailMDv2,
|
|
204
|
+
urlMDv2,
|
|
205
|
+
customEmojiMDv2,
|
|
206
|
+
rawMDv2,
|
|
207
|
+
escapeMDv2,
|
|
117
208
|
};
|