@xbibzlibrary/telebibz 0.1.2 → 0.1.3
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 +16 -1
- package/README.id.md +158 -0
- package/README.md +53 -33
- package/README.zh-CN.md +158 -0
- package/RELEASE_AUTOMATION.md +66 -0
- package/RELEASE_POLICY.md +2 -2
- package/assets/readme-preview.html +75 -0
- package/assets/telebibz-readme-preview.png +0 -0
- package/dist/src/api/index.d.ts +1 -0
- package/dist/src/api/index.d.ts.map +1 -1
- package/dist/src/api/index.js +1 -0
- package/dist/src/api/index.js.map +1 -1
- package/dist/src/api/telegram-types/LICENSE +21 -0
- package/dist/src/api/telegram-types/api.d.ts +22 -0
- package/dist/src/api/telegram-types/checklist.d.ts +72 -0
- package/dist/src/api/telegram-types/inline.d.ts +692 -0
- package/dist/src/api/telegram-types/langs.d.ts +193 -0
- package/dist/src/api/telegram-types/manage.d.ts +1144 -0
- package/dist/src/api/telegram-types/markup.d.ts +268 -0
- package/dist/src/api/telegram-types/message.d.ts +1537 -0
- package/dist/src/api/telegram-types/methods.d.ts +2870 -0
- package/dist/src/api/telegram-types/mod.d.ts +14 -0
- package/dist/src/api/telegram-types/passport.d.ts +163 -0
- package/dist/src/api/telegram-types/payment.d.ts +570 -0
- package/dist/src/api/telegram-types/rich.d.ts +1010 -0
- package/dist/src/api/telegram-types/settings.d.ts +120 -0
- package/dist/src/api/telegram-types/story.d.ts +89 -0
- package/dist/src/api/telegram-types/update.d.ts +84 -0
- package/dist/src/api/telegram.d.ts +7 -0
- package/dist/src/api/telegram.d.ts.map +1 -0
- package/dist/src/api/telegram.js +2 -0
- package/dist/src/api/telegram.js.map +1 -0
- package/dist/src/approval/approval.d.ts +8 -0
- package/dist/src/approval/approval.d.ts.map +1 -1
- package/dist/src/approval/approval.js +9 -0
- package/dist/src/approval/approval.js.map +1 -1
- package/dist/src/cache/cache.d.ts +6 -5
- package/dist/src/cache/cache.d.ts.map +1 -1
- package/dist/src/cache/cache.js +7 -3
- package/dist/src/cache/cache.js.map +1 -1
- package/dist/src/context/context.d.ts.map +1 -1
- package/dist/src/context/context.js +26 -3
- package/dist/src/context/context.js.map +1 -1
- package/dist/src/core/bot.d.ts +6 -4
- package/dist/src/core/bot.d.ts.map +1 -1
- package/dist/src/core/bot.js +48 -7
- package/dist/src/core/bot.js.map +1 -1
- package/dist/src/core/events.d.ts +4 -0
- package/dist/src/core/events.d.ts.map +1 -1
- package/dist/src/core/events.js.map +1 -1
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +1 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/queue/queue.d.ts +25 -0
- package/dist/src/queue/queue.d.ts.map +1 -1
- package/dist/src/queue/queue.js +175 -51
- package/dist/src/queue/queue.js.map +1 -1
- package/dist/src/router/router.d.ts +8 -1
- package/dist/src/router/router.d.ts.map +1 -1
- package/dist/src/router/router.js +75 -17
- package/dist/src/router/router.js.map +1 -1
- package/dist/src/state/conversation.d.ts +6 -0
- package/dist/src/state/conversation.d.ts.map +1 -1
- package/dist/src/state/conversation.js +79 -11
- package/dist/src/state/conversation.js.map +1 -1
- package/dist/src/state/menu.d.ts +53 -5
- package/dist/src/state/menu.d.ts.map +1 -1
- package/dist/src/state/menu.js +116 -17
- package/dist/src/state/menu.js.map +1 -1
- package/dist/src/storage/storage.d.ts +115 -12
- package/dist/src/storage/storage.d.ts.map +1 -1
- package/dist/src/storage/storage.js +130 -4
- package/dist/src/storage/storage.js.map +1 -1
- package/dist/src/telegram-features.d.ts +33 -0
- package/dist/src/telegram-features.d.ts.map +1 -0
- package/dist/src/telegram-features.js +69 -0
- package/dist/src/telegram-features.js.map +1 -0
- package/dist/src/testing.d.ts +1 -0
- package/dist/src/testing.d.ts.map +1 -1
- package/dist/src/testing.js +16 -0
- package/dist/src/testing.js.map +1 -1
- package/dist-cjs/src/api/index.js +1 -0
- package/dist-cjs/src/api/telegram-types/LICENSE +21 -0
- package/dist-cjs/src/api/telegram-types/api.d.ts +22 -0
- package/dist-cjs/src/api/telegram-types/checklist.d.ts +72 -0
- package/dist-cjs/src/api/telegram-types/inline.d.ts +692 -0
- package/dist-cjs/src/api/telegram-types/langs.d.ts +193 -0
- package/dist-cjs/src/api/telegram-types/manage.d.ts +1144 -0
- package/dist-cjs/src/api/telegram-types/markup.d.ts +268 -0
- package/dist-cjs/src/api/telegram-types/message.d.ts +1537 -0
- package/dist-cjs/src/api/telegram-types/methods.d.ts +2870 -0
- package/dist-cjs/src/api/telegram-types/mod.d.ts +14 -0
- package/dist-cjs/src/api/telegram-types/passport.d.ts +163 -0
- package/dist-cjs/src/api/telegram-types/payment.d.ts +570 -0
- package/dist-cjs/src/api/telegram-types/rich.d.ts +1010 -0
- package/dist-cjs/src/api/telegram-types/settings.d.ts +120 -0
- package/dist-cjs/src/api/telegram-types/story.d.ts +89 -0
- package/dist-cjs/src/api/telegram-types/update.d.ts +84 -0
- package/dist-cjs/src/api/telegram.js +2 -0
- package/dist-cjs/src/approval/approval.js +11 -1
- package/dist-cjs/src/cache/cache.js +7 -3
- package/dist-cjs/src/context/context.js +26 -3
- package/dist-cjs/src/core/bot.js +48 -7
- package/dist-cjs/src/index.js +1 -0
- package/dist-cjs/src/queue/queue.js +177 -51
- package/dist-cjs/src/router/router.js +75 -17
- package/dist-cjs/src/state/conversation.js +79 -11
- package/dist-cjs/src/state/menu.js +118 -18
- package/dist-cjs/src/storage/storage.js +135 -5
- package/dist-cjs/src/telegram-features.js +74 -0
- package/dist-cjs/src/testing.js +17 -0
- package/docs/API.id.md +1800 -0
- package/docs/API.md +1799 -0
- package/docs/API.zh-CN.md +1794 -0
- package/docs/README.md +26 -15
- package/package.json +13 -2
|
@@ -0,0 +1,1794 @@
|
|
|
1
|
+
# telebibz API 参考 — 简体中文
|
|
2
|
+
|
|
3
|
+
[English](API.md) · [Bahasa Indonesia](API.id.md) · **简体中文**
|
|
4
|
+
|
|
5
|
+

