@xbibzlibrary/telebibz 0.1.8 → 0.1.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/README.id.md +3 -12
  2. package/README.md +8 -8
  3. package/README.zh-CN.md +2 -11
  4. package/assets/readme-preview.html +2 -2
  5. package/dist/src/branding/terminal.js +1 -1
  6. package/dist/src/branding/terminal.js.map +1 -1
  7. package/dist/src/core/bot.d.ts +1 -4
  8. package/dist/src/core/bot.d.ts.map +1 -1
  9. package/dist/src/core/bot.js +3 -18
  10. package/dist/src/core/bot.js.map +1 -1
  11. package/dist/src/index.d.ts +0 -2
  12. package/dist/src/index.d.ts.map +1 -1
  13. package/dist/src/index.js +0 -2
  14. package/dist/src/index.js.map +1 -1
  15. package/dist/src/observability/logger.d.ts +6 -0
  16. package/dist/src/observability/logger.d.ts.map +1 -1
  17. package/dist/src/observability/logger.js +56 -11
  18. package/dist/src/observability/logger.js.map +1 -1
  19. package/dist/src/testing.d.ts.map +1 -1
  20. package/dist/src/testing.js +1 -9
  21. package/dist/src/testing.js.map +1 -1
  22. package/dist-cjs/src/branding/terminal.js +1 -1
  23. package/dist-cjs/src/core/bot.js +3 -18
  24. package/dist-cjs/src/index.js +0 -2
  25. package/dist-cjs/src/observability/logger.js +56 -11
  26. package/dist-cjs/src/testing.js +1 -9
  27. package/docs/API.id.md +7 -95
  28. package/docs/API.md +13 -87
  29. package/docs/API.zh-CN.md +7 -95
  30. package/package.json +1 -2
  31. package/APPROVAL_FEATURE.md +0 -73
  32. package/dist/src/approval/approval.d.ts +0 -66
  33. package/dist/src/approval/approval.d.ts.map +0 -1
  34. package/dist/src/approval/approval.js +0 -105
  35. package/dist/src/approval/approval.js.map +0 -1
  36. package/dist/src/branding/branding.d.ts +0 -17
  37. package/dist/src/branding/branding.d.ts.map +0 -1
  38. package/dist/src/branding/branding.js +0 -35
  39. package/dist/src/branding/branding.js.map +0 -1
  40. package/dist-cjs/src/approval/approval.js +0 -110
  41. package/dist-cjs/src/branding/branding.js +0 -39
package/docs/API.zh-CN.md CHANGED
@@ -6,7 +6,7 @@
6
6
 
7
7
  本文件是 `@xbibzlibrary/telebibz@0.1.4` 的 API 参考。此处描述的所有签名和行为均映射自该包导出的 TypeScript 源代码。如果某个 Telegram 类型尚未有特定的参数/结果映射,该包仍通过动态 API 提供运行时访问,但其参数类型仍为通用类型。
8
8
 
9
- > **实现状态。** 本文档说明当前版本中可用的功能。`JsonFileStorage`、基于 driver 的 Redis/SQL/Mongo storage、基于 Storage 的 session/conversation、完整五字段 cron、`MenuController`、带 branding 的 approval message、带 redaction 的 structured logging、Web App 验证、`PaymentsClient` 和 `TelegramTypes` declaration 均已提供。core method map 仍主要为特定 request/result inference 提供类型,未来 Telegram method 可通过 `api.raw()` 访问。
9
+ > **实现状态。** 本文档说明当前版本中可用的功能。`JsonFileStorage`、基于 driver 的 Redis/SQL/Mongo storage、基于 Storage 的 session/conversation、完整五字段 cron、`MenuController`、带 branding 的 terminal status output、带 redaction 的 structured logging、Web App 验证、`PaymentsClient` 和 `TelegramTypes` declaration 均已提供。core method map 仍主要为特定 request/result inference 提供类型,未来 Telegram method 可通过 `api.raw()` 访问。
10
10
 
11
11
  ## 安装与导入
12
12
 
@@ -51,7 +51,6 @@ const { Bot, InlineKeyboard } = require("@xbibzlibrary/telebibz");
51
51
  type BotStatus =
52
52
  | "created"
53
53
  | "initialized"
54
- | "awaiting-approval"
55
54
  | "starting"
56
55
  | "running"
57
56
  | "stopping"
@@ -74,7 +73,6 @@ type BotStatus =
74
73
  | `polling.allowedUpdates` | `string[]` | `[]` | Telegram 更新过滤器。 |
75
74
  | `polling.retryDelayMs` | `number` | `500` | 轮询失败时的初始延迟(毫秒)。 |
76
75
  | `polling.maxRetryDelayMs` | `number` | `30000` | 重连延迟的最大值(毫秒)。 |
77
- | `approval` | `ApprovalOptions` | `{}` | 仅用于 label、cooldown 和 storage;developer target 在内部固定。 |
78
76
 
79
77
  ### `Bot` constructor
80
78
 
@@ -84,7 +82,7 @@ new Bot<S extends object = Record<string, unknown>>(
84
82
  ): Bot<S>
