@xbibzlibrary/telebibz 3.1.2 → 3.1.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.
Files changed (39) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/LICENSE +1 -1
  3. package/NOTICE.md +15 -8
  4. package/README.id.md +85 -16
  5. package/README.md +84 -16
  6. package/examples/08-rich-message.js +34 -0
  7. package/index.d.ts +300 -96
  8. package/index.js +8 -2
  9. package/lib/api.js +20 -6
  10. package/lib/composer.js +14 -2
  11. package/lib/context.js +83 -21
  12. package/lib/file.js +8 -1
  13. package/lib/inline-query.js +6 -1
  14. package/lib/logger.js +44 -14
  15. package/lib/ratelimit.js +16 -3
  16. package/lib/rich.js +170 -0
  17. package/lib/runner.js +14 -5
  18. package/lib/session.js +12 -4
  19. package/lib/telebibz.js +40 -24
  20. package/lib/telegram-methods.js +192 -0
  21. package/package.json +14 -10
  22. package/test/audit.test.js +320 -0
  23. package/test/types.test.ts +40 -0
  24. package/types/telegram-bot-api/LICENSE +21 -0
  25. package/types/telegram-bot-api/api.d.ts +22 -0
  26. package/types/telegram-bot-api/checklist.d.ts +72 -0
  27. package/types/telegram-bot-api/inline.d.ts +692 -0
  28. package/types/telegram-bot-api/langs.d.ts +193 -0
  29. package/types/telegram-bot-api/manage.d.ts +1153 -0
  30. package/types/telegram-bot-api/markup.d.ts +278 -0
  31. package/types/telegram-bot-api/message.d.ts +1557 -0
  32. package/types/telegram-bot-api/methods.d.ts +2860 -0
  33. package/types/telegram-bot-api/mod.d.ts +14 -0
  34. package/types/telegram-bot-api/passport.d.ts +163 -0
  35. package/types/telegram-bot-api/payment.d.ts +576 -0
  36. package/types/telegram-bot-api/rich.d.ts +1198 -0
  37. package/types/telegram-bot-api/settings.d.ts +120 -0
  38. package/types/telegram-bot-api/story.d.ts +89 -0
  39. package/types/telegram-bot-api/update.d.ts +86 -0
