@xbibzlibrary/telebibz 0.1.10 → 0.1.12

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 (53) hide show
  1. package/README.id.md +2 -2
  2. package/README.md +21 -1
  3. package/README.zh-CN.md +3 -3
  4. package/dist/src/api/client.d.ts +1 -1
  5. package/dist/src/api/client.d.ts.map +1 -1
  6. package/dist/src/api/client.js +13 -1
  7. package/dist/src/api/client.js.map +1 -1
  8. package/dist/src/api/transport.d.ts.map +1 -1
  9. package/dist/src/api/transport.js +45 -4
  10. package/dist/src/api/transport.js.map +1 -1
  11. package/dist/src/core/bot.d.ts +10 -0
  12. package/dist/src/core/bot.d.ts.map +1 -1
  13. package/dist/src/core/bot.js +43 -1
  14. package/dist/src/core/bot.js.map +1 -1
  15. package/dist/src/core/events.d.ts.map +1 -1
  16. package/dist/src/core/events.js +13 -2
  17. package/dist/src/core/events.js.map +1 -1
  18. package/dist/src/observability/logger.d.ts.map +1 -1
  19. package/dist/src/observability/logger.js +17 -1
  20. package/dist/src/observability/logger.js.map +1 -1
  21. package/dist/src/plugins/plugin.d.ts.map +1 -1
  22. package/dist/src/plugins/plugin.js +22 -1
  23. package/dist/src/plugins/plugin.js.map +1 -1
  24. package/dist/src/queue/queue.d.ts.map +1 -1
  25. package/dist/src/queue/queue.js +11 -4
  26. package/dist/src/queue/queue.js.map +1 -1
  27. package/dist/src/state/conversation.d.ts +11 -1
  28. package/dist/src/state/conversation.d.ts.map +1 -1
  29. package/dist/src/state/conversation.js +32 -5
  30. package/dist/src/state/conversation.js.map +1 -1
  31. package/dist/src/storage/storage.js +1 -1
  32. package/dist/src/storage/storage.js.map +1 -1
  33. package/dist/src/telegram-features.d.ts.map +1 -1
  34. package/dist/src/telegram-features.js +2 -0
  35. package/dist/src/telegram-features.js.map +1 -1
  36. package/dist/src/webhook/handler.d.ts.map +1 -1
  37. package/dist/src/webhook/handler.js +10 -1
  38. package/dist/src/webhook/handler.js.map +1 -1
  39. package/dist-cjs/src/api/client.js +13 -1
  40. package/dist-cjs/src/api/transport.js +45 -4
  41. package/dist-cjs/src/core/bot.js +43 -1
  42. package/dist-cjs/src/core/events.js +13 -2
  43. package/dist-cjs/src/observability/logger.js +17 -1
  44. package/dist-cjs/src/plugins/plugin.js +22 -1
  45. package/dist-cjs/src/queue/queue.js +11 -4
  46. package/dist-cjs/src/state/conversation.js +33 -5
  47. package/dist-cjs/src/storage/storage.js +1 -1
  48. package/dist-cjs/src/telegram-features.js +2 -0
  49. package/dist-cjs/src/webhook/handler.js +10 -1
  50. package/docs/API.id.md +3 -3
  51. package/docs/API.md +23 -4
  52. package/docs/API.zh-CN.md +3 -3
  53. package/package.json +1 -1
@@ -11,6 +11,7 @@ const storage_js_1 = require("../storage/storage.js");
11
11
  const plugin_js_1 = require("../plugins/plugin.js");
12
12
  const logger_js_1 = require("../observability/logger.js");
13
13
  const terminal_js_1 = require("../branding/terminal.js");
