@xbibzlibrary/telebibz 3.1.3 → 3.1.5

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/CHANGELOG.md CHANGED
@@ -1,11 +1,35 @@
1
1
  # Changelog
2
2
 
3
- ## Unreleased — Telegram Bot API 10.3
3
+ ## Unreleased — Telegram Bot API 10.3 / Rich Messages
4
4
 
5
- - Vendored schema TypeScript `@grammyjs/types@5.0.0` (MIT), typed payload signatures, and an audited 185-method runtime registry kept in exact sync by tests; no new runtime dependency.
6
- - Rich-message builders/entities/blocks, live-photo and paid-media builders, guest-query/ephemeral/draft helpers, inline rich-article builder, and the `08-rich-message.js` example.
7
- - Typed `callApi(method, payload)` and Telegram schema exports; positive/negative TypeScript consumer checks run in CI and before automated npm release.
8
- - English/Indonesian documentation and npm publishing setup guidance updated; tests now 30 feature + 17 audit checks.
5
+ ### API surface and types
6
+
7
+ - Added a runtime registry containing **185 Bot API method names**. An audit test compares the registry exactly with all vendored method signatures and rejects duplicates.
8
+ - Vendored `@grammyjs/types@5.0.0` declarations (MIT; attribution/license in `NOTICE.md` and `types/telegram-bot-api/LICENSE`) for Bot API methods, objects, updates and payloads. These declarations add **no runtime dependency**; TypeScript is a development-only dependency for type tests.
9
+ - `api.callApi(method, payload)` and `api.raw(...)` now provide method-specific parameter and result typing for all methods in the vendored schema. `TelegramTypes`, `TelegramMethodName`, `TelegramMethodPayload<M>`, `TelegramMethodResult<M>`, `TelegramApiMethods` and `TelegramApiPayloads` are exported. The dynamic API Proxy remains available but is not runtime schema validation.
10
+
11
+ ### Rich Message authoring
12
+
13
+ - Added `rich.html()`, `rich.markdown()`, `rich.blocks()`, `inputRichMessage()`, and `RichMessageBuilder` (`html`, `markdown`, `blocks`, `add`, `media`, `rtl`, `skipEntityDetection`, `build`, `buildDraft`). Exactly one content mode—HTML, Markdown or blocks—is required.
14
+ - Added RichText entity builders for bold/italic/underline/strikethrough/spoiler/marked/code/subscript/superscript, date-time, mentions, custom emoji, math, links, email/phone/card, hashtag/cashtag/bot command, anchors/references, and inline rich-text buttons.
15
+ - Added block builders for paragraph, heading, preformatted text, footer, divider, math, anchor, list, block/expandable/pull quotations, collage, slideshow, table, details, map, animation, audio, document, photo, video, voice note, buttons and draft-only thinking blocks.
16
+ - `rich.button()` now builds a `RichMessageButton` for the buttons block; `rich.buttonText()` builds the separate inline `RichTextButton` entity. Runtime validation checks the one-action rule, supported style/alignment and 1–8 button count. This correction followed a live Telegram parse error.
17
+ - Added draft-safe `rich.draftHtml()`, `rich.draftMarkdown()`, `rich.draftBlocks()` and `RichMessageBuilder.buildDraft()`. They reject new `File` uploads; use existing Telegram file IDs for draft media.
18
+ - Added animated custom emoji support via `rich.customEmoji(customEmojiId, alternativeText)` and rich-message embedding of media through `tg://...` references plus `media` entries.
19
+
20
+ ### Bot API 10.x helpers and media
21
+
22
+ - Added/typed API and Context support for `sendRichMessage`, `editMessageText` with `rich_message`, `sendLivePhoto`, `sendMessageDraft`, `sendRichMessageDraft`, `answerGuestQuery`, ephemeral send/edit/delete methods, and `iq.richArticle()`.
23
+ - Added live-photo and paid-media builders (`InputMediaBuilder.livePhoto()` and `InputPaidMediaBuilder.livePhoto()`). Existing multipart transport now has audit coverage for nested rich-media attachments.
24
+ - Added Context helpers: `replyWithRichMessage`, `editRichMessage`, `replyWithLivePhoto`, `sendMessageDraft`, `sendRichMessageDraft`, `replyEphemeral`, `editEphemeralRichMessage`, ephemeral media/caption/reply-markup edits, and `deleteEphemeralMessage`.
25
+ - Added `examples/08-rich-message.js` and expanded both English and Indonesian README sections with usage, helper catalogs, typing, media, drafts, animated emoji, limits/permissions and troubleshooting.
26
+
27
+ ### Verification, live-test findings and publishing
28
+
29
+ - Automated suite: **30/30** existing feature checks + **18/18** audit checks (**48 total**); `npm run typecheck`, JavaScript syntax, workflow YAML, `npm pack --dry-run`, `git diff --check`, and `npm audit --omit=dev` pass.
30
+ - Live smoke test: rich blocks, HTML/Markdown, animated custom emoji, plain/rich drafts, photo/audio/document/video/voice-note media blocks, collage/slideshow, standalone live photo, GIF with `sendAnimation`, and rich HTML media references succeeded. For a rich `animation` block, MP4 succeeded while GIF returned `RICH_MESSAGE_VIDEO_INVALID`; the same GIF worked with standalone `sendAnimation`.
31
+ - Live ephemeral sends returned Telegram `BOT_NOT_ADMIN`; paid-media sending was not attempted because it can involve Telegram Stars. Callback execution and all 185 endpoints were not live-tested; see `VERIFIKASI-MENDALAM.md`. No bot token or user/chat identifier is recorded in repo documentation.
32
+ - CI/release workflows now run TypeScript checks. Auto-publish remains gated by GitHub Environment `npm-release` and secret `NPM_TOKEN`; no credential is committed and no release is implied by this Unreleased entry.
9
33
 
10
34
  ## 3.1.0 — wizard: tombol pilihan + mode edit/delete (2026-09-13)
11
35
 