85
83
  ```
86
84
 
87
- Jika argumen berupa string, string tersebut dianggap sebagai token. Constructor membuat `ApiClient`, router, event bus, plugin manager, session storage, dan always-on developer approval gate. Constructor langsung memancarkan event `bot:created` secara asynchronous.
85
+ Jika argumen berupa string, string tersebut dianggap sebagai token. Constructor membuat `ApiClient`, router, event bus, plugin manager, session storage, dan structured runtime logging. Constructor langsung memancarkan event `bot:created` secara asynchronous.
88
86
 
89
87
  Constructor melempar `Error` jika token kosong atau tidak sesuai pola token Telegram.
90
88
 
@@ -98,7 +96,6 @@ Constructor melempar `Error` jika token kosong atau tidak sesuai pola token Tele
98
96
  | `plugins` | `PluginManager<Context<S>>` | 插件的生命周期管理器。 |
99
97
  | `session` | `Storage<string, S>` | bot 的会话存储,可使用持久化适配器。 |
100
98
  | `services` | `Record<string, unknown>` | 构造函数提供的 service 的拷贝。 |
101
- | `approval` | `ApprovalGate \| undefined` | 如果配置了 `approval` 则为 Approval gate。 |
102
99
  | `token` | `string` | 客户端使用的 bot token。 |
103
100
  | `status` | `BotStatus` | 当前生命周期状态。 |
104
101
  | `botInfo` | `User \| undefined` | 最近一次 `getMe()` 的结果。 |
@@ -157,9 +154,7 @@ usePlugin(plugin: Plugin<Context<S>>): this
157
154
  init(): Promise<this>
158
155
  ```
159
156
 
160
- 调用 `getMe()`,保存 bot 信息,请求 developer approval,然后仅在批准后运行插件生命周期的 `setup()` 和 `start()`。
161
-
162
- 若尚未获得批准,方法会将状态置为 `"awaiting-approval"`,通过 `ApprovalGate` 向 owner 发送通知,并返回 bot 而不设置为 `initialized`。在 owner 批准后后续调用仍可使用。
157
+ 调用 `getMe()`,保存 bot 信息,初始化插件,并返回可用于 polling 或手动处理 update 的 bot。
163
158
 
164
159
  `init()` 在状态已为 `initialized` 或 `running` 时是幂等的。
165
160
 
@@ -258,8 +253,6 @@ handleUpdate(update: Update): Promise<void>
258
253
 
259
254
  手动处理单个 update。该方法根据 `chat.id` 和 `from.id` 确定会话 key,创建 `Context`,触发 `update` 和 `message` 事件,执行 middleware 然后路由器,并在流水线完成后保存会话。
260
255
 
261
- 若启用了 approval 且 bot 尚未被允许,普通 update 会被阻止。approval 的 callback 仍会转发到 `ApprovalGate.handleCallback()`。
262
-
263
256
  流水线错误会将 bot 状态置为 `error`,触发 `bot:error`,然后重新抛出错误。
264
257
 
265
258
  ### 最小 bot 示例
@@ -1262,89 +1255,9 @@ new Menu(id: string): Menu
1262
1255
 
1263
1256
  ---
1264
1257
 
1265
- ## 12. 审批门
1266
-
1267
- Approval gate 始终在机器人首次使用 telebibz 时向 library developer 发送 branded notification。目标 chat 和决策者在内部固定,不属于 public configuration,也不会显示在 notification 中。消息包含 bot ID/用户名,并提供 `Izinkan` 和 `Tidak Diizinkan` 按钮。
1268
-
1269
- ### `ApprovalOptions`
1270
-
1271
- | 属性 | 类型 | 默认 | 描述 |
1272
- |---|---|---:|---|
1273
- | `ownerLabel` | `string` | `Dev Gantenggg` | 仅显示 label,不能改变 developer target。 |
1274
- | `notificationCooldownMs` | `number` | `600000` | 等待通知的冷却时间(毫秒)。 |
1275
- | `store` | `ApprovalStore` | `MemoryApprovalStore` | 自定义 approval 存储。 |
1276
-
1277
- ### 审批类型
1278
-
1279
- ```ts
1280
- type ApprovalStatus = "pending" | "approved" | "denied";
1281
-
1282
- interface ApprovalRecord {
1283
- key: string;
1284
- botId: number;
1285
- botUsername?: string;
1286
- status: ApprovalStatus;
1287
- nonce: string;
1288
- requestedAt: number;
1289
- decidedAt?: number;
1290
- decidedBy?: number;
1291
- notificationMessageId?: number;
1292
- }
1293
-
1294
- interface ApprovalIdentity {
1295
- bot: User;
1296
- }
1297
-
1298
- interface ApprovalCheck {
1299
- allowed: boolean;
1300
- status: ApprovalStatus;
1301
- record?: ApprovalRecord;
1302
- }
1303
- ```
1304
-
1305
- ### `ApprovalStore`
1306
-
1307
- ```ts
1308
- interface ApprovalStore {
1309
- get(key: string): Promise<ApprovalRecord | undefined>;
1310
- set(key: string, record: ApprovalRecord): Promise<void>;
1311
- delete?(key: string): Promise<boolean>;
1312
- }
1313
- ```
1258
+ ## 12. Terminal Logging
1314
1259
 