14
+ const conversation_js_1 = require("../state/conversation.js");
14
15
  class Bot {
15
16
  api;
16
17
  router;
@@ -64,6 +65,30 @@ class Bot {
64
65
  onText(text, handler) { this.router.text(text, handler); return this; }
65
66
  onRegex(expression, handler) { this.router.regex(expression, handler); return this; }
66
67
  usePlugin(plugin) { this.plugins.use(plugin); return this; }
68
+ /**
69
+ * Routes subsequent messages to the active Wizard step for the same chat/user.
70
+ * The application starts the wizard once with `wizard.run(ctx, key)`; this
71
+ * middleware keeps routing replies until the wizard is completed or cancelled.
72
+ */
73
+ useWizard(wizard, options = {}) {
74
+ const cancelCommand = options.cancelCommand ?? "/cancel";
75
+ this.use(async (ctx, next) => {
76
+ const message = ctx.message;
77
+ const key = (0, conversation_js_1.conversationKeyFromContext)(ctx);
78
+ const state = await wizard.manager.getAsync(key);
79
+ if (!message?.text || state?.status !== "active") {
80
+ await next();
81
+ return;
82
+ }
83
+ if (message.text.trim() === cancelCommand) {
84
+ wizard.manager.cancel(key);
85
+ await ctx.reply("Conversation cancelled.");
86
+ return;
87
+ }
88
+ await wizard.run(ctx, key);
89
+ });
90
+ return this;
91
+ }
67
92
  async init() {
68
93
  if (this.statusValue === "initialized" || this.statusValue === "running")
69
94
  return this;
@@ -106,6 +131,7 @@ class Bot {
106
131
  await this.events.emit("bot:stopping", { bot: this });
107
132
  this.pollingAbort?.abort();
108
133
  this.pollingAbort = undefined;
134
+ await this.plugins.stop();
109
135
  await this.plugins.dispose();
110
136
  this.statusValue = "stopped";
111
137
  this.logger.info("bot.stopped");
@@ -134,7 +160,7 @@ class Bot {
134
160
  ?? update.edited_business_message
135
161
  ?? update.guest_message
136
162
  ?? update.callback_query?.message;
137
- const key = message?.chat?.id !== undefined ? `${message.chat.id}:${message.from?.id ?? "anonymous"}` : `update:${update.update_id}`;
163
+ const key = this.conversationKey(update);
138
164
  const session = await this.session.get(key) ?? {};
139
165
  if (!this.me)
140
166
  await this.init();
@@ -153,6 +179,7 @@ class Bot {
153
179
  await this.events.emit("callback", { data: update.callback_query.data ?? "", update });
154
180
  const pipeline = (0, compose_js_1.compose)([...this.middlewares, async (context) => this.router.handle(context)]);
155
181
  try {
182
+ await this.plugins.update(ctx);
156
183
  await pipeline(ctx);
157
184
  await this.session.set(key, ctx.session);
158
185
  }
@@ -165,6 +192,21 @@ class Bot {
165
192
  throw error;
166
193
  }
167
194
  }
195
+ conversationKey(update) {
196
+ const message = update.message
197
+ ?? update.edited_message
198
+ ?? update.channel_post
199
+ ?? update.edited_channel_post
200
+ ?? update.business_message
201
+ ?? update.edited_business_message
202
+ ?? update.guest_message
203
+ ?? update.callback_query?.message;
204
+ if (message?.chat?.id !== undefined)
205
+ return `${message.chat.id}:${message.from?.id ?? "anonymous"}`;
206
+ if (update.callback_query?.from?.id !== undefined)
207
+ return `user:${update.callback_query.from.id}`;
208
+ return `update:${update.update_id}`;
209
+ }
168
210
  async waitForRetry(delayMs, signal) {
169
211
  if (signal.aborted)
170
212
  return false;
@@ -18,8 +18,19 @@ class EventBus {
18
18
  }
19
19
  async emit(event, payload) {
20
20
  const listeners = [...(this.listeners.get(event) ?? [])];
21
- for (const listener of listeners)
22
- await listener(payload);
21
+ const errors = [];
22
+ for (const listener of listeners) {
23
+ try {
24
+ await listener(payload);
25
+ }
26
+ catch (error) {
27
+ errors.push(error);
28
+ }
29
+ }
30
+ if (errors.length === 1)
31
+ throw errors[0];
32
+ if (errors.length > 1)
33
+ throw new AggregateError(errors, `One or more listeners failed for event ${String(event)}`);
23
34
  }
24
35
  removeAllListeners() { this.listeners.clear(); }
25
36
  listenerCount(event) { return this.listeners.get(event)?.size ?? 0; }
@@ -52,6 +52,22 @@ function updateType(update) {
52
52
  return "callback_query";
53
53
  if (update.inline_query)
54
54
  return "inline_query";
55
+ if (update.business_message)
56
+ return "business_message";
57
+ if (update.edited_business_message)
58
+ return "edited_business_message";
59
+ if (update.guest_message)
60
+ return "guest_message";
61
+ if (update.edited_guest_message)
62
+ return "edited_guest_message";
63
+ if (update.message_reaction)
64
+ return "message_reaction";
65
+ if (update.message_reaction_count)
66
+ return "message_reaction_count";
67
+ if (update.chat_boost)
68
+ return "chat_boost";
69
+ if (update.removed_chat_boost)
70
+ return "removed_chat_boost";
55
71
  if (update.message)
56
72
  return "message";
57
73
  if (update.edited_message)
@@ -74,7 +90,7 @@ function updateType(update) {
74
90
  }
75
91
  function summarizeUpdate(update, includeContent = false) {
76
92
  const callback = update.callback_query;
77
- const message = update.message ?? update.edited_message ?? update.channel_post ?? update.edited_channel_post ?? update.business_message ?? update.edited_business_message ?? callback?.message;
93
+ const message = (update.message ?? update.edited_message ?? update.channel_post ?? update.edited_channel_post ?? update.business_message ?? update.edited_business_message ?? update.guest_message ?? update.edited_guest_message ?? callback?.message);
78
94
  const summary = { updateId: update.update_id, type: updateType(update) };
79
95
  if (message?.chat?.id !== undefined)
80
96
  summary.chatId = message.chat.id;
@@ -14,7 +14,28 @@ class PluginManager {
14
14
  plugins = [];
15
15
  api;
16
16
  constructor(bot) {
17
- this.api = { bot, services: new ServiceContainer(), registerMiddleware: () => undefined, registerRoute: () => undefined };
17
+ const host = bot;
18
+ this.api = {
19
+ bot,
20
+ services: new ServiceContainer(),
21
+ registerMiddleware: (middleware) => {
22
+ if (typeof middleware !== "function" || !host.use)
23
+ throw new TypeError("Plugin middleware must be a function and the bot must support use().");
24
+ host.use(middleware);
25
+ },
26
+ registerRoute: (route) => {
27
+ if (typeof route === "function") {
28
+ route(bot);
29
+ return;
30
+ }
31
+ if (!route || typeof route !== "object" || !host.router?.route)
32
+ throw new TypeError("Plugin route must be a registration function or route descriptor.");
33
+ const descriptor = route;
34
+ if (typeof descriptor.matcher !== "function" || !Array.isArray(descriptor.middleware) || descriptor.middleware.some((item) => typeof item !== "function"))
35
+ throw new TypeError("Plugin route descriptor is invalid.");
36
+ host.router.route(descriptor.matcher, ...descriptor.middleware);
37
+ },
38
+ };
18
39
  }
19
40
  use(plugin) { if (this.plugins.some((existing) => existing.name === plugin.name))
20
41
  throw new Error(`Plugin already registered: ${plugin.name}`); this.plugins.push(plugin); return this; }
@@ -42,10 +42,17 @@ class TaskQueue {
42
42
  return false; job.status = "cancelled"; this.controllers.get(id)?.abort(); return true; }
43
43
  async onIdle() { while (this.pending.some((job) => job.status === "queued") || this.active)
44
44
  await sleep(10); }
45
- async close() { this.closed = true; this.draining = false; for (const id of this.controllers.keys())
46
- this.cancel(id); for (const job of this.pending)
47
- if (job.status === "queued")
48
- job.status = "cancelled"; this.pending.length = 0; }
45
+ async close() {
46
+ this.closed = true;
47
+ this.draining = false;
48
+ for (const id of this.controllers.keys())
49
+ this.cancel(id);
50
+ for (const job of this.pending)
51
+ if (job.status === "queued")
52
+ job.status = "cancelled";
53
+ this.pending.length = 0;
54
+ await this.onIdle();
55
+ }
49
56
  async drain() {
50
57
  if (this.draining || this.closed)
51
58
  return;
@@ -1,7 +1,11 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.Wizard = exports.ConversationManager = exports.ConversationFlow = void 0;
4
+ exports.conversationKeyFromContext = conversationKeyFromContext;
4
5
  const storage_js_1 = require("../storage/storage.js");
6
+ function conversationKeyFromContext(ctx) {
7
+ return ctx.chat?.id !== undefined ? `${ctx.chat.id}:${ctx.from?.id ?? "anonymous"}` : `update:${ctx.update.update_id}`;
8
+ }
5
9
  class ConversationFlow {
6
10
  ctx;
7
11
  state;
@@ -27,7 +31,13 @@ class ConversationManager {
27
31
  start(key, name, values = {}) {
28
32
  const state = { name, step: 0, values, status: "active", updatedAt: Date.now() };
29
33
  this.active.set(key, state);
30
- void this.storage.set(key, state);
34
+ void this.storage.set(key, state).catch(() => undefined);
35
+ return state;
36
+ }
37
+ async startAsync(key, name, values = {}) {
38
+ const state = { name, step: 0, values, status: "active", updatedAt: Date.now() };
39
+ this.active.set(key, state);
40
+ await this.storage.set(key, state);
31
41
  return state;
32
42
  }
33
43
  get(key) { return this.active.get(key); }
@@ -46,7 +56,7 @@ class ConversationManager {
46
56
  return false;
47
57
  state.status = "cancelled";
48
58
  state.updatedAt = Date.now();
49
- void this.storage.set(key, state);
59
+ void this.storage.set(key, state).catch(() => undefined);
50
60
  return true;
51
61
  }
52
62
  async cancelAsync(key) {
@@ -86,15 +96,21 @@ class ConversationManager {
86
96
  return removed;
87
97
  }
88
98
  async run(ctx, key, name, steps) {
89
- const state = (await this.getAsync(key)) ?? this.start(key, name);
99
+ const existing = await this.getAsync(key);
100
+ const state = existing ?? await this.startAsync(key, name);
90
101
  if (state.name !== name)
91
102
  throw new Error(`Conversation ${key} belongs to ${state.name}, not ${name}`);
103
+ if (state.status !== "active")
104
+ return state;
92
105
  const flow = new ConversationFlow(ctx, state);
93
106
  const step = steps[state.step];
94
107
  if (!step)
95
108
  state.status = "completed";
96
- else
109
+ else {
97
110
  await step(flow);
111
+ if (state.step >= steps.length)
112
+ state.status = "completed";
113
+ }
98
114
  state.updatedAt = Date.now();
99
115
  await this.storage.set(key, state);
100
116
  this.active.set(key, state);
@@ -104,8 +120,20 @@ class ConversationManager {
104
120
  exports.ConversationManager = ConversationManager;
105
121
  class Wizard {
106
122
  stepsList = [];
123
+ defaultManager;
124
+ constructor(manager) {
125
+ this.defaultManager = manager ?? new ConversationManager();
126
+ }
107
127
  step(step) { this.stepsList.push(step); return this; }
108
- async run(ctx, key, manager = new ConversationManager()) { return manager.run(ctx, key, "wizard", this.stepsList.map((step) => step.run)); }
128
+ /**
129
+ * Runs the active step for a conversation key. The default manager belongs to
130
+ * this Wizard instance and is intentionally reused across updates; passing a
131
+ * manager is useful when the application owns persistent storage explicitly.
132
+ */
133
+ async run(ctx, key = conversationKeyFromContext(ctx), manager) {
134
+ return (manager ?? this.defaultManager).run(ctx, key, "wizard", this.stepsList.map((step) => step.run));
135
+ }
109
136
  get steps() { return this.stepsList; }
137
+ get manager() { return this.defaultManager; }
110
138
  }
111
139
  exports.Wizard = Wizard;
@@ -115,7 +115,7 @@ class RedisStorage {
115
115
  key(key) { return `${this.prefix}${key}`; }
116
116
  unkey(key) { return key.slice(this.prefix.length); }
117
117
  async get(key) { const value = await this.client.get(this.key(key)); return value === null ? undefined : JSON.parse(value); }
118
- async set(key, value, options = {}) { const ttl = options.ttlMs; const redisOptions = ttl === undefined ? {} : ttl >= 1000 ? { PX: Math.max(1, Math.floor(ttl)) } : { PX: Math.max(1, Math.floor(ttl)) }; await this.client.set(this.key(key), JSON.stringify(value), redisOptions); }
118
+ async set(key, value, options = {}) { const ttl = options.ttlMs; const redisOptions = ttl === undefined ? {} : { PX: Math.max(1, Math.floor(ttl)) }; await this.client.set(this.key(key), JSON.stringify(value), redisOptions); }
119
119
  async delete(key) { return (await this.client.del(this.key(key))) > 0; }
120
120
  async has(key) { return (await this.client.exists(this.key(key))) > 0; }
121
121
  async clear() { const keys = await this.client.keys(`${this.prefix}*`); if (keys.length)
@@ -48,6 +48,8 @@ function validateWebAppInitData(initData, botToken, maxAgeSeconds = 86_400, nowM
48
48
  if (!Number.isFinite(maxAgeSeconds) || maxAgeSeconds < 0)
49
49
  throw new RangeError("maxAgeSeconds must be non-negative");
50
50
  const parsed = parseWebAppInitData(initData);
51
+ if (!/^[0-9a-fA-F]{64}$/.test(parsed.hash))
52
+ throw new Error("Web App init data hash must be a 64-character hexadecimal string");
51
53
  const checkString = Object.entries(parsed.data).sort(([left], [right]) => left.localeCompare(right)).map(([key, value]) => `${key}=${value}`).join("\n");
52
54
  const secret = (0, node_crypto_1.createHmac)("sha256", "WebAppData").update(botToken).digest();
53
55
  const expected = (0, node_crypto_1.createHmac)("sha256", secret).update(checkString).digest("hex");
@@ -1,13 +1,17 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.createWebhookHandler = createWebhookHandler;
4
+ const node_crypto_1 = require("node:crypto");
4
5
  function createWebhookHandler(bot, options = {}) {
5
6
  const maxBodyBytes = options.maxBodyBytes ?? 1_048_576;
6
7
  return async (request) => {
7
8
  if (request.method !== "POST")
8
9
  return new Response("Method Not Allowed", { status: 405, headers: { allow: "POST" } });
9
- if (options.secretToken && request.headers.get("x-telegram-bot-api-secret-token") !== options.secretToken)
10
+ if (options.secretToken && !secureEqual(request.headers.get("x-telegram-bot-api-secret-token") ?? "", options.secretToken))
10
11
  return new Response("Unauthorized", { status: 401 });
12
+ const contentType = request.headers.get("content-type") ?? "";
13
+ if (!/^application\/json(?:\s*;|\s*$)/i.test(contentType))
14
+ return new Response("Unsupported Media Type", { status: 415 });
11
15
  const contentLength = Number(request.headers.get("content-length") ?? 0);
12
16
  if (contentLength > maxBodyBytes)
13
17
  return new Response("Payload Too Large", { status: 413 });
@@ -27,3 +31,8 @@ function createWebhookHandler(bot, options = {}) {
27
31
  }
28
32
  };
29
33
  }
34
+ function secureEqual(left, right) {
35
+ const leftBuffer = Buffer.from(left, "utf8");
36
+ const rightBuffer = Buffer.from(right, "utf8");
37
+ return leftBuffer.length === rightBuffer.length && (0, node_crypto_1.timingSafeEqual)(leftBuffer, rightBuffer);
38
+ }
package/docs/API.id.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  ![telebibz overview](https://cdn.jsdelivr.net/npm/@xbibzlibrary/telebibz@latest/assets/telebibz-readme-preview.png)
6
6
 
7
- Dokumen ini adalah referensi API untuk `@xbibzlibrary/telebibz@0.1.4`. Seluruh signature dan perilaku yang dijelaskan di sini dipetakan dari source TypeScript yang diekspor package. Jika suatu tipe Telegram belum memiliki pemetaan parameter/result khusus, package tetap menyediakan akses runtime melalui API dinamis, tetapi tipe parameternya masih generik.
7
+ Dokumen ini adalah referensi API untuk rilis `@xbibzlibrary/telebibz` yang sedang dipublikasikan. Seluruh signature dan perilaku yang dijelaskan di sini dipetakan dari source TypeScript yang diekspor package. Jika suatu tipe Telegram belum memiliki pemetaan parameter/result khusus, package tetap menyediakan akses runtime melalui API dinamis, tetapi tipe parameternya masih generik.
8
8
 
9
9
  > **Status implementasi.** Dokumentasi ini menjelaskan kemampuan yang tersedia pada rilis saat ini. `JsonFileStorage`, storage Redis/SQL/Mongo berbasis driver, session/conversation berbasis Storage, cron lima field lengkap, `MenuController`, terminal status output branded, structured logging dengan redaction, validasi Web App, `PaymentsClient`, dan declaration `TelegramTypes` sudah tersedia. Core method map tetap khusus untuk inferensi request/result tertentu, sedangkan `api.raw()` tersedia untuk method Telegram berikutnya.
10
10
 
@@ -1239,8 +1239,8 @@ interface MenuItem {
1239
1239
  label: string;
1240
1240
  callbackData?: string;
1241
1241
  url?: string;
1242
- visible?: boolean | (() => boolean | Promise<boolean>);
1243
- permission?: string;
1242
+ visible?: boolean | ((context: MenuContext, item: MenuItem) => boolean | Promise<boolean>);
1243
+ permission?: string | ((context: MenuContext, item: MenuItem) => boolean | Promise<boolean>);
1244
1244
  }
1245
1245
  ```
1246
1246
 
package/docs/API.md CHANGED
@@ -3,7 +3,7 @@
3
3
 
4
4
  ![telebibz overview](https://cdn.jsdelivr.net/npm/@xbibzlibrary/telebibz@latest/assets/telebibz-readme-preview.png)
5
5
 
6
- This document is the API reference for `@xbibzlibrary/telebibz@0.1.4`. All signatures and behaviors described here are mapped from the package's exported TypeScript source. If a Telegram type does not have a specific parameter/result mapping, the package still provides runtime access via a dynamic API, but the parameter types remain generic.
6
+ This document is the API reference for the current published `@xbibzlibrary/telebibz` release. All signatures and behaviors described here are mapped from the package's exported TypeScript source. If a Telegram type does not have a specific parameter/result mapping, the package still provides runtime access via a dynamic API, but the parameter types remain generic.
7
7
 
8
8
  > **Implementation status.** This documentation describes the capabilities available in the current release. `JsonFileStorage`, driver-based Redis/SQL/Mongo storage, storage-backed sessions/conversations, full five-field cron, `MenuController`, terminal branding, structured redacted logging, Web App validation, PaymentsClient, and vendored `TelegramTypes` declarations are included. The core method map remains specialized for selected request/result inference, while `api.raw()` remains available for future Telegram methods.
9
9
 
@@ -147,6 +147,24 @@ usePlugin(plugin: Plugin<Context<S>>): this
147
147
 
148
148
  Registers a plugin. Plugin names must be unique.
149
149
 
150
+ ### `bot.useWizard(wizard, options?)`
151
+
152
+ ```ts
153
+ useWizard(wizard: Wizard<S>, options?: { cancelCommand?: string }): this
154
+ ```
155
+
156
+ Installs conversation middleware for a `Wizard`. Once an application starts the wizard with `wizard.run(ctx)`, subsequent text messages from the same chat/user are automatically routed to the active step until the wizard is completed or cancelled. The default conversation key is `${chat.id}:${from.id}`; `/cancel` cancels the active wizard by default.
157
+
158
+ ```ts
159
+ const wizard = new Wizard()
160
+ .step({ id: "prompt-name", run: async (flow) => { flow.next(); await flow.ctx.reply("Siapa nama kamu?"); } })
161
+ .step({ id: "name", run: async (flow) => { flow.set("name", flow.ctx.message?.text?.trim()); flow.next(); await flow.ctx.reply("Berapa umur kamu?"); } })
162
+ .step({ id: "age", run: (flow) => { const age = Number(flow.ctx.message?.text?.trim()); if (!Number.isInteger(age)) return; flow.set("age", age); flow.next(); } });
163
+
164
+ bot.useWizard(wizard);
165
+ bot.command("start", (ctx) => wizard.run(ctx));
166
+ ```
167
+
150
168
  ### `bot.init()`
151
169
 
152
170
  ```ts
@@ -1140,8 +1158,9 @@ new Wizard<S>()
1140
1158
  | Method/property | Description |
1141
1159
  |---|---|
1142
1160
  | `step(definition)` | Adds a step and returns the wizard. `optional` is stored in the definition but not specially handled by the runner. |
1143
- | `run(ctx, key, manager?)` | Runs the wizard steps via `ConversationManager` with the name `"wizard"`. |
1161
+ | `run(ctx, key?, manager?)` | Runs the active wizard step. If `key` is omitted, it uses `${chat.id}:${from.id}` and reuses the Wizard's default manager across updates. |
1144
1162
  | `steps` | Read-only list of steps. |
1163
+ | `manager` | Reusable `ConversationManager<S>` for `bot.useWizard()` or explicit state inspection. |
1145
1164
 
1146
1165
  ### Forms
1147
1166
 
@@ -1238,8 +1257,8 @@ interface MenuItem {
1238
1257
  label: string;
1239
1258
  callbackData?: string;
1240
1259
  url?: string;
1241
- visible?: boolean | (() => boolean | Promise<boolean>);
1242
- permission?: string;
1260
+ visible?: boolean | ((context: MenuContext, item: MenuItem) => boolean | Promise<boolean>);
1261
+ permission?: string | ((context: MenuContext, item: MenuItem) => boolean | Promise<boolean>);
1243
1262
  }
1244
1263
  ```
1245
1264
 
package/docs/API.zh-CN.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  ![telebibz 概览](https://cdn.jsdelivr.net/npm/@xbibzlibrary/telebibz@latest/assets/telebibz-readme-preview.png)
6
6
 
7
- 本文件是 `@xbibzlibrary/telebibz@0.1.4` 的 API 参考。此处描述的所有签名和行为均映射自该包导出的 TypeScript 源代码。如果某个 Telegram 类型尚未有特定的参数/结果映射,该包仍通过动态 API 提供运行时访问,但其参数类型仍为通用类型。
7
+ 本文件是当前发布的 `@xbibzlibrary/telebibz` API 参考。此处描述的所有签名和行为均映射自该包导出的 TypeScript 源代码。如果某个 Telegram 类型尚未有特定的参数/结果映射,该包仍通过动态 API 提供运行时访问,但其参数类型仍为通用类型。
8
8
 
9
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
 
@@ -1233,8 +1233,8 @@ interface MenuItem {
1233
1233
  label: string;
1234
1234
  callbackData?: string;
1235
1235
  url?: string;
1236
- visible?: boolean | (() => boolean | Promise<boolean>);
1237
- permission?: string;
1236
+ visible?: boolean | ((context: MenuContext, item: MenuItem) => boolean | Promise<boolean>);
1237
+ permission?: string | ((context: MenuContext, item: MenuItem) => boolean | Promise<boolean>);
1238
1238
  }
1239
1239
  ```
1240
1240
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xbibzlibrary/telebibz",
3
- "version": "0.1.10",
3
+ "version": "0.1.12",
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,