@xbibzlibrary/telebibz 0.4.4 → 0.4.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/README.id.md CHANGED
@@ -19,6 +19,20 @@ Panduan cepat storage (Memory/JSON/Redis/SQL/Mongo): [English](docs/STORAGE.md)
19
19
 
20
20
  Panduan mulai: [English](docs/GETTING_STARTED.md) · [Bahasa Indonesia](docs/GETTING_STARTED.id.md) · [简体中文](docs/GETTING_STARTED.zh-CN.md)
21
21
 
22
+ File (upload & download): [English](docs/FILES.md) · [Bahasa Indonesia](docs/FILES.id.md) · [简体中文](docs/FILES.zh-CN.md)
23
+
24
+ Error & rate limit: [English](docs/ERRORS.md) · [Bahasa Indonesia](docs/ERRORS.id.md) · [简体中文](docs/ERRORS.zh-CN.md)
25
+
26
+ Deployment webhook: [English](docs/WEBHOOK.md) · [Bahasa Indonesia](docs/WEBHOOK.id.md) · [简体中文](docs/WEBHOOK.zh-CN.md)
27
+
28
+ Testing (offline dengan MockTransport): [English](docs/TESTING.md) · [Bahasa Indonesia](docs/TESTING.id.md) · [简体中文](docs/TESTING.zh-CN.md)
29
+
30
+ Migrasi dari Telegraf: [English](docs/MIGRATION_TELEGRAF.md) · [Bahasa Indonesia](docs/MIGRATION_TELEGRAF.id.md) · [简体中文](docs/MIGRATION_TELEGRAF.zh-CN.md)
31
+
32
+ Cookbook produksi (13 resep): [English](docs/COOKBOOK.md) · [Bahasa Indonesia](docs/COOKBOOK.id.md) · [简体中文](docs/COOKBOOK.zh-CN.md)
33
+
34
+ Katalog dokumentasi lengkap: [docs/README.md](docs/README.md)
35
+
22
36
  Showcase komunitas: [SHOWCASE.md](SHOWCASE.md)
23
37
 