package/README.id.md CHANGED
@@ -19,8 +19,8 @@ produksi yang *benar-benar dipakai*, dokumentasi 🇮🇩 Indonesia-first, dan n
19
19
  [![npm version](https://img.shields.io/npm/v/@xbibzlibrary/telebibz?style=for-the-badge&logo=npm&logoColor=white&color=CB3837&label=telebibz)](https://www.npmjs.com/package/@xbibzlibrary/telebibz)
20
20
  [![downloads](https://img.shields.io/npm/dm/@xbibzlibrary/telebibz?style=for-the-badge&logo=npm&logoColor=white&color=green&label=unduh%2Fbulan)](https://www.npmjs.com/package/@xbibzlibrary/telebibz)
21
21
  [![node](https://img.shields.io/node/v/@xbibzlibrary/telebibz?style=for-the-badge&logo=node.js&logoColor=white&color=339933&label=node)](https://nodejs.org)
22
- [![tests](https://img.shields.io/badge/test-30%2F30%20lulus-brightgreen?style=for-the-badge&logo=checkmarx&logoColor=white)](#-test--bukti-live)
23
- [![size](https://img.shields.io/badge/kode-1.7k%20baris-orange?style=for-the-badge&logo=codeigniter&logoColor=white)](#-analitik--statistik)
22
+ [![tests](https://img.shields.io/badge/test-48%2F48%20lulus-brightgreen?style=for-the-badge&logo=checkmarx&logoColor=white)](#-test--bukti-live)
23
+ [![size](https://img.shields.io/badge/kode-2.1k%20baris-orange?style=for-the-badge&logo=codeigniter&logoColor=white)](#-analitik--statistik)
24
24
  [![license](https://img.shields.io/npm/l/@xbibzlibrary/telebibz?style=for-the-badge&color=blue)](LICENSE)
25
25
  [![views](https://komarev.com/ghpvc/?username=XbibzOfficial777&repo=telebibz&style=for-the-badge&color=blueviolet&label=kunjungan+repo)](https://github.com/XbibzOfficial777/telebibz)
26
26
 
@@ -38,7 +38,7 @@ produksi yang *benar-benar dipakai*, dokumentasi 🇮🇩 Indonesia-first, dan n
38
38
  |---|---|---|
39
39
  | ⚡ [Kenapa telebibz?](#kenapa) | 📊 [Matriks fitur vs grammY](#matriks) | 📥 [Instalasi & persyaratan](#instalasi) |
40
40
  | 🚀 [Mulai cepat](#mulai) | 🧠 [Cara kerja (arsitektur)](#arsitektur) | 📖 [Dokumentasi lengkap](#dokumentasi) |
41
- | 🎛️ [Handler & filter](#handler) | 💬 [Shortcut Context](#context) | 🔘 [Keyboard & tombol](#keyboard) |
41
+ | 🎛️ [Handler & filter](#handler) | 💬 [Shortcut Context](#context) | 🧱 [Rich Message & Bot API 10.3](#rich-messages) |
42
42
  | 🍽️ [Menu interaktif](#menu) | 🧙 [Wizard (form + tombol + edit/delete)](#wizard) | ❓ [Mode inline](#inline) |
43
43
  | 📣 [Broadcast](#broadcast) | 📎 [File & media](#file) | 🛡️ [Keandalan & rate limit](#ratelimit) |
44
44
  | 🗃️ [Session](#session) | 🇮🇩 [Error manusiawi](#error) | 🕸️ [Webhook & serverless](#webhook) |
@@ -67,6 +67,8 @@ produksi yang *benar-benar dipakai*, dokumentasi 🇮🇩 Indonesia-first, dan n
67
67
  | Fitur | grammY | telebibz |
68
68
  |---|:---:|:---:|
69
69
  | Proxy API **segala metode** (auto-generated) | ✅ | ✅ |
70
+ | **185 signature payload Bot API spesifik** (`callApi`) | ✅ | ✅ |
71
+ | Rich Message: block, entity, draft, media, emoji bergerak | beragam per API | ✅ bawaan |
70
72
  | ~60 shortcut bertipe (sendMessage, banChatMember…) | ✅ | ✅ |
71
73
  | Context lengkap (~70 pintasan reply/edit/admin/react) | ✅ | ✅ |
72
74
  | Context flavor business (`business_connection_id` otomatis) | plugin | ✅ bawaan |
@@ -90,7 +92,7 @@ produksi yang *benar-benar dipakai*, dokumentasi 🇮🇩 Indonesia-first, dan n
90
92
  | Humanisasi error + saran (🇮🇩) | ❌ | ✅ `humanize()` |
91
93
  | Banner boot + log debug | ❌ | ✅ (`DEBUG=telebibz*`) |
92
94
  | Proxy HTTP(S) untuk VPS | ⚠️ manual | ✅ opsi `proxy` transport |
93
- | TypeScript | ✅ full | d.ts longgar (JS-first) |
95
+ | TypeScript | ✅ full | ✅ tipe spesifik untuk 185 metode Bot API |
94
96
  | Bahasa dokumentasi | en | **🇬🇧 + 🇮🇩** |
95
97
 
96
98
  <a id="instalasi"></a>
@@ -268,63 +270,256 @@ Akun business: balasan dalam konteks business otomatis menyertakan
268
270
  `business_connection_id`.
269
271
 
270
272
  <a id="rich-messages"></a>
271
- ### 🧱 Rich Messages, draft, live photo & pesan ephemeral (Bot API 10.3)
273
+ ### 🧱 Rich Message, emoji bergerak, media & draft (Bot API 10.3)
272
274
 
273
- TeleBibz menyediakan builder rich-message dan Context helper; untuk seluruh
274
- **185 metode Bot API** tersedia juga `ctx.api.callApi(method, payload)` yang bertipe,
275
- sementara proxy menerima metode baru lewat object payload.
275
+ **Rich Message** adalah format Telegram terstruktur: satu pesan dapat memuat rich-text entities, heading, list, kutipan, tabel, peta, tombol, blok media, dan elemen lainnya. TeleBibz menyediakan builder dan shortcut Context; [referensi resmi Telegram Bot API](https://core.telegram.org/bots/api) tetap menjadi sumber otoritatif untuk batasan dan kelayakan fitur.
276
+
277
+ > **Catatan cakupan:** schema memuat **185 nama metode Bot API** dan payload bertipe melalui `api.callApi()`. Ini tidak berarti seluruh 185 endpoint aman atau bermakna untuk dites live: sebagian memerlukan update nyata, hak admin, pembayaran, atau chat tertentu. Matriks live test dan batasannya ada di [`VERIFIKASI-MENDALAM.md`](VERIFIKASI-MENDALAM.md).
278
+
279
+ #### 1. Pilih tepat satu mode konten
280
+
281
+ Satu rich message harus memakai tepat satu sumber konten: `html`, `markdown`, atau `blocks`. Opsi seperti `media`, `is_rtl`, dan `skip_entity_detection` bersifat tambahan, bukan mode konten.
282
+
283
+ ```js
284
+ const { TeleBibz, rich, RichMessageBuilder } = require('@xbibzlibrary/telebibz');
285
+ const bot = new TeleBibz(process.env.BOT_TOKEN);
286
+
287
+ bot.cmd('laporan', async (ctx) => {
288
+ const message = rich.blocks([
289
+ rich.heading('Laporan mingguan', 2),
290
+ rich.paragraph(['Pesanan: ', rich.bold('42'), ' · status ', rich.italic('siap')]),
291
+ rich.table([
292
+ [
293
+ { text: 'Metrik', is_header: true, align: 'left', valign: 'middle' },
294
+ { text: 'Nilai', is_header: true, align: 'right', valign: 'middle' },
295
+ ],
296
+ [
297
+ { text: 'Pendapatan', align: 'left', valign: 'middle' },
298
+ { text: 'Rp1.250.000', align: 'right', valign: 'middle' },
299
+ ],
300
+ ], { bordered: true, striped: true, compact: true, caption: 'Minggu ini' }),
301
+ rich.details('Informasi tambahan', [rich.paragraph('Bagian ini dapat dibuka.')]),
302
+ rich.buttons([
303
+ rich.button('Buka dashboard', { url: 'https://example.com' }, 'primary'),
304
+ rich.button('Konfirmasi', { callback_data: 'report:ack' }, 'success'),
305
+ ], 'center'),
306
+ ]);
307
+ return ctx.replyWithRichMessage(message);
308
+ });
309
+
310
+ bot.launch();
311
+ ```
312
+
313
+ Konstruktor tingkat rendah: `rich.html(html, options)`, `rich.markdown(markdown, options)`, dan `rich.blocks(blocks, options)`. `inputRichMessage(content, options)` memvalidasi pemilihan satu mode. Untuk merakit pesan bertahap, gunakan `RichMessageBuilder`:
314
+
315
+ ```js
316
+ const message = new RichMessageBuilder()
317
+ .blocks([rich.heading('Pengumuman', 2)])
318
+ .add(rich.paragraph('Blok berikutnya bisa ditambahkan.'))
319
+ .rtl(false)
320
+ .skipEntityDetection()
321
+ .build();
322
+ await ctx.replyWithRichMessage(message);
323
+ ```
324
+
325
+ Pada satu builder, pemanggilan `.html()`, `.markdown()`, atau `.blocks()` kedua akan error. Buat builder baru untuk mode lain. `.media()` digunakan bersama konten HTML/Markdown yang menunjuk media melalui tautan `tg://...`.
326
+
327
+ #### 2. Rich-text entities
328
+
329
+ Rich text dapat berupa string biasa, array campuran string dan objek entity, atau objek rich-text bertingkat. Helper yang tersedia:
330
+
331
+ | Helper | Entity | Kegunaan |
332
+ |---|---|---|
333
+ | `rich.bold(text)`, `rich.italic(text)`, `rich.underline(text)`, `rich.strikethrough(text)` | penekanan | format teks inline |
334
+ | `rich.spoiler(text)`, `rich.marked(text)`, `rich.code(text)` | spoiler / marked / code | teks tersembunyi atau teknis |
335
+ | `rich.subscript(text)`, `rich.superscript(text)` | posisi huruf | rumus dan referensi |
336
+ | `rich.dateTime(text, unixTime, format)` | tanggal/waktu | timestamp lokal atau relatif |
337
+ | `rich.url(text, url)`, `rich.email(text, email)`, `rich.phone(text, phone)` | tautan/kontak | tautan atau informasi kontak |
338
+ | `rich.mention(text, username)`, `rich.textMention(text, user)` | mention | username atau objek User |
339
+ | `rich.hashtag(text, value)`, `rich.cashtag(text, value)`, `rich.botCommand(text, value)` | entity Telegram | tag dan command |
340
+ | `rich.customEmoji(customEmojiId, alternativeText)` | custom emoji | custom emoji statis maupun bergerak |
341
+ | `rich.mathText(expression)`, `rich.anchorText(name)`, `rich.anchorLink(text, name)`, `rich.reference(text, name)`, `rich.referenceLink(text, name)` | rumus/navigasi | teks panjang terstruktur |
342
+ | `rich.buttonText(text, action, style)` | tombol di rich-text inline | entity tombol di dalam satu rangkaian teks |
343
+
344
+ Contoh custom emoji bergerak (gunakan **ID custom emoji yang nyata** dan dapat dipakai bot):
345
+
346
+ ```js
347
+ const emojiId = 'CUSTOM_EMOJI_ID';
348
+ const [sticker] = await ctx.api.callApi('getCustomEmojiStickers', {
349
+ custom_emoji_ids: [emojiId],
350
+ });
351
+ if (!sticker) throw new Error('Custom emoji ID tidak ditemukan');
352
+ await ctx.replyWithRichMessage(rich.blocks([
353
+ rich.paragraph(['Status build: ', rich.bold('lulus'), ' ', rich.customEmoji(emojiId, '👍')]),
354
+ ]));
355
+ ```
356
+
357
+ Emoji Unicode biasa tidak otomatis menjadi emoji custom bergerak. ID harus valid dan dapat digunakan bot; Telegram dapat menolak ID atau fitur yang tidak tersedia bagi bot tersebut.
358
+
359
+ #### 3. Katalog blok terstruktur
360
+
361
+ Setiap helper menghasilkan objek `InputRichBlock`. Blok dapat disusun bertingkat sesuai schema Telegram.
362
+
363
+ | Kelompok | Helper | Catatan |
364
+ |---|---|---|
365
+ | Teks | `paragraph(text)`, `heading(text, size)`, `pre(text, language)`, `footer(text)`, `divider()` | ukuran heading 1–6; `pre` dapat menyebut bahasa kode |
366
+ | Rumus/navigasi | `mathBlock(expression)`, `anchor(name)` | pasangan entity inline tersedia untuk isi teks |
367
+ | List/kutipan | `list(items)`, `quote(blocks, credit)`, `expandableQuote(text, credit)`, `pullQuote(text, credit)` | item list bisa string atau item terstruktur; kutipan dapat berisi blok lain |
368
+ | Tabel/disclosure | `table(cells, options)`, `details(summary, blocks, open)` | tiap sel memberi `align` dan `valign`; opsi tabel: `bordered`, `striped`, `compact`, `caption` |
369
+ | Lokasi | `map(location, zoom, width, height, caption, credit)` | `location` berbentuk `{ latitude, longitude }` |
370
+ | Media | `animation(media, caption)`, `audio(media, caption)`, `document(media, caption)`, `photo(media, caption)`, `video(media, caption)`, `voiceNote(media, caption)` | `media` adalah objek `InputMedia*`; upload `File` dikumpulkan menjadi multipart `attach://` |
371
+ | Tata letak | `collage(blocks, caption, credit)`, `slideshow(blocks, caption, credit)` | gabungkan blok media yang didukung menjadi galeri atau urutan |
372
+ | Tombol | `buttons(buttons, align)`, `button(text, action, style)` | 1–8 tombol; `align`: `left`, `center`, atau `right` |
373
+ | Khusus draft | `thinking(text)` | hanya untuk `sendRichMessageDraft`, bukan pesan rich yang disimpan |
374
+
375
+ Setiap sel tabel berbentuk `{ text, align: 'left'|'center'|'right', valign: 'top'|'middle'|'bottom' }`; sel header dapat memakai `is_header: true`. Satu tombol harus memilih tepat satu aksi seperti `url`, `callback_data`, `web_app`, `copy_text`, atau `disabled`. `rich.button()` menghasilkan objek tombol untuk block `buttons`; `rich.buttonText()` menghasilkan bentuk entity RichText yang berbeda. Style `link` hanya berlaku untuk tombol callback.
376
+
377
+ #### 4. Upload media di dalam rich message
378
+
379
+ Gunakan `file_id` Telegram untuk file yang sudah ada di server, atau bungkus bytes/path/stream dengan `File`/`InputFile` agar transport membuat multipart upload:
380
+
381
+ ```js
382
+ const fs = require('node:fs');
383
+ const { File, InputMediaBuilder } = require('@xbibzlibrary/telebibz');
384
+ const image = new File(fs.readFileSync('./hero.png'), 'hero.png');
385
+ const video = new File(fs.readFileSync('./clip.mp4'), 'clip.mp4');
386
+
387
+ await ctx.replyWithRichMessage(rich.blocks([
388
+ rich.paragraph('Contoh blok media:'),
389
+ rich.photo(InputMediaBuilder.photo(image)),
390
+ rich.video(InputMediaBuilder.video(video)),
391
+ ]));
392
+ ```
393
+
394
+ HTML/Markdown dapat merujuk media menggunakan ID unik dan entri `media` yang cocok:
395
+
396
+ ```js
397
+ const photo = new File(fs.readFileSync('./hero.png'), 'hero.png');
398
+ await ctx.replyWithRichMessage(rich.html(
399
+ '<b>Foto utama</b><br><a href="tg://photo?id=hero">Buka foto</a>',
400
+ { media: [{ id: 'hero', media: InputMediaBuilder.photo(photo) }] },
401
+ ));
402
+ ```
403
+
404
+ Live smoke test berhasil mengirim media multipart bertingkat, blok photo/audio/document/video/voice note, collage/slideshow, dan media reference HTML. Pada server Telegram yang diuji, MP4 diterima sebagai rich `animation`, sedangkan GIF pada blok itu menghasilkan `RICH_MESSAGE_VIDEO_INVALID`. Metode standalone `sendAnimation` menerima GIF yang sama. Jika blok rich animation ditolak, coba MP4. Live photo dikirim lewat metode terpisah, bukan tipe `InputRichBlock`.
405
+
406
+ #### 5. Draft dan preview streaming
407
+
408
+ Draft hanya preview sementara, bukan pesan chat yang tersimpan. Kirim pesan final secara terpisah untuk menyimpan jawaban. Blok `thinking` hanya untuk rich draft. Builder draft menolak upload baru melalui `File`; gunakan `file_id` Telegram jika draft perlu media.
276
409
 
277
410
  ```js
278
- const { rich, RichMessageBuilder } = require('@xbibzlibrary/telebibz');
279
-
280
- bot.cmd('laporan', (ctx) => ctx.replyWithRichMessage(rich.blocks([
281
- rich.heading('Laporan', 2),
282
- rich.paragraph(['Status: ', rich.bold('berhasil')]),
283
- rich.table([
284
- [{ text: 'Item', align: 'left', valign: 'middle', is_header: true },
285
- { text: 'Total', align: 'right', valign: 'middle', is_header: true }],
286
- [{ text: 'Pesanan', align: 'left', valign: 'middle' },
287
- { text: '3', align: 'right', valign: 'middle' }],
288
- ], { bordered: true, striped: true, compact: true }),
289
- rich.details('Catatan', [rich.paragraph('Rincian tambahan')]),
290
- rich.buttons([rich.button('Buka', { url: 'https://example.com' }, 'primary')]),
291
- ])));
292
-
293
- // HTML, Markdown, blok berisi media, list, kutipan, peta, tabel, collage,
294
- // slideshow, button, rich-text entities (bold/customEmoji/dateTime, dll.) juga didukung.
295
- const content = new RichMessageBuilder().markdown('**Hai!**').rtl().build();
296
- await ctx.replyWithRichMessage(content);
297
-
298
- // Live photo: kedua file dapat berupa file_id atau InputFile.
299
- await ctx.replyWithLivePhoto('video-file-id', 'photo-file-id', { caption: 'Momen' });
300
-
301
- // Draft adalah preview sementara; kirim Rich Message final agar tersimpan.
302
- await ctx.sendRichMessageDraft(1, rich.draftBlocks([
303
- rich.paragraph('Sedang menulis…'), rich.thinking('Memproses'),
304
- ]), { can_stop: true, keep_on_stop: true });
305
- await ctx.replyWithRichMessage(rich.markdown('**Jawaban final**'));
306
-
307
- // Ephemeral: pesan hanya terlihat oleh penerima tertentu.
308
- await ctx.replyEphemeral('Pesan privat sementara', ctx.from.id);
309
-
310
- // Akses lengkap dan bertipe untuk metode/parameter Bot API (termasuk semua metode 10.3).
411
+ bot.cmd('preview', async (ctx) => {
412
+ const draftId = Date.now(); // tidak nol dan unik untuk draft ini
413
+ await ctx.sendMessageDraft(draftId, 'Sedang menyiapkan jawaban…', { can_stop: true });
414
+ await ctx.sendRichMessageDraft(draftId + 1, rich.draftBlocks([
415
+ rich.paragraph(['Menyiapkan ', rich.bold('laporan Anda'), '…']),
416
+ rich.thinking('Mengumpulkan data'),
417
+ ]), { can_stop: true, keep_on_stop: true });
418
+ // Kirim jawaban final agar tersimpan:
419
+ return ctx.replyWithRichMessage(rich.markdown('**Laporan siap**'));
420
+ });
421
+ ```
422
+
423
+ Konstruktor khusus draft: `rich.draftHtml()`, `rich.draftMarkdown()`, `rich.draftBlocks()`, dan `new RichMessageBuilder()...buildDraft()`. Hasilnya mengikuti schema `sendRichMessageDraft` yang tidak menerima upload `File` baru. `sendMessageDraft` dan `sendRichMessageDraft` juga bisa dipanggil dengan payload object tepat melalui `ctx.api.callApi()`.
424
+
425
+ #### 6. Live photo, paid media, ephemeral, dan edit
426
+
427
+ **Live photo** mengirim pasangan video dan foto statis yang terkait. Keduanya bisa berupa `file_id` atau `File`/`InputFile`; URL tidak didukung oleh schema metode saat ini.
428
+
429
+ ```js
430
+ await ctx.replyWithLivePhoto('VIDEO_FILE_ID', 'PHOTO_FILE_ID', { caption: 'Sebuah momen' });
431
+ // Atau API mentah bertipe:
432
+ await ctx.api.sendLivePhoto({ chat_id: ctx.chatId, live_photo: videoFile, photo: photoFile });
433
+ ```
434
+
435
+ `InputMediaBuilder.livePhoto(video, photo)` dan `InputPaidMediaBuilder.livePhoto(video, photo)` membangun objek media. `sendPaidMedia` membutuhkan `star_count` antara 1 dan 25.000:
436
+
437
+ ```js
438
+ const fs = require('node:fs');
439
+ const { File, InputPaidMediaBuilder } = require('@xbibzlibrary/telebibz');
440
+ const paidPhoto = new File(fs.readFileSync('./paid.png'), 'paid.png');
441
+ await ctx.api.callApi('sendPaidMedia', {
442
+ chat_id: ctx.chatId,
443
+ star_count: 1,
444
+ media: [InputPaidMediaBuilder.photo(paidPhoto)],
445
+ caption: 'Contoh foto berbayar',
446
+ });
447
+ ```
448
+
449
+ Fitur ini membuat paywall/pembayaran sungguhan: jangan dites ke pengguna tanpa persetujuan dan periksa penggunaan Telegram Stars sebelum memublikasikan konten berbayar.
450
+
451
+ **Pesan ephemeral** ditujukan ke penerima melalui `ephemeral_message_parameters`; kelayakannya bergantung pada aturan dan izin Telegram untuk bot/chat terkait. Helper Context meliputi `replyEphemeral(text, receiverUserId)`, `editEphemeralMessageText`, `editEphemeralRichMessage`, `editEphemeralMessageMedia`, `editEphemeralMessageCaption`, `editEphemeralMessageReplyMarkup`, dan `deleteEphemeralMessage`.
452
+
453
+ ```js
454
+ await ctx.replyEphemeral('Pemberitahuan sementara', ctx.from.id);
455
+ const sent = await ctx.api.callApi('sendRichMessage', {
456
+ chat_id: ctx.chatId,
457
+ rich_message: rich.blocks([rich.paragraph('Preview rich privat')]),
458
+ ephemeral_message_parameters: { receiver_user_id: ctx.from.id },
459
+ });
460
+ // Jika Telegram mengembalikan ephemeral_message_id, ID tersebut dapat dipakai untuk edit/hapus.
461
+ if (sent.ephemeral_message_id) {
462
+ await ctx.editEphemeralRichMessage(ctx.from.id, sent.ephemeral_message_id,
463
+ rich.blocks([rich.paragraph('Pemberitahuan sementara diperbarui')]));
464
+ }
465
+ ```
466
+
467
+ Telegram bisa menolak dengan `BOT_NOT_ADMIN` atau error izin lain; baca `ApiError` yang diterima dan jangan menganggap payload lolos test lokal berarti bot pasti berhak mengirimnya.
468
+
469
+ Untuk mengedit rich message biasa, gunakan `ctx.editRichMessage(content, extra)`. API di bawahnya adalah `editMessageText` dengan `rich_message` dan target `chat_id`/`message_id` (atau `inline_message_id`). Operasi edit mengubah pesan yang ada; gunakan pesan khusus test.
470
+
471
+ #### 7. Shortcut Context dan metode Bot API modern
472
+
473
+ | Helper | Fungsi / argumen |
474
+ |---|---|
475
+ | `ctx.replyWithRichMessage(content, extra?)` | kirim rich message tersimpan ke chat saat ini |
476
+ | `ctx.editRichMessage(content, extra?)` | edit rich message saat ini; mendukung callback dan inline target |
477
+ | `ctx.replyWithLivePhoto(video, photo, extra?)` | kirim live photo ke chat saat ini |
478
+ | `ctx.sendMessageDraft(draftId, text, extra?)` | preview teks sementara |
479
+ | `ctx.sendRichMessageDraft(draftId, richMessage, extra?)` | preview rich sementara |
480
+ | `ctx.replyEphemeral(text, receiverUserId, extra?)` | kirim teks ephemeral ke penerima tertentu |
481
+ | `ctx.guestQueryId`, `ctx.answerGuestQuery(result)` | proses update `guest_message` yang nyata |
482
+ | `ctx.editEphemeralRichMessage(...)`, `ctx.deleteEphemeralMessage(...)` | edit/hapus pesan ephemeral milik penerima |
483
+
484
+ Jawaban guest query membutuhkan `guest_query_id` dari update masuk yang asli; ID buatan tidak dapat dipakai untuk tes live yang bermakna. Artikel inline rich dapat dibuat dengan `iq.richArticle(id, title, richMessage, extra)` lalu dikembalikan saat menangani inline query sungguhan.
485
+
486
+ #### 8. Akses penuh Bot API dengan tipe
487
+
488
+ Semua **185 nama metode dan signature payload** pada schema Bot API vendored tersedia lewat `api.callApi()` dan `api.raw()` di TypeScript:
489
+
490
+ ```js
491
+ // JavaScript maupun TypeScript: nama metode + object payload
311
492
  await ctx.api.callApi('sendRichMessage', {
312
493
  chat_id: ctx.chatId,
313
- rich_message: rich.html('<b>Rich HTML</b>'),
494
+ rich_message: rich.markdown('**Halo**'),
495
+ });
496
+
497
+ await ctx.api.callApi('sendMessageDraft', {
498
+ chat_id: ctx.chatId,
499
+ draft_id: Date.now(),
500
+ text: 'Preview',
501
+ can_stop: false,
314
502
  });
315
503
  ```
316
504
 
317
- `RichMessageBuilder` menyediakan `.html()`, `.markdown()`, `.blocks()`, `.add()`,
318
- `.media()`, `.rtl()`, `.skipEntityDetection()`, `.build()`, dan `.buildDraft()`.
319
- `rich.draftHtml()`, `rich.draftMarkdown()`, dan `rich.draftBlocks()` menghasilkan
320
- konten draft-safe serta menolak upload `File` (draft dapat memakai file_id Telegram yang ada).
321
- Rich block builder mencakup
322
- paragraph/heading/pre/list/table/details/quote/map/media/buttons/thinking; block
323
- `thinking` hanya untuk `sendRichMessageDraft`. `InputMediaBuilder.livePhoto()` dan
324
- `InputPaidMediaBuilder.livePhoto()` membentuk payload media live photo. API raw modern lain tersedia lewat
325
- `ctx.api.sendMessageDraft()`, `ctx.api.answerGuestQuery()`, `ctx.api.editEphemeralMessage*()`
326
- dan `ctx.api.deleteEphemeralMessage()`. Tipe resmi Telegram dapat diakses sebagai
327
- `TelegramTypes`; rich input types diekspor langsung.
505
+ Namespace `TelegramTypes` mengekspor tipe object Telegram. Alias yang berguna: `TelegramMethodName`, `TelegramMethodPayload<M>`, `TelegramMethodResult<M>`, `TelegramApiMethods`, dan `TelegramApiPayloads`. Proxy runtime juga menerima `ctx.api.metodeApaPun({ ...payload })` untuk metode baru, tetapi shorthand dinamis ini tidak memvalidasi schema saat runtime. Pengecekan TypeScript terjadi ketika compile saja. Jalankan `npm run typecheck` untuk memeriksa deklarasi dan contoh consumer.
506
+
507
+ Deklarasi ini vendored dari `@grammyjs/types@5.0.0` berlisensi MIT (lihat [`NOTICE.md`](NOTICE.md) dan lisensi di folder types); tidak ada dependency runtime baru. Referensi/changelog Telegram resmi—bukan package vendored—tetap otoritas saat ada perbedaan field, izin, atau batasan.
508
+
509
+ #### Batas payload dan pemecahan masalah
510
+
511
+ Schema tipe Bot API 10.3 mencatat batas Rich Message berikut: **32.768 karakter UTF-8**, **500 block** (termasuk konten nested/list/tabel/kutipan/details), **16 level nesting**, **50 attachment media**, dan **20 kolom tabel**. Block map memakai zoom 0–24 dan lebar/tinggi 0–10.000; block tombol berisi 1–8 tombol. Batas Telegram dapat berubah, jadi cek [referensi resmi](https://core.telegram.org/bots/api) sebelum membuat payload besar. Server Telegram tetap validator terakhir.
512
+
513
+ Error yang umum:
514
+
515
+ - `RICH_MESSAGE_VIDEO_INVALID`: server yang diuji menolak GIF pada block Rich Message `animation`; coba MP4. Endpoint standalone `sendAnimation` menerima GIF tersebut.
516
+ - `BOT_NOT_ADMIN` pada pesan ephemeral: respons ini terkait izin/kelayakan Telegram, bukan bukti bahwa bentuk JSON salah. Periksa akses bot/chat dan syarat metode resmi.
517
+ - `chat not found`: pada private chat, penerima biasanya perlu membuka bot dan menekan **Start** sebelum bot dapat mengirim pesan.
518
+ - Error upload: bungkus bytes/path/stream dengan `File`/`InputFile` dan pastikan tipe helper cocok dengan file.
519
+
520
+ #### Hasil live test dan batasan
521
+
522
+ Pada 2026-09-28, smoke test live dengan bot yang diberikan pengguna berhasil untuk rich blocks, HTML, Markdown, custom emoji bergerak, draft teks/rich, multipart media, blok photo/audio/document/video/voice-note, collage/slideshow, live photo, GIF melalui `sendAnimation`, dan HTML media reference. Ephemeral `sendMessage`/`sendRichMessage` ditolak Telegram dengan `BOT_NOT_ADMIN`; paid media tidak dikirim karena dapat melibatkan Stars; callback tidak diklik; seluruh 185 endpoint tidak dijalankan live. Rincian hasil dan batas cakupan ada di [`VERIFIKASI-MENDALAM.md`](VERIFIKASI-MENDALAM.md).
328
523
 
329
524
  <a id="keyboard"></a>
330
525
  ### 🔘 Keyboard & tombol
@@ -572,12 +767,12 @@ await bot.api.sendDiceCustom({ chat_id: 1, emoji: '🎲' });
572
767
 
573
768
  | Metrik | Nilai |
574
769
  |---|---|
575
- | 📦 Modul sumber | **16 file** di `lib/` |
576
- | 📝 Total baris kode | **±1.700** (tanpa build step) |
577
- | 🔌 Metode Bot API | **90+** — 75 shortcut bertipe + Proxy tanpa batas |
770
+ | 📦 Modul sumber | **18 file** di `lib/` |
771
+ | 📝 Total baris kode | **2.105** di `lib/` (tanpa build step) |
772
+ | 🔌 Metode Bot API | **185 signature bertipe** lewat `api.callApi()` + dynamic Proxy |
578
773
  | ⌨️ Shortcut Context | **50+** (reply/edit/delete/admin/react…) |
579
- | 🧪 Test offline | **47/47 lulus**, termasuk transport HTTP lokal dan fitur Bot API 10.3 |
580
- | 🧩 Contoh siap jalan | **7** di `examples/` |
774
+ | 🧪 Test offline | **48/48 lulus**, termasuk transport HTTP lokal dan fitur Bot API 10.3 |
775
+ | 🧩 Contoh siap jalan | **8** di `examples/` |
581
776
  | 📦 Dependency runtime | **4** — semuanya terpakai & ter-test |
582
777
 
583
778
  ### ⬇️ Download & popularitas (live dari npm)
@@ -589,23 +784,27 @@ await bot.api.sendDiceCustom({ chat_id: 1, emoji: '🎲' });
589
784
 
590
785
  ### 📏 Peta ukuran modul (baris kode)
591
786
 
592
- ```
593
- wizard.js █████████████████████████ 247 ← form + tombol + edit/delete
594
- telebibz.js ███████████████████▎ 193 ← kelas utama & siklus hidup
595
- context.js ███████████████████ 190 ← ctx + 50-an shortcut
596
- composer.js █████████████████▍ 174 ← mesin middleware & filter
597
- api.js ███████████████▍ 154 ← 75 shortcut + Proxy + transformer
598
- net.js ███████████▌ 115 ← transport axios + multipart
599
- menus.js █████████ 90 ← Menu/MenuContainer
600
- keyboard.js ████████▎ 83 ← btn/url/kb + kelas fluent
601
- ratelimit.js ██████ 61 ← autoRetry · throttler · limiter
602
- runner.js ████▌ 45 ← polling tahan-409
603
- file.js ████ 41 ← InputFile + InputMediaBuilder
604
- logger.js ███▊ 38 ← log + banner
605
- session.js ███▌ 36 ← session swappable
606
- errors.js ███▌ 35 ← humanize error 🇮
607
- broadcast.js ███▌ 35 ← blast aman rate-limit
608
- inline-query.js██▊ 28 ← matcher + builder hasil
787
+ Angka berikut dihitung dari source terkini `lib/*.js`; ini informasi source, bukan ukuran bundle.
788
+
789
+ ```text
790
+ context.js 250 accessor Context, reply, edit dan helper
791
+ wizard.js 247 form terpandu, tombol, mode edit/delete
792
+ telebibz.js 209 kelas bot, lifecycle dan dispatch update
793
+ telegram-methods.js 192 registry runtime 185 nama metode Bot API
794
+ composer.js 185 komposisi middleware dan filter
795
+ rich.js 175 builder Rich Message, entity dan block
796
+ api.js 168 adapter API, Proxy dan transformer
797
+ net.js 115 transport HTTP dan multipart upload
798
+ menus.js 90 Menu dan MenuContainer
799
+ keyboard.js 83 builder keyboard dan fluent class
800
+ ratelimit.js 74 retry, throttler dan limiter
801
+ logger.js 68 log dan banner boot
802
+ runner.js 54 polling dan retry
803
+ file.js 48 File/InputFile dan media builder
804
+ session.js 44 session storage swappable
805
+ errors.js 35 terjemahan error
806
+ broadcast.js 35 helper broadcast
807
+ inline-query.js 33 pencocokan inline dan builder hasil
609
808
  ```
610
809
 
611
810
  ### 🗺️ Kesehatan repo
@@ -643,24 +842,23 @@ Jalankan dengan `BOT_TOKEN=123:abc node examples/01-quickstart.js`.
643
842
  ## 🔬 Test & Bukti Live
644
843
 
645
844
  ```bash
646
- npm test # 47 test offline (mock + HTTP server lokal), tanpa token Telegram
845
+ npm test # 48 test offline (mock + HTTP server lokal), tanpa token Telegram
647
846
  npm run typecheck # cek declaration TypeScript + payload method-specific
648
847
  ```
649
848
 
650
- Audit saat ini tidak membutuhkan token Telegram. Versi repo sebelumnya mencatat
651
- **30 test offline + 10 test live** pada `@xbibzrat_bot`; pengujian live tersebut
652
- bersifat historis dan tidak diulang pada audit ini. Lihat [`VERIFIKASI-MENDALAM.md`](VERIFIKASI-MENDALAM.md)
653
- untuk matriks test, perbandingan Bot API 10.3, dan batasan yang tersisa.
849
+ 48 test otomatis dan `npm run typecheck` berjalan offline dengan mock tanpa token bot. Secara terpisah dilakukan live smoke test pada bot uji milik pengguna: rich blocks, HTML/Markdown, custom emoji bergerak, draft, blok media, collage/slideshow, live photo, dan referensi media multipart berhasil. Pengiriman ephemeral ditolak Telegram dengan `BOT_NOT_ADMIN`; paid media dan semua 185 endpoint tidak diuji live. Lihat [`VERIFIKASI-MENDALAM.md`](VERIFIKASI-MENDALAM.md) untuk matriks lengkap, batasan, serta hasil yang gagal.
654
850
 
655
851
  Log debug: `DEBUG=telebibz:net,telebibz:ratelimit node botkamu.js`.
656
852
 
657
853
  <a id="struktur"></a>
658
- ## 📂 Struktur Repo (16 file inti)
854
+ ## 📂 Struktur Repo (18 modul JavaScript + tipe API vendored)
659
855
 
660
856
  | File | Peran |
661
857
  |---|---|
662
858
  | `lib/net.js` | transport axios keep-alive + multipart `attach://` |
663
859
  | `lib/api.js` | metode Bot API + Proxy segala metode + transformer |
860
+ | `lib/rich.js` | entity Rich Message, builder block, helper draft-safe |
861
+ | `lib/telegram-methods.js` | registry 185 nama metode Bot API |
664
862
  | `lib/composer.js` | middleware, filter `on('message:photo')`, `errorBoundary` |
665
863
  | `lib/context.js` | objek ctx + 50-an pintasan reply/edit/delete/callback |
666
864
  | `lib/session.js` | sesi per user:chat (storage swappable) |
@@ -674,11 +872,13 @@ Log debug: `DEBUG=telebibz:net,telebibz:ratelimit node botkamu.js`.
674
872
  | `lib/inline-query.js` | matcher query + builder hasil inline |
675
873
  | `lib/errors.js` | humanisasi error + saran |
676
874
  | `lib/logger.js` | log berbingkai + banner boot |
677
- | `index.js` / `index.d.ts` | pintu ekspor + tipe TypeScript |
875
+ | `types/telegram-bot-api/` | tipe Bot API vendored (MIT), tanpa kode runtime |
876
+ | `index.js` / `index.d.ts` | ekspor dan surface method/payload bertipe |
678
877
 
679
878
  <a id="changelog"></a>
680
879
  ## 🕐 Changelog
681
880
 
881
+ - **Unreleased — Bot API 10.3** — rich message, emoji bergerak, draft, live photo, payload bertipe untuk 185 metode; 48 test offline dan live verification lebih luas. Detail: [`CHANGELOG.md`](CHANGELOG.md).
682
882
  - **3.1.0** — wizard: tombol pilihan (reply/inline), mode `edit`/`delete`, cleanup otomatis, helper programatis · test 24 → 30
683
883
  - **3.0.0** — parity grammY production-grade: axios keep-alive, transformer, menu, inline query, limiter
684
884
  - **2.0.0** — engine ditulis ulang dari nol, transport multipart, webhook Node murni
package/README.md CHANGED
@@ -19,8 +19,8 @@ dependencies that are *actually used*, and an Indonesia-first community.
19
19
  [![npm version](https://img.shields.io/npm/v/@xbibzlibrary/telebibz?style=for-the-badge&logo=npm&logoColor=white&color=CB3837&label=telebibz)](https://www.npmjs.com/package/@xbibzlibrary/telebibz)
20
20
  [![downloads](https://img.shields.io/npm/dm/@xbibzlibrary/telebibz?style=for-the-badge&logo=npm&logoColor=white&color=green&label=downloads%2Fmonth)](https://www.npmjs.com/package/@xbibzlibrary/telebibz)
21
21
  [![node](https://img.shields.io/node/v/@xbibzlibrary/telebibz?style=for-the-badge&logo=node.js&logoColor=white&color=339933&label=node)](https://nodejs.org)
22
- [![tests](https://img.shields.io/badge/tests-30%2F30%20passing-brightgreen?style=for-the-badge&logo=checkmarx&logoColor=white)](#-testing--live-proof)
23
- [![size](https://img.shields.io/badge/code-1.7k%20lines-orange?style=for-the-badge&logo=codeigniter&logoColor=white)](#-analytics--statistics)
22
+ [![tests](https://img.shields.io/badge/tests-48%2F48%20passing-brightgreen?style=for-the-badge&logo=checkmarx&logoColor=white)](#-testing--live-proof)
23
+ [![size](https://img.shields.io/badge/code-2.1k%20lines-orange?style=for-the-badge&logo=codeigniter&logoColor=white)](#-analytics--statistics)
24
24
  [![license](https://img.shields.io/npm/l/@xbibzlibrary/telebibz?style=for-the-badge&color=blue)](LICENSE)
25
25
  [![views](https://komarev.com/ghpvc/?username=XbibzOfficial777&repo=telebibz&style=for-the-badge&color=blueviolet&label=repo+views)](https://github.com/XbibzOfficial777/telebibz)
26
26
 
@@ -38,7 +38,7 @@ dependencies that are *actually used*, and an Indonesia-first community.
38
38
  |---|---|---|
39
39
  | ⚡ [Why telebibz?](#why) | 📊 [Feature matrix vs grammY](#matrix) | 📥 [Installation & requirements](#install) |
40
40
  | 🚀 [Quick start](#quickstart) | 🧠 [How it works (architecture)](#architecture) | 📖 [Full documentation](#docs) |
41
- | 🎛️ [Handlers & filters](#handlers) | 💬 [Context shortcuts](#context) | 🔘 [Keyboards & buttons](#keyboards) |
41
+ | 🎛️ [Handlers & filters](#handlers) | 💬 [Context shortcuts](#context) | 🧱 [Rich Messages & Bot API 10.3](#rich-messages) |
42
42
  | ️ [Interactive menus](#menus) | 🧙 [Wizard (forms + buttons + edit/delete)](#wizard) | ❓ [Inline mode](#inline) |
43
43
  | 📣 [Broadcast](#broadcast) | 📎 [Files & media](#files) | 🛡️ [Reliability & rate limiting](#ratelimit) |
44
44
  | 🗃️ [Sessions](#sessions) | 🇮🇩 [Human-readable errors](#errors) | 🕸️ [Webhooks & serverless](#webhook) |
@@ -66,6 +66,8 @@ dependencies that are *actually used*, and an Indonesia-first community.
66
66
  | Feature | grammY | telebibz |
67
67
  |---|:---:|:---:|
68
68
  | Proxy API for **any method** (auto-generated) | ✅ | ✅ |
69
+ | **185 method-specific Bot API payload signatures** (`callApi`) | ✅ | ✅ |
70
+ | Rich Messages: blocks, entities, drafts, media, animated emoji | varies by API | ✅ built-in |
69
71
  | ~60 typed shortcuts (sendMessage, banChatMember…) | ✅ | ✅ |
70
72
  | Full Context (~70 shortcuts reply/edit/admin/react) | ✅ | ✅ |
71
73
  | Business flavor (`business_connection_id` automatic) | plugin | ✅ built-in |
@@ -89,7 +91,7 @@ dependencies that are *actually used*, and an Indonesia-first community.
89
91
  | Humanized errors + suggestions | ❌ | ✅ `humanize()` |
90
92
  | Boot banner + debug logging | ❌ | ✅ (`DEBUG=telebibz*`) |
91
93
  | HTTP(S) proxy for VPS | ⚠️ manual | ✅ `proxy` transport option |
92
- | TypeScript | ✅ full | loose d.ts (JS-first) |
94
+ | TypeScript | ✅ full | ✅ method-specific types for 185 Bot API methods |
93
95
  | Documentation language | en | **🇬🇧 + 🇮🇩** |
94
96
 
95
97
  <a id="install"></a>
@@ -267,62 +269,256 @@ Business accounts: replies inside a business context automatically carry
267
269
  `business_connection_id`.
268
270
 
269
271
  <a id="rich-messages"></a>
270
- ### 🧱 Rich Messages, drafts, live photos & ephemeral messages (Bot API 10.3)
272
+ ### 🧱 Rich Messages, animated emoji, media & drafts (Bot API 10.3)
271
273
 
272
- TeleBibz includes rich-message builders and Context helpers. All **185 Bot API
273
- methods** are also available through typed `ctx.api.callApi(method, payload)`;
274
- the proxy accepts newer methods using an object payload.
274
+ A **Rich Message** is Telegram's structured message format: one message can combine rich-text entities, headings, lists, quotations, tables, maps, buttons, media blocks, and more. TeleBibz provides builders and Context shortcuts; the official [Telegram Bot API reference](https://core.telegram.org/bots/api) remains authoritative for current limits and eligibility.
275
+
276
+ > **Coverage note:** the package includes a type-level registry for **185 Bot API methods** and typed payloads through `api.callApi()`. This does not mean all 185 endpoints can be safely or meaningfully tested live: many require real updates, admin rights, payments, or specific chats. See [`VERIFIKASI-MENDALAM.md`](VERIFIKASI-MENDALAM.md) for the live-test matrix and known limitations.
277
+
278
+ #### 1. Choose exactly one content mode
279
+
280
+ A rich message has exactly one content source: `html`, `markdown`, or `blocks`. Optional settings such as `media`, `is_rtl`, and `skip_entity_detection` do not count as content modes.
281
+
282
+ ```js
283
+ const { TeleBibz, rich, RichMessageBuilder } = require('@xbibzlibrary/telebibz');
284
+ const bot = new TeleBibz(process.env.BOT_TOKEN);
285
+
286
+ bot.cmd('rich', async (ctx) => {
287
+ const message = rich.blocks([
288
+ rich.heading('Weekly report', 2),
289
+ rich.paragraph(['Orders: ', rich.bold('42'), ' · status ', rich.italic('ready')]),
290
+ rich.table([
291
+ [
292
+ { text: 'Metric', is_header: true, align: 'left', valign: 'middle' },
293
+ { text: 'Value', is_header: true, align: 'right', valign: 'middle' },
294
+ ],
295
+ [
296
+ { text: 'Revenue', align: 'left', valign: 'middle' },
297
+ { text: '$1,250', align: 'right', valign: 'middle' },
298
+ ],
299
+ ], { bordered: true, striped: true, compact: true, caption: 'This week' }),
300
+ rich.details('More information', [rich.paragraph('This section can be expanded.')]),
301
+ rich.buttons([
302
+ rich.button('Open dashboard', { url: 'https://example.com' }, 'primary'),
303
+ rich.button('Acknowledge', { callback_data: 'report:ack' }, 'success'),
304
+ ], 'center'),
305
+ ]);
306
+ return ctx.replyWithRichMessage(message);
307
+ });
308
+
309
+ bot.launch();
310
+ ```
311
+
312
+ The lower-level constructors are `rich.html(html, options)`, `rich.markdown(markdown, options)`, and `rich.blocks(blocks, options)`. `inputRichMessage(content, options)` validates that one content mode is selected. `RichMessageBuilder` is useful when assembling a message incrementally:
313
+
314
+ ```js
315
+ const message = new RichMessageBuilder()
316
+ .blocks([rich.heading('Notice', 2)])
317
+ .add(rich.paragraph('More blocks can be appended.'))
318
+ .rtl(false)
319
+ .skipEntityDetection()
320
+ .build();
321
+ await ctx.replyWithRichMessage(message);
322
+ ```
323
+
324
+ Calling `.html()`, `.markdown()`, or `.blocks()` more than once on the same builder is an error: create a new builder when you want a different mode. `.media()` is intended for HTML/Markdown content that references media by `tg://...` links.
325
+
326
+ #### 2. Rich-text entities
327
+
328
+ Rich text can be a plain string, an array mixing strings and entity objects, or a nested rich-text object. Common entity helpers include:
329
+
330
+ | Helper | Entity produced | Typical use |
331
+ |---|---|---|
332
+ | `rich.bold(text)`, `rich.italic(text)`, `rich.underline(text)`, `rich.strikethrough(text)` | emphasis | inline formatting |
333
+ | `rich.spoiler(text)`, `rich.marked(text)`, `rich.code(text)` | spoiler / marked / code | hidden or technical text |
334
+ | `rich.subscript(text)`, `rich.superscript(text)` | script position | formulas and references |
335
+ | `rich.dateTime(text, unixTime, format)` | date/time | localized or relative timestamp |
336
+ | `rich.url(text, url)`, `rich.email(text, email)`, `rich.phone(text, phone)` | explicit links/contact | clickable or recognized values |
337
+ | `rich.mention(text, username)`, `rich.textMention(text, user)` | user mention | username or user object |
338
+ | `rich.hashtag(text, value)`, `rich.cashtag(text, value)`, `rich.botCommand(text, value)` | Telegram entities | searchable tags/commands |
339
+ | `rich.customEmoji(customEmojiId, alternativeText)` | custom emoji | standard or animated custom emoji |
340
+ | `rich.mathText(expression)`, `rich.anchorText(name)`, `rich.anchorLink(text, name)`, `rich.reference(text, name)`, `rich.referenceLink(text, name)` | formula/navigation | structured long-form text |
341
+ | `rich.buttonText(text, action, style)` | inline rich-text button entity | a button inside a rich-text run |
342
+
343
+ Example with an animated custom emoji (use a **real** custom emoji ID that Telegram returns for your bot):
344
+
345
+ ```js
346
+ const emojiId = 'CUSTOM_EMOJI_ID';
347
+ const [sticker] = await ctx.api.callApi('getCustomEmojiStickers', {
348
+ custom_emoji_ids: [emojiId],
349
+ });
350
+ if (!sticker) throw new Error('Unknown custom emoji ID');
351
+ await ctx.replyWithRichMessage(rich.blocks([
352
+ rich.paragraph(['Build status: ', rich.bold('passed'), ' ', rich.customEmoji(emojiId, '👍')]),
353
+ ]));
354
+ ```
355
+
356
+ A regular Unicode emoji is not automatically an animated custom emoji. The custom ID must be valid and usable by the bot; Telegram may reject unavailable IDs or features the bot is not eligible to use.
357
+
358
+ #### 3. Structured block catalog
359
+
360
+ Each block helper returns an `InputRichBlock` object. Blocks can be nested where the schema permits it.
361
+
362
+ | Block family | Helpers | Notes |
363
+ |---|---|---|
364
+ | Text | `paragraph(text)`, `heading(text, size)`, `pre(text, language)`, `footer(text)`, `divider()` | heading size is 1–6; preformatted blocks can name a language |
365
+ | Math/navigation | `mathBlock(expression)`, `anchor(name)` | use the corresponding rich-text formula/reference helpers for inline content |
366
+ | Lists/quotes | `list(items)`, `quote(blocks, credit)`, `expandableQuote(text, credit)`, `pullQuote(text, credit)` | a list item may be a string or a structured item; quotations can contain nested blocks |
367
+ | Tables/disclosure | `table(cells, options)`, `details(summary, blocks, open)` | each cell supplies `align` and `valign`; options: `bordered`, `striped`, `compact`, `caption` |
368
+ | Location | `map(location, zoom, width, height, caption, credit)` | `location` is `{ latitude, longitude }` |
369
+ | Media | `animation(media, caption)`, `audio(media, caption)`, `document(media, caption)`, `photo(media, caption)`, `video(media, caption)`, `voiceNote(media, caption)` | `media` is an `InputMedia*` object; `File` uploads are collected recursively into multipart `attach://` fields |
370
+ | Layout | `collage(blocks, caption, credit)`, `slideshow(blocks, caption, credit)` | compose allowed media blocks into a gallery or sequence |
371
+ | Buttons | `buttons(buttons, align)`, `button(text, action, style)` | 1–8 buttons; alignment is `left`, `center`, or `right` |
372
+ | Draft-only | `thinking(text)` | use only in `sendRichMessageDraft`, not a persisted rich message |
373
+
374
+ For `table`, each cell should look like `{ text, align: 'left'|'center'|'right', valign: 'top'|'middle'|'bottom' }`; header cells may set `is_header: true`. A button action must provide exactly one supported action field, such as `url`, `callback_data`, `web_app`, `copy_text`, or `disabled`. `rich.button()` returns the button object for a buttons block. `rich.buttonText()` returns the distinct inline entity shape. Link style is only valid for callback buttons.
375
+
376
+ #### 4. Uploading media inside rich messages
377
+
378
+ Use Telegram `file_id`s for files already on Telegram, or wrap bytes/path/stream in `File` (or `InputFile`) for multipart upload:
379
+
380
+ ```js
381
+ const fs = require('node:fs');
382
+ const { File, InputMediaBuilder } = require('@xbibzlibrary/telebibz');
383
+ const image = new File(fs.readFileSync('./hero.png'), 'hero.png');
384
+ const video = new File(fs.readFileSync('./clip.mp4'), 'clip.mp4');
385
+
386
+ await ctx.replyWithRichMessage(rich.blocks([
387
+ rich.paragraph('Uploaded media blocks:'),
388
+ rich.photo(InputMediaBuilder.photo(image)),
389
+ rich.video(InputMediaBuilder.video(video)),
390
+ ]));
391
+ ```
392
+
393
+ HTML/Markdown can refer to media by a unique ID and a matching `media` entry:
394
+
395
+ ```js
396
+ const photo = new File(fs.readFileSync('./hero.png'), 'hero.png');
397
+ await ctx.replyWithRichMessage(rich.html(
398
+ '<b>Hero image</b><br><a href="tg://photo?id=hero">Open photo</a>',
399
+ { media: [{ id: 'hero', media: InputMediaBuilder.photo(photo) }] },
400
+ ));
401
+ ```
402
+
403
+ A successful live smoke test confirmed nested multipart media, photo/audio/document/video/voice-note blocks, collage/slideshow, and a rich HTML media reference. On the tested Bot API server, an MP4 worked in a rich `animation` block; a GIF in that block returned `RICH_MESSAGE_VIDEO_INVALID`. The standalone `sendAnimation` method did accept the same GIF. If a rich animation block is rejected, try MP4. Live-photo video/photo pairing is a separate API operation, not an `InputRichBlock` type.
404
+
405
+ #### 5. Drafts and streaming previews
406
+
407
+ Drafts are temporary previews, not stored chat messages. Send a final message separately to persist the completed answer. `thinking` blocks are for rich drafts only. Draft helpers reject `File` uploads; use existing Telegram file IDs if a draft needs media.
275
408
 
276
409
  ```js
277
- const { rich, RichMessageBuilder } = require('@xbibzlibrary/telebibz');
278
-
279
- bot.cmd('report', (ctx) => ctx.replyWithRichMessage(rich.blocks([
280
- rich.heading('Report', 2),
281
- rich.paragraph(['Status: ', rich.bold('success')]),
282
- rich.table([
283
- [{ text: 'Item', align: 'left', valign: 'middle', is_header: true },
284
- { text: 'Total', align: 'right', valign: 'middle', is_header: true }],
285
- [{ text: 'Orders', align: 'left', valign: 'middle' },
286
- { text: '3', align: 'right', valign: 'middle' }],
287
- ], { bordered: true, striped: true, compact: true }),
288
- rich.details('Notes', [rich.paragraph('Additional details')]),
289
- rich.buttons([rich.button('Open', { url: 'https://example.com' }, 'primary')]),
290
- ])));
291
-
292
- // HTML, Markdown, media blocks, lists, quotes, maps, tables, collages,
293
- // slideshows, buttons and rich-text entities (bold/customEmoji/dateTime, etc.) are supported.
294
- const content = new RichMessageBuilder().markdown('**Hello!**').rtl().build();
295
- await ctx.replyWithRichMessage(content);
296
-
297
- // Live photo: both inputs may be Telegram file_ids or InputFile instances.
298
- await ctx.replyWithLivePhoto('video-file-id', 'photo-file-id', { caption: 'Moment' });
299
-
300
- // A draft is a temporary preview. Send the final rich message to persist it.
301
- await ctx.sendRichMessageDraft(1, rich.draftBlocks([
302
- rich.paragraph('Writing…'), rich.thinking('Working'),
303
- ]), { can_stop: true, keep_on_stop: true });
304
- await ctx.replyWithRichMessage(rich.markdown('**Final answer**'));
305
-
306
- // Ephemeral: visible only to a specific recipient.
307
- await ctx.replyEphemeral('A temporary private message', ctx.from.id);
308
-
309
- // Fully typed raw access to every Bot API method and its payload.
410
+ bot.cmd('stream-preview', async (ctx) => {
411
+ const draftId = Date.now(); // non-zero and unique for this draft
412
+ await ctx.sendMessageDraft(draftId, 'Preparing a response…', { can_stop: true });
413
+ await ctx.sendRichMessageDraft(draftId + 1, rich.draftBlocks([
414
+ rich.paragraph(['Working on ', rich.bold('your report'), '…']),
415
+ rich.thinking('Collecting data'),
416
+ ]), { can_stop: true, keep_on_stop: true });
417
+ // Persist the final answer explicitly:
418
+ return ctx.replyWithRichMessage(rich.markdown('**Report ready**'));
419
+ });
420
+ ```
421
+
422
+ Draft constructors are `rich.draftHtml()`, `rich.draftMarkdown()`, `rich.draftBlocks()`, and `new RichMessageBuilder()...buildDraft()`. Their return types match `sendRichMessageDraft`'s no-new-upload schema. `sendMessageDraft` and `sendRichMessageDraft` also accept exact object payloads through `ctx.api.callApi()`.
423
+
424
+ #### 6. Live photos, paid media, ephemeral messages, and editing
425
+
426
+ **Live photo** sends a video and its corresponding still image. Both can be file IDs or `File`/`InputFile` values; URLs are not supported by the current method schema.
427
+
428
+ ```js
429
+ await ctx.replyWithLivePhoto('VIDEO_FILE_ID', 'PHOTO_FILE_ID', { caption: 'A moment' });
430
+ // Or raw typed API:
431
+ await ctx.api.sendLivePhoto({ chat_id: ctx.chatId, live_photo: videoFile, photo: photoFile });
432
+ ```
433
+
434
+ `InputMediaBuilder.livePhoto(video, photo)` and `InputPaidMediaBuilder.livePhoto(video, photo)` build media objects. `sendPaidMedia` requires a `star_count` from 1 to 25,000:
435
+
436
+ ```js
437
+ const fs = require('node:fs');
438
+ const { File, InputPaidMediaBuilder } = require('@xbibzlibrary/telebibz');
439
+ const paidPhoto = new File(fs.readFileSync('./paid.png'), 'paid.png');
440
+ await ctx.api.callApi('sendPaidMedia', {
441
+ chat_id: ctx.chatId,
442
+ star_count: 1,
443
+ media: [InputPaidMediaBuilder.photo(paidPhoto)],
444
+ caption: 'Paid photo sample',
445
+ });
446
+ ```
447
+
448
+ This is a real payment/paywall feature: do not test it on users without consent, and account for Telegram Stars before publishing paid content.
449
+
450
+ An **ephemeral message** is addressed to a recipient using `ephemeral_message_parameters`; availability depends on Telegram's bot/chat eligibility and permissions. Context helpers include `replyEphemeral(text, receiverUserId)`, `editEphemeralMessageText`, `editEphemeralRichMessage`, `editEphemeralMessageMedia`, `editEphemeralMessageCaption`, `editEphemeralMessageReplyMarkup`, and `deleteEphemeralMessage`.
451
+
452
+ ```js
453
+ await ctx.replyEphemeral('Temporary notice', ctx.from.id);
454
+ const sent = await ctx.api.callApi('sendRichMessage', {
455
+ chat_id: ctx.chatId,
456
+ rich_message: rich.blocks([rich.paragraph('Private rich preview')]),
457
+ ephemeral_message_parameters: { receiver_user_id: ctx.from.id },
458
+ });
459
+ // If Telegram returns an ephemeral_message_id, it can be used with the edit/delete helpers.
460
+ if (sent.ephemeral_message_id) {
461
+ await ctx.editEphemeralRichMessage(ctx.from.id, sent.ephemeral_message_id,
462
+ rich.blocks([rich.paragraph('Updated temporary notice')]));
463
+ }
464
+ ```
465
+
466
+ Telegram can reject requests with `BOT_NOT_ADMIN` or other permission errors; inspect the returned `ApiError` and do not treat a local payload test as proof of eligibility.
467
+
468
+ Rich edits use `ctx.editRichMessage(content, extra)`. The underlying Bot API is `editMessageText` with `rich_message` and a target `chat_id`/`message_id` (or `inline_message_id`). These operations modify an existing message; use a message created for testing when validating them.
469
+
470
+ #### 7. Context helpers and modern Bot API methods
471
+
472
+ | Helper | Effect / arguments |
473
+ |---|---|
474
+ | `ctx.replyWithRichMessage(content, extra?)` | sends a persisted rich message to the current chat |
475
+ | `ctx.editRichMessage(content, extra?)` | rich-edits the current message; callback and inline targets are handled |
476
+ | `ctx.replyWithLivePhoto(video, photo, extra?)` | sends a live photo to the current chat |
477
+ | `ctx.sendMessageDraft(draftId, text, extra?)` | streams a plain-text preview |
478
+ | `ctx.sendRichMessageDraft(draftId, richMessage, extra?)` | streams a rich preview |
479
+ | `ctx.replyEphemeral(text, receiverUserId, extra?)` | sends an ephemeral plain-text message |
480
+ | `ctx.guestQueryId`, `ctx.answerGuestQuery(result)` | handles a real incoming `guest_message` update |
481
+ | `ctx.editEphemeralRichMessage(...)`, `ctx.deleteEphemeralMessage(...)` | edits/deletes a recipient's ephemeral message |
482
+
483
+ Guest query answers require the `guest_query_id` from an actual incoming guest update; it cannot be fabricated for a meaningful live test. Inline rich articles can be built with `iq.richArticle(id, title, richMessage, extra)` and returned while handling an actual inline query.
484
+
485
+ #### 8. Complete typed Bot API access
486
+
487
+ All **185 method names and payload signatures** in the vendored Bot API schema are available in TypeScript through `api.callApi()` and `api.raw()`:
488
+
489
+ ```js
490
+ // JavaScript or TypeScript: method name + payload object
310
491
  await ctx.api.callApi('sendRichMessage', {
311
492
  chat_id: ctx.chatId,
312
- rich_message: rich.html('<b>Rich HTML</b>'),
493
+ rich_message: rich.markdown('**Hello**'),
494
+ });
495
+
496
+ await ctx.api.callApi('sendMessageDraft', {
497
+ chat_id: ctx.chatId,
498
+ draft_id: Date.now(),
499
+ text: 'Preview',
500
+ can_stop: false,
313
501
  });
314
502
  ```
315
503
 
316
- `RichMessageBuilder` supports `.html()`, `.markdown()`, `.blocks()`, `.add()`,
317
- `.media()`, `.rtl()`, `.skipEntityDetection()`, `.build()`, and `.buildDraft()`.
318
- `rich.draftHtml()`, `rich.draftMarkdown()`, and `rich.draftBlocks()` return draft-safe content and reject `File` uploads (drafts may use existing Telegram file IDs). Rich block helpers cover
319
- paragraphs, headings, code, lists, tables, details, quotations, maps, media,
320
- buttons, and thinking. `InputMediaBuilder.livePhoto()` and
321
- `InputPaidMediaBuilder.livePhoto()` construct live-photo media payloads. The
322
- `thinking` block is only valid in `sendRichMessageDraft`. Other modern methods are available via
323
- `ctx.api.sendMessageDraft()`, `ctx.api.answerGuestQuery()`,
324
- `ctx.api.editEphemeralMessage*()`, and `ctx.api.deleteEphemeralMessage()`.
325
- Telegram types are available under `TelegramTypes`; rich input types are exported directly.
504
+ The `TelegramTypes` namespace exports the vendored Telegram object types. Useful type aliases include `TelegramMethodName`, `TelegramMethodPayload<M>`, `TelegramMethodResult<M>`, `TelegramApiMethods`, and `TelegramApiPayloads`. The runtime proxy also permits `ctx.api.anyMethod({ ...payload })` for newly added methods, but that dynamic shorthand has no runtime schema validation. TypeScript checks happen at compile time only. Run `npm run typecheck` to validate declarations and consumer examples.
505
+
506
+ The declarations are vendored from `@grammyjs/types@5.0.0` under MIT (see [`NOTICE.md`](NOTICE.md) and the vendored license); they add no runtime dependency. Telegram's official reference/changelog—not the vendored package—remains the authority when a field, permission, or limit differs.
507
+
508
+ #### Limits and troubleshooting
509
+
510
+ The Bot API 10.3 type schema documents these Rich Message ceilings: **32,768 UTF-8 characters**, **500 blocks** (including nested/list/table/quotation/details content), **16 nesting levels**, **50 media attachments**, and **20 table columns**. Map blocks use zoom 0–24 and width/height 0–10,000; the buttons block allows 1–8 buttons. Telegram can update limits, so check the [official reference](https://core.telegram.org/bots/api) before building large payloads. The server remains the final validator.
511
+
512
+ Common failures:
513
+
514
+ - `RICH_MESSAGE_VIDEO_INVALID`: the tested server rejected a GIF in a Rich Message `animation` block; try MP4. The standalone `sendAnimation` endpoint accepted the GIF.
515
+ - `BOT_NOT_ADMIN` on ephemeral sends: this is a Telegram eligibility/permission response, not proof that the JSON shape is invalid. Verify bot/chat access and official method requirements.
516
+ - `chat not found`: in a private chat, the user usually needs to open the bot and press **Start** before the bot can initiate a message.
517
+ - Upload errors: wrap bytes, paths, or streams in `File`/`InputFile`; confirm the media helper's type matches the file.
518
+
519
+ #### Live verification and limits
520
+
521
+ On 2026-09-28, a live smoke test against a user-provided bot succeeded for rich blocks, HTML, Markdown, animated custom emoji, plain/rich drafts, multipart media, photo/audio/document/video/voice-note blocks, collage/slideshow, live photo, GIF via `sendAnimation`, and HTML media references. Ephemeral `sendMessage`/`sendRichMessage` failed with Telegram's `BOT_NOT_ADMIN`; paid media was not sent because it can involve Stars; callback buttons were not clicked; all 185 endpoints were not exercised live. See [`VERIFIKASI-MENDALAM.md`](VERIFIKASI-MENDALAM.md) for the exact outcomes and test boundaries.
326
522
 
327
523
  <a id="keyboards"></a>
328
524
  ### 🔘 Keyboards & buttons
@@ -570,12 +766,12 @@ await bot.api.sendDiceCustom({ chat_id: 1, emoji: '🎲' });
570
766
 
571
767
  | Metric | Value |
572
768
  |---|---|
573
- | 📦 Source modules | **16 files** in `lib/` |
574
- | 📝 Total lines of code | **~1,700** (no build step) |
575
- | 🔌 Bot API methods | **90+** — 75 typed shortcuts + unbounded Proxy |
769
+ | 📦 Source modules | **18 files** in `lib/` |
770
+ | 📝 Total lines of code | **2,105** in `lib/` (no build step) |
771
+ | 🔌 Bot API methods | **185 typed method signatures** via `api.callApi()` + dynamic Proxy |
576
772
  | ⌨️ Context shortcuts | **50+** (reply/edit/delete/admin/react…) |
577
- | 🧪 Offline tests | **47/47 passing**, including local HTTP transport and Bot API 10.3 tests |
578
- | 🧩 Ready examples | **7** in `examples/` |
773
+ | 🧪 Offline tests | **48/48 passing**, including local HTTP transport and Bot API 10.3 tests |
774
+ | 🧩 Ready examples | **8** in `examples/` |
579
775
  | 📦 Runtime dependencies | **4** — all used, all tested |
580
776
 
581
777
  ### ⬇️ Downloads & popularity (live from npm)
@@ -587,23 +783,27 @@ await bot.api.sendDiceCustom({ chat_id: 1, emoji: '🎲' });
587
783
 
588
784
  ### 📏 Module size map (lines of code)
589
785
 
590
- ```
591
- wizard.js █████████████████████████ 247 ← forms + buttons + edit/delete
592
- telebibz.js ███████████████████▎ 193 ← main class & lifecycle
593
- context.js ███████████████████ 190 ← ctx + 50-ish shortcuts
594
- composer.js █████████████████▍ 174 ← middleware engine & filters
595
- api.js ███████████████▍ 154 ← 75 shortcuts + Proxy + transformers
596
- net.js ███████████▌ 115 ← axios transport + multipart
597
- menus.js █████████ 90 ← Menu/MenuContainer
598
- keyboard.js ████████▎ 83 ← btn/url/kb + fluent classes
599
- ratelimit.js ██████ 61 ← autoRetry · throttler · limiter
600
- runner.js ████▌ 45 ← 409-resilient polling
601
- file.js ████ 41 ← InputFile + InputMediaBuilder
602
- logger.js ███▊ 38 ← logs + banner
603
- session.js ███▌ 36 ← swappable sessions
604
- errors.js ███▌ 35 ← humanized errors 🇮
605
- broadcast.js ███ 35 ← rate-limit-safe blast
606
- inline-query.js██▊ 28 ← matcher + result builders
786
+ The figures below are generated from the current `lib/*.js` source. They are informational, not bundle-size measurements.
787
+
788
+ ```text
789
+ context.js 250 Context accessors, replies, edits and helpers
790
+ wizard.js 247 Guided forms, buttons, edit/delete modes
791
+ telebibz.js 209 Bot class, lifecycle, update dispatch
792
+ telegram-methods.js 192 Runtime registry for 185 API method names
793
+ composer.js 185 Middleware composition and filters
794
+ rich.js 175 Rich Message, entity and block builders
795
+ api.js 168 Typed API adapters, Proxy and transformers
796
+ net.js 115 HTTP transport and multipart upload
797
+ menus.js 90 Menu and MenuContainer
798
+ keyboard.js 83 Keyboard builders and fluent classes
799
+ ratelimit.js 74 Retry, throttler and limiter
800
+ logger.js 68 Logs and boot banner
801
+ runner.js 54 Polling and retry handling
802
+ file.js 48 File/InputFile and media builders
803
+ session.js 44 Swappable session storage
804
+ errors.js 35 Error translations
805
+ broadcast.js 35 Broadcast helper
806
+ inline-query.js 33 Inline matching and result builders
607
807
  ```
608
808
 
609
809
  ### 🗺️ Repo health
@@ -641,24 +841,23 @@ Run any of them with `BOT_TOKEN=123:abc node examples/01-quickstart.js`.
641
841
  ## 🔬 Testing & Live Proof
642
842
 
643
843
  ```bash
644
- npm test # 47 offline checks, including local HTTP transport + Bot API 10.3 tests
844
+ npm test # 48 offline checks, including local HTTP transport + Bot API 10.3 tests
645
845
  npm run typecheck # verify declarations and method-specific Bot API payload types
646
846
  ```
647
847
 
648
- The current audit needs no Telegram token. An earlier release of this repository
649
- records **30 offline + 10 live checks** on `@xbibzrat_bot`; that live run is historical
650
- and was not repeated in the current audit. See [`VERIFIKASI-MENDALAM.md`](VERIFIKASI-MENDALAM.md)
651
- for the test matrix, Bot API 10.3 comparison, and remaining limitations.
848
+ The 48 automated checks and `npm run typecheck` are offline/mocked and do not need a bot token. A separate live smoke test was performed with the owner's test bot: rich blocks, HTML/Markdown, animated custom emoji, drafts, media blocks, collage/slideshow, live photo, and multipart media references passed. Ephemeral sends were rejected by Telegram with `BOT_NOT_ADMIN`; paid media and all 185 endpoints were not tested live. See [`VERIFIKASI-MENDALAM.md`](VERIFIKASI-MENDALAM.md) for the exact matrix, known limits, and failures.
652
849
 
653
850
  Debug logging: `DEBUG=telebibz:net,telebibz:ratelimit node yourbot.js`.
654
851
 
655
852
  <a id="structure"></a>
656
- ## 📂 Repo Structure (16 core files)
853
+ ## 📂 Repo Structure (18 JavaScript modules + vendored API types)
657
854
 
658
855
  | File | Role |
659
856
  |---|---|
660
857
  | `lib/net.js` | axios keep-alive transport + multipart `attach://` |
661
858
  | `lib/api.js` | Bot API methods + any-method Proxy + transformers |
859
+ | `lib/rich.js` | Rich Message entities, block builders, draft-safe helpers |
860
+ | `lib/telegram-methods.js` | registry of 185 Bot API method names |
662
861
  | `lib/composer.js` | middleware, `on('message:photo')` filters, `errorBoundary` |
663
862
  | `lib/context.js` | ctx object + 50-ish reply/edit/delete/callback shortcuts |
664
863
  | `lib/session.js` | per user:chat sessions (swappable storage) |
@@ -672,11 +871,13 @@ Debug logging: `DEBUG=telebibz:net,telebibz:ratelimit node yourbot.js`.
672
871
  | `lib/inline-query.js` | query matcher + inline result builders |
673
872
  | `lib/errors.js` | humanized errors + suggestions |
674
873
  | `lib/logger.js` | framed logs + boot banner |
675
- | `index.js` / `index.d.ts` | export door + TypeScript types |
874
+ | `types/telegram-bot-api/` | vendored Bot API types (MIT); no runtime code |
875
+ | `index.js` / `index.d.ts` | export door + typed method/payload surface |
676
876
 
677
877
  <a id="changelog"></a>
678
878
  ## 🕐 Changelog
679
879
 
880
+ - **Unreleased — Bot API 10.3** — rich messages, animated emoji, drafts, live photos, all 185 typed method payloads; 48 offline checks and richer live verification. Details: [`CHANGELOG.md`](CHANGELOG.md).
680
881
  - **3.1.0** — wizard: choice buttons (reply/inline), `edit`/`delete` modes, auto cleanup, programmatic helpers · tests 24 → 30
681
882
  - **3.0.0** — production-grade grammY parity: axios keep-alive, transformers, menus, inline query, limiter
682
883
  - **2.0.0** — engine rewritten from scratch, multipart transport, native Node webhook
package/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  // Public TypeScript surface for @xbibzlibrary/telebibz.
2
2
  import type { ApiMethods, Opts } from './types/telegram-bot-api/methods.js';
3
- import type { InputRichBlock, InputRichMessage, InputRichMessageMedia } from './types/telegram-bot-api/rich.js';
3
+ import type { InputRichBlock, InputRichBlockButtons, InputRichMessage, InputRichMessageMedia, RichMessageButton, RichTextButton } from './types/telegram-bot-api/rich.js';
4
4
  export * as TelegramTypes from './types/telegram-bot-api/mod.js';
5
5
  export type * from './types/telegram-bot-api/rich.js';
6
6
 
@@ -267,6 +267,9 @@ export const rich: {
267
267
  draftHtml(html: string, options?: Partial<InputRichMessage<TelegramFileInput>>): TelegramMethodPayload<'sendRichMessageDraft'>['rich_message'];
268
268
  draftMarkdown(markdown: string, options?: Partial<InputRichMessage<TelegramFileInput>>): TelegramMethodPayload<'sendRichMessageDraft'>['rich_message'];
269
269
  draftBlocks(blocks: InputRichBlock<TelegramFileInput>[], options?: Partial<InputRichMessage<TelegramFileInput>>): TelegramMethodPayload<'sendRichMessageDraft'>['rich_message'];
270
+ button(text: string, action: Record<string, unknown>, style?: 'danger' | 'success' | 'primary' | 'link'): RichMessageButton;
271
+ buttonText(text: string, action: Record<string, unknown>, style?: 'danger' | 'success' | 'primary' | 'link'): RichTextButton;
272
+ buttons(buttons: RichMessageButton[], align?: 'left' | 'center' | 'right'): InputRichBlockButtons;
270
273
  block(type: string, props?: Record<string, unknown>): InputRichBlock<TelegramFileInput>;
271
274
  [builder: string]: (...args: any[]) => any;
272
275
  };
package/lib/rich.js CHANGED
@@ -40,6 +40,19 @@ function block(type, props = {}) {
40
40
 
41
41
  const textEntity = (type, text, extra = {}) => ({ type, text, ...extra });
42
42
  const caption = (text, credit) => ({ text, ...(credit !== undefined ? { credit } : {}) });
43
+ function richMessageButton(text, action, style) {
44
+ if (!action || typeof action !== 'object') throw new TypeError('rich.button() membutuhkan satu jenis aksi button');
45
+ const keys = ['url', 'callback_data', 'web_app', 'login_url', 'switch_inline_query', 'switch_inline_query_current_chat', 'switch_inline_query_chosen_chat', 'copy_text', 'disabled'];
46
+ const defined = keys.filter((key) => Object.hasOwn(action, key));
47
+ if (defined.length !== 1) throw new TypeError('RichMessageButton harus memiliki tepat satu field aksi');
48
+ if (style !== undefined && !['danger', 'success', 'primary', 'link'].includes(style)) {
49
+ throw new TypeError('Style rich button harus danger, success, primary, atau link');
50
+ }
51
+ if (style === 'link' && defined[0] !== 'callback_data') {
52
+ throw new TypeError('Style link hanya didukung pada rich callback button');
53
+ }
54
+ return { text, ...action, ...(style ? { style } : {}) };
55
+ }
43
56
 
44
57
  const rich = {
45
58
  html: (html, options = {}) => inputRichMessage({ html: String(html) }, options),
@@ -76,19 +89,8 @@ const rich = {
76
89
  anchorLink: (text, anchorName) => textEntity('anchor_link', text, { anchor_name: anchorName }),
77
90
  reference: (text, name) => textEntity('reference', text, { name }),
78
91
  referenceLink: (text, referenceName) => textEntity('reference_link', text, { reference_name: referenceName }),
79
- button: (text, action, style) => {
80
- if (!action || typeof action !== 'object') throw new TypeError('rich.button() membutuhkan satu jenis aksi button');
81
- const keys = ['url', 'callback_data', 'web_app', 'login_url', 'switch_inline_query', 'switch_inline_query_current_chat', 'switch_inline_query_chosen_chat', 'copy_text', 'disabled'];
82
- const defined = keys.filter((key) => Object.hasOwn(action, key));
83
- if (defined.length !== 1) throw new TypeError('RichMessageButton harus memiliki tepat satu field aksi');
84
- if (style !== undefined && !['danger', 'success', 'primary', 'link'].includes(style)) {
85
- throw new TypeError('Style rich button harus danger, success, primary, atau link');
86
- }
87
- if (style === 'link' && defined[0] !== 'callback_data') {
88
- throw new TypeError('Style link hanya didukung pada rich callback button');
89
- }
90
- return { type: 'button', button: { text, ...action, ...(style ? { style } : {}) } };
91
- },
92
+ button: (text, action, style) => richMessageButton(text, action, style),
93
+ buttonText: (text, action, style) => ({ type: 'button', button: richMessageButton(text, action, style) }),
92
94
 
93
95
  // InputRichBlock builders.
94
96
  paragraph: (text) => block('paragraph', { text }),
@@ -130,6 +132,9 @@ const rich = {
130
132
  video: (video, captionText, credit) => block('video', { video, ...(captionText !== undefined ? { caption: caption(captionText, credit) } : {}) }),
131
133
  voiceNote: (voiceNote, captionText, credit) => block('voice_note', { voice_note: voiceNote, ...(captionText !== undefined ? { caption: caption(captionText, credit) } : {}) }),
132
134
  buttons: (buttons, align) => {
135
+ if (!Array.isArray(buttons) || buttons.length < 1 || buttons.length > 8) {
136
+ throw new RangeError('rich.buttons(): harus berisi 1–8 button');
137
+ }
133
138
  if (align !== undefined && !['left', 'center', 'right'].includes(align)) {
134
139
  throw new TypeError('rich.buttons(): align harus left, center, atau right');
135
140
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xbibzlibrary/telebibz",
3
- "version": "3.1.3",
3
+ "version": "3.1.5",
4
4
  "description": "Telegram Bot API 10.3 framework: all 185 methods with typed payloads, rich messages, live photos, drafts, ephemeral messages, inline queries, sessions, webhooks, menus and wizards. Xbibz Technology ID",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -261,7 +261,8 @@ async function main() {
261
261
  assert.equal(message.blocks[2].items[0].blocks[0].text, 'item satu');
262
262
  assert.equal(message.blocks[3].is_compact, true);
263
263
  assert.equal(message.blocks[4].is_open, true);
264
- assert.equal(message.blocks[5].buttons[0].button.callback_data, 'ok');
264
+ assert.equal(message.blocks[5].buttons[0].callback_data, 'ok');
265
+ assert.deepEqual(rich.buttonText('Inline', { callback_data: 'inline' }), { type: 'button', button: { text: 'Inline', callback_data: 'inline' } });
265
266
  assert.deepEqual(new RichMessageBuilder().add(rich.paragraph('ok')).rtl().build(), { blocks: [{ type: 'paragraph', text: 'ok' }], is_rtl: true });
266
267
  assert.deepEqual(new RichMessageBuilder().markdown('draft').buildDraft(), { markdown: 'draft' });
267
268
  assert.deepEqual(rich.draftBlocks([rich.paragraph('partial'), rich.thinking('working')]), { blocks: [{ type: 'paragraph', text: 'partial' }, { type: 'thinking', text: 'working' }] });
@@ -269,11 +270,45 @@ async function main() {
269
270
  assert.throws(() => inputRichMessage({ html: 'x', markdown: 'y' }), /tepat satu/);
270
271
  assert.throws(() => rich.heading('bad', 7), /1–6/);
271
272
  assert.throws(() => rich.button('Link', { url: 'https://example.com' }, 'link'), /callback button/);
272
- assert.throws(() => rich.buttons([], 'justify'), /align/);
273
+ assert.throws(() => rich.buttons([rich.button('OK', { callback_data: 'ok' })], 'justify'), /align/);
273
274
  assert.deepEqual(InputMediaBuilder.livePhoto('video', 'photo'), { type: 'live_photo', media: 'video', photo: 'photo' });
274
275
  assert.deepEqual(InputPaidMediaBuilder.livePhoto('video', 'photo'), { type: 'live_photo', media: 'video', photo: 'photo' });
275
276
  });
276
277
 
278
+ await test('Seluruh family RichText dan InputRichBlock builder memancarkan discriminant schema', () => {
279
+ const { rich } = require('..');
280
+ const entities = [
281
+ rich.bold('x'), rich.italic('x'), rich.underline('x'), rich.strikethrough('x'), rich.spoiler('x'),
282
+ rich.subscript('x'), rich.superscript('x'), rich.marked('x'), rich.code('x'), rich.dateTime('now', 1),
283
+ rich.textMention('x', { id: 1, is_bot: false, first_name: 'A' }), rich.customEmoji('emoji-id', '✨'),
284
+ rich.mathText('x'), rich.url('x', 'https://example.com'), rich.email('a@b.com', 'a@b.com'),
285
+ rich.phone('123', '+123'), rich.bankCard('1234', '1234'), rich.mention('@bot', 'bot'),
286
+ rich.hashtag('#test', 'test'), rich.cashtag('$TEST', 'TEST'), rich.botCommand('/start', 'start'),
287
+ rich.anchorText('anchor'), rich.anchorLink('link', 'anchor'), rich.reference('ref', 'anchor'),
288
+ rich.referenceLink('ref', 'anchor'), rich.buttonText('btn', { callback_data: 'cb' }),
289
+ ];
290
+ assert.deepEqual(entities.map((entity) => entity.type), [
291
+ 'bold', 'italic', 'underline', 'strikethrough', 'spoiler', 'subscript', 'superscript', 'marked', 'code', 'date_time',
292
+ 'text_mention', 'custom_emoji', 'mathematical_expression', 'url', 'email_address', 'phone_number', 'bank_card_number',
293
+ 'mention', 'hashtag', 'cashtag', 'bot_command', 'anchor', 'anchor_link', 'reference', 'reference_link', 'button',
294
+ ]);
295
+ const media = (type) => ({ type, media: `${type}-file-id` });
296
+ const blocks = [
297
+ rich.paragraph('x'), rich.heading('x'), rich.pre('x'), rich.footer('x'), rich.divider(), rich.mathBlock('x'),
298
+ rich.anchor('a'), rich.list(['x']), rich.quote([rich.paragraph('x')]), rich.expandableQuote('x'), rich.pullQuote('x'),
299
+ rich.collage([rich.photo(media('photo'))]), rich.slideshow([rich.video(media('video'))]),
300
+ rich.table([[{ text: 'x', align: 'left', valign: 'middle' }]]), rich.details('s', [rich.paragraph('x')]),
301
+ rich.map({ latitude: 0, longitude: 0 }, 1, 100, 100), rich.animation(media('animation')), rich.audio(media('audio')),
302
+ rich.document(media('document')), rich.photo(media('photo')), rich.video(media('video')),
303
+ rich.voiceNote(media('voice_note')), rich.buttons([rich.button('x', { url: 'https://example.com' })]), rich.thinking('x'),
304
+ ];
305
+ assert.deepEqual(blocks.map((item) => item.type), [
306
+ 'paragraph', 'heading', 'pre', 'footer', 'divider', 'mathematical_expression', 'anchor', 'list', 'blockquote',
307
+ 'expandable_blockquote', 'pullquote', 'collage', 'slideshow', 'table', 'details', 'map', 'animation', 'audio',
308
+ 'document', 'photo', 'video', 'voice_note', 'buttons', 'thinking',
309
+ ]);
310
+ });
311
+
277
312
  await test('Context memetakan rich send/edit, live photo, ephemeral dan draft ke payload Bot API', async () => {
278
313
  const { rich } = require('..');
279
314
  const calls = [];