@xbibzlibrary/telebibz 0.4.4 → 0.4.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.id.md +14 -0
- package/README.md +14 -0
- package/README.zh-CN.md +14 -0
- package/docs/COOKBOOK.id.md +321 -0
- package/docs/COOKBOOK.md +321 -0
- package/docs/COOKBOOK.zh-CN.md +321 -0
- package/docs/ERRORS.id.md +194 -0
- package/docs/ERRORS.md +194 -0
- package/docs/ERRORS.zh-CN.md +194 -0
- package/docs/FILES.id.md +243 -0
- package/docs/FILES.md +243 -0
- package/docs/FILES.zh-CN.md +243 -0
- package/docs/GETTING_STARTED.id.md +6 -2
- package/docs/GETTING_STARTED.md +6 -2
- package/docs/GETTING_STARTED.zh-CN.md +6 -2
- package/docs/MIGRATION_TELEGRAF.id.md +147 -0
- package/docs/MIGRATION_TELEGRAF.md +154 -0
- package/docs/MIGRATION_TELEGRAF.zh-CN.md +147 -0
- package/docs/README.md +38 -19
- package/docs/TESTING.id.md +203 -0
- package/docs/TESTING.md +203 -0
- package/docs/TESTING.zh-CN.md +203 -0
- package/docs/WEBHOOK.id.md +212 -0
- package/docs/WEBHOOK.md +215 -0
- package/docs/WEBHOOK.zh-CN.md +212 -0
- package/package.json +1 -1
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
# Webhook 指南(简体中文)
|
|
2
|
+
|
|
3
|
+
通过 webhook 提供更新服务 —— 适用于无长轮询的平台(serverless、容器),或需要公网 HTTPS 端点的场景。
|
|
4
|
+
|
|
5
|
+
## 目录
|
|
6
|
+
|
|
7
|
+
1. [轮询 vs webhook](#1-轮询-vs-webhook)
|
|
8
|
+
2. [处理器:`createWebhookHandler()`](#2-处理器createwebhookhandler)
|
|
9
|
+
3. [常见框架](#3-常见框架)
|
|
10
|
+
4. [向 Telegram 注册 URL](#4-向-telegram-注册-url)
|
|
11
|
+
5. [Secret token](#5-secret-token)
|
|
12
|
+
6. [`webhookReply` 模式](#6-webhookreply-模式)
|
|
13
|
+
7. [本地开发:隧道](#7-本地开发隧道)
|
|
14
|
+
8. [生产检查清单](#8-生产检查清单)
|
|
15
|
+
9. [故障排查](#9-故障排查)
|
|
16
|
+
|
|
17
|
+
## 1. 轮询 vs webhook
|
|
18
|
+
|
|
19
|
+
| | 轮询(`bot.start()`) | Webhook |
|
|
20
|
+
|---|---|---|
|
|
21
|
+
| 主动连接 Telegram | 是(长轮询) | 否 —— Telegram 主动连接你 |
|
|
22
|
+
| 需要公网域名 + HTTPS | 否 | 是 |
|
|
23
|
+
| 适合 | 本地脚本、开发、VPS | Serverless(Lambda/Workers/Cloud Functions)、容器、k8s |
|
|
24
|
+
| 并发 | 相同的逐更新流水线 | 相同的逐更新流水线 |
|
|
25
|
+
| 自定义 `POST /<path>` 路由 | — | 是 —— 处理器返回 `Response`,路由仍归你 |
|
|
26
|
+
|
|
27
|
+
同一时间只有一种生效:webhook 注册后 Telegram 把更新推到该 URL,并忽略 `getUpdates`。
|
|
28
|
+
|
|
29
|
+
## 2. 处理器:`createWebhookHandler()`
|
|
30
|
+
|
|
31
|
+
处理器接受 **Web 标准 `Request`** 并返回 **`Response`** —— 可运行于 Node、Bun、Deno 与 edge runtime:
|
|
32
|
+
|
|
33
|
+
```ts
|
|
34
|
+
import { createWebhookHandler } from "@xbibzlibrary/telebibz";
|
|
35
|
+
|
|
36
|
+
const handleUpdate = createWebhookHandler(bot, {
|
|
37
|
+
secretToken: process.env.TELEGRAM_WEBHOOK_SECRET, // 校验 X-Telegram-Bot-Api-Secret-Token
|
|
38
|
+
webhookReply: true, // 通过响应体应答 API(可选)
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
export default {
|
|
42
|
+
async fetch(request: Request): Promise<Response> {
|
|
43
|
+
if (request.method === "POST" && new URL(request.url).pathname === "/telegram") {
|
|
44
|
+
return handleUpdate(request);
|
|
45
|
+
}
|
|
46
|
+
return new Response("Not Found", { status: 404 });
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
- Body 超过 1 MB → `413 Payload Too Large`(可用 `maxBodyBytes` 调整)。
|
|
52
|
+
- Secret 不匹配 → `401 Unauthorized`。
|
|
53
|
+
- 非 POST 方法 → `405 Method Not Allowed`。
|
|
54
|
+
- 更新经正常流水线处理 —— 错误处理器、session、conversation 全部照常工作。
|
|
55
|
+
|
|
56
|
+
对 Express 风格的 Node 服务器(req/res 对象而非 `Request`),使用 `webhookCallback()` —— 见[常见框架](#3-常见框架)。
|
|
57
|
+
|
|
58
|
+
## 3. 常见框架
|
|
59
|
+
|
|
60
|
+
### Express
|
|
61
|
+
|
|
62
|
+
```ts
|
|
63
|
+
import express from "express";
|
|
64
|
+
import { webhookCallback } from "@xbibzlibrary/telebibz";
|
|
65
|
+
|
|
66
|
+
const app = express();
|
|
67
|
+
app.use(express.json({ limit: "1mb" }));
|
|
68
|
+
app.post("/telegram", webhookCallback(bot, "express", { secretToken: process.env.TELEGRAM_WEBHOOK_SECRET }));
|
|
69
|
+
app.listen(3000);
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Node `http`
|
|
73
|
+
|
|
74
|
+
```ts
|
|
75
|
+
import http from "node:http";
|
|
76
|
+
import { webhookCallback } from "@xbibzlibrary/telebibz";
|
|
77
|
+
|
|
78
|
+
const callback = webhookCallback(bot, "http", { secretToken: process.env.TELEGRAM_WEBHOOK_SECRET });
|
|
79
|
+
http
|
|
80
|
+
.createServer(async (req, res) => {
|
|
81
|
+
if (req.method === "POST" && req.url === "/telegram") return callback(req, res);
|
|
82
|
+
res.writeHead(404).end();
|
|
83
|
+
})
|
|
84
|
+
.listen(3000);
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Fastify
|
|
88
|
+
|
|
89
|
+
```ts
|
|
90
|
+
import Fastify from "fastify";
|
|
91
|
+
import { webhookCallback } from "@xbibzlibrary/telebibz";
|
|
92
|
+
|
|
93
|
+
const fastify = Fastify({ bodyLimit: 1_048_576 });
|
|
94
|
+
fastify.post("/telegram", (req, reply) => webhookCallback(bot, "fastify", { secretToken: process.env.TELEGRAM_WEBHOOK_SECRET })(req, reply));
|
|
95
|
+
await fastify.listen({ port: 3000 });
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Koa(配合 `koa-bodyparser` 使 `ctx.request.body` 被解析)
|
|
99
|
+
|
|
100
|
+
```ts
|
|
101
|
+
import Koa from "koa";
|
|
102
|
+
import bodyParser from "koa-bodyparser";
|
|
103
|
+
import { webhookCallback } from "@xbibzlibrary/telebibz";
|
|
104
|
+
|
|
105
|
+
const app = new Koa();
|
|
106
|
+
app.use(bodyParser());
|
|
107
|
+
app.use(async (ctx) => {
|
|
108
|
+
if (ctx.method === "POST" && ctx.path === "/telegram") {
|
|
109
|
+
await webhookCallback(bot, "koa", { secretToken: process.env.TELEGRAM_WEBHOOK_SECRET })(ctx.request, ctx);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
ctx.status = 404;
|
|
113
|
+
});
|
|
114
|
+
app.listen(3000);
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## 4. 向 Telegram 注册 URL
|
|
118
|
+
|
|
119
|
+
webhook 只会推送到你注册的 URL。服务器在公网地址就绪后,调用一次 `setWebhook`:
|
|
120
|
+
|
|
121
|
+
```ts
|
|
122
|
+
const secret = process.env.TELEGRAM_WEBHOOK_SECRET;
|
|
123
|
+
|
|
124
|
+
await bot.api.methods.setWebhook({
|
|
125
|
+
url: "https://bot.example.com/telegram",
|
|
126
|
+
secret_token: secret, // 与处理器的 secretToken 完全一致
|
|
127
|
+
max_connections: 40, // 默认 40;按容量调整
|
|
128
|
+
allowed_updates: ["message", "callback_query"], // 可选:减少流量
|
|
129
|
+
drop_pending_updates: false,
|
|
130
|
+
});
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
**撤销 webhook** —— 两种选择:
|
|
134
|
+
|
|
135
|
+
```ts
|
|
136
|
+
await bot.api.methods.deleteWebhook({ drop_pending_updates: true }); // 回到轮询
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
当 bot 运行在本地 Bot API 服务器上时,`setWebhook` 还接受 `ip_address`,避免公网 DNS 解析。
|
|
140
|
+
|
|
141
|
+
## 5. Secret token
|
|
142
|
+
|
|
143
|
+
没有 secret,任何知道 URL 的人都能伪造更新。secret 用于确认请求确实来自 Telegram:
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
openssl rand -hex 32
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
保存为环境变量,并把**完全相同的值**传给 `setWebhook`(`secret_token` 参数)和处理器(`secretToken` 选项)。比较采用常数时间 —— 无法计时攻击。规则:1–256 个字符,仅限 `A-Z a-z 0-9 _ -`。
|
|
150
|
+
|
|
151
|
+
注意 `webhookReply` 与 secret 无关 —— 该模式决定 API 响应*如何*发送,而非请求来自谁。
|
|
152
|
+
|
|
153
|
+
## 6. `webhookReply` 模式
|
|
154
|
+
|
|
155
|
+
Telegram 允许 bot 直接在 webhook 响应体里应答一次 API 调用。开启后每次回复省去一次往返 —— 在 serverless 上尤其有价值:
|
|
156
|
+
|
|
157
|
+
```ts
|
|
158
|
+
const handleUpdate = createWebhookHandler(bot, { webhookReply: true });
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
每个 update 只有**一次** API 调用能享受此机制 —— 最先完成的那次获胜;其余照常走 HTTP 请求。响应体被占用后,处理器返回 `{}`(Telegram 仍视为成功)。
|
|
162
|
+
|
|
163
|
+
Telegraf 中称为 `telegram.webhookReply`;概念与默认值在 telebibz 中完全一致。
|
|
164
|
+
|
|
165
|
+
## 7. 本地开发:隧道
|
|
166
|
+
|
|
167
|
+
Telegram 只向**公网 HTTPS** URL 推送。开发时把公网 URL 隧道到 localhost:
|
|
168
|
+
|
|
169
|
+
```bash
|
|
170
|
+
# cloudflared(无需账号)
|
|
171
|
+
cloudflared tunnel --url http://localhost:3000
|
|
172
|
+
|
|
173
|
+
# 或 ngrok
|
|
174
|
+
ngrok http 3000
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
然后注册得到的 URL:
|
|
178
|
+
|
|
179
|
+
```bash
|
|
180
|
+
TOKEN="…"
|
|
181
|
+
URL="https://random-words.loca.lt" # 来自隧道输出
|
|
182
|
+
SECRET="…"
|
|
183
|
+
curl "https://api.telegram.org/bot$TOKEN/setWebhook" \
|
|
184
|
+
-d "url=$URL/telegram" -d "secret_token=$SECRET"
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
开发完成后记得 `deleteWebhook`,让 `bot.start()` 重新可用。
|
|
188
|
+
|
|
189
|
+
## 8. 生产检查清单
|
|
190
|
+
|
|
191
|
+
- [ ] 公网 HTTPS + 有效证书(Telegram 拒绝自签名)
|
|
192
|
+
- [ ] `secret_token` 已设置且两端一致
|
|
193
|
+
- [ ] `max_connections` 已调整(默认 40)
|
|
194
|
+
- [ ] Body parser 上限 ≥ 1 MB(`express.json({ limit: "1mb" })` 等)
|
|
195
|
+
- [ ] 上游超时 > `handlerTimeout`(让 `UpdateTimeoutError` 先接管,而不是负载均衡器返回 504)
|
|
196
|
+
- [ ] 重新部署时考虑 `drop_pending_updates`
|
|
197
|
+
- [ ] 错误可观测:`bot.catch()` + `update:error`
|
|
198
|
+
- [ ] 优雅关机:退出前 `bot.stop()`
|
|
199
|
+
|
|
200
|
+
## 9. 故障排查
|
|
201
|
+
|
|
202
|
+
| 症状 | 原因 | 修复 |
|
|
203
|
+
|---|---|---|
|
|
204
|
+
| Telegram 一直超时(`getUpdates` 空转) | webhook 处于激活状态 —— Telegram 忽略轮询 | `deleteWebhook` 或改用处理器 |
|
|
205
|
+
| 每个请求都 401 | 处理器 secret ≠ 已注册的 `secret_token` | 两端改为一致 |
|
|
206
|
+
| 413 Payload Too Large | Body 超过 `maxBodyBytes`(默认 1 MB) | 调大 `maxBodyBytes` 与 body parser 上限 |
|
|
207
|
+
| 代理返回 502 | webhook 发送 `content-type: application/json` —— 代理拒绝 | 移除 content-type 改写;处理器接受 JSON |
|
|
208
|
+
| `getUpdates` 报 `409 Conflict` | webhook 仍注册着 | 先 `deleteWebhook` |
|
|
209
|
+
| 更新收到后挂起 | handler 在等待缓慢的网络调用 | 降低 `handlerTimeout`;通过 `update:error` 保证可观测 |
|
|
210
|
+
| Serverless:回复从未送达 | 单个 handler 里 await 过多 | 开启 `webhookReply`,让第一次调用搭响应的便车 |
|
|
211
|
+
|
|
212
|
+
English: [WEBHOOK.md](WEBHOOK.md) · Bahasa Indonesia: [WEBHOOK.id.md](WEBHOOK.id.md)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xbibzlibrary/telebibz",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.5",
|
|
4
4
|
"description": "Telegram Bot framework for Node.js and TypeScript with a typed API client, routing, middleware, webhooks, keyboards, conversations, plugins, queues, and a polished terminal experience.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"telegram",
|