24
38
  ![overview telebibz](https://cdn.jsdelivr.net/npm/@xbibzlibrary/telebibz@latest/assets/telebibz-readme-preview.png)
package/README.md CHANGED
@@ -21,6 +21,20 @@ Storage quick start (Memory/JSON/Redis/SQL/Mongo): [English](docs/STORAGE.md) ·
21
21
 
22
22
  Getting started: [English](docs/GETTING_STARTED.md) · [Bahasa Indonesia](docs/GETTING_STARTED.id.md) · [简体中文](docs/GETTING_STARTED.zh-CN.md)
23
23
 
24
+ Files (upload & download): [English](docs/FILES.md) · [Bahasa Indonesia](docs/FILES.id.md) · [简体中文](docs/FILES.zh-CN.md)
25
+
26
+ Errors & rate limits: [English](docs/ERRORS.md) · [Bahasa Indonesia](docs/ERRORS.id.md) · [简体中文](docs/ERRORS.zh-CN.md)
27
+
28
+ Webhook deployment: [English](docs/WEBHOOK.md) · [Bahasa Indonesia](docs/WEBHOOK.id.md) · [简体中文](docs/WEBHOOK.zh-CN.md)
29
+
30
+ Testing (offline with MockTransport): [English](docs/TESTING.md) · [Bahasa Indonesia](docs/TESTING.id.md) · [简体中文](docs/TESTING.zh-CN.md)
31
+
32
+ Migrating from Telegraf: [English](docs/MIGRATION_TELEGRAF.md) · [Bahasa Indonesia](docs/MIGRATION_TELEGRAF.id.md) · [简体中文](docs/MIGRATION_TELEGRAF.zh-CN.md)
33
+
34
+ Production cookbook (13 recipes): [English](docs/COOKBOOK.md) · [Bahasa Indonesia](docs/COOKBOOK.id.md) · [简体中文](docs/COOKBOOK.zh-CN.md)
35
+
36
+ Full documentation catalog: [docs/README.md](docs/README.md)
37
+
24
38
  Community showcase: [SHOWCASE.md](SHOWCASE.md)
25
39
 
26
40
  ![telebibz overview](https://cdn.jsdelivr.net/npm/@xbibzlibrary/telebibz@latest/assets/telebibz-readme-preview.png)
package/README.zh-CN.md CHANGED
@@ -19,6 +19,20 @@ GitHub Packages 指南:[English](docs/GITHUB_PACKAGES.md) · [Bahasa Indonesia
19
19
 
20
20
  入门指南:[English](docs/GETTING_STARTED.md) · [Bahasa Indonesia](docs/GETTING_STARTED.id.md) · [简体中文](docs/GETTING_STARTED.zh-CN.md)
21
21
 
22
+ 文件(上传与下载):[English](docs/FILES.md) · [Bahasa Indonesia](docs/FILES.id.md) · [简体中文](docs/FILES.zh-CN.md)
23
+
24
+ 错误与限流:[English](docs/ERRORS.md) · [Bahasa Indonesia](docs/ERRORS.id.md) · [简体中文](docs/ERRORS.zh-CN.md)
25
+
26
+ Webhook 部署:[English](docs/WEBHOOK.md) · [Bahasa Indonesia](docs/WEBHOOK.id.md) · [简体中文](docs/WEBHOOK.zh-CN.md)
27
+
28
+ 测试(用 MockTransport 离线进行):[English](docs/TESTING.md) · [Bahasa Indonesia](docs/TESTING.id.md) · [简体中文](docs/TESTING.zh-CN.md)
29
+
30
+ 从 Telegraf 迁移:[English](docs/MIGRATION_TELEGRAF.md) · [Bahasa Indonesia](docs/MIGRATION_TELEGRAF.id.md) · [简体中文](docs/MIGRATION_TELEGRAF.zh-CN.md)
31
+
32
+ 生产实战手册(13 个配方):[English](docs/COOKBOOK.md) · [Bahasa Indonesia](docs/COOKBOOK.id.md) · [简体中文](docs/COOKBOOK.zh-CN.md)
33
+
34
+ 完整文档目录:[docs/README.md](docs/README.md)
35
+
22
36
  社区 showcase:[SHOWCASE.md](SHOWCASE.md)
23
37
 
24
38
  ![telebibz 概览](https://cdn.jsdelivr.net/npm/@xbibzlibrary/telebibz@latest/assets/telebibz-readme-preview.png)
@@ -0,0 +1,321 @@
1
+ # Cookbook produksi (Bahasa Indonesia)
2
+
3
+ Resep lengkap dan terverifikasi untuk kebutuhan bot sungguhan: rate limit per user, middleware auth, broadcast, tugas terjadwal, antrian background, menu berpaginasi, form, cache, validasi Mini App, dan pembayaran. Setiap resep berdiri sendiri — salin ke bot Anda dan sesuaikan namanya.
4
+
5
+ ## Daftar isi
6
+
7
+ 1. [Rate limit per user](#1-rate-limit-per-user)
8
+ 2. [Middleware auth (allowlist / khusus admin)](#2-middleware-auth-allowlist--khusus-admin)
9
+ 3. [Broadcast ke ribuan user](#3-broadcast-ke-ribuan-user)
10
+ 4. [Pesan terjadwal (interval, sekali, cron)](#4-pesan-terjadwal-interval-sekali-cron)
11
+ 5. [Job background dengan retry](#5-job-background-dengan-retry)
12
+ 6. [Menu berpaginasi](#6-menu-berpaginasi)
13
+ 7. [Menu dengan permission](#7-menu-dengan-permission)
14
+ 8. [Form multi-step dengan validasi](#8-form-multi-step-dengan-validasi)
15
+ 9. [Mengedit pesan dan inline keyboard](#9-mengedit-pesan-dan-inline-keyboard)
16
+ 10. [Cache hasil mahal](#10-cache-hasil-mahal)
17
+ 11. [Validasi initData Mini App](#11-validasi-initdata-mini-app)
18
+ 12. [Pembayaran dengan Telegram Stars / invoice](#12-pembayaran-dengan-telegram-stars--invoice)
19
+ 13. [Logging terstruktur dan hook metrik](#13-logging-terstruktur-dan-hook-metrik)
20
+
21
+ ## 1. Rate limit per user
22
+
23
+ `TokenBucketLimiter` menjaga bucket independen per kunci — kuncikan berdasarkan user atau chat:
24
+
25
+ ```ts
26
+ import { Bot, TokenBucketLimiter } from "@xbibzlibrary/telebibz";
27
+
28
+ const limiter = new TokenBucketLimiter(5, 0.5); // burst 5, isi ulang 0.5 token/s (= 1 pesan per 2s berkelanjutan)
29
+
30
+ const bot = new Bot(process.env.TELEGRAM_BOT_TOKEN!);
31
+ bot.use(async (ctx, next) => {
32
+ const key = `user:${ctx.from?.id ?? "anon"}`;
33
+ const result = limiter.consume(key);
34
+ if (!result.allowed) {
35
+ const seconds = Math.ceil((result.retryAfterMs ?? 1000) / 1000);
36
+ await ctx.reply(`⏳ Terlalu banyak permintaan. Coba lagi dalam ${seconds} detik.`);
37
+ return; // jangan panggil next(): update dijatuhkan
38
+ }
39
+ await next();
40
+ });
41
+ ```
42
+
43
+ `consume(key, cost)` mendukung aksi berbobot (mis. unggahan biaya 5, teks biaya 1). `limiter.clear(key?)` mereset state. Kombinasikan dengan flood gate transport — limiter ini membentuk *user Anda*; flood gate mematuhi *Telegram*.
44
+
45
+ ## 2. Middleware auth (allowlist / khusus admin)
46
+
47
+ ```ts
48
+ const ADMINS = new Set([Number(process.env.ADMIN_ID)]);
49
+ const bot = new Bot(process.env.TELEGRAM_BOT_TOKEN!);
50
+
51
+ bot.use(async (ctx, next) => {
52
+ if (ADMINS.has(ctx.from?.id ?? 0)) return await next(); // admin: semua boleh
53
+ if (ctx.chat?.type === "private") return await next(); // DM: diizinkan
54
+ return undefined; // grup: jatuhkan diam-diam
55
+ });
56
+
57
+ bot.command("stats", async (ctx) => { // route khusus admin
58
+ if (!ADMINS.has(ctx.from?.id ?? 0)) return;
59
+ await ctx.reply("Statistik rahasia");
60
+ });
61
+ ```
62
+
63
+ ## 3. Broadcast ke ribuan user
64
+
65
+ ```ts
66
+ const report = await bot.broadcast(
67
+ subscriberIds,
68
+ (chatId) => bot.api.methods.sendMessage({ chat_id: chatId, text: "📰 Newsletter #42" }),
69
+ {
70
+ concurrency: 64, // batasi bila downstream Anda butuh (default: paralel penuh)
71
+ onProgress: (p) => console.log(`${p.delivered}/${p.total}`),
72
+ },
73
+ );
74
+
75
+ console.log(`Terkirim ${report.delivered}/${report.total} dalam ${report.durationMs}ms`);
76
+ for (const failure of report.failures) {
77
+ console.error(`chat ${failure.chatId}: ${failure.error}`);
78
+ }
79
+ ```
80
+
81
+ Setiap chat tetap dicoba; 429 di-retry tepat selama `retry_after` yang diperintahkan Telegram. Kegagalan tidak pernah membatalkan keseluruhan proses — semuanya masuk report.
82
+
83
+ ## 4. Pesan terjadwal (interval, sekali, cron)
84
+
85
+ ```ts
86
+ import { Scheduler, parseCronExpression, nextCronOccurrence } from "@xbibzlibrary/telebibz";
87
+
88
+ const scheduler = new Scheduler({ onError: (error, id) => console.error(`job ${id} gagal`, error) });
89
+
90
+ // Setiap 6 jam
91
+ scheduler.every("digest", 6 * 60 * 60 * 1000, async () => {
92
+ await bot.api.methods.sendMessage({ chat_id: ADMIN_CHAT, text: "Ringkasan terjadwal" });
93
+ });
94
+
95
+ // Sekali, setelah 5 menit (pola pengingat)
96
+ scheduler.after("remind-42", 5 * 60 * 1000, async () => {
97
+ await bot.api.methods.sendMessage({ chat_id: 42, text: "⏰ Pengingat!" });
98
+ });
99
+
100
+ // Cron: hari kerja 09:00 (ekspresi lima field)
101
+ scheduler.cron("morning", "0 9 * * 1-5", async () => {
102
+ await bot.api.methods.sendMessage({ chat_id: 42, text: "Selamat pagi!" });
103
+ });
104
+
105
+ scheduler.cancel("digest"); // hentikan satu job
106
+ scheduler.clear(); // hentikan semua
107
+ ```
108
+
109
+ Helper murni untuk test dan pratinjau — tanpa timer:
110
+
111
+ ```ts
112
+ parseCronExpression("*/15 * * * *"); // field tervalidasi
113
+ nextCronOccurrence("0 9 * * 1", new Date()); // eksekusi berikutnya sebagai Date
114
+ ```
115
+
116
+ ## 5. Job background dengan retry
117
+
118
+ ```ts
119
+ import { TaskQueue } from "@xbibzlibrary/telebibz";
120
+
121
+ const queue = new TaskQueue(
122
+ async (job) => {
123
+ await fetch(`https://api.example.com/process`, { method: "POST", body: JSON.stringify(job.data) });
124
+ },
125
+ { concurrency: 8, retries: 3, backoffMs: 500, maxBackoffMs: 30_000, onError: (error, job) => log.error("job gagal", { job: job.id, error }) },
126
+ );
127
+
128
+ bot.command("process", async (ctx) => {
129
+ const job = queue.add({ url: ctx.message?.text?.split(" ")[1] }, { priority: 10 }); // lebih tinggi lebih dulu
130
+ await ctx.reply(`Job ${job.id} masuk antrian`);
131
+ });
132
+
133
+ bot.command("cancel", async (ctx) => {
134
+ const id = ctx.message?.text?.split(" ")[1];
135
+ if (id && queue.cancel(id)) await ctx.reply("Dibatalkan");
136
+ });
137
+ ```
138
+
139
+ ## 6. Menu berpaginasi
140
+
141
+ `MenuController` merender satu halaman sekaligus dan me-routing callback navigasi:
142
+
143
+ ```ts
144
+ import { Bot, MenuController, InlineKeyboard } from "@xbibzlibrary/telebibz";
145
+
146
+ const products = Array.from({ length: 57 }, (_v, i) => ({ id: i + 1, name: `Produk ${i + 1}` }));
147
+
148
+ const menu = new MenuController({
149
+ id: "products",
150
+ items: () => products, // atau async () => await db.products()
151
+ pageSize: 10,
152
+ label: (item) => item.name,
153
+ callback: async (item) => { /* user memilih sebuah produk */ },
154
+ labels: { previous: "◀", next: "▶" },
155
+ });
156
+
157
+ bot.callback("products:*", async (ctx) => {
158
+ // Lewatkan data callback LENGKAP — controller mengharapkan prefix "products:"-nya sendiri.
159
+ const result = await menu.handle(ctx.callbackQuery?.data ?? "");
160
+ if (result === undefined) return void (await ctx.answerCallbackQuery());
161
+ if (result.type === "noop") return void (await ctx.answerCallbackQuery());
162
+ if (result.type === "page") {
163
+ await ctx.reply(`Halaman ${result.page.page + 1}/${result.page.pageCount}`, { reply_markup: result.keyboard });
164
+ } else {
165
+ await ctx.answerCallbackQuery(`Dipilih: ${result.item.name}`);
166
+ }
167
+ });
168
+ bot.command("shop", async (ctx) => {
169
+ const result = await menu.handle("products:page:0"); // "<id>:page:<n>"
170
+ if (result?.type === "page") await ctx.reply("Produk:", { reply_markup: result.keyboard });
171
+ });
172
+ ```
173
+
174
+ ## 7. Menu dengan permission
175
+
176
+ ```ts
177
+ import { Menu } from "@xbibzlibrary/telebibz";
178
+
179
+ const menu = new Menu("main")
180
+ .breadcrumb("Beranda")
181
+ .item({ id: "profile", label: "👤 Profil", callbackData: "open:profile" })
182
+ .item({ id: "stats", label: "📊 Statistik", permission: (context) => context.permissions?.includes("admin") ?? false })
183
+ .item({ id: "help", label: "❓ Bantuan", url: "https://example.com/help" });
184
+
185
+ // build() async: ia mengevaluasi visibilitas/permission untuk context yang diberikan.
186
+ const keyboard = await menu.build({ permissions: ["admin"] }, { columns: 1, includeBreadcrumbs: true });
187
+ await ctx.reply("Menu utama:", { reply_markup: keyboard.build() });
188
+ ```
189
+
190
+ `visible` menyembunyikan item sepenuhnya; `permission` menerima `MenuContext` Anda (`{ userId, permissions }`).
191
+
192
+ ## 8. Form multi-step dengan validasi
193
+
194
+ ```ts
195
+ import { Bot, Form, validators } from "@xbibzlibrary/telebibz";
196
+
197
+ const registration = new Form({
198
+ name: { parse: validators.string, required: true },
199
+ age: { parse: validators.integer, validate: (age) => (age >= 13 ? undefined : "Minimal 13 tahun") },
200
+ email: { parse: validators.email },
201
+ });
202
+ ```
203
+
204
+ Sambungkan lewat step `Wizard` atau `ConversationManager` — beri satu field per pesan; `validators` mencakup `string`, `number`, `integer`, `email`, `url`, dan cek kustom mengembalikan pesan errornya.
205
+
206
+ ## 9. Mengedit pesan dan inline keyboard
207
+
208
+ ```ts
209
+ import { InlineKeyboard } from "@xbibzlibrary/telebibz";
210
+
211
+ bot.action("vote:up", async (ctx) => {
212
+ votes += 1;
213
+ const keyboard = new InlineKeyboard()
214
+ .text(`👍 ${votes}`, "vote:up")
215
+ .text("👎 0", "vote:down")
216
+ .build();
217
+ // Tukar keyboard pesan tombol tersebut di tempat
218
+ await ctx.api.methods.editMessageReplyMarkup({
219
+ chat_id: ctx.chat!.id,
220
+ message_id: ctx.callbackQuery!.message!.message_id,
221
+ reply_markup: keyboard,
222
+ });
223
+ await ctx.answerCallbackQuery(); // hentikan spinner
224
+ });
225
+ ```
226
+
227
+ `ctx.edit(text, extra)` menulis ulang teks pesan saat ini (keyboard ikut lewat `reply_markup` di `extra`); `editMessageLiveLocation`, `stopPoll`, dan seluruh surface method tersedia di `ctx.api.methods`. Data tombol dibatasi **64 byte** — builder memvalidasi saat konstruksi, bukan crash saat runtime.
228
+
229
+ ## 10. Cache hasil mahal
230
+
231
+ ```ts
232
+ import { MemoryCache } from "@xbibzlibrary/telebibz";
233
+
234
+ const weather = new MemoryCache<string>("weather"); // namespace; TTL di-set per penulisan
235
+ bot.command("weather", async (ctx) => {
236
+ const city = ctx.message?.text?.split(" ")[1] ?? "Jakarta";
237
+ let text = await weather.get(city);
238
+ if (text === undefined) {
239
+ text = await fetchWeather(city);
240
+ await weather.set(city, text, 5 * 60 * 1000); // cache 5 menit
241
+ }
242
+ await ctx.reply(text);
243
+ });
244
+ ```
245
+
246
+ ## 11. Validasi initData Mini App
247
+
248
+ ```ts
249
+ import { validateWebAppInitData } from "@xbibzlibrary/telebibz";
250
+
251
+ bot.command("app", async (ctx) => {
252
+ await ctx.reply("Buka aplikasinya:", {
253
+ reply_markup: new InlineKeyboard().webApp("🚀 Buka", "https://app.example.com").build(),
254
+ });
255
+ });
256
+
257
+ // Di endpoint backend aplikasi Anda — verifikasi yang dikirim Mini App:
258
+ app.post("/api/data", express.json(), (req, res) => {
259
+ try {
260
+ const initData = validateWebAppInitData(req.body.initData, process.env.TELEGRAM_BOT_TOKEN!, 3600);
261
+ res.json({ user: initData.user, ok: true }); // signature + kesegaran terverifikasi
262
+ } catch {
263
+ res.status(401).json({ ok: false });
264
+ }
265
+ });
266
+ ```
267
+
268
+ `validateWebAppInitData` memeriksa signature HMAC dan jendela kesegaran `auth_date` (default 24 jam; di sini 1 jam).
269
+
270
+ ## 12. Pembayaran dengan Telegram Stars / invoice
271
+
272
+ ```ts
273
+ import { Bot, PaymentsClient, InlineKeyboard } from "@xbibzlibrary/telebibz";
274
+
275
+ const bot = new Bot(process.env.TELEGRAM_BOT_TOKEN!);
276
+ const payments = new PaymentsClient(bot.api);
277
+
278
+ // Link yang berfungsi di mana saja (bio, website, chat)
279
+ const link = await payments.createInvoiceLink({
280
+ title: "Premium",
281
+ description: "30 hari premium",
282
+ payload: "premium-30d",
283
+ currency: "XTR",
284
+ prices: [{ label: "Premium", amount: 100 }],
285
+ });
286
+ await ctx.reply(`Bayar di sini: ${link}`);
287
+
288
+ // Invoice dalam chat + pre-checkout + pembayaran sukses
289
+ bot.on("pre_checkout_query", async (ctx) => {
290
+ const query = ctx.update.pre_checkout_query;
291
+ if (!query) return;
292
+ await ctx.api.methods.answerPreCheckoutQuery({ pre_checkout_query_id: query.id, ok: true });
293
+ });
294
+ bot.on("message:successful_payment", async (ctx) => {
295
+ await ctx.reply("✅ Pembayaran diterima. Terima kasih!");
296
+ });
297
+
298
+ // Riwayat Stars & refund
299
+ const history = await payments.getStarTransactions({ limit: 50 });
300
+ await payments.refundStarPayment({ user_id: userId, telegram_payment_charge_id: "charge-id" });
301
+ ```
302
+
303
+ ## 13. Logging terstruktur dan hook metrik
304
+
305
+ ```ts
306
+ const bot = new Bot({
307
+ token: process.env.TELEGRAM_BOT_TOKEN!,
308
+ logger: { level: "info", format: "json" }, // baris machine-readable untuk ingestion
309
+ });
310
+
311
+ bot.events.on("api:response", ({ method, durationMs }) => {
312
+ if (durationMs > 3_000) console.warn(JSON.stringify({ event: "slow_api", method, durationMs }));
313
+ });
314
+ bot.events.on("update:error", ({ error }) => {
315
+ console.error(JSON.stringify({ event: "handler_error", error: String(error) }));
316
+ });
317
+ ```
318
+
319
+ Nilai sensitif (token, nomor telepon) otomatis di-redact; `includeUpdateContent: true` mengaktifkan pencatatan teks pesan bila benar-benar diperlukan.
320
+
321
+ English: [COOKBOOK.md](COOKBOOK.md) · 简体中文: [COOKBOOK.zh-CN.md](COOKBOOK.zh-CN.md)