@xbibzlibrary/telebibz 3.1.3 → 3.1.4
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 +29 -5
- package/README.id.md +282 -82
- package/README.md +282 -81
- package/index.d.ts +4 -1
- package/lib/rich.js +18 -13
- package/package.json +1 -1
- package/test/audit.test.js +37 -2
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
|
-
|
|
6
|
-
|
|
7
|
-
-
|
|
8
|
-
-
|
|
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
|
[](https://www.npmjs.com/package/@xbibzlibrary/telebibz)
|
|
20
20
|
[](https://www.npmjs.com/package/@xbibzlibrary/telebibz)
|
|
21
21
|
[](https://nodejs.org)
|
|
22
|
-
[](#-test--bukti-live)
|
|
23
|
+
[](#-analitik--statistik)
|
|
24
24
|
[](LICENSE)
|
|
25
25
|
[](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) |
|
|
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 |
|
|
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
|
|
273
|
+
### 🧱 Rich Message, emoji bergerak, media & draft (Bot API 10.3)
|
|
272
274
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
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
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
//
|
|
299
|
-
await ctx.
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
await ctx.
|
|
309
|
-
|
|
310
|
-
|
|
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.
|
|
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
|
-
`
|
|
318
|
-
|
|
319
|
-
`
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
`
|
|
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 | **
|
|
576
|
-
| 📝 Total baris kode |
|
|
577
|
-
| 🔌 Metode Bot API | **
|
|
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 | **
|
|
580
|
-
| 🧩 Contoh siap jalan | **
|
|
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
|
-
|
|
594
|
-
|
|
595
|
-
context.js
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
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 #
|
|
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
|
-
|
|
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 (
|
|
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
|
-
| `
|
|
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
|
[](https://www.npmjs.com/package/@xbibzlibrary/telebibz)
|
|
20
20
|
[](https://www.npmjs.com/package/@xbibzlibrary/telebibz)
|
|
21
21
|
[](https://nodejs.org)
|
|
22
|
-
[](#-testing--live-proof)
|
|
23
|
+
[](#-analytics--statistics)
|
|
24
24
|
[](LICENSE)
|
|
25
25
|
[](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) |
|
|
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 |
|
|
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,
|
|
272
|
+
### 🧱 Rich Messages, animated emoji, media & drafts (Bot API 10.3)
|
|
271
273
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
the
|
|
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
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
//
|
|
298
|
-
await ctx.
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
await ctx.
|
|
308
|
-
|
|
309
|
-
|
|
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.
|
|
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
|
-
`
|
|
317
|
-
|
|
318
|
-
`
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
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 | **
|
|
574
|
-
| 📝 Total lines of code |
|
|
575
|
-
| 🔌 Bot API methods | **
|
|
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 | **
|
|
578
|
-
| 🧩 Ready 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
|
-
|
|
592
|
-
|
|
593
|
-
context.js
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
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 #
|
|
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
|
|
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 (
|
|
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
|
-
| `
|
|
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
|
-
|
|
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
|
+
"version": "3.1.4",
|
|
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",
|
package/test/audit.test.js
CHANGED
|
@@ -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].
|
|
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 = [];
|