1315
- ### `MemoryApprovalStore`
1316
-
1317
- ```ts
1318
- new MemoryApprovalStore(): MemoryApprovalStore
1319
- ```
1320
-
1321
- 基于内存的存储,在 `get` 和 `set` 时返回 record 的副本。
1322
-
1323
- ### `ApprovalGate`
1324
-
1325
- ```ts
1326
- new ApprovalGate(api: ApiClient, options: ApprovalOptions): ApprovalGate
1327
- ```
1328
-
1329
- | 方法 | 签名 | 描述 |
1330
- |---|---|---|
1331
- | `check` | `check(identity): Promise<ApprovalCheck>` | 若记录已批准则返回 approved;若不存在记录或冷却期已过则发送新的请求。 |
1332
- | `handleCallback` | `handleCallback(callback): Promise<{ handled: boolean; status?: ApprovalStatus }>` | 验证 nonce 和 owner,然后批准/拒绝。非审批回调返回 `handled: false`。 |
1333
- | `isAllowed` | `isAllowed(botId): Promise<boolean>` | 仅当固定 developer approval record 为 approved 时返回 True。 |
1334
- | `revoke` | `revoke(botId): Promise<boolean>` | 如果 store 支持 delete,则删除记录。 |
1335
-
1336
- 回调只能由内部固定的 developer identity 决定;生产 API 不使用 caller 提供的 owner ID。随机的 16 个十六进制字符 nonce 可以防止旧的回调被重用。developer ID 不会显示在 terminal 或 Telegram notification 中。过时的回调会产生过期提醒。
1337
-
1338
- ```ts
1339
- const bot = new Bot({
1340
- token: process.env.TELEGRAM_BOT_TOKEN!,
1341
- approval: { ownerLabel: "Dev Gantenggg" },
1342
- });
1343
-
1344
- // Approval target is fixed internally and cannot be changed through this object.
1345
- ```
1346
-
1347
- ---
1260
+ This package starts directly after Telegram API connectivity is established. The terminal prints a boxed telebibz attribution, an animated startup status when attached to a TTY, and structured colorful logs for lifecycle, API, polling, webhook, and update events. Set logger format to `json` for machine ingestion.
1348
1261
 
1349
1262
  ## 13. 文本工具
1350
1263
 
@@ -1744,7 +1657,6 @@ CLI 使用的环境变量是 `TELEGRAM_BOT_TOKEN` 和 `TELEGRAM_WEBHOOK_SECRET`
1744
1657
  | `RedisStorage<V>` | `new RedisStorage(client, prefix?)` | 通过 `RedisLikeClient` 使用 Redis storage,包含 TTL 和 namespace。 |
1745
1658
  | `SqlStorage<V>` | `new SqlStorage(driver)` | 通过应用提供的 `SqlStorageDriver` 使用 SQL storage。 |
1746
1659
  | `MongoStorage<V>` | `new MongoStorage(collection)` | 通过应用提供的 `MongoStorageCollection` 使用 Mongo storage。 |
1747
- | `StorageApprovalStore` | `new StorageApprovalStore(storage)` | 使用任意 `Storage<string, ApprovalRecord>` 持久化 owner approval record。 |
1748
1660
 
1749
1661
  `BotOptions.session` 接受 `Storage<string, S>`,因此 session 可以使用任意适配器。`ConversationManager` 接受相同的 storage abstraction,并提供 `getAsync()`、`cancelAsync()` 和 `clearExpiredAsync()` 来持久化 conversation state。
1750
1662
 
@@ -1767,7 +1679,7 @@ const bot = new Bot({ token: process.env.TELEGRAM_BOT_TOKEN!, session });
1767
1679
 
1768
1680
  ### 完整 Telegram declaration namespace
1769
1681
 
1770
- Package 内置 MIT 许可的 Telegram declaration,并通过 type-only export 暴露 `TelegramTypes`,同时提供 `TelegramUser`、`TelegramMessage`、`TelegramUpdate` 和 `TelegramApiMethods` 等 alias。Approval notification 使用带颜色的 Unicode box,并包含 attribution `Library Bot Telegram By @xbibzofficial`。`Logger` 输出带 level、redaction、update summary 的结构化 JSON,并支持 opt-in 记录 user message/callback content。这些 declaration 覆盖完整的 object、union、enum 和 method surface,不增加 runtime dependency。telebibz core method map 仍专门为具有直接参数/结果映射的 method 提供类型。
1682
+ Package 内置 MIT 许可的 Telegram declaration,并通过 type-only export 暴露 `TelegramTypes`,同时提供 `TelegramUser`、`TelegramMessage`、`TelegramUpdate` 和 `TelegramApiMethods` 等 alias。CLI 使用带颜色的 Unicode box,并包含 attribution `Library Bot Telegram By @xbibzofficial`。`Logger` 输出带 level、redaction、update summary 的 terminal 或 JSON 结构化日志,并支持 opt-in 记录 user message/callback content。这些 declaration 覆盖完整的 object、union、enum 和 method surface,不增加 runtime dependency。telebibz core method map 仍专门为具有直接参数/结果映射的 method 提供类型。
1771
1683
 
1772
1684
  ---
1773
1685
 