|
|
6
|
+
|
|
7
|
+
本文件是 `@xbibzlibrary/telebibz@0.1.2` 的 API 参考。此处描述的所有签名和行为均映射自该包导出的 TypeScript 源代码。如果某个 Telegram 类型尚未有特定的参数/结果映射,该包仍通过动态 API 提供运行时访问,但其参数类型仍为通用类型。
|
|
8
|
+
|
|
9
|
+
> **实现状态。** 本文档说明了当前发布版本中实际可用的功能。`MemoryStorage`、`MemoryCache`、`TaskQueue` 和 `Scheduler` 是内存原语;分布式适配器、外部持久化,以及针对整个 Telegram Bot API 的完整模式类型尚未包含在本次发布中。
|
|
10
|
+
|
|
11
|
+
## 安装与导入
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install @xbibzlibrary/telebibz
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
ESM:
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
import {
|
|
21
|
+
Bot,
|
|
22
|
+
InlineKeyboard,
|
|
23
|
+
compose,
|
|
24
|
+
escapeHtml,
|
|
25
|
+
type Context,
|
|
26
|
+
} from "@xbibzlibrary/telebibz";
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
CommonJS:
|
|
30
|
+
|
|
31
|
+
```js
|
|
32
|
+
const { Bot, InlineKeyboard } = require("@xbibzlibrary/telebibz");
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
可用的子路径导出如下:
|
|
36
|
+
|
|
37
|
+
| 子路径 | 内容 |
|
|
38
|
+
|---|---|
|
|
39
|
+
| `@xbibzlibrary/telebibz` | 来自 `src/index.ts` 的全部主要公共 API |
|
|
40
|
+
| `@xbibzlibrary/telebibz/api` | Client、transport、errors,以及所有 Telegram API 类型 |
|
|
41
|
+
| `@xbibzlibrary/telebibz/keyboard` | `InlineKeyboard`、`ReplyKeyboard` 以及键盘辅助函数 |
|
|
42
|
+
| `@xbibzlibrary/telebibz/testing` | `MockTransport` 以及测试工厂 |
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## 1. 核心 Bot
|
|
47
|
+
|
|
48
|
+
### `BotStatus`
|
|
49
|
+
|
|
50
|
+
```ts
|
|
51
|
+
type BotStatus =
|
|
52
|
+
| "created"
|
|
53
|
+
| "initialized"
|
|
54
|
+
| "awaiting-approval"
|
|
55
|
+
| "starting"
|
|
56
|
+
| "running"
|
|
57
|
+
| "stopping"
|
|
58
|
+
| "stopped"
|
|
59
|
+
| "error";
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### `BotOptions<S>`
|
|
63
|
+
|
|
64
|
+
| 属性 | 类型 | 默认 | 说明 |
|
|
65
|
+
|---|---|---:|---|
|
|
66
|
+
| `token` | `string` | 必需 | Token BotFather dengan format `<digits>:<token>`. |
|
|
67
|
+
| `apiBaseUrl` | `string` | `https://api.telegram.org` | Telegram API 的基础 URL。结尾的 `/` 会被自动移除。 |
|
|
68
|
+
| `transport` | `Transport` | `FetchTransport` | 用于 mock、proxy 或其他实现的自定义 transport。 |
|
|
69
|
+
| `transportOptions` | `Omit<FetchTransportOptions, "baseUrl">` | `{}` | 超时、重试、退避、jitter、headers 和 fetch 实现。 |
|
|
70
|
+
| `session` | `Storage<string, S>` | 新的存储 | 基于 chat/user key 的会话存储,可使用持久化适配器。 |
|
|
71
|
+
| `services` | `Record<string, unknown>` | `{}` | 通过 `ctx.services` 可用的依赖/服务。 |
|
|
72
|
+
| `polling.timeout` | `number` | `30` | 用于 `getUpdates` 的长轮询超时(秒)。 |
|
|
73
|
+
| `polling.limit` | `number` | `100` | 每次轮询请求的最大 update 数量。 |
|
|
74
|
+
| `polling.allowedUpdates` | `string[]` | `[]` | Telegram 更新过滤器。 |
|
|
75
|
+
| `polling.retryDelayMs` | `number` | `500` | 轮询失败时的初始延迟(毫秒)。 |
|
|
76
|
+
| `polling.maxRetryDelayMs` | `number` | `30000` | 重连延迟的最大值(毫秒)。 |
|
|
77
|
+
| `approval` | `ApprovalOptions` | disabled | 启用所有者审批门。 |
|
|
78
|
+
|
|
79
|
+
### `Bot` constructor
|
|
80
|
+
|
|
81
|
+
```ts
|
|
82
|
+
new Bot<S extends object = Record<string, unknown>>(
|
|
83
|
+
options: string | BotOptions<S>,
|
|
84
|
+
): Bot<S>
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Jika argumen berupa string, string tersebut dianggap sebagai token. Constructor membuat `ApiClient`, router, event bus, plugin manager, session storage, dan approval gate bila dikonfigurasi. Constructor langsung memancarkan event `bot:created` secara asynchronous.
|
|
88
|
+
|
|
89
|
+
Constructor melempar `Error` jika token kosong atau tidak sesuai pola token Telegram.
|
|
90
|
+
|
|
91
|
+
### `Bot` 的属性和 getter
|
|
92
|
+
|
|
93
|
+
| API | 类型 | 描述 |
|
|
94
|
+
|---|---|---|
|
|
95
|
+
| `api` | `ApiClient` | 类型化/动态的 Telegram 客户端。 |
|
|
96
|
+
| `router` | `Router<Context<S>>` | bot 的主路由器。 |
|
|
97
|
+
| `events` | `EventBus<EventMap>` | 生命周期、update、API、webhook 和 polling 的事件总线。 |
|
|
98
|
+
| `plugins` | `PluginManager<Context<S>>` | 插件的生命周期管理器。 |
|
|
99
|
+
| `session` | `Storage<string, S>` | bot 的会话存储,可使用持久化适配器。 |
|
|
100
|
+
| `services` | `Record<string, unknown>` | 构造函数提供的 service 的拷贝。 |
|
|
101
|
+
| `approval` | `ApprovalGate \| undefined` | 如果配置了 `approval` 则为 Approval gate。 |
|
|
102
|
+
| `token` | `string` | 客户端使用的 bot token。 |
|
|
103
|
+
| `status` | `BotStatus` | 当前生命周期状态。 |
|
|
104
|
+
| `botInfo` | `User \| undefined` | 最近一次 `getMe()` 的结果。 |
|
|
105
|
+
|
|
106
|
+
### `bot.use(...middleware)`
|
|
107
|
+
|
|
108
|
+
```ts
|
|
109
|
+
use(...middleware: Middleware<Context<S>>[]): this
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
添加全局 middleware。middleware 在每个 update 的 router 之前执行,按注册顺序。返回 bot 实例以便链式调用。
|
|
113
|
+
|
|
114
|
+
### `bot.command(name, handler)`
|
|
115
|
+
|
|
116
|
+
```ts
|
|
117
|
+
command(name: string, handler: Middleware<Context<S>>): this
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
注册 Telegram 命令,可带或不带前导 `/`。匹配时取 `/` 之后的第一个 token,并忽略 `@` 后的 bot mention。例如 `/start@my_bot` 会匹配 `"start"`。
|
|
121
|
+
|
|
122
|
+
### `bot.callback(pattern, handler)`
|
|
123
|
+
|
|
124
|
+
```ts
|
|
125
|
+
callback(pattern: string | RegExp, handler: Middleware<Context<S>>): this
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
callback query 路由的快捷方式。以 `*` 结尾的字符串表示前缀匹配;其他字符串必须完全相等。
|
|
129
|
+
|
|
130
|
+
### `bot.onText(text, handler)`
|
|
131
|
+
|
|
132
|
+
```ts
|
|
133
|
+
onText(text: string, handler: Middleware<Context<S>>): this
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
处理 `message.text` 与 `text` 完全相同的消息。
|
|
137
|
+
|
|
138
|
+
### `bot.onRegex(expression, handler)`
|
|
139
|
+
|
|
140
|
+
```ts
|
|
141
|
+
onRegex(expression: RegExp, handler: Middleware<Context<S>>): this
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
使用 `RegExp` 处理消息文本。路由参数不会自动提取到 `ctx.params`;如需提取请使用 predicate 或自定义 middleware。
|
|
145
|
+
|
|
146
|
+
### `bot.usePlugin(plugin)`
|
|
147
|
+
|
|
148
|
+
```ts
|
|
149
|
+
usePlugin(plugin: Plugin<Context<S>>): this
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
注册插件。插件名称必须唯一。
|
|
153
|
+
|
|
154
|
+
### `bot.init()`
|
|
155
|
+
|
|
156
|
+
```ts
|
|
157
|
+
init(): Promise<this>
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
调用 `getMe()`,保存 bot 信息,若 approval gate 启用则处理它,然后运行插件生命周期的 `setup()` 和 `start()`。
|
|
161
|
+
|
|
162
|
+
若尚未获得批准,方法会将状态置为 `"awaiting-approval"`,通过 `ApprovalGate` 向 owner 发送通知,并返回 bot 而不设置为 `initialized`。在 owner 批准后后续调用仍可使用。
|
|
163
|
+
|
|
164
|
+
`init()` 在状态已为 `initialized` 或 `running` 时是幂等的。
|
|
165
|
+
|
|
166
|
+
### `bot.start()`
|
|
167
|
+
|
|
168
|
+
```ts
|
|
169
|
+
start(): Promise<void>
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
是 `launch({ mode: "polling" })` 的快捷方式。此方法开始 long polling 并等待直到轮询被停止或发生致命错误。
|
|
173
|
+
|
|
174
|
+
### `bot.launch(options?)`
|
|
175
|
+
|
|
176
|
+
```ts
|
|
177
|
+
launch(options?: {
|
|
178
|
+
mode: "polling";
|
|
179
|
+
timeout?: number;
|
|
180
|
+
allowedUpdates?: string[];
|
|
181
|
+
}): Promise<void>
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
以 polling 模式运行 bot。启动时生命周期依次变为 `starting` 然后 `running`,之后 `getUpdates()` 循环按顺序处理每个 update。轮询失败会触发 `polling:reconnect` 并使用指数退避。
|
|
185
|
+
|
|
186
|
+
除 `"polling"` 外的模式会抛出错误,并建议对 webhook 使用 `createWebhookHandler()`。
|
|
187
|
+
|
|
188
|
+
### `bot.stop()`
|
|
189
|
+
|
|
190
|
+
```ts
|
|
191
|
+
stop(): Promise<void>
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
通过 `AbortController` 停止轮询,调用 `plugins.dispose()`,将状态设置为 `stopped`,并触发 stopping/stopped 事件。当状态为 `created` 或 `stopped` 时调用不会有任何效果。
|
|
195
|
+
|
|
196
|
+
### `bot.restart()`
|
|
197
|
+
|
|
198
|
+
```ts
|
|
199
|
+
restart(): Promise<void>
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
先运行 `stop()` 然后 `start()`。
|
|
203
|
+
|
|
204
|
+
### `bot.health()`
|
|
205
|
+
|
|
206
|
+
```ts
|
|
207
|
+
health(): Promise<HealthStatus>
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
调用 `getMe()` 检查 API 可达性。请求失败不会抛出错误;失败会以 `apiReachable: false` 和错误信息的形式返回。
|
|
211
|
+
|
|
212
|
+
```ts
|
|
213
|
+
interface HealthStatus {
|
|
214
|
+
status: BotStatus;
|
|
215
|
+
apiReachable: boolean;
|
|
216
|
+
bot?: User;
|
|
217
|
+
checkedAt: string; // ISO timestamp
|
|
218
|
+
error?: string;
|
|
219
|
+
}
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
### `bot.getMe()`
|
|
223
|
+
|
|
224
|
+
```ts
|
|
225
|
+
getMe(): Promise<User>
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
从 Telegram 获取 bot 数据并更新 `botInfo`。
|
|
229
|
+
|
|
230
|
+
### `bot.setCommands(commands, scope?, languageCode?)`
|
|
231
|
+
|
|
232
|
+
```ts
|
|
233
|
+
setCommands(
|
|
234
|
+
commands: BotCommand[],
|
|
235
|
+
scope?: BotCommandScope,
|
|
236
|
+
languageCode?: string,
|
|
237
|
+
): Promise<true>
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
相当于 `setMyCommands` 的快捷方式。`languageCode` 会映射为 Telegram 的 `language_code` 字段。
|
|
241
|
+
|
|
242
|
+
### `bot.deleteCommands(scope?, languageCode?)`
|
|
243
|
+
|
|
244
|
+
```ts
|
|
245
|
+
deleteCommands(
|
|
246
|
+
scope?: BotCommandScope,
|
|
247
|
+
languageCode?: string,
|
|
248
|
+
): Promise<true>
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
相当于 `deleteMyCommands` 的快捷方式。
|
|
252
|
+
|
|
253
|
+
### `bot.handleUpdate(update)`
|
|
254
|
+
|
|
255
|
+
```ts
|
|
256
|
+
handleUpdate(update: Update): Promise<void>
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
手动处理单个 update。该方法根据 `chat.id` 和 `from.id` 确定会话 key,创建 `Context`,触发 `update` 和 `message` 事件,执行 middleware 然后路由器,并在流水线完成后保存会话。
|
|
260
|
+
|
|
261
|
+
若启用了 approval 且 bot 尚未被允许,普通 update 会被阻止。approval 的 callback 仍会转发到 `ApprovalGate.handleCallback()`。
|
|
262
|
+
|
|
263
|
+
流水线错误会将 bot 状态置为 `error`,触发 `bot:error`,然后重新抛出错误。
|
|
264
|
+
|
|
265
|
+
### 最小 bot 示例
|
|
266
|
+
|
|
267
|
+
```ts
|
|
268
|
+
import { Bot, InlineKeyboard } from "@xbibzlibrary/telebibz";
|
|
269
|
+
|
|
270
|
+
const bot = new Bot({
|
|
271
|
+
token: process.env.TELEGRAM_BOT_TOKEN!,
|
|
272
|
+
polling: { allowedUpdates: ["message", "callback_query"] },
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
bot.command("start", async (ctx) => {
|
|
276
|
+
await ctx.reply("来自 telebibz 的问候", {
|
|
277
|
+
reply_markup: new InlineKeyboard()
|
|
278
|
+
.text("状态", "status")
|
|
279
|
+
.build(),
|
|
280
|
+
});
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
bot.callback("status", async (ctx) => {
|
|
284
|
+
await ctx.answerCallbackQuery("机器人已激活");
|
|
285
|
+
await ctx.reply("状态:running");
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
await bot.start();
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
---
|
|
292
|
+
|
|
293
|
+
## 2. 事件总线
|
|
294
|
+
|
|
295
|
+
### `EventMap`
|
|
296
|
+
|
|
297
|
+
| 事件 | 负载 |
|
|
298
|
+
|---|---|
|
|
299
|
+
| `bot:created` | `{ bot: unknown }` |
|
|
300
|
+
| `bot:initialized` | `{ bot: unknown }` |
|
|
301
|
+
| `bot:starting` | `{ bot: unknown }` |
|
|
302
|
+
| `bot:started` | `{ bot: unknown }` |
|
|
303
|
+
| `bot:stopping` | `{ bot: unknown }` |
|
|
304
|
+
| `bot:stopped` | `{ bot: unknown }` |
|
|
305
|
+
| `bot:error` | `{ bot: unknown; error: unknown }` |
|
|
306
|
+
| `update` | `{ update: unknown }` |
|
|
307
|
+
| `message` | `{ message: unknown }` |
|
|
308
|
+
| `command` | `{ name: string; update: unknown }` |
|
|
309
|
+
| `callback` | `{ data: string; update: unknown }` |
|
|
310
|
+
| `api:request` | `{ method: string; payload: unknown }` |
|
|
311
|
+
| `api:response` | `{ method: string; durationMs: number; response: unknown }` |
|
|
312
|
+
| `api:error` | `{ method: string; durationMs: number; error: unknown }` |
|
|
313
|
+
| `webhook:request` | `{ update: unknown }` |
|
|
314
|
+
| `polling:reconnect` | `{ error: unknown; attempt: number }` |
|
|
315
|
+
|
|
316
|
+
### `EventBus<Events>`
|
|
317
|
+
|
|
318
|
+
```ts
|
|
319
|
+
new EventBus<Events extends Record<string, unknown> = EventMap>()
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
| 方法 | 签名 | 行为 |
|
|
323
|
+
|---|---|---|
|
|
324
|
+
| `on` | `on<K>(event: K, listener: (payload: Events[K]) => void \| Promise<void>): () => void` | 添加监听器并返回取消订阅函数。 |
|
|
325
|
+
| `once` | `once<K>(event: K, listener: ...): () => void` | 监听器仅调用一次,然后被移除。 |
|
|
326
|
+
| `off` | `off<K>(event: K, listener: ...): void` | 移除指定的监听器。 |
|
|
327
|
+
| `emit` | `emit<K>(event: K, payload: Events[K]): Promise<void>` | 依次调用监听器并等待每个完成。 |
|
|
328
|
+
| `removeAllListeners` | `removeAllListeners(): void` | 移除所有监听器。 |
|
|
329
|
+
| `listenerCount` | `listenerCount<K>(event: K): number` | 返回事件的监听器数量。 |
|
|
330
|
+
|
|
331
|
+
```ts
|
|
332
|
+
const unsubscribe = bot.events.on("bot:error", ({ error }) => {
|
|
333
|
+
console.error(error);
|
|
334
|
+
});
|
|
335
|
+
unsubscribe();
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
---
|
|
339
|
+
|
|
340
|
+
## 3. API 客户端、传输 与 错误
|
|
341
|
+
|
|
342
|
+
### 基本类型
|
|
343
|
+
|
|
344
|
+
```ts
|
|
345
|
+
type ChatId = number | string;
|
|
346
|
+
type ParseMode = "Markdown" | "MarkdownV2" | "HTML";
|
|
347
|
+
type InputFile =
|
|
348
|
+
| string
|
|
349
|
+
| Uint8Array
|
|
350
|
+
| ArrayBuffer
|
|
351
|
+
| Blob
|
|
352
|
+
| NodeJS.ReadableStream
|
|
353
|
+
| { source: string | Uint8Array | ArrayBuffer | Blob | NodeJS.ReadableStream; filename?: string };
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
`InputFile` string 可以是普通字符串,或在作为 upload 对象中的 `source` 时为文件路径。在 Node.js 中,绝对路径、`./...` 和 `../...` 会被 `FetchTransport` 读取,然后作为 multipart 文件发送。
|
|
357
|
+
|
|
358
|
+
### `TelegramResponse<T>`
|
|
359
|
+
|
|
360
|
+
```ts
|
|
361
|
+
interface TelegramResponse<T> {
|
|
362
|
+
ok: boolean;
|
|
363
|
+
result?: T;
|
|
364
|
+
description?: string;
|
|
365
|
+
error_code?: number;
|
|
366
|
+
parameters?: ResponseParameters;
|
|
367
|
+
}
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
### `TransportRequest`, `TransportResponse`, 和 `Transport`
|
|
371
|
+
|
|
372
|
+
```ts
|
|
373
|
+
interface TransportRequest {
|
|
374
|
+
method: string;
|
|
375
|
+
payload?: Record<string, unknown>;
|
|
376
|
+
signal?: AbortSignal;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
interface TransportResponse<T = unknown> {
|
|
380
|
+
status: number;
|
|
381
|
+
headers: Headers;
|
|
382
|
+
data: TelegramResponse<T>;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
interface Transport {
|
|
386
|
+
request<T>(request: TransportRequest): Promise<TransportResponse<T>>;
|
|
387
|
+
}
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
### `FetchTransportOptions`
|
|
391
|
+
|
|
392
|
+
| 属性 | 默认 | 描述 |
|
|
393
|
+
|---|---:|---|
|
|
394
|
+
| `baseUrl` | `https://api.telegram.org` | 在 `/<method>` 之前的 URL 前缀。 |
|
|
395
|
+
| `fetch` | `globalThis.fetch` | 自定义 fetch 实现。 |
|
|
396
|
+
| `timeoutMs` | `30000` | 每次尝试的超时(毫秒)。 |
|
|
397
|
+
| `retries` | `2` | 初始尝试后的网络错误重试次数。 |
|
|
398
|
+
| `backoffMs` | `250` | 初始指数退避延迟(毫秒)。 |
|
|
399
|
+
| `maxBackoffMs` | `8000` | 传输延迟上限(毫秒)。 |
|
|
400
|
+
| `jitter` | `0.2` | 对指数延迟的随机抖动,范围为 ±20%。 |
|
|
401
|
+
| `headers` | `{}` | 额外的请求头。 |
|
|
402
|
+
|
|
403
|
+
### `new FetchTransport(options?)`
|
|
404
|
+
|
|
405
|
+
```ts
|
|
406
|
+
new FetchTransport(options?: FetchTransportOptions): FetchTransport
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
内置的 transport 基于 `fetch`。不含上传的 payload 会以 JSON 发送。包含 `Uint8Array`、`ArrayBuffer`、`Blob` 或嵌套上传的 payload 会使用 `FormData` 作为 `multipart/form-data` 发送。
|
|
410
|
+
|
|
411
|
+
### `fetchTransport.request(request)`
|
|
412
|
+
|
|
413
|
+
```ts
|
|
414
|
+
request<T>(request: TransportRequest): Promise<TransportResponse<T>>
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
发送 POST 到 `${baseUrl}/${method}`。以 `/` 开头的 method 会被正规化。外部的 AbortSignal 会转发到内部的 controller。被判定为可重试的网络错误会按指数退避并加抖动进行重试;当重试耗尽时,错误会被封装为 `TelegramNetworkError`。
|
|
418
|
+
|
|
419
|
+
### `ApiHookContext`, `ApiClientOptions`, dan `ApiMethods`
|
|
420
|
+
|
|
421
|
+
```ts
|
|
422
|
+
interface ApiHookContext {
|
|
423
|
+
method: string;
|
|
424
|
+
payload: unknown;
|
|
425
|
+
startedAt: number;
|
|
426
|
+
durationMs?: number;
|
|
427
|
+
response?: TelegramResponse<unknown>;
|
|
428
|
+
error?: unknown;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
interface ApiClientOptions {
|
|
432
|
+
transport: Transport;
|
|
433
|
+
hooks?: {
|
|
434
|
+
onRequest?: (context: ApiHookContext) => void | Promise<void>;
|
|
435
|
+
onResponse?: (context: ApiHookContext) => void | Promise<void>;
|
|
436
|
+
onError?: (context: ApiHookContext) => void | Promise<void>;
|
|
437
|
+
};
|
|
438
|
+
}
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
`ApiMethods` 是基于 184 个 `TelegramMethodName` 的映射类型:
|
|
442
|
+
|
|
443
|
+
```ts
|
|
444
|
+
type ApiMethods = {
|
|
445
|
+
[M in TelegramMethodName]:
|
|
446
|
+
(...args: ApiCallArgs<M>) => Promise<ApiResult<M>>;
|
|
447
|
+
};
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
### `new ApiClient(options)`
|
|
451
|
+
|
|
452
|
+
```ts
|
|
453
|
+
new ApiClient(options: ApiClientOptions): ApiClient
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
在 `client.methods` 上创建动态方法代理。Hook `onRequest` 在调用 transport 之前触发,`onResponse` 在收到响应后触发,`onError` 在请求失败或 Telegram 返回 `ok: false` 时触发。
|
|
457
|
+
|
|
458
|
+
### `api.methods.<method>(params?)`
|
|
459
|
+
|
|
460
|
+
动态方法可以直接调用。无参数的方法(如 `getMe()`)无需传入参数;其他方法接受单个对象参数。
|
|
461
|
+
|
|
462
|
+
```ts
|
|
463
|
+
const me = await bot.api.methods.getMe();
|
|
464
|
+
const chat = await bot.api.methods.getChat({ chat_id: "@channel" });
|
|
465
|
+
const message = await bot.api.methods.sendMessage({
|
|
466
|
+
chat_id: 123456789,
|
|
467
|
+
text: "你好",
|
|
468
|
+
});
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
### `api.call(method, ...args)`
|
|
472
|
+
|
|
473
|
+
```ts
|
|
474
|
+
call<M extends TelegramMethodName>(
|
|
475
|
+
method: M,
|
|
476
|
+
...args: ApiCallArgs<M>
|
|
477
|
+
): Promise<ApiResult<M>>
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
用于基于字符串字面量调用方法的类型化形式。
|
|
481
|
+
|
|
482
|
+
### `api.request(method, payload?, signal?)`
|
|
483
|
+
|
|
484
|
+
```ts
|
|
485
|
+
request<M extends TelegramMethodName>(
|
|
486
|
+
method: M,
|
|
487
|
+
payload?: ApiParams<M>,
|
|
488
|
+
signal?: AbortSignal,
|
|
489
|
+
): Promise<ApiResult<M>>
|
|
490
|
+
```
|
|
491
|
+
|
|
492
|
+
低层请求方法,允许显式传入 `AbortSignal`。
|
|
493
|
+
|
|
494
|
+
### `api.raw(method, payload?, signal?)`
|
|
495
|
+
|
|
496
|
+
```ts
|
|
497
|
+
raw(
|
|
498
|
+
method: string,
|
|
499
|
+
payload?: Record<string, unknown>,
|
|
500
|
+
signal?: AbortSignal,
|
|
501
|
+
): Promise<unknown>
|
|
502
|
+
```
|
|
503
|
+
|
|
504
|
+
在 transport 上调用任意字符串方法。用于调用尚未包含在 `TelegramMethodMap` 的新的 Telegram 方法或参数。即使响应为 `ok: false`,也会被转换为 `TelegramError`。
|
|
505
|
+
|
|
506
|
+
### 可用的类型化参数和返回值
|
|
507
|
+
|
|
508
|
+
以下类型在此发布版本中已被映射:
|
|
509
|
+
|
|
510
|
+
| 方法 | 参数 | 返回值 |
|
|
511
|
+
|---|---|---|
|
|
512
|
+
| `getMe` | 无 | `User` |
|
|
513
|
+
| `getUpdates` | `GetUpdatesParams` | `Update[]` |
|
|
514
|
+
| `setWebhook` | `SetWebhookParams` | `boolean` |
|
|
515
|
+
| `deleteWebhook` | `{ drop_pending_updates?: boolean }` | `boolean` |
|
|
516
|
+
| `getWebhookInfo` | 无 | `WebhookInfo` |
|
|
517
|
+
| `sendMessage` | `SendMessageParams` | `Message` |
|
|
518
|
+
| `editMessageText` | `EditMessageTextParams` | `Message \| true` |
|
|
519
|
+
| `deleteMessage` | `DeleteMessageParams` | `true` |
|
|
520
|
+
| `answerCallbackQuery` | `AnswerCallbackQueryParams` | `true` |
|
|
521
|
+
| `getChat` | `GetChatParams` | `Chat` |
|
|
522
|
+
| `getFile` | `GetFileParams` | `File` |
|
|
523
|
+
| `getUserProfilePhotos` | `{ user_id: number; offset?: number; limit?: number }` | `UserProfilePhotos` |
|
|
524
|
+
| `sendPhoto` | `SendPhotoParams` | `Message` |
|
|
525
|
+
| `sendDocument` | `SendDocumentParams` | `Message` |
|
|
526
|
+
|
|
527
|
+
可用的附加参数类型包括 `ReplyParameters`、`LinkPreviewOptions`、`InlineKeyboardButton`、`ReplyMarkup`、`BotCommand`、`BotCommandScope`,以及从 `api/types.ts` 导出的所有 Telegram update 类型。
|
|
528
|
+
|
|
529
|
+
### API 错误
|
|
530
|
+
|
|
531
|
+
```ts
|
|
532
|
+
type TelegramErrorKind =
|
|
533
|
+
| "retryable"
|
|
534
|
+
| "rate-limit"
|
|
535
|
+
| "authentication"
|
|
536
|
+
| "validation"
|
|
537
|
+
| "network"
|
|
538
|
+
| "server"
|
|
539
|
+
| "unknown";
|
|
540
|
+
```
|
|
541
|
+
|
|
542
|
+
#### `TelegramError`
|
|
543
|
+
|
|
544
|
+
```ts
|
|
545
|
+
new TelegramError(message: string, options: {
|
|
546
|
+
method: string;
|
|
547
|
+
payload: unknown;
|
|
548
|
+
errorCode?: number;
|
|
549
|
+
parameters?: ResponseParameters;
|
|
550
|
+
status?: number;
|
|
551
|
+
kind?: TelegramErrorKind;
|
|
552
|
+
cause?: unknown;
|
|
553
|
+
})
|
|
554
|
+
```
|
|
555
|
+
|
|
556
|
+
公开属性有 `kind`、`errorCode`、`parameters`、`method`、`payload` 和 `status`。Getter `retryAfter` 读取 `parameters.retry_after`;getter `migrateToChatId` 读取 `parameters.migrate_to_chat_id`。
|
|
557
|
+
|
|
558
|
+
#### 错误子类
|
|
559
|
+
|
|
560
|
+
| 类 | `name` | 强制的 `kind` |
|
|
561
|
+
|---|---|---|
|
|
562
|
+
| `TelegramRateLimitError` | `TelegramRateLimitError` | `rate-limit` |
|
|
563
|
+
| `TelegramAuthError` | `TelegramAuthError` | `authentication` |
|
|
564
|
+
| `TelegramValidationError` | `TelegramValidationError` | `validation` |
|
|
565
|
+
| `TelegramNetworkError` | `TelegramNetworkError` | `network` |
|
|
566
|
+
|
|
567
|
+
这四个子类使用与 `TelegramError` 相同的构造器选项。
|
|
568
|
+
|
|
569
|
+
#### `classifyTelegramError(errorCode?, status?)`
|
|
570
|
+
|
|
571
|
+
```ts
|
|
572
|
+
classifyTelegramError(
|
|
573
|
+
errorCode?: number,
|
|
574
|
+
status?: number,
|
|
575
|
+
): TelegramErrorKind
|
|
576
|
+
```
|
|
577
|
+
|
|
578
|
+
实际分类:`429` 映射为 `rate-limit`;错误 `401` 或 HTTP `401/403` 映射为 `authentication`;错误代码 `400–499` 映射为 `validation`;HTTP `500+` 映射为 `server`;其他为 `unknown`。
|
|
579
|
+
|
|
580
|
+
#### `telegramErrorFromResponse(response, context)`
|
|
581
|
+
|
|
582
|
+
```ts
|
|
583
|
+
telegramErrorFromResponse<T>(
|
|
584
|
+
response: TelegramResponse<T>,
|
|
585
|
+
context: { method: string; payload: unknown; status?: number },
|
|
586
|
+
): TelegramError
|
|
587
|
+
```
|
|
588
|
+
|
|
589
|
+
将失败的 Telegram 响应转换为相应的子类。`429`、认证和验证错误会产生相应的子类;其他错误会生成普通的 `TelegramError`。
|
|
590
|
+
|
|
591
|
+
---
|
|
592
|
+
|
|
593
|
+
## 4. 上下文
|
|
594
|
+
|
|
595
|
+
### `ContextOptions<S>`
|
|
596
|
+
|
|
597
|
+
```ts
|
|
598
|
+
interface ContextOptions<S extends object = Record<string, unknown>> {
|
|
599
|
+
update: Update;
|
|
600
|
+
api: ApiClient;
|
|
601
|
+
session: S;
|
|
602
|
+
services: Record<string, unknown>;
|
|
603
|
+
}
|
|
604
|
+
```
|
|
605
|
+
|
|
606
|
+
### `Context<S>` 属性
|
|
607
|
+
|
|
608
|
+
| 属性 | 内容 |
|
|
609
|
+
|---|---|
|
|
610
|
+
| `update` | Telegram 的原始 Update. |
|
|
611
|
+
| `api` | 机器人 `ApiClient`. |
|
|
612
|
+
| `session` | 当前 update key 的可变 session 对象. |
|
|
613
|
+
| `state` | 每个上下文的临时对象,不会自动保存到 session. |
|
|
614
|
+
| `services` | 通过 `BotOptions.services` 提供的服务. |
|
|
615
|
+
| `params` | 路由参数对象;内置路由器目前不会自动填充. |
|
|
616
|
+
| `message` | 来自 message/edited/channel/business/guest 更新的主要 message. |
|
|
617
|
+
| `chat` | 若可用则为 `message.chat`. |
|
|
618
|
+
| `from` / `sender` | 来自 message、callback query 或 inline query 的用户. |
|
|
619
|
+
| `callbackQuery` | `update.callback_query`. |
|
|
620
|
+
| `inlineQuery` | `update.inline_query`. |
|
|
621
|
+
| `poll` | `update.poll`. |
|
|
622
|
+
| `pollAnswer` | `update.poll_answer`. |
|
|
623
|
+
| `chatMember` | `update.chat_member`. |
|
|
624
|
+
| `myChatMember` | `update.my_chat_member`. |
|
|
625
|
+
| `chatJoinRequest` | `update.chat_join_request`. |
|
|
626
|
+
| `reaction` | `update.message_reaction`. |
|
|
627
|
+
| `boost` | `chat_boost` 或 `removed_chat_boost`. |
|
|
628
|
+
|
|
629
|
+
### `new Context(options)`
|
|
630
|
+
|
|
631
|
+
```ts
|
|
632
|
+
new Context<S>(options: ContextOptions<S>): Context<S>
|
|
633
|
+
```
|
|
634
|
+
|
|
635
|
+
### Context 消息方法
|
|
636
|
+
|
|
637
|
+
| 方法 | 签名 | 行为 |
|
|
638
|
+
|---|---|---|
|
|
639
|
+
| `reply` | `reply(text, extra?): Promise<Message>` | 将消息发送到更新的聊天,并在存在消息时设置 `reply_parameters.message_id`. |
|
|
640
|
+
| `send` | `send(text, extra?): Promise<Message>` | 向更新的聊天发送消息,不带回复引用. |
|
|
641
|
+
| `edit` | `edit(text, extra?): Promise<Message \| true>` | 使用 `editMessageText` 编辑更新的消息. |
|
|
642
|
+
| `delete` | `delete(): Promise<true>` | 删除更新的消息. |
|
|
643
|
+
| `copy` | `copy(fromChatId, messageId, extra?): Promise<unknown>` | 向上下文聊天调用 `copyMessage`. |
|
|
644
|
+
| `forward` | `forward(fromChatId, messageId, extra?): Promise<Message>` | 向上下文聊天调用 `forwardMessage`. |
|
|
645
|
+
| `pin` | `pin(messageId?, extra?): Promise<true>` | 调用 `pinChatMessage`,默认消息 ID 来自上下文. |
|
|
646
|
+
| `unpin` | `unpin(messageId?, extra?): Promise<true>` | 调用 `unpinChatMessage`,默认消息 ID 来自上下文. |
|
|
647
|
+
| `react` | `react(reaction, extra?): Promise<true>` | 调用 `setMessageReaction`. |
|
|
648
|
+
| `answerCallbackQuery` | `answerCallbackQuery(text?, extra?): Promise<true>` | 回答活动的回调查询。如果不是回调更新则抛出错误. |
|
|
649
|
+
| `answerInlineQuery` | `answerInlineQuery(results, extra?): Promise<true>` | 回答活动的内联查询。如果不是内联更新则抛出错误. |
|
|
650
|
+
| `getChat` | `getChat(): Promise<Chat>` | 获取上下文聊天的详细信息. |
|
|
651
|
+
| `getUserProfilePhotos` | `getUserProfilePhotos(userId?, extra?): Promise<unknown>` | 获取上下文用户的头像照片. |
|
|
652
|
+
| `getFile` | `getFile(fileId): Promise<unknown>` | 根据 ID 获取文件. |
|
|
653
|
+
| `withReplyMarkup` | `withReplyMarkup(markup): this` | 将标记保存到 `ctx.state.reply_markup` 并返回上下文。此方法不会自动发送消息. |
|
|
654
|
+
|
|
655
|
+
`reply`、`send`、`getChat` 以及其他一些辅助方法在更新缺少所需聊天时会抛出错误。`edit` 和 `delete` 需要同时有聊天和消息。
|
|
656
|
+
|
|
657
|
+
---
|
|
658
|
+
|
|
659
|
+
## 5. 中间件与路由器
|
|
660
|
+
|
|
661
|
+
### Middleware types
|
|
662
|
+
|
|
663
|
+
```ts
|
|
664
|
+
type Next = () => Promise<void>;
|
|
665
|
+
type Middleware<Context> = (ctx: Context, next: Next) => void | Promise<void>;
|
|
666
|
+
```
|
|
667
|
+
|
|
668
|
+
以洋葱模型(onion pattern)组合中间件。`next()` 会执行下一个中间件。如果同一个中间件多次调用 `next()`,`compose` 会抛出 `Error("next() called multiple times")`。
|
|
669
|
+
|
|
670
|
+
### `middleware(handler)`
|
|
671
|
+
|
|
672
|
+
```ts
|
|
673
|
+
middleware<Context>(handler: Middleware<Context>): Middleware<Context>
|
|
674
|
+
```
|
|
675
|
+
|
|
676
|
+
用于为中间件提供注解/类型推导的标识辅助函数。
|
|
677
|
+
|
|
678
|
+
### `RoutableContext`
|
|
679
|
+
|
|
680
|
+
路由器所需的最小上下文:`update`、`message`、`callbackQuery` 和 `params`。
|
|
681
|
+
|
|
682
|
+
### `Router<Context>`
|
|
683
|
+
|
|
684
|
+
```ts
|
|
685
|
+
new Router<Context extends RoutableContext>(): Router<Context>
|
|
686
|
+
```
|
|
687
|
+
|
|
688
|
+
路由按照优先级和注册顺序处理。匹配的路由不会自动阻止后续路由;所有匹配的路由都可以被执行。如果没有任何路由匹配,则在 `handle` 上的 `terminal` 会被调用。
|
|
689
|
+
|
|
690
|
+
| 方法 | 签名 | 匹配 |
|
|
691
|
+
|---|---|---|
|
|
692
|
+
| `use` | `use(...middleware): this` | 全局路由中间件,具有最高优先级,先执行。 |
|
|
693
|
+
| `route` | `route(matcher, ...middleware): this` | 布尔或异步的自定义匹配器。 |
|
|
694
|
+
| `command` | `command(name: string \| RegExp, ...middleware): this` | 以 `/` 开头的消息文本的第一个命令。 |
|
|
695
|
+
| `text` | `text(value: string, ...middleware): this` | 精确文本匹配。 |
|
|
696
|
+
| `regex` | `regex(expression: RegExp, ...middleware): this` | 对消息文本或空字符串使用 `RegExp.test`。 |
|
|
697
|
+
| `callback` | `callback(pattern: string \| RegExp, ...middleware): this` | 对 callback 数据进行精确匹配、以 `*` 结尾作为前缀匹配,或使用正则匹配。 |
|
|
698
|
+
| `chat` | `chat(chatId: number \| string, ...middleware): this` | 匹配 `message.chat.id`,数值或字符串等价。 |
|
|
699
|
+
| `predicate` | `predicate(matcher, ...middleware): this` | 自定义 matcher 的语义别名。 |
|
|
700
|
+
| `nest` | `nest(child: Router<Context>): this` | 将子路由作为嵌套路由运行。 |
|
|
701
|
+
| `handle` | `handle(ctx, terminal?): Promise<void>` | 评估并执行所有匹配的路由。 |
|
|
702
|
+
|
|
703
|
+
```ts
|
|
704
|
+
const router = new Router<Context>();
|
|
705
|
+
router.use(async (ctx, next) => {
|
|
706
|
+
console.log("before");
|
|
707
|
+
await next();
|
|
708
|
+
});
|
|
709
|
+
router.callback("page:*", async (ctx) => {
|
|
710
|
+
await ctx.answerCallbackQuery();
|
|
711
|
+
});
|
|
712
|
+
router.predicate((ctx) => Boolean(ctx.from?.id), async (ctx) => {
|
|
713
|
+
await ctx.reply("Authenticated update");
|
|
714
|
+
});
|
|
715
|
+
```
|
|
716
|
+
|
|
717
|
+
**RegExp 注意事项。** 路由器直接调用 `.test()`。对于带有 `g` 或 `y` 标志的表达式,JavaScript 中有状态的 `lastIndex` 可能会影响重复匹配。
|
|
718
|
+
|
|
719
|
+
---
|
|
720
|
+
|
|
721
|
+
## 6. Keyboard builders
|
|
722
|
+
|
|
723
|
+
### `InlineKeyboard`
|
|
724
|
+
|
|
725
|
+
```ts
|
|
726
|
+
new InlineKeyboard(): InlineKeyboard
|
|
727
|
+
InlineKeyboard.from(rows: InlineKeyboardButton[][]): InlineKeyboard
|
|
728
|
+
```
|
|
729
|
+
|
|
730
|
+
构建器以可变方式保存 `rows`,并且所有构建器方法都返回 `this`。
|
|
731
|
+
|
|
732
|
+
| Method | Signature | 描述 |
|
|
733
|
+
|---|---|---|
|
|
734
|
+
| `from` | `static from(rows): InlineKeyboard` | 从 `rows` 创建键盘并复制每一行。 |
|
|
735
|
+
| `text` | `text(text, callbackData): this` | 回调按钮。 |
|
|
736
|
+
| `url` | `url(text, url): this` | URL 按钮。 |
|
|
737
|
+
| `webApp` | `webApp(text, url): this` | Web 应用按钮。 |
|
|
738
|
+
| `pay` | `pay(text = "Pay"): this` | 支付按钮。 |
|
|
739
|
+
| `copy` | `copy(text, copiedText): this` | 复制文本按钮。 |
|
|
740
|
+
| `button` | `button(button): this` | 将一个按钮添加到最后一行,或创建第一行。 |
|
|
741
|
+
| `row` | `row(...buttons): this` | 添加新行。 |
|
|
742
|
+
| `conditional` | `conditional(condition, factory): this` | 仅当 condition 为 true 时执行 factory。 |
|
|
743
|
+
| `grid` | `grid(buttons, columns): this` | 将按钮按列数分配到各行。 |
|
|
744
|
+
| `build` | `build(): InlineKeyboardMarkup` | 生成新的 markup。 |
|
|
745
|
+
| `asReplyMarkup` | `asReplyMarkup(): InlineKeyboardMarkup` | `build` 的别名。 |
|
|
746
|
+
|
|
747
|
+
每个 inline 按钮必须有 text 并且恰好一个 action。回调数据限制为最多 64 字节 UTF-8;超出会抛出 `RangeError`。
|
|
748
|
+
|
|
749
|
+
```ts
|
|
750
|
+
const keyboard = new InlineKeyboard()
|
|
751
|
+
.text("允许", "approve:123")
|
|
752
|
+
.url("文档", "https://example.com")
|
|
753
|
+
.row(
|
|
754
|
+
{ text: "A", callback_data: "a" },
|
|
755
|
+
{ text: "B", callback_data: "b" },
|
|
756
|
+
)
|
|
757
|
+
.build();
|
|
758
|
+
```
|
|
759
|
+
|
|
760
|
+
### `ReplyKeyboard`
|
|
761
|
+
|
|
762
|
+
```ts
|
|
763
|
+
new ReplyKeyboard(): ReplyKeyboard
|
|
764
|
+
```
|
|
765
|
+
|
|
766
|
+
| Method | Signature | 描述 |
|
|
767
|
+
|---|---|---|
|
|
768
|
+
| `text` | `text(text): this` | 普通文本按钮。 |
|
|
769
|
+
| `contact` | `contact(text): this` | 请求联系人。 |
|
|
770
|
+
| `location` | `location(text): this` | 请求位置。 |
|
|
771
|
+
| `poll` | `poll(text, type?): this` | 请求投票,类型为 `quiz` 或 `regular`。 |
|
|
772
|
+
| `webApp` | `webApp(text, url): this` | Web 应用按钮。 |
|
|
773
|
+
| `button` | `button(button): this` | 将一个按钮添加到最后一行。 |
|
|
774
|
+
| `row` | `row(...buttons): this` | 添加新行。 |
|
|
775
|
+
| `grid` | `grid(buttons, columns): this` | 将按钮划分为网格。 |
|
|
776
|
+
| `build` | `build(options?): ReplyKeyboardMarkup` | 生成 markup 并合并 options。 |
|
|
777
|
+
| `asReplyMarkup` | `asReplyMarkup(): ReplyKeyboardMarkup` | `build()` 的别名,不带 options。 |
|
|
778
|
+
|
|
779
|
+
columns 必须为正整数;否则,`grid` 会抛出 `RangeError`。
|
|
780
|
+
|
|
781
|
+
### `removeKeyboard(selective?)`
|
|
782
|
+
|
|
783
|
+
```ts
|
|
784
|
+
removeKeyboard(selective = false): ReplyMarkup
|
|
785
|
+
```
|
|
786
|
+
|
|
787
|
+
生成 `{ remove_keyboard: true }`,如果请求则包含 `selective: true`。
|
|
788
|
+
|
|
789
|
+
### `forceReply(placeholder?, selective?)`
|
|
790
|
+
|
|
791
|
+
```ts
|
|
792
|
+
forceReply(placeholder?: string, selective = false): ReplyMarkup
|
|
793
|
+
```
|
|
794
|
+
|
|
795
|
+
生成 ForceReply。仅当 placeholder 为 truthy 时才会添加占位符。
|
|
796
|
+
|
|
797
|
+
---
|
|
798
|
+
|
|
799
|
+
## 7. 存储与缓存
|
|
800
|
+
|
|
801
|
+
### `Storage<K, V>`
|
|
802
|
+
|
|
803
|
+
```ts
|
|
804
|
+
interface Storage<K, V> {
|
|
805
|
+
get(key: K): Promise<V | undefined>;
|
|
806
|
+
set(key: K, value: V, options?: { ttlMs?: number }): Promise<void>;
|
|
807
|
+
delete(key: K): Promise<boolean>;
|
|
808
|
+
has(key: K): Promise<boolean>;
|
|
809
|
+
clear(): Promise<void>;
|
|
810
|
+
keys(): AsyncIterable<K>;
|
|
811
|
+
values(): AsyncIterable<V>;
|
|
812
|
+
entries(): AsyncIterable<[K, V]>;
|
|
813
|
+
update<T extends V>(
|
|
814
|
+
key: K,
|
|
815
|
+
updater: (current: V | undefined) => T | Promise<T>,
|
|
816
|
+
options?: { ttlMs?: number },
|
|
817
|
+
): Promise<T>;
|
|
818
|
+
}
|
|
819
|
+
```
|
|
820
|
+
|
|
821
|
+
### `MemoryStorage<K, V>`
|
|
822
|
+
|
|
823
|
+
```ts
|
|
824
|
+
new MemoryStorage<K, V>(): MemoryStorage<K, V>
|
|
825
|
+
```
|
|
826
|
+
|
|
827
|
+
基于 `Map` 的内存实现。TTL 在读取或迭代键时惰性清理;没有后台定时器。`update` 使得每个键的 updater 操作串行执行,从而避免对同一键的并发更新出现意外覆盖。
|
|
828
|
+
|
|
829
|
+
```ts
|
|
830
|
+
const sessions = new MemoryStorage<string, { count: number }>();
|
|
831
|
+
await sessions.set("user:1", { count: 0 }, { ttlMs: 60_000 });
|
|
832
|
+
await sessions.update("user:1", (current) => ({
|
|
833
|
+
count: (current?.count ?? 0) + 1,
|
|
834
|
+
}));
|
|
835
|
+
```
|
|
836
|
+
|
|
837
|
+
### `Cache<K, V>`
|
|
838
|
+
|
|
839
|
+
```ts
|
|
840
|
+
interface Cache<K = string, V = unknown> {
|
|
841
|
+
get(key: K): Promise<V | undefined>;
|
|
842
|
+
set(key: K, value: V, ttlMs?: number): Promise<void>;
|
|
843
|
+
delete(key: K): Promise<boolean>;
|
|
844
|
+
invalidate(prefix?: string): Promise<void>;
|
|
845
|
+
getOrSet(key: K, factory: () => V | Promise<V>, ttlMs?: number): Promise<V>;
|
|
846
|
+
}
|
|
847
|
+
```
|
|
848
|
+
|
|
849
|
+
### `MemoryCache`
|
|
850
|
+
|
|
851
|
+
```ts
|
|
852
|
+
new MemoryCache(namespace = "telebibz"): MemoryCache
|
|
853
|
+
```
|
|
854
|
+
|
|
855
|
+
对字符串键的缓存,会对每个 key 在内部添加命名空间。
|
|
856
|
+
|
|
857
|
+
| Method | 行为 |
|
|
858
|
+
|---|---|
|
|
859
|
+
| `get` | 获取 value 或 `undefined`。 |
|
|
860
|
+
| `set` | 存储 value 并可选 TTL。 |
|
|
861
|
+
| `delete` | 删除 key 并返回布尔值。 |
|
|
862
|
+
| `invalidate(prefix = "")` | 删除命名空间中以 prefix 开头的所有 key。 |
|
|
863
|
+
| `getOrSet` | 返回缓存命中;若未命中,执行 factory,存储结果后返回。 |
|
|
864
|
+
|
|
865
|
+
`getOrSet` 不使用去重锁;当 key 还不存在且并发调用时,factory 可能会被执行多次。
|
|
866
|
+
|
|
867
|
+
### `RateLimitResult`
|
|
868
|
+
|
|
869
|
+
```ts
|
|
870
|
+
interface RateLimitResult {
|
|
871
|
+
allowed: boolean;
|
|
872
|
+
remaining: number;
|
|
873
|
+
resetAt: number;
|
|
874
|
+
retryAfterMs?: number;
|
|
875
|
+
}
|
|
876
|
+
```
|
|
877
|
+
|
|
878
|
+
### `TokenBucketLimiter`
|
|
879
|
+
|
|
880
|
+
```ts
|
|
881
|
+
new TokenBucketLimiter(
|
|
882
|
+
capacity: number,
|
|
883
|
+
refillPerSecond: number,
|
|
884
|
+
): TokenBucketLimiter
|
|
885
|
+
```
|
|
886
|
+
|
|
887
|
+
构造函数在任一参数非正时抛出 `RangeError`。`consume(key, cost = 1)` 在有可用令牌时扣减;若令牌不足,返回 `allowed: false` 并给出估计的 `retryAfterMs`。`clear(key?)` 删除单个桶或全部桶。
|
|
888
|
+
|
|
889
|
+
---
|
|
890
|
+
|
|
891
|
+
## 8. 队列和调度器
|
|
892
|
+
|
|
893
|
+
### `Job<T>` 和 `QueueOptions`
|
|
894
|
+
|
|
895
|
+
```ts
|
|
896
|
+
interface Job<T = unknown> {
|
|
897
|
+
id: string;
|
|
898
|
+
data: T;
|
|
899
|
+
attempts: number;
|
|
900
|
+
priority: number;
|
|
901
|
+
runAt: number;
|
|
902
|
+
status: "queued" | "running" | "completed" | "failed" | "cancelled";
|
|
903
|
+
error?: unknown;
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
interface QueueOptions {
|
|
907
|
+
concurrency?: number;
|
|
908
|
+
retries?: number;
|
|
909
|
+
backoffMs?: number;
|
|
910
|
+
maxBackoffMs?: number;
|
|
911
|
+
}
|
|
912
|
+
```
|
|
913
|
+
|
|
914
|
+
### `TaskQueue<T>`
|
|
915
|
+
|
|
916
|
+
```ts
|
|
917
|
+
new TaskQueue<T>(
|
|
918
|
+
worker: (job: Job<T>, signal: AbortSignal) => Promise<void>,
|
|
919
|
+
options?: QueueOptions,
|
|
920
|
+
): TaskQueue<T>
|
|
921
|
+
```
|
|
922
|
+
|
|
923
|
+
| 方法 | 签名 | 描述 |
|
|
924
|
+
|---|---|---|
|
|
925
|
+
| `add` | `add(data, options?): Job<T>` | 添加作业;options 包含 `id`, `priority`, `delayMs`。作业会立即被调度。 |
|
|
926
|
+
| `get` | `get(id): Job<T> \| undefined` | 返回作业状态的副本。 |
|
|
927
|
+
| `cancel` | `cancel(id): boolean` | 取消处于 queued 或 running 状态的作业并中止 worker 的 signal。 |
|
|
928
|
+
| `onIdle` | `onIdle(): Promise<void>` | 等待直到 pending 和 active 为空。 |
|
|
929
|
+
| `close` | `close(): Promise<void>` | 停止新的排空并取消活动的控制器。 |
|
|
930
|
+
|
|
931
|
+
优先级更高的作业先执行;若相同,则较早的 `runAt` 先执行。重试会一直进行直到超过 `retries`。重试延迟为指数增长,`maxBackoffMs` 的默认上限为 30 秒。
|
|
932
|
+
|
|
933
|
+
### `ScheduledJob`
|
|
934
|
+
|
|
935
|
+
```ts
|
|
936
|
+
interface ScheduledJob {
|
|
937
|
+
id: string;
|
|
938
|
+
cancel: () => void;
|
|
939
|
+
}
|
|
940
|
+
```
|
|
941
|
+
|
|
942
|
+
### `Scheduler`
|
|
943
|
+
|
|
944
|
+
```ts
|
|
945
|
+
new Scheduler(): Scheduler
|
|
946
|
+
```
|
|
947
|
+
|
|
948
|
+
| 方法 | 签名 | 描述 |
|
|
949
|
+
|---|---|---|
|
|
950
|
+
| `every` | `every(id, intervalMs, task): ScheduledJob` | 使用 `setInterval` 运行任务。使用相同 id 替换定时器。 |
|
|
951
|
+
| `after` | `after(id, delayMs, task): ScheduledJob` | 使用 `setTimeout` 执行一次任务。 |
|
|
952
|
+
| `cron` | `cron(id, expression, task): ScheduledJob` | 支持分钟字段的简单格式 `*/N`,等同于间隔 `N * 60_000`。 |
|
|
953
|
+
| `cancel` | `cancel(id): boolean` | 取消定时器。 |
|
|
954
|
+
| `clear` | `clear(): void` | 取消所有定时器。 |
|
|
955
|
+
|
|
956
|
+
内置调度器不支持完整的 cron 格式。除 `*/N` 外的表达式会抛出 `Error`。
|
|
957
|
+
|
|
958
|
+
---
|
|
959
|
+
|
|
960
|
+
## 9. 插件与服务
|
|
961
|
+
|
|
962
|
+
### `Plugin<Context>`
|
|
963
|
+
|
|
964
|
+
```ts
|
|
965
|
+
interface Plugin<Context = unknown> {
|
|
966
|
+
name: string;
|
|
967
|
+
version?: string;
|
|
968
|
+
install?: (api: PluginApi<Context>) => void | Promise<void>;
|
|
969
|
+
setup?: (api: PluginApi<Context>) => void | Promise<void>;
|
|
970
|
+
onStart?: (api: PluginApi<Context>) => void | Promise<void>;
|
|
971
|
+
onUpdate?: (context: Context) => void | Promise<void>;
|
|
972
|
+
onStop?: (api: PluginApi<Context>) => void | Promise<void>;
|
|
973
|
+
dispose?: (api: PluginApi<Context>) => void | Promise<void>;
|
|
974
|
+
}
|
|
975
|
+
```
|
|
976
|
+
|
|
977
|
+
### `PluginApi<Context>`
|
|
978
|
+
|
|
979
|
+
```ts
|
|
980
|
+
interface PluginApi<Context> {
|
|
981
|
+
bot: unknown;
|
|
982
|
+
services: ServiceContainer;
|
|
983
|
+
registerMiddleware: (middleware: unknown) => void;
|
|
984
|
+
registerRoute: (route: unknown) => void;
|
|
985
|
+
}
|
|
986
|
+
```
|
|
987
|
+
|
|
988
|
+
在此发行版中,`registerMiddleware` 和 `registerRoute` 可作为 hook API 提供,但实现管理器尚未将它们自动连接到 bot/router。插件可以直接使用 `api.bot` 和 `api.services`。
|
|
989
|
+
|
|
990
|
+
### `ServiceContainer`
|
|
991
|
+
|
|
992
|
+
```ts
|
|
993
|
+
new ServiceContainer(): ServiceContainer
|
|
994
|
+
```
|
|
995
|
+
|
|
996
|
+
| 方法 | 签名 | 描述 |
|
|
997
|
+
|---|---|---|
|
|
998
|
+
| `register` | `register<T>(name: string \| symbol, value: T): this` | 存储服务并支持链式调用。 |
|
|
999
|
+
| `get` | `get<T>(name: string \| symbol): T` | 获取服务;如果未注册则抛出错误。 |
|
|
1000
|
+
| `has` | `has(name: string \| symbol): boolean` | 检查服务是否存在。 |
|
|
1001
|
+
| `delete` | `delete(name: string \| symbol): boolean` | 删除服务。 |
|
|
1002
|
+
|
|
1003
|
+
### `PluginManager<Context>`
|
|
1004
|
+
|
|
1005
|
+
```ts
|
|
1006
|
+
new PluginManager<Context>(bot: unknown): PluginManager<Context>
|
|
1007
|
+
```
|
|
1008
|
+
|
|
1009
|
+
| 方法 | 行为 |
|
|
1010
|
+
|---|---|
|
|
1011
|
+
| `use(plugin)` | 添加插件;重复名称会抛出错误。 |
|
|
1012
|
+
| `setup()` | 对每个插件先执行 `install` 然后 `setup`。 |
|
|
1013
|
+
| `start()` | 按注册顺序执行 `onStart`。 |
|
|
1014
|
+
| `update(context)` | 按注册顺序执行 `onUpdate`。 |
|
|
1015
|
+
| `stop()` | 执行 `onStop`。 |
|
|
1016
|
+
| `dispose()` | 按相反的注册顺序执行 `dispose`。 |
|
|
1017
|
+
| `list()` | 返回只读的插件列表。 |
|
|
1018
|
+
|
|
1019
|
+
`Bot.handleUpdate()` 在此发行版中不会自动调用 `plugins.update()`;如果插件需要 update 生命周期,请显式调用管理器。
|
|
1020
|
+
|
|
1021
|
+
---
|
|
1022
|
+
|
|
1023
|
+
## 10. Webhook
|
|
1024
|
+
|
|
1025
|
+
### `WebhookOptions`
|
|
1026
|
+
|
|
1027
|
+
```ts
|
|
1028
|
+
interface WebhookOptions {
|
|
1029
|
+
secretToken?: string;
|
|
1030
|
+
maxBodyBytes?: number;
|
|
1031
|
+
onError?: (error: unknown) => void | Promise<void>;
|
|
1032
|
+
}
|
|
1033
|
+
```
|
|
1034
|
+
|
|
1035
|
+
### `createWebhookHandler(bot, options?)`
|
|
1036
|
+
|
|
1037
|
+
```ts
|
|
1038
|
+
createWebhookHandler<S extends object>(
|
|
1039
|
+
bot: Bot<S>,
|
|
1040
|
+
options?: WebhookOptions,
|
|
1041
|
+
): (request: Request) => Promise<Response>
|
|
1042
|
+
```
|
|
1043
|
+
|
|
1044
|
+
处理程序接收标准的 Web `Request` 并返回 `Response`。
|
|
1045
|
+
|
|
1046
|
+
| 情况 | 响应 |
|
|
1047
|
+
|---|---|
|
|
1048
|
+
| 方法不是 POST | `405 Method Not Allowed`, header `allow: POST` |
|
|
1049
|
+
| Secret 头部不匹配 | `401 Unauthorized` |
|
|
1050
|
+
| `Content-Length` 或 body 超过限制 | `413 Payload Too Large` |
|
|
1051
|
+
| JSON 无效或 `update_id` 不是整数 | 对于 update id 返回 `400 Bad Request`; 解析时的异常返回 `500` |
|
|
1052
|
+
| `bot.handleUpdate` 成功 | `200 OK`,body 为 `OK` |
|
|
1053
|
+
| 其他异常 | `500 Internal Server Error` 并调用 `onError` |
|
|
1054
|
+
|
|
1055
|
+
默认 `maxBodyBytes` 为 `1_048_576` bytes。Telegram 的 secret 从 header `x-telegram-bot-api-secret-token` 读取。
|
|
1056
|
+
|
|
1057
|
+
```ts
|
|
1058
|
+
import { Bot, createWebhookHandler } from "@xbibzlibrary/telebibz";
|
|
1059
|
+
|
|
1060
|
+
const bot = new Bot(process.env.TELEGRAM_BOT_TOKEN!);
|
|
1061
|
+
const handler = createWebhookHandler(bot, {
|
|
1062
|
+
secretToken: process.env.TELEGRAM_WEBHOOK_SECRET,
|
|
1063
|
+
});
|
|
1064
|
+
|
|
1065
|
+
export default { fetch: handler };
|
|
1066
|
+
```
|
|
1067
|
+
|
|
1068
|
+
---
|
|
1069
|
+
|
|
1070
|
+
## 11. 会话、向导、表单和菜单
|
|
1071
|
+
|
|
1072
|
+
### Conversation
|
|
1073
|
+
|
|
1074
|
+
```ts
|
|
1075
|
+
interface ConversationState {
|
|
1076
|
+
name: string;
|
|
1077
|
+
step: number;
|
|
1078
|
+
values: Record<string, unknown>;
|
|
1079
|
+
status: "active" | "completed" | "cancelled";
|
|
1080
|
+
updatedAt: number;
|
|
1081
|
+
}
|
|
1082
|
+
```
|
|
1083
|
+
|
|
1084
|
+
#### `ConversationFlow<S>`
|
|
1085
|
+
|
|
1086
|
+
```ts
|
|
1087
|
+
new ConversationFlow(ctx: Context<S>, state: ConversationState)
|
|
1088
|
+
```
|
|
1089
|
+
|
|
1090
|
+
| 方法/属性 | 签名 | 描述 |
|
|
1091
|
+
|---|---|---|
|
|
1092
|
+
| `ctx` | `Context<S>` | 当前的更新上下文。 |
|
|
1093
|
+
| `state` | `ConversationState` | 可变的会话状态。 |
|
|
1094
|
+
| `values` | `Record<string, unknown>` | 是 `state.values` 的别名。 |
|
|
1095
|
+
| `set` | `set<T>(key, value): this` | 保存值并更新 `updatedAt`。 |
|
|
1096
|
+
| `get` | `get<T>(key): T \| undefined` | 获取类型化的值。 |
|
|
1097
|
+
| `next` | `next(): this` | 将 step 增加 1。 |
|
|
1098
|
+
| `previous` | `previous(): this` | 将 step 减少,但最低为 0。 |
|
|
1099
|
+
| `complete` | `complete(): void` | 将状态置为 `completed`。 |
|
|
1100
|
+
| `cancel` | `cancel(): void` | 将状态置为 `cancelled`。 |
|
|
1101
|
+
|
|
1102
|
+
#### `ConversationManager<S>`
|
|
1103
|
+
|
|
1104
|
+
```ts
|
|
1105
|
+
new ConversationManager<S>(): ConversationManager<S>
|
|
1106
|
+
```
|
|
1107
|
+
|
|
1108
|
+
| 方法 | 签名 | 描述 |
|
|
1109
|
+
|---|---|---|
|
|
1110
|
+
| `start` | `start(key, name, values?): ConversationState` | 创建或替换会话状态。 |
|
|
1111
|
+
| `get` | `get(key): ConversationState \| undefined` | 获取活动状态。 |
|
|
1112
|
+
| `cancel` | `cancel(key): boolean` | 如果存在则标记为 cancelled。 |
|
|
1113
|
+
| `clearExpired` | `clearExpired(maxAgeMs): number` | 删除 `updatedAt` 早于阈值的状态。 |
|
|
1114
|
+
| `run` | `run(ctx, key, name, steps): Promise<ConversationState>` | 根据 `state.step` 运行对应的步骤;如果没有步骤,则状态为 completed。 |
|
|
1115
|
+
|
|
1116
|
+
```ts
|
|
1117
|
+
const conversations = new ConversationManager();
|
|
1118
|
+
await conversations.run(ctx, "chat:1", "profile", [
|
|
1119
|
+
async (flow) => {
|
|
1120
|
+
flow.set("name", ctx.message?.text);
|
|
1121
|
+
flow.next();
|
|
1122
|
+
},
|
|
1123
|
+
async (flow) => {
|
|
1124
|
+
await flow.ctx.reply(`Nama: ${flow.get<string>("name")}`);
|
|
1125
|
+
flow.complete();
|
|
1126
|
+
},
|
|
1127
|
+
]);
|
|
1128
|
+
```
|
|
1129
|
+
|
|
1130
|
+
#### `Wizard<S>` dan `WizardStep<S>`
|
|
1131
|
+
|
|
1132
|
+
```ts
|
|
1133
|
+
interface WizardStep<S> {
|
|
1134
|
+
id: string;
|
|
1135
|
+
run: (flow: ConversationFlow<S>) => void | Promise<void>;
|
|
1136
|
+
optional?: boolean;
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
new Wizard<S>()
|
|
1140
|
+
```
|
|
1141
|
+
|
|
1142
|
+
| 方法/属性 | 描述 |
|
|
1143
|
+
|---|---|
|
|
1144
|
+
| `step(definition)` | 添加步骤并返回 wizard。`optional` 保存在定义中,但 runner 还没有特殊处理。 |
|
|
1145
|
+
| `run(ctx, key, manager?)` | 通过 `ConversationManager` 使用 name 为 `"wizard"` 运行 wizard 的步骤。 |
|
|
1146
|
+
| `steps` | 只读的步骤列表。 |
|
|
1147
|
+
|
|
1148
|
+
### 表单
|
|
1149
|
+
|
|
1150
|
+
```ts
|
|
1151
|
+
interface ValidationIssue {
|
|
1152
|
+
path: string;
|
|
1153
|
+
message: string;
|
|
1154
|
+
code?: string;
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
interface Field<T> {
|
|
1158
|
+
name: string;
|
|
1159
|
+
parse: (input: unknown) => T;
|
|
1160
|
+
validate?: (value: T) => string | undefined | Promise<string | undefined>;
|
|
1161
|
+
transform?: (value: T) => T | Promise<T>;
|
|
1162
|
+
required?: boolean;
|
|
1163
|
+
}
|
|
1164
|
+
```
|
|
1165
|
+
|
|
1166
|
+
#### `Form<T>`
|
|
1167
|
+
|
|
1168
|
+
```ts
|
|
1169
|
+
new Form<T extends Record<string, unknown>>(): Form<T>
|
|
1170
|
+
```
|
|
1171
|
+
|
|
1172
|
+
| 方法 | 描述 |
|
|
1173
|
+
|---|---|
|
|
1174
|
+
| `field(definition)` | 根据 `name` 注册类型化字段。 |
|
|
1175
|
+
| `parse(input)` | 处理所有字段。返回 success 或 issues 的联合结果。顺序:required 检查、parse、transform、validate。 |
|
|
1176
|
+
| `reset()` | 清除内部保存的解析数据。 |
|
|
1177
|
+
|
|
1178
|
+
Result parse:
|
|
1179
|
+
|
|
1180
|
+
```ts
|
|
1181
|
+
type FormResult<T> =
|
|
1182
|
+
| { success: true; data: T }
|
|
1183
|
+
| { success: false; issues: ValidationIssue[] };
|
|
1184
|
+
```
|
|
1185
|
+
|
|
1186
|
+
Issue 使用 code `required`、`parse` 或 `invalid`。
|
|
1187
|
+
|
|
1188
|
+
#### `validators`
|
|
1189
|
+
|
|
1190
|
+
| 验证器 | 输入 | 结果/错误 |
|
|
1191
|
+
|---|---|---|
|
|
1192
|
+
| `validators.string` | `unknown` | String;否则 `TypeError("Expected string")`. |
|
|
1193
|
+
| `validators.number` | `unknown` | 有限的 Number,包括数字字符串;否则 `TypeError("Expected number")`. |
|
|
1194
|
+
| `validators.integer` | `unknown` | Integer;否则 `TypeError("Expected integer")`. |
|
|
1195
|
+
| `validators.email` | `unknown` | 符合简易 email 模式的 String;否则 `TypeError("Expected email")`. |
|
|
1196
|
+
| `validators.url` | `unknown` | 可被 `URL` 构造函数接受的 String;否则 `TypeError("Expected URL")`. |
|
|
1197
|
+
|
|
1198
|
+
### 分页与菜单
|
|
1199
|
+
|
|
1200
|
+
#### `Page<T>`
|
|
1201
|
+
|
|
1202
|
+
```ts
|
|
1203
|
+
interface Page<T> {
|
|
1204
|
+
items: T[];
|
|
1205
|
+
page: number;
|
|
1206
|
+
pageCount: number;
|
|
1207
|
+
hasPrevious: boolean;
|
|
1208
|
+
hasNext: boolean;
|
|
1209
|
+
}
|
|
1210
|
+
```
|
|
1211
|
+
|
|
1212
|
+
#### `paginate(items, page, pageSize)`
|
|
1213
|
+
|
|
1214
|
+
```ts
|
|
1215
|
+
paginate<T>(
|
|
1216
|
+
items: readonly T[],
|
|
1217
|
+
page: number,
|
|
1218
|
+
pageSize: number,
|
|
1219
|
+
): Page<T>
|
|
1220
|
+
```
|
|
1221
|
+
|
|
1222
|
+
Page 使用 0 为基的索引。超出范围的 page 会被夹取到最后一页。空集合仍然具有 `pageCount: 1`。负数或非整数的 `page` 或 `pageSize < 1` 会抛出 `RangeError`。
|
|
1223
|
+
|
|
1224
|
+
#### `paginationButtons(page, prefix)`
|
|
1225
|
+
|
|
1226
|
+
```ts
|
|
1227
|
+
paginationButtons(
|
|
1228
|
+
page: Page<unknown>,
|
|
1229
|
+
prefix: string,
|
|
1230
|
+
): InlineKeyboardButton[]
|
|
1231
|
+
```
|
|
1232
|
+
|
|
1233
|
+
生成 `Previous` 按钮、带回调 `${prefix}:noop` 的指示器 `${page + 1}/${pageCount}`,以及根据 page 标志显示 `Next`。
|
|
1234
|
+
|
|
1235
|
+
#### `MenuItem`
|
|
1236
|
+
|
|
1237
|
+
```ts
|
|
1238
|
+
interface MenuItem {
|
|
1239
|
+
id: string;
|
|
1240
|
+
label: string;
|
|
1241
|
+
callbackData?: string;
|
|
1242
|
+
url?: string;
|
|
1243
|
+
visible?: boolean | (() => boolean | Promise<boolean>);
|
|
1244
|
+
permission?: string;
|
|
1245
|
+
}
|
|
1246
|
+
```
|
|
1247
|
+
|
|
1248
|
+
#### `Menu`
|
|
1249
|
+
|
|
1250
|
+
```ts
|
|
1251
|
+
new Menu(id: string): Menu
|
|
1252
|
+
```
|
|
1253
|
+
|
|
1254
|
+
| 方法/属性 | 描述 |
|
|
1255
|
+
|---|---|
|
|
1256
|
+
| `item(item)` | 添加 item 并支持链式调用。 |
|
|
1257
|
+
| `breadcrumb(label)` | 添加 breadcrumb 标签。 |
|
|
1258
|
+
| `build()` | 等待可见性谓词,跳过不可见项,然后生成 `InlineKeyboard`。URL 优先于 callback。 |
|
|
1259
|
+
| `breadcrumbs` | `breadcrumbs` 只读的 breadcrumb 数组。 |
|
|
1260
|
+
|
|
1261
|
+
`permission` 仅作为 item 的元数据存储;`Menu.build()` 不会自动执行授权。
|
|
1262
|
+
|
|
1263
|
+
---
|
|
1264
|
+
|
|
1265
|
+
## 12. 审批门
|
|
1266
|
+
|
|
1267
|
+
Approval gate 在机器人首次使用库时向 owner 发送通知。默认消息使用标签 `Dev Gantenggg`,包含 bot ID/用户名 和 owner ID,并提供按钮 `Izinkan` 和 `Tidak Diizinkan`。
|
|
1268
|
+
|
|
1269
|
+
### `ApprovalOptions`
|
|
1270
|
+
|
|
1271
|
+
| 属性 | 类型 | 默认 | 描述 |
|
|
1272
|
+
|---|---|---:|---|
|
|
1273
|
+
| `ownerChatId` | `ChatId` | wajib | 通知目标聊天。 |
|
|
1274
|
+
| `ownerUserId` | `number` | wajib | 可以按下按钮的用户 ID。 |
|
|
1275
|
+
| `ownerLabel` | `string` | `Dev Gantenggg` | 通知上的标签。 |
|
|
1276
|
+
| `requireApproval` | `boolean` | `true` | `false` 会禁用 gate。 |
|
|
1277
|
+
| `notificationCooldownMs` | `number` | `600000` | 等待通知的冷却时间(毫秒)。 |
|
|
1278
|
+
| `store` | `ApprovalStore` | `MemoryApprovalStore` | 自定义 approval 存储。 |
|
|
1279
|
+
|
|
1280
|
+
### 审批类型
|
|
1281
|
+
|
|
1282
|
+
```ts
|
|
1283
|
+
type ApprovalStatus = "pending" | "approved" | "denied";
|
|
1284
|
+
|
|
1285
|
+
interface ApprovalRecord {
|
|
1286
|
+
key: string;
|
|
1287
|
+
botId: number;
|
|
1288
|
+
botUsername?: string;
|
|
1289
|
+
ownerUserId?: number;
|
|
1290
|
+
status: ApprovalStatus;
|
|
1291
|
+
nonce: string;
|
|
1292
|
+
requestedAt: number;
|
|
1293
|
+
decidedAt?: number;
|
|
1294
|
+
decidedBy?: number;
|
|
1295
|
+
notificationMessageId?: number;
|
|
1296
|
+
}
|
|
1297
|
+
|
|
1298
|
+
interface ApprovalIdentity {
|
|
1299
|
+
bot: User;
|
|
1300
|
+
configuredOwnerUserId?: number;
|
|
1301
|
+
}
|
|
1302
|
+
|
|
1303
|
+
interface ApprovalCheck {
|
|
1304
|
+
allowed: boolean;
|
|
1305
|
+
status: ApprovalStatus | "disabled";
|
|
1306
|
+
record?: ApprovalRecord;
|
|
1307
|
+
}
|
|
1308
|
+
```
|
|
1309
|
+
|
|
1310
|
+
### `ApprovalStore`
|
|
1311
|
+
|
|
1312
|
+
```ts
|
|
1313
|
+
interface ApprovalStore {
|
|
1314
|
+
get(key: string): Promise<ApprovalRecord | undefined>;
|
|
1315
|
+
set(key: string, record: ApprovalRecord): Promise<void>;
|
|
1316
|
+
delete?(key: string): Promise<boolean>;
|
|
1317
|
+
}
|
|
1318
|
+
```
|
|
1319
|
+
|
|
1320
|
+
### `MemoryApprovalStore`
|
|
1321
|
+
|
|
1322
|
+
```ts
|
|
1323
|
+
new MemoryApprovalStore(): MemoryApprovalStore
|
|
1324
|
+
```
|
|
1325
|
+
|
|
1326
|
+
基于内存的存储,在 `get` 和 `set` 时返回 record 的副本。
|
|
1327
|
+
|
|
1328
|
+
### `ApprovalGate`
|
|
1329
|
+
|
|
1330
|
+
```ts
|
|
1331
|
+
new ApprovalGate(api: ApiClient, options: ApprovalOptions): ApprovalGate
|
|
1332
|
+
```
|
|
1333
|
+
|
|
1334
|
+
| 方法 | 签名 | 描述 |
|
|
1335
|
+
|---|---|---|
|
|
1336
|
+
| `check` | `check(identity): Promise<ApprovalCheck>` | 若记录已批准则返回 approved;若不存在记录或冷却期已过则发送新的请求。 |
|
|
1337
|
+
| `handleCallback` | `handleCallback(callback): Promise<{ handled: boolean; status?: ApprovalStatus }>` | 验证 nonce 和 owner,然后批准/拒绝。非审批回调返回 `handled: false`。 |
|
|
1338
|
+
| `isAllowed` | `isAllowed(botId): Promise<boolean>` | True 若为 approved 或 gate 被禁用。 |
|
|
1339
|
+
| `revoke` | `revoke(botId): Promise<boolean>` | 如果 store 支持 delete,则删除记录。 |
|
|
1340
|
+
|
|
1341
|
+
回调只能由已配置的 `ownerUserId` 决定。随机的 16 个十六进制字符 nonce 可以防止旧的回调被重用。过时的回调会产生过期提醒。
|
|
1342
|
+
|
|
1343
|
+
```ts
|
|
1344
|
+
const bot = new Bot({
|
|
1345
|
+
token: process.env.TELEGRAM_BOT_TOKEN!,
|
|
1346
|
+
approval: {
|
|
1347
|
+
ownerChatId: 7377733784,
|
|
1348
|
+
ownerUserId: 7377733784,
|
|
1349
|
+
ownerLabel: "Dev Gantenggg",
|
|
1350
|
+
},
|
|
1351
|
+
});
|
|
1352
|
+
```
|
|
1353
|
+
|
|
1354
|
+
---
|
|
1355
|
+
|
|
1356
|
+
## 13. 文本工具
|
|
1357
|
+
|
|
1358
|
+
### `escapeMarkdownV2(value)`
|
|
1359
|
+
|
|
1360
|
+
```ts
|
|
1361
|
+
escapeMarkdownV2(value: string): string
|
|
1362
|
+
```
|
|
1363
|
+
|
|
1364
|
+
对 Telegram MarkdownV2 字符进行转义:`\\_ * [ ] ( ) ~ ` > # + - = | { } . !`.
|
|
1365
|
+
|
|
1366
|
+
### `escapeHtml(value)`
|
|
1367
|
+
|
|
1368
|
+
```ts
|
|
1369
|
+
escapeHtml(value: string): string
|
|
1370
|
+
```
|
|
1371
|
+
|
|
1372
|
+
将 `&`、`<`、`>` 和 `"` 转换为 HTML 实体。
|
|
1373
|
+
|
|
1374
|
+
### `md`
|
|
1375
|
+
|
|
1376
|
+
下面可用的 MarkdownV2 辅助对象:
|
|
1377
|
+
|
|
1378
|
+
| Method | 输出(概念) |
|
|
1379
|
+
|---|---|
|
|
1380
|
+
| `md.bold(value)` | `*escaped value*` |
|
|
1381
|
+
| `md.italic(value)` | `_escaped value_` |
|
|
1382
|
+
| `md.link(label, url)` | `[escaped label](escaped url)` |
|
|
1383
|
+
| `md.code(value)` | 使用已转义反引号的行内代码。 |
|
|
1384
|
+
| `md.pre(value, language?)` | 带可选语言标签的代码块。 |
|
|
1385
|
+
| `md.escape(value)` | 别名 `escapeMarkdownV2`. |
|
|
1386
|
+
|
|
1387
|
+
### `splitMessage(text, options?)`
|
|
1388
|
+
|
|
1389
|
+
```ts
|
|
1390
|
+
splitMessage(
|
|
1391
|
+
text: string,
|
|
1392
|
+
options?: {
|
|
1393
|
+
limit?: number;
|
|
1394
|
+
parseMode?: "Markdown" | "MarkdownV2" | "HTML";
|
|
1395
|
+
},
|
|
1396
|
+
): string[]
|
|
1397
|
+
```
|
|
1398
|
+
|
|
1399
|
+
将文本拆分为片段,默认字符限制为 `4096`。在可能的情况下,拆分会优先选择段落边界、换行或空格;仅当边界位于窗口的一半以上时才使用该边界。`parseMode` 作为 API 的选项被接受,但目前并不会改变拆分算法。
|
|
1400
|
+
|
|
1401
|
+
小于 1 的 limit 会抛出 `RangeError`。
|
|
1402
|
+
|
|
1403
|
+
### `splitCaption(text)`
|
|
1404
|
+
|
|
1405
|
+
```ts
|
|
1406
|
+
splitCaption(text: string): string[]
|
|
1407
|
+
```
|
|
1408
|
+
|
|
1409
|
+
`splitMessage(text, { limit: 1024 })` 的快捷方式。
|
|
1410
|
+
|
|
1411
|
+
### `template(templateText, values)`
|
|
1412
|
+
|
|
1413
|
+
```ts
|
|
1414
|
+
template(
|
|
1415
|
+
templateText: string,
|
|
1416
|
+
values: Record<string, unknown>,
|
|
1417
|
+
): string
|
|
1418
|
+
```
|
|
1419
|
+
|
|
1420
|
+
替换占位符 `{{ key }}` 以及类似 `{{ user.name }}` 的嵌套路径。`null` 或 `undefined` 的值会被替换为空字符串;其他值将使用 `String()` 转换。
|
|
1421
|
+
|
|
1422
|
+
```ts
|
|
1423
|
+
template("你好 {{ user.name }}", { user: { name: "Ayu" } });
|
|
1424
|
+
// "你好 Ayu"
|
|
1425
|
+
```
|
|
1426
|
+
|
|
1427
|
+
---
|
|
1428
|
+
|
|
1429
|
+
## 14. Testing utilities
|
|
1430
|
+
|
|
1431
|
+
Import dari `@xbibzlibrary/telebibz/testing` atau root package.
|
|
1432
|
+
|
|
1433
|
+
### `MockTransport`
|
|
1434
|
+
|
|
1435
|
+
```ts
|
|
1436
|
+
new MockTransport(): MockTransport
|
|
1437
|
+
```
|
|
1438
|
+
|
|
1439
|
+
| API | Deskripsi |
|
|
1440
|
+
|---|---|
|
|
1441
|
+
| `calls` | Array semua `TransportRequest` yang diterima. |
|
|
1442
|
+
| `respond(method, response)` | Mengatur response statis atau callback berdasarkan payload dan mengembalikan transport. |
|
|
1443
|
+
| `request(request)` | Mencatat request dan mengembalikan response mock. Response default adalah `{ ok: true, result: true }`. |
|
|
1444
|
+
|
|
1445
|
+
Status mock adalah `200` bila `ok: true`, atau `error_code`/`500` bila `ok: false`.
|
|
1446
|
+
|
|
1447
|
+
```ts
|
|
1448
|
+
const transport = new MockTransport()
|
|
1449
|
+
.respond("getMe", {
|
|
1450
|
+
ok: true,
|
|
1451
|
+
result: { id: 1, is_bot: true, first_name: "Test" },
|
|
1452
|
+
});
|
|
1453
|
+
```
|
|
1454
|
+
|
|
1455
|
+
### `createMockUpdate(overrides?)`
|
|
1456
|
+
|
|
1457
|
+
```ts
|
|
1458
|
+
createMockUpdate(overrides?: Partial<Update>): Update
|
|
1459
|
+
```
|
|
1460
|
+
|
|
1461
|
+
Membuat update message default dengan `update_id: 1`, chat private id `1`, user id `2`, dan text `/start`. Object `overrides` digabung shallow dengan default.
|
|
1462
|
+
|
|
1463
|
+
### `createTestBot()`
|
|
1464
|
+
|
|
1465
|
+
```ts
|
|
1466
|
+
createTestBot(): { bot: Bot; transport: MockTransport }
|
|
1467
|
+
```
|
|
1468
|
+
|
|
1469
|
+
Membuat bot dengan token test `123456:TEST_TOKEN`, mock `getMe()` yang menghasilkan bot id `99`, dan transport yang dapat diperiksa melalui `transport.calls`.
|
|
1470
|
+
|
|
1471
|
+
### `createMockContext(bot, update?)`
|
|
1472
|
+
|
|
1473
|
+
```ts
|
|
1474
|
+
createMockContext(
|
|
1475
|
+
bot: Bot,
|
|
1476
|
+
update?: Update,
|
|
1477
|
+
): Context
|
|
1478
|
+
```
|
|
1479
|
+
|
|
1480
|
+
Membuat context menggunakan API bot, session kosong, dan services kosong.
|
|
1481
|
+
|
|
1482
|
+
---
|
|
1483
|
+
|
|
1484
|
+
|
|
1485
|
+
## 15. 生成的 Telegram 方法命名空间
|
|
1486
|
+
|
|
1487
|
+
`generated/api.ts` 是生成器的内部源,定义了:
|
|
1488
|
+
|
|
1489
|
+
```ts
|
|
1490
|
+
const TELEGRAM_API_VERSION = "10.2";
|
|
1491
|
+
const TELEGRAM_METHOD_NAMES: readonly string[];
|
|
1492
|
+
type TelegramMethodName = typeof TELEGRAM_METHOD_NAMES[number];
|
|
1493
|
+
type GeneratedMethodSpec = {
|
|
1494
|
+
params: Record<string, unknown>;
|
|
1495
|
+
result: unknown;
|
|
1496
|
+
};
|
|
1497
|
+
type GeneratedTelegramMethodMap = {
|
|
1498
|
+
[K in TelegramMethodName]: GeneratedMethodSpec;
|
|
1499
|
+
};
|
|
1500
|
+
const GENERATED_METHODS: Record<TelegramMethodName, TelegramMethodName>;
|
|
1501
|
+
```
|
|
1502
|
+
|
|
1503
|
+
`TELEGRAM_METHOD_NAMES` 包含生成器源中的 184 个方法名。该命名空间是 `api.methods`、`api.call` 和 `api.request` 的代理基础,但在此版本中生成的文件并未作为公共包子路径导出。尚未映射的特定参数/返回值可以通过 `api.raw()` 调用,或在 TypeScript 中通过类型转换传参。
|
|
1504
|
+
|
|
1505
|
+
以下为未经分组的规范方法列表,生成时运行时命名空间中可用的方法名为:
|
|
1506
|
+
|
|
1507
|
+
```text
|
|
1508
|
+
addStickerToSet,
|
|
1509
|
+
answerCallbackQuery,
|
|
1510
|
+
answerChatJoinRequestQuery,
|
|
1511
|
+
answerGuestQuery,
|
|
1512
|
+
answerInlineQuery,
|
|
1513
|
+
answerPreCheckoutQuery,
|
|
1514
|
+
answerShippingQuery,
|
|
1515
|
+
answerWebAppQuery,
|
|
1516
|
+
approveChatJoinRequest,
|
|
1517
|
+
approveSuggestedPost,
|
|
1518
|
+
banChatMember,
|
|
1519
|
+
banChatSenderChat,
|
|
1520
|
+
close,
|
|
1521
|
+
closeForumTopic,
|
|
1522
|
+
closeGeneralForumTopic,
|
|
1523
|
+
convertGiftToStars,
|
|
1524
|
+
copyMessage,
|
|
1525
|
+
copyMessages,
|
|
1526
|
+
createChatInviteLink,
|
|
1527
|
+
createChatSubscriptionInviteLink,
|
|
1528
|
+
createForumTopic,
|
|
1529
|
+
createInvoiceLink,
|
|
1530
|
+
createNewStickerSet,
|
|
1531
|
+
declineChatJoinRequest,
|
|
1532
|
+
declineSuggestedPost,
|
|
1533
|
+
deleteAllMessageReactions,
|
|
1534
|
+
deleteBusinessMessages,
|
|
1535
|
+
deleteChatPhoto,
|
|
1536
|
+
deleteChatStickerSet,
|
|
1537
|
+
deleteEphemeralMessage,
|
|
1538
|
+
deleteForumTopic,
|
|
1539
|
+
deleteMessage,
|
|
1540
|
+
deleteMessageReaction,
|
|
1541
|
+
deleteMessages,
|
|
1542
|
+
deleteMyCommands,
|
|
1543
|
+
deleteStickerFromSet,
|
|
1544
|
+
deleteStickerSet,
|
|
1545
|
+
deleteStory,
|
|
1546
|
+
deleteWebhook,
|
|
1547
|
+
editChatInviteLink,
|
|
1548
|
+
editChatSubscriptionInviteLink,
|
|
1549
|
+
editEphemeralMessageCaption,
|
|
1550
|
+
editEphemeralMessageMedia,
|
|
1551
|
+
editEphemeralMessageReplyMarkup,
|
|
1552
|
+
editEphemeralMessageText,
|
|
1553
|
+
editForumTopic,
|
|
1554
|
+
editGeneralForumTopic,
|
|
1555
|
+
editMessageCaption,
|
|
1556
|
+
editMessageChecklist,
|
|
1557
|
+
editMessageLiveLocation,
|
|
1558
|
+
editMessageMedia,
|
|
1559
|
+
editMessageReplyMarkup,
|
|
1560
|
+
editMessageText,
|
|
1561
|
+
editStory,
|
|
1562
|
+
editUserStarSubscription,
|
|
1563
|
+
exportChatInviteLink,
|
|
1564
|
+
forwardMessage,
|
|
1565
|
+
forwardMessages,
|
|
1566
|
+
getAvailableGifts,
|
|
1567
|
+
getBusinessAccountGifts,
|
|
1568
|
+
getBusinessAccountStarBalance,
|
|
1569
|
+
getBusinessConnection,
|
|
1570
|
+
getChat,
|
|
1571
|
+
getChatAdministrators,
|
|
1572
|
+
getChatGifts,
|
|
1573
|
+
getChatMember,
|
|
1574
|
+
getChatMemberCount,
|
|
1575
|
+
getChatMenuButton,
|
|
1576
|
+
getCustomEmojiStickers,
|
|
1577
|
+
getFile,
|
|
1578
|
+
getForumTopicIconStickers,
|
|
1579
|
+
getGameHighScores,
|
|
1580
|
+
getManagedBotAccessSettings,
|
|
1581
|
+
getManagedBotToken,
|
|
1582
|
+
getMe,
|
|
1583
|
+
getMyCommands,
|
|
1584
|
+
getMyDefaultAdministratorRights,
|
|
1585
|
+
getMyDescription,
|
|
1586
|
+
getMyName,
|
|
1587
|
+
getMyShortDescription,
|
|
1588
|
+
getMyStarBalance,
|
|
1589
|
+
getStarTransactions,
|
|
1590
|
+
getStickerSet,
|
|
1591
|
+
getUpdates,
|
|
1592
|
+
getUserChatBoosts,
|
|
1593
|
+
getUserGifts,
|
|
1594
|
+
getUserPersonalChatMessages,
|
|
1595
|
+
getUserProfileAudios,
|
|
1596
|
+
getUserProfilePhotos,
|
|
1597
|
+
getWebhookInfo,
|
|
1598
|
+
giftPremiumSubscription,
|
|
1599
|
+
hideGeneralForumTopic,
|
|
1600
|
+
leaveChat,
|
|
1601
|
+
logOut,
|
|
1602
|
+
pinChatMessage,
|
|
1603
|
+
postStory,
|
|
1604
|
+
promoteChatMember,
|
|
1605
|
+
readBusinessMessage,
|
|
1606
|
+
refundStarPayment,
|
|
1607
|
+
removeBusinessAccountProfilePhoto,
|
|
1608
|
+
removeChatVerification,
|
|
1609
|
+
removeMyProfilePhoto,
|
|
1610
|
+
removeUserVerification,
|
|
1611
|
+
reopenForumTopic,
|
|
1612
|
+
reopenGeneralForumTopic,
|
|
1613
|
+
replaceManagedBotToken,
|
|
1614
|
+
replaceStickerInSet,
|
|
1615
|
+
repostStory,
|
|
1616
|
+
restrictChatMember,
|
|
1617
|
+
revokeChatInviteLink,
|
|
1618
|
+
savePreparedInlineMessage,
|
|
1619
|
+
savePreparedKeyboardButton,
|
|
1620
|
+
sendAnimation,
|
|
1621
|
+
sendAudio,
|
|
1622
|
+
sendChatAction,
|
|
1623
|
+
sendChatJoinRequestWebApp,
|
|
1624
|
+
sendChecklist,
|
|
1625
|
+
sendContact,
|
|
1626
|
+
sendDice,
|
|
1627
|
+
sendDocument,
|
|
1628
|
+
sendGame,
|
|
1629
|
+
sendGift,
|
|
1630
|
+
sendInvoice,
|
|
1631
|
+
sendLivePhoto,
|
|
1632
|
+
sendLocation,
|
|
1633
|
+
sendMediaGroup,
|
|
1634
|
+
sendMessage,
|
|
1635
|
+
sendMessageDraft,
|
|
1636
|
+
sendPaidMedia,
|
|
1637
|
+
sendPhoto,
|
|
1638
|
+
sendPoll,
|
|
1639
|
+
sendRichMessage,
|
|
1640
|
+
sendRichMessageDraft,
|
|
1641
|
+
sendSticker,
|
|
1642
|
+
sendVenue,
|
|
1643
|
+
sendVideo,
|
|
1644
|
+
sendVideoNote,
|
|
1645
|
+
sendVoice,
|
|
1646
|
+
setBusinessAccountBio,
|
|
1647
|
+
setBusinessAccountGiftSettings,
|
|
1648
|
+
setBusinessAccountName,
|
|
1649
|
+
setBusinessAccountProfilePhoto,
|
|
1650
|
+
setBusinessAccountUsername,
|
|
1651
|
+
setChatAdministratorCustomTitle,
|
|
1652
|
+
setChatDescription,
|
|
1653
|
+
setChatMemberTag,
|
|
1654
|
+
setChatMenuButton,
|
|
1655
|
+
setChatPermissions,
|
|
1656
|
+
setChatPhoto,
|
|
1657
|
+
setChatStickerSet,
|
|
1658
|
+
setChatTitle,
|
|
1659
|
+
setCustomEmojiStickerSetThumbnail,
|
|
1660
|
+
setGameScore,
|
|
1661
|
+
setManagedBotAccessSettings,
|
|
1662
|
+
setMessageReaction,
|
|
1663
|
+
setMyCommands,
|
|
1664
|
+
setMyDefaultAdministratorRights,
|
|
1665
|
+
setMyDescription,
|
|
1666
|
+
setMyName,
|
|
1667
|
+
setMyProfilePhoto,
|
|
1668
|
+
setMyShortDescription,
|
|
1669
|
+
setPassportDataErrors,
|
|
1670
|
+
setStickerEmojiList,
|
|
1671
|
+
setStickerKeywords,
|
|
1672
|
+
setStickerMaskPosition,
|
|
1673
|
+
setStickerPositionInSet,
|
|
1674
|
+
setStickerSetThumbnail,
|
|
1675
|
+
setStickerSetTitle,
|
|
1676
|
+
setUserEmojiStatus,
|
|
1677
|
+
setWebhook,
|
|
1678
|
+
stopMessageLiveLocation,
|
|
1679
|
+
stopPoll,
|
|
1680
|
+
transferBusinessAccountStars,
|
|
1681
|
+
transferGift,
|
|
1682
|
+
unbanChatMember,
|
|
1683
|
+
unbanChatSenderChat,
|
|
1684
|
+
unhideGeneralForumTopic,
|
|
1685
|
+
unpinAllChatMessages,
|
|
1686
|
+
unpinAllForumTopicMessages,
|
|
1687
|
+
unpinAllGeneralForumTopicMessages,
|
|
1688
|
+
unpinChatMessage,
|
|
1689
|
+
upgradeGift,
|
|
1690
|
+
uploadStickerFile,
|
|
1691
|
+
verifyChat
|
|
1692
|
+
```
|
|
1693
|
+
|
|
1694
|
+
> 上述列表遵循生成的源代码。如果 Telegram 添加了新方法,请在 schema 更新后运行 `npm run update:telegram` 或 `telebibz generate`。
|
|
1695
|
+
|
|
1696
|
+
---
|
|
1697
|
+
|
|
1698
|
+
## 16. 命令行界面 (CLI)
|
|
1699
|
+
|
|
1700
|
+
二进制包为 `telebibz`。
|
|
1701
|
+
|
|
1702
|
+
```bash
|
|
1703
|
+
npx telebibz <command>
|
|
1704
|
+
```
|
|
1705
|
+
|
|
1706
|
+
| 命令 | 行为 |
|
|
1707
|
+
|---|---|
|
|
1708
|
+
| `telebibz init [directory]` | 创建目录、最小的 `index.ts`,以及 `.env.example`。默认目录 `my-telebibz-bot`。 |
|
|
1709
|
+
| `telebibz doctor` | 显示 Node 版本、是否存在 `TELEGRAM_BOT_TOKEN`、cwd、包名,然后如果存在 token 则检查健康检查 API。如果 API 无法访问则退出码为 1。 |
|
|
1710
|
+
| `telebibz generate` | 运行 `scripts/generate-api.mjs` 中的生成器方法。 |
|
|
1711
|
+
| `telebibz build` | 运行 `npm run build`。 |
|
|
1712
|
+
| `telebibz test` | 运行 `npm test`。 |
|
|
1713
|
+
| `telebibz webhook` | 检查 `TELEGRAM_BOT_TOKEN`,如有则使用 `TELEGRAM_WEBHOOK_SECRET`,创建处理器并打印就绪状态。此命令不会创建 HTTP 服务器。 |
|
|
1714
|
+
| `telebibz inspect` | 显示 cwd 和 Node 版本。 |
|
|
1715
|
+
| 无命令 | 显示帮助命令列表。 |
|
|
1716
|
+
|
|
1717
|
+
CLI 使用的环境变量是 `TELEGRAM_BOT_TOKEN` 和 `TELEGRAM_WEBHOOK_SECRET`。
|
|
1718
|
+
|
|
1719
|
+
---
|
|
1720
|
+
|
|
1721
|
+
## 17. 主要 Telegram 类型
|
|
1722
|
+
|
|
1723
|
+
该包直接导出最常用的数据类型。
|
|
1724
|
+
|
|
1725
|
+
| Type | 重要内容 |
|
|
1726
|
+
|---|---|
|
|
1727
|
+
| `User` | ID、机器人标志、姓名、用户名、语言和能力标志。 |
|
|
1728
|
+
| `Chat` | ID、类型、标题/用户名/名称、论坛/私信 标志。 |
|
|
1729
|
+
| `Message` | ID、日期、聊天、发送者、文本/说明、实体、回复、标记,以及用于额外 Telegram 字段的索引签名。 |
|
|
1730
|
+
| `Update` | 包含源支持的所有更新字段,包括消息、回调、内联、投票、成员、加入请求、反应、提升、业务和扩展字段。 |
|
|
1731
|
+
| `CallbackQuery` | ID、来自用户、消息/内联消息 ID、聊天实例、数据。 |
|
|
1732
|
+
| `InlineQuery` | ID、来自用户、查询、偏移、聊天类型、位置。 |
|
|
1733
|
+
| `Poll`, `PollAnswer` | 投票数据和答案。 |
|
|
1734
|
+
| `ChatMemberUpdated`, `ChatJoinRequest` | 成员变更和加入请求。 |
|
|
1735
|
+
| `InlineKeyboardMarkup`, `ReplyKeyboardMarkup`, `ReplyKeyboardRemove`, `ForceReply` | Telegram 的回复标记(reply markup)格式。 |
|
|
1736
|
+
| `MessageEntity`, `ReplyParameters`, `LinkPreviewOptions` | 实体元数据、回复参数和链接预览选项。 |
|
|
1737
|
+
| `BotCommand`, `BotCommandScope`, `WebhookInfo`, `File`, `UserProfilePhotos`, `ChatMember`, `ChatAdministratorRights` | API 的结果/参数辅助类型。 |
|
|
1738
|
+
|
|
1739
|
+
---
|
|
1740
|
+
|
|
1741
|
+
## 18. 持久化、完整 cron、菜单和完整 Telegram 声明
|
|
1742
|
+
|
|
1743
|
+
### 持久化 storage 适配器
|
|
1744
|
+
|
|
1745
|
+
所有适配器都实现相同的 `Storage<K, V>` contract。核心 package 不包含 vendor runtime dependency;Redis、SQL 和 Mongo 适配器接收由应用或所选 vendor client 提供的小型 driver interface。
|
|
1746
|
+
|
|
1747
|
+
| Class | 构造函数 | 用途 |
|
|
1748
|
+
|---|---|---|
|
|
1749
|
+
| `MemoryStorage<K, V>` | `new MemoryStorage()` | 带 TTL 和按 key 原子 `update()` 的快速内存 storage。 |
|
|
1750
|
+
| `JsonFileStorage<V>` | `new JsonFileStorage(filePath)` | 适用于单进程部署的原子 JSON 文件持久化。 |
|
|
1751
|
+
| `RedisStorage<V>` | `new RedisStorage(client, prefix?)` | 通过 `RedisLikeClient` 使用 Redis storage,包含 TTL 和 namespace。 |
|
|
1752
|
+
| `SqlStorage<V>` | `new SqlStorage(driver)` | 通过应用提供的 `SqlStorageDriver` 使用 SQL storage。 |
|
|
1753
|
+
| `MongoStorage<V>` | `new MongoStorage(collection)` | 通过应用提供的 `MongoStorageCollection` 使用 Mongo storage。 |
|
|
1754
|
+
| `StorageApprovalStore` | `new StorageApprovalStore(storage)` | 使用任意 `Storage<string, ApprovalRecord>` 持久化 owner approval record。 |
|
|
1755
|
+
|
|
1756
|
+
`BotOptions.session` 接受 `Storage<string, S>`,因此 session 可以使用任意适配器。`ConversationManager` 接受相同的 storage abstraction,并提供 `getAsync()`、`cancelAsync()` 和 `clearExpiredAsync()` 来持久化 conversation state。
|
|
1757
|
+
|
|
1758
|
+
```ts
|
|
1759
|
+
const session = new JsonFileStorage<Record<string, unknown>>("./data/sessions.json");
|
|
1760
|
+
const bot = new Bot({ token: process.env.TELEGRAM_BOT_TOKEN!, session });
|
|
1761
|
+
```
|
|
1762
|
+
|
|
1763
|
+
### 完整五字段 cron
|
|
1764
|
+
|
|
1765
|
+
`parseCronExpression()` 支持标准五字段 `minute hour day-of-month month day-of-week`,包括 wildcard、list、range 和 step,例如 `*/15 9-17 1,15 * 1-5`。`nextCronOccurrence()` 计算下一个本地 occurrence。`Scheduler.cron()` 使用 one-shot timer,并在每次执行后重新调度;任务错误会交给 `Scheduler({ onError })`,不会成为未处理的 promise rejection。
|
|
1766
|
+
|
|
1767
|
+
### Router matching mode
|
|
1768
|
+
|
|
1769
|
+
`new Router()` 默认使用 **first-match**,防止意外的 double reply。只有在确实需要 fan-out 时才使用 `new Router({ matchMode: "all" })`。RegExp matcher 在测试前会重置 `lastIndex`,因此 global 或 sticky expression 可以安全复用。
|
|
1770
|
+
|
|
1771
|
+
### MenuController 和 permission
|
|
1772
|
+
|
|
1773
|
+
`Menu` 支持基于 permission 的 item、异步 visibility/permission predicate、breadcrumb 和多列布局。`MenuController` 增加 stateful page rendering,以及对 `select`、`page`、`noop` 和外部 callback data 的 dispatch。
|
|
1774
|
+
|
|
1775
|
+
### 完整 Telegram declaration namespace
|
|
1776
|
+
|
|
1777
|
+
Package 内置 MIT 许可的 Telegram declaration,并通过 type-only export 暴露 `TelegramTypes`,同时提供 `TelegramUser`、`TelegramMessage`、`TelegramUpdate` 和 `TelegramApiMethods` 等 alias。这些 declaration 覆盖完整的 object、union、enum 和 method surface,不增加 runtime dependency。telebibz core method map 仍专门为具有直接参数/结果映射的 method 提供类型。
|
|
1778
|
+
|
|
1779
|
+
---
|
|
1780
|
+
|
|
1781
|
+
## 19. 兼容性和需要注意的限制
|
|
1782
|
+
|
|
1783
|
+
Library menargetkan Node.js `>=20`,使用 ESM 作为主要模块,并提供 CommonJS 构建。Webhook 需要运行时提供 Web `Request`、`Response`、`Headers`、`FormData`、`Blob` 和 `AbortController`;现代 Node.js 原生提供了这些。
|
|
1784
|
+
|
|
1785
|
+
API 生成的方法列表(generated method list)和 API 方法映射(API method map)并不相同。`TelegramMethodName` 包含 184 个运行时名称,但 `TelegramMethodMap` 仅对 API 客户端部分列出的子集提供了带类型的参数/结果。对于其他方法,使用 `api.raw()` 或在应用端添加类型声明。
|
|
1786
|
+
|
|
1787
|
+
Approval 状态和其他内存 primitive 会在进程重启时丢失,除非应用提供持久化适配器。`BotOptions.session` 接受 generic `Storage<string, S>` contract,`ApprovalGate` 可以使用 `StorageApprovalStore` 或自定义 `ApprovalStore`。
|
|
1788
|
+
|
|
1789
|
+
---
|
|
1790
|
+
|
|
1791
|
+
## 参考文献
|
|
1792
|
+
|
|
1793
|
+
[1]: https://core.telegram.org/bots/api "Telegram Bot API — 官方文档"
|
|
1794
|
+
[2]: https://www.npmjs.com/package/@xbibzlibrary/telebibz "@xbibzlibrary/telebibz 在 npm 上"
|