@@ -0,0 +1,320 @@
1
+ 'use strict';
2
+
3
+ const assert = require('node:assert/strict');
4
+ const http = require('node:http');
5
+ const os = require('node:os');
6
+ const path = require('node:path');
7
+ const fs = require('node:fs/promises');
8
+ const { Readable } = require('node:stream');
9
+ const { TeleBibz, File } = require('..');
10
+ const { Composer, BotError } = require('../lib/composer');
11
+ const { pollLoop } = require('../lib/runner');
12
+ const { createTransport, ApiError } = require('../lib/net');
13
+ const { makeApi } = require('../lib/api');
14
+
15
+ const checks = [];
16
+ async function test(name, fn) {
17
+ await fn();
18
+ checks.push(name);
19
+ console.log(` ✓ audit: ${name}`);
20
+ }
21
+
22
+ async function main() {
23
+ await test('errorBoundary benar-benar membungkus handler dan memberi BotError + ctx', async () => {
24
+ const root = new Composer();
25
+ let received;
26
+ root.errorBoundary((err) => { received = err; }, async () => { throw new Error('boom'); });
27
+ const ctx = { update: {} };
28
+ await root.run(ctx);
29
+ assert.ok(received instanceof BotError);
30
+ assert.equal(received.message, 'boom');
31
+ assert.equal(received.ctx, ctx);
32
+ });
33
+
34
+ await test('regex global di hears/callback tidak kehilangan match berturut-turut', async () => {
35
+ const bot = new TeleBibz('123456:TESTTOKEN', { silent: true, transport: async () => true });
36
+ let textHits = 0;
37
+ bot.hears(/ping/g, () => { textHits++; });
38
+ const msg = (text) => ({ message: { text, chat: { id: 1, type: 'private' }, from: { id: 1 } } });
39
+ await bot.handleUpdate(msg('ping')); await bot.handleUpdate(msg('ping'));
40
+ assert.equal(textHits, 2);
41
+ let callbackHits = 0;
42
+ bot.action(/go/g, () => { callbackHits++; });
43
+ const cb = () => ({ callback_query: { id: 'q', data: 'go', from: { id: 1 }, message: { message_id: 1, chat: { id: 1 } } } });
44
+ await bot.handleUpdate(cb()); await bot.handleUpdate(cb());
45
+ assert.equal(callbackHits, 2);
46
+ const { matchInlineQuery } = require('../lib/inline-query');
47
+ const inlineMatcher = matchInlineQuery(/cat/g);
48
+ assert.equal(inlineMatcher({ inline_query: { query: 'cat' } }), true);
49
+ assert.equal(inlineMatcher({ inline_query: { query: 'cat' } }), true);
50
+ });
51
+
52
+ await test('Composer route/lazy/fork meneruskan middleware dan fork tidak memblokir', async () => {
53
+ const c = new Composer();
54
+ const order = [];
55
+ c.route((ctx) => ctx.kind, { private: async (_ctx, next) => { order.push('route'); await next(); } });
56
+ c.lazy(async () => async (_ctx, next) => { order.push('lazy'); await next(); });
57
+ c.fork(async () => { await new Promise((r) => setTimeout(r, 5)); order.push('fork'); });
58
+ await c.run({ kind: 'private' });
59
+ assert.deepEqual(order, ['route', 'lazy']);
60
+ await new Promise((r) => setTimeout(r, 15));
61
+ assert.deepEqual(order, ['route', 'lazy', 'fork']);
62
+ });
63
+
64
+ await test('session adapter hasil deserialize ditulis ulang setelah update', async () => {
65
+ const data = new Map();
66
+ const storage = {
67
+ read: async (key) => data.has(key) ? structuredClone(data.get(key)) : undefined,
68
+ write: async (key, val) => data.set(key, structuredClone(val)),
69
+ delete: async (key) => data.delete(key),
70
+ };
71
+ const bot = new TeleBibz('123456:TESTTOKEN', { silent: true, transport: async () => true, session: { storage } });
72
+ let observed = [];
73
+ bot.on(':text', (ctx) => { observed.push(ctx.session.count || 0); ctx.session.count = (ctx.session.count || 0) + 1; });
74
+ const update = (text) => ({ message: { text, from: { id: 7 }, chat: { id: 7, type: 'private' } } });
75
+ await bot.handleUpdate(update('first')); await bot.handleUpdate(update('second'));
76
+ assert.deepEqual(observed, [0, 1]);
77
+ assert.equal(data.get('7:7').count, 2);
78
+ });
79
+
80
+ await test('guest_message diteruskan ke Context dan answerGuestQuery membentuk payload resmi', async () => {
81
+ const calls = [];
82
+ const bot = new TeleBibz('123456:TESTTOKEN', { silent: true, transport: async (m, p) => { calls.push({ m, p }); return true; } });
83
+ let seen;
84
+ bot.on('guest_message:text', async (ctx) => {
85
+ seen = { text: ctx.msg.text, chat: ctx.chat.id, query: ctx.guestQueryId };
86
+ await ctx.answerGuestQuery({ type: 'article', id: '1', title: 'Balasan', input_message_content: { message_text: 'Hai' } });
87
+ });
88
+ await bot.handleUpdate({ guest_message: { guest_query_id: 'guest-1', text: 'halo', chat: { id: 22, type: 'private' }, from: { id: 9 } } });
89
+ assert.deepEqual(seen, { text: 'halo', chat: 22, query: 'guest-1' });
90
+ assert.deepEqual(calls[0], { m: 'answerGuestQuery', p: { guest_query_id: 'guest-1', result: { type: 'article', id: '1', title: 'Balasan', input_message_content: { message_text: 'Hai' } } } });
91
+ });
92
+
93
+ await test('business voice reply dan callback reaction membawa konteks yang benar', async () => {
94
+ const calls = [];
95
+ const bot = new TeleBibz('123456:TESTTOKEN', { silent: true, transport: async (m, p) => { calls.push({ m, p }); return true; } });
96
+ bot.on('business_message', (ctx) => ctx.replyWithVoice('voice-id'));
97
+ await bot.handleUpdate({ business_message: { business_connection_id: 'biz-1', message_id: 8, from: { id: 3 }, chat: { id: 55, type: 'private' } } });
98
+ bot.action('react', (ctx) => ctx.react('🔥'));
99
+ await bot.handleUpdate({ callback_query: { id: 'cb', data: 'react', from: { id: 3 }, message: { message_id: 12, chat: { id: 56, type: 'private' } } } });
100
+ const voice = calls.find((c) => c.m === 'sendVoice');
101
+ const reaction = calls.find((c) => c.m === 'setMessageReaction');
102
+ assert.equal(voice.p.business_connection_id, 'biz-1');
103
+ assert.equal(reaction.p.chat_id, 56);
104
+ assert.equal(reaction.p.message_id, 12);
105
+ });
106
+
107
+ await test('Bot API 10.3 seluruh tipe Update diminta saat polling default', async () => {
108
+ let bot;
109
+ let captured;
110
+ const transport = async (method, payload) => {
111
+ if (method === 'getMe') return { id: 1, is_bot: true, username: 'auditbot' };
112
+ if (method === 'getUpdates') { captured = payload.allowed_updates; bot.stop(); return []; }
113
+ return true;
114
+ };
115
+ bot = new TeleBibz('123456:TESTTOKEN', { silent: true, transport });
116
+ await bot.launch({ noSignalHandlers: true });
117
+ await bot.runPromise;
118
+ for (const field of [
119
+ 'channel_post', 'edited_channel_post', 'deleted_business_messages', 'guest_message',
120
+ 'chat_boost', 'removed_chat_boost', 'stopped_message_generation', 'purchased_paid_media',
121
+ ]) assert.ok(captured.includes(field), `allowed_updates hilang: ${field}`);
122
+ assert.equal(new Set(captured).size, captured.length, 'allowed_updates berisi duplikat');
123
+ });
124
+
125
+ await test('poller retry ECONNRESET dan 429 sesuai retry_after tanpa fatal stop', async () => {
126
+ let calls = 0, stopped = false, fatals = 0;
127
+ const api = { callApi: async () => {
128
+ calls++;
129
+ if (calls === 1) { const e = new Error('socket reset'); e.code = 'ECONNRESET'; e.network = true; throw e; }
130
+ if (calls === 2) { const e = new Error('Too Many Requests'); e.error_code = 429; e.parameters = { retry_after: 0 }; throw e; }
131
+ stopped = true; return [];
132
+ } };
133
+ await pollLoop({ api, handleUpdate: async () => {}, allowedUpdates: [], dropPending: false, shouldStop: () => stopped, onFatal: () => fatals++ });
134
+ assert.equal(calls, 3);
135
+ assert.equal(fatals, 0);
136
+ });
137
+
138
+ await test('throttler tetap menjalankan antrean setelah satu request gagal', async () => {
139
+ const { throttler } = require('../lib/ratelimit');
140
+ const calls = [];
141
+ const run = throttler({ perSecond: 10000 });
142
+ const prev = async (_method, payload) => { calls.push(payload.id); if (payload.id === 1) throw new Error('one-off'); return payload.id; };
143
+ const settled = await Promise.allSettled([run(prev, 'a', { id: 1 }), run(prev, 'b', { id: 2 })]);
144
+ assert.equal(settled[0].status, 'rejected');
145
+ assert.equal(settled[1].status, 'fulfilled');
146
+ assert.deepEqual(calls, [1, 2]);
147
+ });
148
+
149
+ await test('transport lokal memvalidasi JSON, multipart attach://, dan ApiError', async () => {
150
+ const received = [];
151
+ const server = http.createServer((req, res) => {
152
+ const chunks = [];
153
+ req.on('data', (c) => chunks.push(c));
154
+ req.on('end', () => {
155
+ const body = Buffer.concat(chunks).toString();
156
+ received.push({ url: req.url, type: req.headers['content-type'], body });
157
+ if (req.url.endsWith('/bad')) {
158
+ res.writeHead(400, { 'content-type': 'application/json' });
159
+ res.end(JSON.stringify({ ok: false, error_code: 400, description: 'bad payload', parameters: { retry_after: 2 } }));
160
+ } else {
161
+ res.writeHead(200, { 'content-type': 'application/json' });
162
+ res.end(JSON.stringify({ ok: true, result: { accepted: true } }));
163
+ }
164
+ });
165
+ });
166
+ await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve));
167
+ const root = `http://127.0.0.1:${server.address().port}`;
168
+ try {
169
+ const transport = createTransport('123456:TESTTOKEN', { apiRoot: root });
170
+ assert.deepEqual(await transport('sendMessage', { chat_id: 1, text: 'hi' }), { accepted: true });
171
+ assert.match(received[0].type, /application\/json/);
172
+ assert.deepEqual(JSON.parse(received[0].body), { chat_id: 1, text: 'hi' });
173
+ await transport('sendMediaGroup', { chat_id: 1, media: [{ type: 'photo', media: new File(Buffer.from('pic'), 'pic.jpg') }] });
174
+ assert.match(received[1].type, /multipart\/form-data; boundary=/);
175
+ assert.match(received[1].body, /attach:\/\/file0/);
176
+ const { rich, InputMediaBuilder } = require('..');
177
+ await transport('sendRichMessage', {
178
+ chat_id: 1,
179
+ rich_message: rich.blocks([rich.photo(InputMediaBuilder.photo(new File(Buffer.from('rich-image'), 'rich.jpg')))]),
180
+ });
181
+ assert.match(received[2].body, /attach:\/\/file0/);
182
+ assert.match(received[2].body, /rich_message/);
183
+ const bot = new TeleBibz('123456:TESTTOKEN', { silent: true, apiRoot: root });
184
+ assert.deepEqual(await bot.init(), { accepted: true });
185
+ assert.ok(received.some((r) => r.url === '/bot123456:TESTTOKEN/getMe'));
186
+ await assert.rejects(transport('bad', {}), (e) => e instanceof ApiError && e.error_code === 400 && e.method === 'bad' && e.parameters.retry_after === 2);
187
+ } finally {
188
+ await new Promise((resolve) => server.close(resolve));
189
+ }
190
+ });
191
+
192
+ await test('webhook memverifikasi secret token Telegram dan membatasi ukuran request', async () => {
193
+ const bot = new TeleBibz('123456:TESTTOKEN', { silent: true, transport: async () => true });
194
+ let hits = 0;
195
+ bot.on(':text', () => { hits++; });
196
+ assert.throws(() => bot.webhook({ secretToken: '' }), /secretToken/);
197
+ const handler = bot.webhook({ secretToken: 'secret-123', maxBodyBytes: 200 });
198
+ const invoke = async (body, headers = {}, method = 'POST') => {
199
+ const req = Readable.from([body]); req.headers = headers; req.method = method;
200
+ let status = 0;
201
+ const res = { set statusCode(v) { status = v; }, setHeader() {}, end() {} };
202
+ await handler(req, res); return status;
203
+ };
204
+ const update = JSON.stringify({ message: { text: 'secure', from: { id: 1 }, chat: { id: 1, type: 'private' } } });
205
+ assert.equal(await invoke(update), 401);
206
+ assert.equal(await invoke(update, { 'x-telegram-bot-api-secret-token': 'secret-123' }), 200);
207
+ assert.equal(await invoke('x'.repeat(201), { 'x-telegram-bot-api-secret-token': 'secret-123' }), 413);
208
+ assert.equal(hits, 1);
209
+ });
210
+
211
+ await test('File input Buffer, Uint8Array, path, dan async stream', async () => {
212
+ const tmp = path.join(os.tmpdir(), `telebibz-${process.pid}.bin`);
213
+ await fs.writeFile(tmp, 'path-data');
214
+ try {
215
+ assert.equal((await new File(Buffer.from('buf')).data()).toString(), 'buf');
216
+ assert.equal((await new File(new Uint8Array([65, 66])).data()).toString(), 'AB');
217
+ assert.equal((await new File(tmp).data()).toString(), 'path-data');
218
+ assert.equal((await new File(Readable.from(['stream-'])).data()).toString(), 'stream-');
219
+ } finally { await fs.rm(tmp, { force: true }); }
220
+ });
221
+
222
+ await test('downloadFile menghormati apiRoot khusus dan menulis stream dengan benar', async () => {
223
+ const dest = path.join(os.tmpdir(), `telebibz-download-${process.pid}.txt`);
224
+ let requestedUrl;
225
+ const api = makeApi('123456:TESTTOKEN', async (method) => method === 'getFile' ? { file_path: 'files/a.txt' } : true, { apiRoot: 'https://api.example.test' });
226
+ try {
227
+ await api.downloadFile('file-id', dest, { adapter: async (config) => {
228
+ requestedUrl = config.url;
229
+ return { data: Readable.from(['downloaded']), status: 200, statusText: 'OK', headers: {}, config };
230
+ } });
231
+ assert.equal(requestedUrl, 'https://api.example.test/file/bot123456:TESTTOKEN/files/a.txt');
232
+ assert.equal(await fs.readFile(dest, 'utf8'), 'downloaded');
233
+ } finally { await fs.rm(dest, { force: true }); }
234
+ });
235
+
236
+ await test('Proxy API current Bot API accepts modern methods via payload object', async () => {
237
+ const calls = [];
238
+ const api = makeApi('123456:TESTTOKEN', async (m, p) => { calls.push({ m, p }); return true; });
239
+ await api.sendRichMessage({ chat_id: 1, rich_message: { blocks: [] }, ephemeral_message_parameters: {} });
240
+ await api.sendLivePhoto({ chat_id: 1, live_photo: 'live-id', photo: 'photo-id' });
241
+ await api.sendMessageDraft({ chat_id: 1, draft_id: 1, text: 'partial', can_stop: true });
242
+ assert.equal(api.then, undefined, 'Proxy jangan berubah menjadi thenable');
243
+ assert.equal(await api, api, 'await api tidak boleh mengirim request semu');
244
+ assert.deepEqual(calls.map((c) => c.m), ['sendRichMessage', 'sendLivePhoto', 'sendMessageDraft']);
245
+ assert.equal(calls[0].p.ephemeral_message_parameters !== undefined, true);
246
+ assert.equal(calls[2].p.can_stop, true);
247
+ });
248
+
249
+ await test('Rich Message builder mencakup format, nested blocks, style dan validasi eksklusif', () => {
250
+ const { rich, RichMessageBuilder, inputRichMessage, InputMediaBuilder, InputPaidMediaBuilder } = require('..');
251
+ const message = rich.blocks([
252
+ rich.heading('Ringkasan', 2),
253
+ rich.paragraph(['Halo ', rich.bold('dunia'), ' ', rich.customEmoji('emoji-1', '✨')]),
254
+ rich.list(['item satu', { blocks: [rich.code('x=1')], has_checkbox: true }]),
255
+ rich.table([[{ text: 'A', align: 'left', valign: 'middle' }]], { compact: true, striped: true }),
256
+ rich.details('Info', [rich.paragraph('Isi')], true),
257
+ rich.buttons([rich.button('OK', { callback_data: 'ok' }, 'success')], 'center'),
258
+ ]);
259
+ assert.equal(message.blocks[0].type, 'heading');
260
+ assert.deepEqual(message.blocks[1].text[1], { type: 'bold', text: 'dunia' });
261
+ assert.equal(message.blocks[2].items[0].blocks[0].text, 'item satu');
262
+ assert.equal(message.blocks[3].is_compact, true);
263
+ assert.equal(message.blocks[4].is_open, true);
264
+ assert.equal(message.blocks[5].buttons[0].button.callback_data, 'ok');
265
+ assert.deepEqual(new RichMessageBuilder().add(rich.paragraph('ok')).rtl().build(), { blocks: [{ type: 'paragraph', text: 'ok' }], is_rtl: true });
266
+ assert.deepEqual(new RichMessageBuilder().markdown('draft').buildDraft(), { markdown: 'draft' });
267
+ assert.deepEqual(rich.draftBlocks([rich.paragraph('partial'), rich.thinking('working')]), { blocks: [{ type: 'paragraph', text: 'partial' }, { type: 'thinking', text: 'working' }] });
268
+ assert.throws(() => rich.draftBlocks([rich.photo(InputMediaBuilder.photo(new File(Buffer.from('not allowed'), 'upload.jpg')))]), /tidak mendukung upload/);
269
+ assert.throws(() => inputRichMessage({ html: 'x', markdown: 'y' }), /tepat satu/);
270
+ assert.throws(() => rich.heading('bad', 7), /1–6/);
271
+ assert.throws(() => rich.button('Link', { url: 'https://example.com' }, 'link'), /callback button/);
272
+ assert.throws(() => rich.buttons([], 'justify'), /align/);
273
+ assert.deepEqual(InputMediaBuilder.livePhoto('video', 'photo'), { type: 'live_photo', media: 'video', photo: 'photo' });
274
+ assert.deepEqual(InputPaidMediaBuilder.livePhoto('video', 'photo'), { type: 'live_photo', media: 'video', photo: 'photo' });
275
+ });
276
+
277
+ await test('Context memetakan rich send/edit, live photo, ephemeral dan draft ke payload Bot API', async () => {
278
+ const { rich } = require('..');
279
+ const calls = [];
280
+ const bot = new TeleBibz('123456:TESTTOKEN', { silent: true, transport: async (m, p) => { calls.push({ m, p }); return { message_id: 19 }; } });
281
+ bot.cmd('features', async (ctx) => {
282
+ const body = rich.markdown('**Rich**');
283
+ await ctx.replyWithRichMessage(body, { message_thread_id: 7 });
284
+ await ctx.replyWithLivePhoto('video-id', 'photo-id', { caption: 'Moment' });
285
+ await ctx.replyEphemeral('Only you', ctx.from.id, { ephemeral_message_parameters: { callback_query_id: 'q' } });
286
+ await ctx.sendMessageDraft(3, 'Draft', { can_stop: true });
287
+ await ctx.sendRichMessageDraft(4, rich.blocks([rich.thinking('Thinking')]), { can_stop: true });
288
+ await ctx.editRichMessage(body);
289
+ });
290
+ await bot.handleUpdate({ message: { message_id: 8, text: '/features', from: { id: 77 }, chat: { id: 77, type: 'private' } } });
291
+ const payload = (method) => calls.find((c) => c.m === method).p;
292
+ assert.deepEqual(payload('sendRichMessage').rich_message, rich.markdown('**Rich**'));
293
+ assert.equal(payload('sendRichMessage').message_thread_id, 7);
294
+ assert.equal(payload('sendLivePhoto').live_photo, 'video-id');
295
+ assert.deepEqual(payload('sendMessage').ephemeral_message_parameters, { callback_query_id: 'q', receiver_user_id: 77 });
296
+ assert.equal(payload('sendMessageDraft').can_stop, true);
297
+ assert.equal(payload('sendRichMessageDraft').draft_id, 4);
298
+ assert.equal(payload('editMessageText').rich_message.markdown, '**Rich**');
299
+ });
300
+
301
+ await test('Seluruh 185 nama metode Bot API 10.3 lewat jalur callApi generic', async () => {
302
+ const { TELEGRAM_API_METHODS } = require('..');
303
+ const calls = [];
304
+ const api = makeApi('123456:TESTTOKEN', async (method, payload) => { calls.push([method, payload]); return true; });
305
+ assert.equal(TELEGRAM_API_METHODS.length, 185);
306
+ assert.equal(new Set(TELEGRAM_API_METHODS).size, TELEGRAM_API_METHODS.length, 'registry tidak boleh berisi duplikat');
307
+ const methodTypes = await fs.readFile(path.join(__dirname, '../types/telegram-bot-api/methods.d.ts'), 'utf8');
308
+ const declaredMethods = [...methodTypes.matchAll(/^ {4}([A-Za-z0-9_]+)\(/gm)].map((match) => match[1]);
309
+ assert.deepEqual([...TELEGRAM_API_METHODS].sort(), declaredMethods.sort(), 'registry runtime harus sinkron dengan schema method TypeScript vendored');
310
+ await Promise.all(TELEGRAM_API_METHODS.map((method) => api.callApi(method, {})));
311
+ assert.deepEqual(calls.map(([method]) => method).sort(), [...TELEGRAM_API_METHODS].sort());
312
+ for (const method of ['sendRichMessage', 'sendRichMessageDraft', 'editEphemeralMessageText', 'answerGuestQuery', 'setManagedBotAccessSettings']) {
313
+ assert.ok(TELEGRAM_API_METHODS.includes(method), `${method} belum terdaftar`);
314
+ }
315
+ });
316
+
317
+ console.log(`\n${checks.length}/${checks.length} audit checks passed`);
318
+ }
319
+
320
+ main().catch((err) => { console.error(err.stack || err); process.exitCode = 1; });
@@ -0,0 +1,40 @@
1
+ import { TeleBibz, rich, type TelegramMethodName, type TelegramMethodPayload, TelegramTypes } from '..';
2
+
3
+ const bot = new TeleBibz('123456:TESTTOKEN', { silent: true });
4
+ const message = rich.blocks([
5
+ rich.heading('Typed rich content', 2),
6
+ rich.paragraph(['Status: ', rich.bold('ready')]),
7
+ rich.table([[{ text: 'Count', align: 'right', valign: 'middle' }]]),
8
+ ]);
9
+
10
+ bot.api.callApi('sendRichMessage', { chat_id: 42, rich_message: message });
11
+ bot.api.callApi('sendMessageDraft', { chat_id: 42, draft_id: 1, text: 'Draft', can_stop: true });
12
+ bot.api.callApi('sendLivePhoto', { chat_id: 42, live_photo: 'video-id', photo: 'photo-id' });
13
+
14
+ const method: TelegramMethodName = 'answerGuestQuery';
15
+ const payload: TelegramMethodPayload<'answerGuestQuery'> = {
16
+ guest_query_id: 'guest-query-id',
17
+ result: { type: 'article', id: '1', title: 'Answer', input_message_content: { message_text: 'Hello' } },
18
+ };
19
+ void method;
20
+ void payload;
21
+ const draft: TelegramTypes.InputRichMessage<never> = rich.draftMarkdown('**streamed text**');
22
+ const blockDraft = rich.draftBlocks([rich.paragraph('partial'), rich.thinking('working')]);
23
+ bot.on('message', (ctx) => {
24
+ void ctx.sendRichMessageDraft(2, draft);
25
+ void ctx.sendRichMessageDraft(4, blockDraft);
26
+ });
27
+
28
+ // A normal rich message can contain media uploads; drafts have the stricter, no-upload schema.
29
+ // @ts-expect-error regular rich builder type may include uploadable media, so it is not a draft payload.
30
+ void bot.on('message', (ctx) => ctx.sendRichMessageDraft(3, rich.markdown('draft')));
31
+
32
+ type RichBlock = TelegramTypes.InputRichBlock<string>;
33
+ const block: RichBlock = { type: 'paragraph', text: 'Typed by the vendored Telegram API schema' };
34
+ void block;
35
+
36
+ // Invalid parameters must remain rejected by method-specific TypeScript typing.
37
+ // @ts-expect-error sendRichMessage requires chat_id and rich_message, not text.
38
+ bot.api.callApi('sendRichMessage', { chat_id: 42, text: 'not a rich message' });
39
+ // @ts-expect-error method name must exist in the official Bot API method schema.
40
+ bot.api.callApi('sendNotARealMethod', {});
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2021-2024 KnorpelSenf
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,22 @@
1
+ export interface ApiError {
2
+ ok: false;
3
+ error_code: number;
4
+ description: string;
5
+ parameters?: ResponseParameters;
6
+ }
7
+ export interface ApiSuccess<T> {
8
+ ok: true;
9
+ result: T;
10
+ }
11
+ /** The response contains an object, which always has a Boolean field 'ok' and may have an optional String field 'description' with a human-readable description of the result. If 'ok' equals True, the request was successful and the result of the query can be found in the 'result' field. In case of an unsuccessful request, 'ok' equals False and the error is explained in the 'description'. An Integer 'error_code' field is also returned, but its contents are subject to change in the future. Some errors may also have an optional field 'parameters' of the type ResponseParameters, which can help to automatically handle the error.
12
+
13
+ - All methods in the Bot API are case-insensitive.
14
+ - All queries must be made using UTF-8. */
15
+ export type ApiResponse<T> = ApiError | ApiSuccess<T>;
16
+ /** Describes why a request was unsuccessful. */
17
+ export interface ResponseParameters {
18
+ /** The group has been migrated to a supergroup with the specified identifier. */
19
+ migrate_to_chat_id?: number;
20
+ /** In case of exceeding flood control, the number of seconds left to wait before the request can be repeated */
21
+ retry_after?: number;
22
+ }
@@ -0,0 +1,72 @@
1
+ import type { Chat, User } from "./manage.js";
2
+ import type { Message, MessageEntity, ParseMode } from "./message.js";
3
+ /** Describes a task in a checklist. */
4
+ export interface ChecklistTask {
5
+ /** Unique identifier of the task */
6
+ id: number;
7
+ /** Text of the task */
8
+ text: string;
9
+ /** Special entities that appear in the task text */
10
+ text_entities?: MessageEntity[];
11
+ /** User that completed the task; omitted if the task wasn't completed by a user */
12
+ completed_by_user?: User;
13
+ /** Chat that completed the task; omitted if the task wasn't completed by a chat */
14
+ completed_by_chat?: Chat;
15
+ /** Point in time (Unix timestamp) when the task was completed; 0 if the task wasn't completed */
16
+ completion_date?: number;
17
+ }
18
+ /** Describes a checklist. */
19
+ export interface Checklist {
20
+ /** Title of the checklist */
21
+ title: string;
22
+ /** Special entities that appear in the checklist title */
23
+ title_entities?: MessageEntity[];
24
+ /** List of tasks in the checklist */
25
+ tasks: ChecklistTask[];
26
+ /** True, if users other than the creator of the list can add tasks to the list */
27
+ others_can_add_tasks?: true;
28
+ /** True, if users other than the creator of the list can mark tasks as done or not done */
29
+ others_can_mark_tasks_as_done?: true;
30
+ }
31
+ /** Describes a task to add to a checklist. */
32
+ export interface InputChecklistTask {
33
+ /** Unique identifier of the task; must be positive and unique among all task identifiers currently present in the checklist */
34
+ id: number;
35
+ /** Text of the task; 1-100 characters after entities parsing */
36
+ text: string;
37
+ /** Mode for parsing entities in the text. See formatting options for more details. */
38
+ parse_mode?: ParseMode;
39
+ /** List of special entities that appear in the text, which can be specified instead of parse_mode. Currently, only bold, italic, underline, strikethrough, spoiler, custom_emoji, and date_time entities are allowed. */
40
+ text_entities?: MessageEntity[];
41
+ }
42
+ /** Describes a checklist to create. */
43
+ export interface InputChecklist {
44
+ /** Title of the checklist; 1-255 characters after entities parsing */
45
+ title: string;
46
+ /** Mode for parsing entities in the title. See formatting options for more details. */
47
+ parse_mode?: ParseMode;
48
+ /** List of special entities that appear in the title, which can be specified instead of parse_mode. Currently, only bold, italic, underline, strikethrough, spoiler, custom_emoji, and date_time entities are allowed. */
49
+ title_entities?: MessageEntity[];
50
+ /** List of 1-30 tasks in the checklist */
51
+ tasks: InputChecklistTask[];
52
+ /** Pass True if other users can add tasks to the checklist */
53
+ others_can_add_tasks?: boolean;
54
+ /** Pass True if other users can mark tasks as done or not done in the checklist */
55
+ others_can_mark_tasks_as_done?: boolean;
56
+ }
57
+ /** Describes a service message about checklist tasks marked as done or not done. */
58
+ export interface ChecklistTasksDone {
59
+ /** Message containing the checklist whose tasks were marked as done or not done. Note that the Message object in this field will not contain the reply_to_message field even if it itself is a reply. */
60
+ checklist_message?: Message;
61
+ /** Identifiers of the tasks that were marked as done */
62
+ marked_as_done_task_ids?: number[];
63
+ /** Identifiers of the tasks that were marked as not done */
64
+ marked_as_not_done_task_ids?: number[];
65
+ }
66
+ /** Describes a service message about tasks added to a checklist. */
67
+ export interface ChecklistTasksAdded {
68
+ /** Message containing the checklist to which the tasks were added. Note that the Message object in this field will not contain the reply_to_message field even if it itself is a reply. */
69
+ checklist_message?: Message;
70
+ /** List of tasks added to the checklist */
71
+ tasks: ChecklistTask[];
72
+ }