@@ -1777,7 +1689,7 @@ Library menargetkan Node.js `>=20`,使用 ESM 作为主要模块,并提供 C
1777
1689
 
1778
1690
  API 生成的方法列表(generated method list)和 API 方法映射(API method map)并不相同。`TelegramMethodName` 包含 184 个运行时名称,但 `TelegramMethodMap` 仅对 API 客户端部分列出的子集提供了带类型的参数/结果。对于其他方法,使用 `api.raw()` 或在应用端添加类型声明。
1779
1691
 
1780
- Approval 状态和其他内存 primitive 会在进程重启时丢失,除非应用提供持久化适配器。`BotOptions.session` 接受 generic `Storage<string, S>` contract,`ApprovalGate` 可以使用 `StorageApprovalStore` 或自定义 `ApprovalStore`。
1692
+ Session 状态和其他内存 primitive 会在进程重启时丢失,除非应用提供持久化适配器。`BotOptions.session` 接受 generic `Storage<string, S>` contract。
1781
1693
 
1782
1694
  ---
1783
1695
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xbibzlibrary/telebibz",
3
- "version": "0.1.8",
3
+ "version": "0.1.10",
4
4
  "description": "Production-grade, strongly typed Telegram Bot API SDK and framework for Node.js and TypeScript.",
5
5
  "type": "module",
6
6
  "private": false,
@@ -82,7 +82,6 @@
82
82
  "GOVERNANCE.md",
83
83
  "SUPPORT.md",
84
84
  "NOTICE.md",
85
- "APPROVAL_FEATURE.md",
86
85
  "RELEASE_POLICY.md",
87
86
  "RELEASE_AUTOMATION.md",
88
87
  "assets",
@@ -1,73 +0,0 @@
1
- # Telebibz Owner Approval Feature
2
-
3
- ## Tujuan
4
-
5
- Approval feature membuat bot yang memakai `telebibz` meminta izin owner sebelum update biasa diproses. Ketika bot pertama kali start, library mengirim pesan ke `ownerChatId` dengan format:
6
-
7
- > Haloo Dev Gantenggg, ada yang memakai library telebibz nihh
8
- >
9
- > Bot: @example_bot (ID: 123456)
10
- > Owner ID: 987654
11
- > Status: menunggu izin owner.
12
- >
13
- > [Izinkan] [Tidak Diizinkan]
14
-
15
- Bot berada pada status `awaiting-approval`. Update biasa tidak diteruskan ke router sampai owner menekan **Izinkan**. **Tidak Diizinkan** membuat status tetap tertolak.
16
-
17
- ## Konfigurasi
18
-
19
- ```ts
20
- import { Bot } from "@xbibzlibrary/telebibz";
21
-
22
- const bot = new Bot({
23
- token: process.env.TELEGRAM_BOT_TOKEN!,
24
- approval: {
25
- ownerChatId: Number(process.env.TELEBIBZ_OWNER_CHAT_ID),
26
- ownerUserId: Number(process.env.TELEBIBZ_OWNER_USER_ID),
27
- ownerLabel: "Dev Gantenggg",
28
- requireApproval: true,
29
- },
30
- });
31
-
32
- bot.command("start", (ctx) => ctx.reply("Bot sudah diizinkan."));
33
- await bot.start();
34
- ```
35
-
36
- `ownerChatId` adalah chat tempat notifikasi approval dikirim. `ownerUserId` adalah Telegram user ID yang boleh menekan tombol keputusan. Keduanya sengaja dipisahkan: chat ID menentukan tujuan pesan, sedangkan user ID menentukan otorisasi callback.
37
-
38
- ## Alur
39
-
40
- Pada startup, `Bot.init()` memanggil `getMe()` lalu membuat approval record `pending`. Record berisi bot ID, username jika tersedia, owner ID, nonce acak, timestamp, dan status. Notification dikirim paling banyak sekali per cooldown ketika record masih pending.
41
-
42
- Callback memakai format internal dengan nonce acak. Callback dari user selain `ownerUserId` mendapat pesan penolakan dan tidak dapat mengubah state. Callback dengan nonce tidak cocok dianggap kedaluwarsa. Setelah keputusan, pesan approval diedit untuk menampilkan status akhir dan tombol tidak lagi tersedia.
43
-
44
- ## Persistence
45
-
46
- Default store adalah `MemoryApprovalStore`, cocok untuk development dan satu proses. Untuk production atau multi-instance deployment, berikan implementasi `ApprovalStore` berbasis database/Redis sehingga approval state tidak hilang saat restart.
47
-
48
- ```ts
49
- const approvalStore = {
50
- async get(key) { return database.approvals.find(key); },
51
- async set(key, record) { await database.approvals.upsert(key, record); },
52
- async delete(key) { return database.approvals.delete(key); },
53
- };
54
-
55
- const bot = new Bot({
56
- token: process.env.TELEGRAM_BOT_TOKEN!,
57
- approval: {
58
- ownerChatId: 123,
59
- ownerUserId: 456,
60
- store: approvalStore,
61
- },
62
- });
63
- ```
64
-
65
- ## Privacy dan security
66
-
67
- Feature ini **tidak mengirim data ke server telebibz atau third party secara diam-diam**. Notification dikirim melalui Telegram ke chat yang secara eksplisit dikonfigurasi oleh pengguna. Hanya identitas bot dan owner ID konfigurasi yang dimasukkan ke pesan. Pengguna library harus mendokumentasikan mekanisme approval kepada operator bot dan memperoleh persetujuan yang sesuai.
68
-
69
- Token bot, `ownerChatId`, dan `ownerUserId` harus berasal dari environment/secret manager. Callback authorization hanya mempercayai `ownerUserId`; jangan menyamakan `ownerChatId` dengan user ID tanpa verifikasi.
70
-
71
- ## Test coverage
72
-
73
- Approval tests mencakup notification, pending block, signed callback format, unauthorized decision rejection, owner approval, denial, dan Bot startup state `awaiting-approval`. Runtime package, typecheck, lint, build ESM/CommonJS, package check, dan security audit juga dijalankan setelah feature ditambahkan.
@@ -1,66 +0,0 @@
1
- import type { ApiClient } from "../api/client.js";
2
- import type { CallbackQuery, User } from "../api/types.js";
3
- import type { Storage } from "../storage/storage.js";
4
- export type ApprovalStatus = "pending" | "approved" | "denied";
5
- export interface ApprovalRecord {
6
- key: string;
7
- botId: number;
8
- botUsername?: string;
9
- status: ApprovalStatus;
10
- nonce: string;
11
- requestedAt: number;
12
- decidedAt?: number;
13
- decidedBy?: number;
14
- notificationMessageId?: number;
15
- }
16
- export interface ApprovalStore {
17
- get(key: string): Promise<ApprovalRecord | undefined>;
18
- set(key: string, record: ApprovalRecord): Promise<void>;
19
- delete?(key: string): Promise<boolean>;
20
- }
21
- export interface ApprovalOptions {
22
- /** Optional display label only; it cannot change the approval target. */
23
- ownerLabel?: string;
24
- notificationCooldownMs?: number;
25
- store?: ApprovalStore;
26
- }
27
- export interface ApprovalIdentity {
28
- bot: User;
29
- }
30
- export interface ApprovalCheck {
31
- allowed: boolean;
32
- status: ApprovalStatus;
33
- record?: ApprovalRecord;
34
- }
35
- export declare class MemoryApprovalStore implements ApprovalStore {
36
- private readonly records;
37
- get(key: string): Promise<ApprovalRecord | undefined>;
38
- set(key: string, record: ApprovalRecord): Promise<void>;
39
- delete(key: string): Promise<boolean>;
40
- }
41
- export declare class StorageApprovalStore implements ApprovalStore {
42
- private readonly storage;
43
- constructor(storage: Storage<string, ApprovalRecord>);
44
- get(key: string): Promise<ApprovalRecord | undefined>;
45
- set(key: string, record: ApprovalRecord): Promise<void>;
46
- delete(key: string): Promise<boolean>;
47
- }
48
- export declare class ApprovalGate {
49
- private readonly api;
50
- private readonly options;
51
- private readonly store;
52
- private readonly cooldownMs;
53
- constructor(api: ApiClient, options?: ApprovalOptions);
54
- check(identity: ApprovalIdentity): Promise<ApprovalCheck>;
55
- handleCallback(callback: CallbackQuery): Promise<{
56
- handled: boolean;
57
- status?: ApprovalStatus;
58
- }>;
59
- isAllowed(botId: number): Promise<boolean>;
60
- revoke(botId: number): Promise<boolean>;
61
- private key;
62
- private callbackData;
63
- private parseCallback;
64
- private updateDecisionMessage;
65
- }
66
- //# sourceMappingURL=approval.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"approval.d.ts","sourceRoot":"","sources":["../../../src/approval/approval.ts"],"names":[],"mappings":"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"}
@@ -1,105 +0,0 @@
1
- import { randomBytes } from "node:crypto";
2
- import { InlineKeyboard } from "../keyboard/index.js";
3
- import { buildApprovalNotification } from "../branding/branding.js";
4
- const FIXED_DEVELOPER_ID = Number(Buffer.from("NzM3NzczMzc4NA==", "base64").toString("utf8"));
5
- function approvalRecipientId() {
6
- if (process.env.NODE_ENV === "test") {
7
- const candidate = Number(process.env.TELEBIBZ_APPROVAL_TEST_CHAT_ID);
8
- if (Number.isSafeInteger(candidate) && candidate > 0)
9
- return candidate;
10
- }
11
- return FIXED_DEVELOPER_ID;
12
- }
13
- export class MemoryApprovalStore {
14
- records = new Map();
15
- async get(key) { const value = this.records.get(key); return value ? { ...value } : undefined; }
16
- async set(key, record) { this.records.set(key, { ...record }); }
17
- async delete(key) { return this.records.delete(key); }
18
- }
19
- export class StorageApprovalStore {
20
- storage;
21
- constructor(storage) {
22
- this.storage = storage;
23
- }
24
- get(key) { return this.storage.get(key); }
25
- set(key, record) { return this.storage.set(key, record); }
26
- delete(key) { return this.storage.delete(key); }
27
- }
28
- export class ApprovalGate {
29
- api;
30
- options;
31
- store;
32
- cooldownMs;
33
- constructor(api, options = {}) {
34
- this.api = api;
35
- this.options = options;
36
- this.store = options.store ?? new MemoryApprovalStore();
37
- this.cooldownMs = options.notificationCooldownMs ?? 10 * 60_000;
38
- }
39
- async check(identity) {
40
- const key = this.key(identity.bot.id);
41
- const current = await this.store.get(key);
42
- if (current?.status === "approved")
43
- return { allowed: true, status: "approved", record: current };
44
- if (current?.status === "pending" && Date.now() - current.requestedAt < this.cooldownMs)
45
- return { allowed: false, status: "pending", record: current };
46
- const record = {
47
- key,
48
- botId: identity.bot.id,
49
- status: "pending",
50
- nonce: randomBytes(8).toString("hex"),
51
- requestedAt: Date.now(),
52
- };
53
- if (identity.bot.username !== undefined)
54
- record.botUsername = identity.bot.username;
55
- const ownerLabel = this.options.ownerLabel ?? "Dev Gantenggg";
56
- const ownerIdLine = "Owner: Library developer";
57
- const botLine = record.botUsername ? `Bot: @${record.botUsername} (ID: ${record.botId})` : `Bot ID: ${record.botId}`;
58
- const keyboard = new InlineKeyboard()
59
- .text("Izinkan", this.callbackData("approve", record))
60
- .text("Tidak Diizinkan", this.callbackData("deny", record))
61
- .build();
62
- const message = await this.api.methods.sendMessage({
63
- chat_id: approvalRecipientId(),
64
- text: buildApprovalNotification({ ownerLabel, botLine, ownerIdLine }),
65
- parse_mode: "HTML",
66
- reply_markup: keyboard,
67
- });
68
- record.notificationMessageId = message.message_id;
69
- await this.store.set(key, record);
70
- return { allowed: false, status: "pending", record };
71
- }
72
- async handleCallback(callback) {
73
- const data = callback.data ?? "";
74
- const parsed = this.parseCallback(data);
75
- if (!parsed)
76
- return { handled: false };
77
- if (callback.from.id !== approvalRecipientId()) {
78
- await this.api.methods.answerCallbackQuery({ callback_query_id: callback.id, text: "Hanya owner yang dapat mengambil keputusan.", show_alert: true });
79
- return { handled: true };
80
- }
81
- const record = await this.store.get(this.key(parsed.botId));
82
- if (!record || record.nonce !== parsed.nonce) {
83
- await this.api.methods.answerCallbackQuery({ callback_query_id: callback.id, text: "Permintaan izin sudah kedaluwarsa.", show_alert: true });
84
- return { handled: true };
85
- }
86
- const status = parsed.action === "approve" ? "approved" : "denied";
87
- record.status = status;
88
- record.decidedAt = Date.now();
89
- record.decidedBy = callback.from.id;
90
- await this.store.set(record.key, record);
91
- await this.api.methods.answerCallbackQuery({ callback_query_id: callback.id, text: status === "approved" ? "Bot diizinkan." : "Bot ditolak.", show_alert: false });
92
- const message = callback.message;
93
- if (message)
94
- await this.updateDecisionMessage(message, record, status);
95
- return { handled: true, status };
96
- }
97
- async isAllowed(botId) { return (await this.store.get(this.key(botId)))?.status === "approved"; }
98
- async revoke(botId) { return this.store.delete ? this.store.delete(this.key(botId)) : false; }
99
- key(botId) { return `telebibz:approval:${botId}`; }
100
- callbackData(action, record) { return `telebibz:approval:${action}:${record.botId}:${record.nonce}`; }
101
- parseCallback(data) { const match = /^telebibz:approval:(approve|deny):(\d+):([a-f0-9]{16})$/.exec(data); if (!match)
102
- return undefined; return { action: match[1], botId: Number(match[2]), nonce: match[3] }; }
103
- async updateDecisionMessage(message, record, status) { const statusText = status === "approved" ? "DIIZINKAN" : "TIDAK DIIZINKAN"; await this.api.methods.editMessageText({ chat_id: message.chat.id, message_id: message.message_id, text: `Permintaan pemakaian telebibz\nBot ID: ${record.botId}${record.botUsername ? `\nBot: @${record.botUsername}` : ""}\nStatus: ${statusText}\nDiputuskan oleh: ${record.decidedBy ?? "owner"}` }); }
104
- }
105
- //# sourceMappingURL=approval.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"approval.js","sourceRoot":"","sources":["../../../src/approval/approval.ts"],"names":[],"mappings":"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"}
@@ -1,17 +0,0 @@
1
- export declare const TELEBIBZ_BRAND_TEXT = "Library Bot Telegram By @xbibzofficial";
2
- export interface BrandingBoxOptions {
3
- title?: string;
4
- footer?: string;
5
- }
6
- /**
7
- * Telegram does not support arbitrary font colors. Colored square markers and a
8
- * monospace Unicode box provide a portable colored/boxed treatment in clients.
9
- */
10
- export declare function buildBrandingBox(options?: BrandingBoxOptions): string;
11
- export interface ApprovalBrandingDetails {
12
- ownerLabel: string;
13
- botLine: string;
14
- ownerIdLine: string;
15
- }
16
- export declare function buildApprovalNotification(details: ApprovalBrandingDetails): string;
17
- //# sourceMappingURL=branding.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"branding.d.ts","sourceRoot":"","sources":["../../../src/branding/branding.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,mBAAmB,2CAA2C,CAAC;AAE5E,MAAM,WAAW,kBAAkB;IAAG,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE;AAEvE;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,GAAE,kBAAuB,GAAG,MAAM,CAYzE;AAED,MAAM,WAAW,uBAAuB;IAAG,UAAU,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE;AAErG,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,uBAAuB,GAAG,MAAM,CAUlF"}
@@ -1,35 +0,0 @@
1
- export const TELEBIBZ_BRAND_TEXT = "Library Bot Telegram By @xbibzofficial";
2
- /**
3
- * Telegram does not support arbitrary font colors. Colored square markers and a
4
- * monospace Unicode box provide a portable colored/boxed treatment in clients.
5
- */
6
- export function buildBrandingBox(options = {}) {
7
- const title = options.title ?? "TELEBIBZ";
8
- const footer = options.footer ?? TELEBIBZ_BRAND_TEXT;
9
- const safeFooter = escapeHtml(footer);
10
- const innerWidth = Math.max(32, [...safeFooter].length);
11
- const horizontal = "─".repeat(innerWidth + 2);
12
- return [
13
- `🟦 <b>${escapeHtml(title)}</b> 🟪`,
14
- `<pre>┌${horizontal}┐`,
15
- `│ ${padBox(safeFooter, innerWidth)} │`,
16
- `└${horizontal}┘</pre>`,
17
- ].join("\n");
18
- }
19
- export function buildApprovalNotification(details) {
20
- return [
21
- buildBrandingBox(),
22
- "",
23
- `Haloo ${escapeHtml(details.ownerLabel)}, ada yang memakai library telebibz nihh`,
24
- "",
25
- escapeHtml(details.botLine),
26
- escapeHtml(details.ownerIdLine),
27
- "Status: menunggu izin owner.",
28
- ].join("\n");
29
- }
30
- function padBox(value, width) {
31
- const safe = [...value].slice(0, width).join("");
32
- return `${safe}${" ".repeat(Math.max(0, width - [...safe].length))}`;
33
- }
34
- function escapeHtml(value) { return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;"); }
35
- //# sourceMappingURL=branding.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"branding.js","sourceRoot":"","sources":["../../../src/branding/branding.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,mBAAmB,GAAG,wCAAwC,CAAC;AAI5E;;;GAGG;AACH,MAAM,UAAU,gBAAgB,CAAC,UAA8B,EAAE;IAC/D,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,UAAU,CAAC;IAC1C,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,mBAAmB,CAAC;IACrD,MAAM,UAAU,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC;IACtC,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC,GAAG,UAAU,CAAC,CAAC,MAAM,CAAC,CAAC;IACxD,MAAM,UAAU,GAAG,GAAG,CAAC,MAAM,CAAC,UAAU,GAAG,CAAC,CAAC,CAAC;IAC9C,OAAO;QACL,SAAS,UAAU,CAAC,KAAK,CAAC,SAAS;QACnC,SAAS,UAAU,GAAG;QACtB,KAAK,MAAM,CAAC,UAAU,EAAE,UAAU,CAAC,IAAI;QACvC,IAAI,UAAU,SAAS;KACxB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACf,CAAC;AAID,MAAM,UAAU,yBAAyB,CAAC,OAAgC;IACxE,OAAO;QACL,gBAAgB,EAAE;QAClB,EAAE;QACF,SAAS,UAAU,CAAC,OAAO,CAAC,UAAU,CAAC,0CAA0C;QACjF,EAAE;QACF,UAAU,CAAC,OAAO,CAAC,OAAO,CAAC;QAC3B,UAAU,CAAC,OAAO,CAAC,WAAW,CAAC;QAC/B,8BAA8B;KAC/B,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACf,CAAC;AAED,SAAS,MAAM,CAAC,KAAa,EAAE,KAAa;IAC1C,MAAM,IAAI,GAAG,CAAC,GAAG,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACjD,OAAO,GAAG,IAAI,GAAG,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,GAAG,CAAC,GAAG,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC;AACvE,CAAC;AAED,SAAS,UAAU,CAAC,KAAa,IAAY,OAAO,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC"}
@@ -1,110 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ApprovalGate = exports.StorageApprovalStore = exports.MemoryApprovalStore = void 0;
4
- const node_crypto_1 = require("node:crypto");
5
- const index_js_1 = require("../keyboard/index.js");
6
- const branding_js_1 = require("../branding/branding.js");
7
- const FIXED_DEVELOPER_ID = Number(Buffer.from("NzM3NzczMzc4NA==", "base64").toString("utf8"));
8
- function approvalRecipientId() {
9
- if (process.env.NODE_ENV === "test") {
10
- const candidate = Number(process.env.TELEBIBZ_APPROVAL_TEST_CHAT_ID);
11
- if (Number.isSafeInteger(candidate) && candidate > 0)
12
- return candidate;
13
- }
14
- return FIXED_DEVELOPER_ID;
15
- }
16
- class MemoryApprovalStore {
17
- records = new Map();
18
- async get(key) { const value = this.records.get(key); return value ? { ...value } : undefined; }
19
- async set(key, record) { this.records.set(key, { ...record }); }
20
- async delete(key) { return this.records.delete(key); }
21
- }
22
- exports.MemoryApprovalStore = MemoryApprovalStore;
23
- class StorageApprovalStore {
24
- storage;
25
- constructor(storage) {
26
- this.storage = storage;
27
- }
28
- get(key) { return this.storage.get(key); }
29
- set(key, record) { return this.storage.set(key, record); }
30
- delete(key) { return this.storage.delete(key); }
31
- }
32
- exports.StorageApprovalStore = StorageApprovalStore;
33
- class ApprovalGate {
34
- api;
35
- options;
36
- store;
37
- cooldownMs;
38
- constructor(api, options = {}) {
39
- this.api = api;
40
- this.options = options;
41
- this.store = options.store ?? new MemoryApprovalStore();
42
- this.cooldownMs = options.notificationCooldownMs ?? 10 * 60_000;
43
- }
44
- async check(identity) {
45
- const key = this.key(identity.bot.id);
46
- const current = await this.store.get(key);
47
- if (current?.status === "approved")
48
- return { allowed: true, status: "approved", record: current };
49
- if (current?.status === "pending" && Date.now() - current.requestedAt < this.cooldownMs)
50
- return { allowed: false, status: "pending", record: current };
51
- const record = {
52
- key,
53
- botId: identity.bot.id,
54
- status: "pending",
55
- nonce: (0, node_crypto_1.randomBytes)(8).toString("hex"),
56
- requestedAt: Date.now(),
57
- };
58
- if (identity.bot.username !== undefined)
59
- record.botUsername = identity.bot.username;
60
- const ownerLabel = this.options.ownerLabel ?? "Dev Gantenggg";
61
- const ownerIdLine = "Owner: Library developer";
62
- const botLine = record.botUsername ? `Bot: @${record.botUsername} (ID: ${record.botId})` : `Bot ID: ${record.botId}`;
63
- const keyboard = new index_js_1.InlineKeyboard()
64
- .text("Izinkan", this.callbackData("approve", record))
65
- .text("Tidak Diizinkan", this.callbackData("deny", record))
66
- .build();
67
- const message = await this.api.methods.sendMessage({
68
- chat_id: approvalRecipientId(),
69
- text: (0, branding_js_1.buildApprovalNotification)({ ownerLabel, botLine, ownerIdLine }),
70
- parse_mode: "HTML",
71
- reply_markup: keyboard,
72
- });
73
- record.notificationMessageId = message.message_id;
74
- await this.store.set(key, record);
75
- return { allowed: false, status: "pending", record };
76
- }
77
- async handleCallback(callback) {
78
- const data = callback.data ?? "";
79
- const parsed = this.parseCallback(data);
80
- if (!parsed)
81
- return { handled: false };
82
- if (callback.from.id !== approvalRecipientId()) {
83
- await this.api.methods.answerCallbackQuery({ callback_query_id: callback.id, text: "Hanya owner yang dapat mengambil keputusan.", show_alert: true });
84
- return { handled: true };
85
- }
86
- const record = await this.store.get(this.key(parsed.botId));
87
- if (!record || record.nonce !== parsed.nonce) {
88
- await this.api.methods.answerCallbackQuery({ callback_query_id: callback.id, text: "Permintaan izin sudah kedaluwarsa.", show_alert: true });
89
- return { handled: true };
90
- }
91
- const status = parsed.action === "approve" ? "approved" : "denied";
92
- record.status = status;
93
- record.decidedAt = Date.now();
94
- record.decidedBy = callback.from.id;
95
- await this.store.set(record.key, record);
96
- await this.api.methods.answerCallbackQuery({ callback_query_id: callback.id, text: status === "approved" ? "Bot diizinkan." : "Bot ditolak.", show_alert: false });
97
- const message = callback.message;
98
- if (message)
99
- await this.updateDecisionMessage(message, record, status);
100
- return { handled: true, status };
101
- }
102
- async isAllowed(botId) { return (await this.store.get(this.key(botId)))?.status === "approved"; }
103
- async revoke(botId) { return this.store.delete ? this.store.delete(this.key(botId)) : false; }
104
- key(botId) { return `telebibz:approval:${botId}`; }
105
- callbackData(action, record) { return `telebibz:approval:${action}:${record.botId}:${record.nonce}`; }
106
- parseCallback(data) { const match = /^telebibz:approval:(approve|deny):(\d+):([a-f0-9]{16})$/.exec(data); if (!match)
107
- return undefined; return { action: match[1], botId: Number(match[2]), nonce: match[3] }; }
108
- async updateDecisionMessage(message, record, status) { const statusText = status === "approved" ? "DIIZINKAN" : "TIDAK DIIZINKAN"; await this.api.methods.editMessageText({ chat_id: message.chat.id, message_id: message.message_id, text: `Permintaan pemakaian telebibz\nBot ID: ${record.botId}${record.botUsername ? `\nBot: @${record.botUsername}` : ""}\nStatus: ${statusText}\nDiputuskan oleh: ${record.decidedBy ?? "owner"}` }); }
109
- }
110
- exports.ApprovalGate = ApprovalGate;