@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,194 @@
|
|
|
1
|
+
# Panduan penanganan error (Bahasa Indonesia)
|
|
2
|
+
|
|
3
|
+
Semua jalur error di telebibz, artinya, dan cara menanganinya — dari error API Telegram sampai kegagalan handler, timeout, dan shutdown.
|
|
4
|
+
|
|
5
|
+
## Daftar isi
|
|
6
|
+
|
|
7
|
+
1. [Taksonomi error](#1-taksonomi-error)
|
|
8
|
+
2. [Anatomi TelegramError](#2-anatomi-telegramerror)
|
|
9
|
+
3. [Rate limit 429 dan flood gate](#3-rate-limit-429-dan-flood-gate)
|
|
10
|
+
4. [Error handler dan `bot.catch()`](#4-error-handler-dan-botcatch)
|
|
11
|
+
5. [handlerTimeout dan `UpdateTimeoutError`](#5-handlertimeout-dan-updatetimeouterror)
|
|
12
|
+
6. [Observasi error berbasis event](#6-observasi-error-berbasis-event)
|
|
13
|
+
7. [Retry transport dan error jaringan](#7-retry-transport-dan-error-jaringan)
|
|
14
|
+
8. [Resep penanganan error](#8-resep-penanganan-error)
|
|
15
|
+
|
|
16
|
+
## 1. Taksonomi error
|
|
17
|
+
|
|
18
|
+
Setiap kegagalan API Telegram adalah `TelegramError` dengan `kind`:
|
|
19
|
+
|
|
20
|
+
| `kind` | Subclass | Arti | Pemicu umum |
|
|
21
|
+
|---|---|---|---|
|
|
22
|
+
| `rate-limit` | `TelegramRateLimitError` | Telegram menjawab 429 | Kirim terlalu cepat; `retryAfter` terisi |
|
|
23
|
+
| `authentication` | `TelegramAuthError` | Token tidak valid/dicabut (401) | Token salah, bot di-revoke, logout |
|
|
24
|
+
| `validation` | `TelegramValidationError` | Parameter request buruk (400) | `file_id` tidak dikenal, payload rusak |
|
|
25
|
+
| `network` | `TelegramNetworkError` | Kegagalan level transport | DNS, socket, respons non-JSON, unduhan gagal |
|
|
26
|
+
| `server` | — | Error server Telegram (5xx) | Transien; di-retry otomatis |
|
|
27
|
+
| `retryable` | — | Error Telegram lain yang bisa di-retry | Varian flood-wait |
|
|
28
|
+
| `unknown` | — | Lainnya | — |
|
|
29
|
+
|
|
30
|
+
Cek `kind` saat reaksinya perlu berbeda:
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
import { TelegramError, TelegramRateLimitError } from "@xbibzlibrary/telebibz";
|
|
34
|
+
|
|
35
|
+
try {
|
|
36
|
+
await ctx.reply("halo");
|
|
37
|
+
} catch (error) {
|
|
38
|
+
if (error instanceof TelegramError) {
|
|
39
|
+
switch (error.kind) {
|
|
40
|
+
case "rate-limit": console.log(`pelankan ${error.retryAfter}s`); break;
|
|
41
|
+
case "authentication": console.error("masalah token — berhenti"); break;
|
|
42
|
+
case "validation": console.warn(error.message); break;
|
|
43
|
+
default: console.error(error.message);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
`instanceof TelegramRateLimitError` juga bisa dipakai bila hanya peduli 429.
|
|
50
|
+
|
|
51
|
+
## 2. Anatomi TelegramError
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
try {
|
|
55
|
+
await bot.api.methods.getChatMember({ chat_id: -100123, user_id: 42 });
|
|
56
|
+
} catch (error) {
|
|
57
|
+
if (error instanceof TelegramError) {
|
|
58
|
+
error.kind; // salah satu dari tujuh kind di atas
|
|
59
|
+
error.errorCode; // error_code Telegram (400, 401, 429, …) bila ada
|
|
60
|
+
error.method; // "getChatMember" — panggilan yang gagal
|
|
61
|
+
error.payload; // payload yang dikirim
|
|
62
|
+
error.retryAfter; // detik, hanya untuk 429 (dari parameters.retry_after)
|
|
63
|
+
error.message; // deskripsi dari Telegram
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## 3. Rate limit 429 dan flood gate
|
|
69
|
+
|
|
70
|
+
Biasanya Anda tidak pernah melihat 429, karena transport sudah menanganinya:
|
|
71
|
+
|
|
72
|
+
1. Telegram menjawab 429 dengan `parameters.retry_after`.
|
|
73
|
+
2. **Flood gate** menjeda permintaan keluar *baru* selama jendela itu — melindungi seluruh trafik, bukan hanya request yang ditolak.
|
|
74
|
+
3. Request yang gagal di-retry otomatis, lalu kegagalan (bila bertahan) muncul sebagai `TelegramRateLimitError`.
|
|
75
|
+
|
|
76
|
+
Flood gate adalah satu-satunya jeda yang pernah diperkenalkan library — tidak pernah ada cooldown proaktif. Atur atau matikan per transport:
|
|
77
|
+
|
|
78
|
+
```ts
|
|
79
|
+
const bot = new Bot({
|
|
80
|
+
token: process.env.TELEGRAM_BOT_TOKEN!,
|
|
81
|
+
transportOptions: {
|
|
82
|
+
floodGate: false, // tangani 429 sepenuhnya sendiri
|
|
83
|
+
retries: 3, // jumlah retry untuk 429/5xx/error jaringan
|
|
84
|
+
backoffMs: 250, // basis backoff eksponensial
|
|
85
|
+
maxBackoffMs: 8_000,
|
|
86
|
+
timeoutMs: 30_000, // timeout per request
|
|
87
|
+
},
|
|
88
|
+
});
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Bila gate dimatikan, tangkap `TelegramRateLimitError` dan patuhi `retryAfter` — kata Telegram final:
|
|
92
|
+
|
|
93
|
+
```ts
|
|
94
|
+
catch (error) {
|
|
95
|
+
if (error instanceof TelegramRateLimitError) {
|
|
96
|
+
await sleep((error.retryAfter ?? 1) * 1000);
|
|
97
|
+
return retry();
|
|
98
|
+
}
|
|
99
|
+
throw error;
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## 4. Error handler dan `bot.catch()`
|
|
104
|
+
|
|
105
|
+
Tanpa error boundary, handler yang melempar akan menolak `handleUpdate()` (dan webhook menjawab 500). Dengan `bot.catch()`, kegagalan diarahkan ke satu tempat:
|
|
106
|
+
|
|
107
|
+
```ts
|
|
108
|
+
bot.catch(async (error, ctx) => {
|
|
109
|
+
console.error("handler gagal:", error);
|
|
110
|
+
|
|
111
|
+
if (error instanceof TelegramError && error.kind === "authentication") {
|
|
112
|
+
process.exitCode = 1; // tak tertolak — biarkan supervisor me-restart
|
|
113
|
+
await bot.stop();
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
await ctx.reply("❌ Terjadi kesalahan. Coba lagi."); // ctx = context update yang gagal
|
|
118
|
+
});
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Perilaku kunci:
|
|
122
|
+
- Hanya update yang gagal yang terdampak — chat lain tetap diproses konkuren.
|
|
123
|
+
- Urutan per chat tetap terjaga: update berikutnya dari chat yang sama tetap menunggu yang ini.
|
|
124
|
+
- Kegagalan `broadcast()` terkumpul di report alih-alih melempar.
|
|
125
|
+
|
|
126
|
+
## 5. handlerTimeout dan `UpdateTimeoutError`
|
|
127
|
+
|
|
128
|
+
`handlerTimeout` (default **90 000 ms**, mengikuti Telegraf) melindungi pipeline dari handler yang menggantung:
|
|
129
|
+
|
|
130
|
+
```ts
|
|
131
|
+
const bot = new Bot({ token, handlerTimeout: 30_000 }); // 0 atau Infinity menonaktifkan
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
- Promise `handleUpdate()` untuk update yang menggantung melempar `UpdateTimeoutError` dan mengalir lewat `update:error` → `bot:error` → `bot.catch()`.
|
|
135
|
+
- **Handler tetap berjalan di belakang** — session dan conversation tetap menyelesaikan penulisannya; timeout hanya melepaskan pipeline.
|
|
136
|
+
- Urutan per chat tidak terpengaruh.
|
|
137
|
+
|
|
138
|
+
## 6. Observasi error berbasis event
|
|
139
|
+
|
|
140
|
+
Untuk metrik/logging yang independen dari boundary, dengarkan event bus:
|
|
141
|
+
|
|
142
|
+
```ts
|
|
143
|
+
bot.events.on("update:error", ({ update, error }) => {
|
|
144
|
+
metrics.increment("handler_errors", { updateId: (update as { update_id?: number }).update_id });
|
|
145
|
+
});
|
|
146
|
+
bot.events.on("bot:error", ({ error }) => log.error("bot error", { error }));
|
|
147
|
+
bot.events.on("api:response", ({ method, durationMs }) => {
|
|
148
|
+
if (durationMs > 3_000) log.warn("panggilan api lambat", { method, durationMs });
|
|
149
|
+
});
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## 7. Retry transport dan error jaringan
|
|
153
|
+
|
|
154
|
+
`FetchTransport` me-retry otomatis saat: 429 (dengan `retry_after` milik Telegram), 5xx, error jaringan, dan respons non-JSON — dengan backoff eksponensial dan jitter. Setelah `retries` percobaan, error terakhir muncul sebagai `TelegramNetworkError` (lengkap `status` dan cause terpotong). Error autentikasi (401) dan validasi (400) **tidak pernah** di-retry — retry tidak akan memperbaikinya.
|
|
155
|
+
|
|
156
|
+
## 8. Resep penanganan error
|
|
157
|
+
|
|
158
|
+
**Retry dengan backoff di sekitar satu panggilan** (di luar retry transport):
|
|
159
|
+
|
|
160
|
+
```ts
|
|
161
|
+
async function withRetry<T>(run: () => Promise<T>, attempts = 3): Promise<T> {
|
|
162
|
+
for (let i = 0; i < attempts; i++) {
|
|
163
|
+
try { return await run(); }
|
|
164
|
+
catch (error) {
|
|
165
|
+
if (error instanceof TelegramError && ["authentication", "validation"].includes(error.kind)) throw error;
|
|
166
|
+
if (i === attempts - 1) throw error;
|
|
167
|
+
await new Promise((r) => setTimeout(r, 500 * 2 ** i));
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
throw new Error("unreachable");
|
|
171
|
+
}
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
**Fail cepat saat token buruk** (polling dengan token tidak valid):
|
|
175
|
+
|
|
176
|
+
```ts
|
|
177
|
+
try {
|
|
178
|
+
await bot.start();
|
|
179
|
+
} catch (error) {
|
|
180
|
+
if (error instanceof TelegramError && error.kind === "authentication") {
|
|
181
|
+
console.error("TELEGRAM_BOT_TOKEN tidak valid atau dicabut");
|
|
182
|
+
}
|
|
183
|
+
throw error;
|
|
184
|
+
}
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
**Shutdown anggun** — `stop()` lebih dulu meng-drain handler yang berjalan:
|
|
188
|
+
|
|
189
|
+
```ts
|
|
190
|
+
process.on("SIGINT", () => { void bot.stop().then(() => process.exit(0)); });
|
|
191
|
+
process.on("SIGTERM", () => { void bot.stop().then(() => process.exit(0)); });
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
English: [ERRORS.md](ERRORS.md) · 简体中文: [ERRORS.zh-CN.md](ERRORS.zh-CN.md)
|
package/docs/ERRORS.md
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
# Error handling guide (English)
|
|
2
|
+
|
|
3
|
+
Every error path in telebibz, what it means, and how to handle it — from Telegram API errors to handler failures, timeouts, and shutdown.
|
|
4
|
+
|
|
5
|
+
## Contents
|
|
6
|
+
|
|
7
|
+
1. [The error taxonomy](#1-the-error-taxonomy)
|
|
8
|
+
2. [TelegramError anatomy](#2-telegramerror-anatomy)
|
|
9
|
+
3. [429 rate limits and the flood gate](#3-429-rate-limits-and-the-flood-gate)
|
|
10
|
+
4. [Handler errors and `bot.catch()`](#4-handler-errors-and-botcatch)
|
|
11
|
+
5. [handlerTimeout and `UpdateTimeoutError`](#5-handlertimeout-and-updatetimeouterror)
|
|
12
|
+
6. [Event-based error observation](#6-event-based-error-observation)
|
|
13
|
+
7. [Transport retries and network errors](#7-transport-retries-and-network-errors)
|
|
14
|
+
8. [Error handling recipes](#8-error-handling-recipes)
|
|
15
|
+
|
|
16
|
+
## 1. The error taxonomy
|
|
17
|
+
|
|
18
|
+
Every Telegram API failure is a `TelegramError` with a `kind`:
|
|
19
|
+
|
|
20
|
+
| `kind` | Subclass | Meaning | Typical trigger |
|
|
21
|
+
|---|---|---|---|
|
|
22
|
+
| `rate-limit` | `TelegramRateLimitError` | Telegram answered 429 | Sending too fast; `retryAfter` is set |
|
|
23
|
+
| `authentication` | `TelegramAuthError` | Token invalid/revoked (401) | Wrong token, revoked bot, logout |
|
|
24
|
+
| `validation` | `TelegramValidationError` | Bad request parameters (400) | Unknown `file_id`, malformed payload |
|
|
25
|
+
| `network` | `TelegramNetworkError` | Transport-level failure | DNS, socket, non-JSON response, download failure |
|
|
26
|
+
| `server` | — | Telegram server error (5xx) | Transient; retried automatically |
|
|
27
|
+
| `retryable` | — | Other retryable Telegram error | Flood-wait variants |
|
|
28
|
+
| `unknown` | — | Anything else | — |
|
|
29
|
+
|
|
30
|
+
Check `kind` when the reaction should differ:
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
import { TelegramError, TelegramRateLimitError } from "@xbibzlibrary/telebibz";
|
|
34
|
+
|
|
35
|
+
try {
|
|
36
|
+
await ctx.reply("hello");
|
|
37
|
+
} catch (error) {
|
|
38
|
+
if (error instanceof TelegramError) {
|
|
39
|
+
switch (error.kind) {
|
|
40
|
+
case "rate-limit": console.log(`slow down ${error.retryAfter}s`); break;
|
|
41
|
+
case "authentication": console.error("token problem — stopping"); break;
|
|
42
|
+
case "validation": console.warn(error.message); break;
|
|
43
|
+
default: console.error(error.message);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
`instanceof TelegramRateLimitError` works too when you only care about 429s.
|
|
50
|
+
|
|
51
|
+
## 2. TelegramError anatomy
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
try {
|
|
55
|
+
await bot.api.methods.getChatMember({ chat_id: -100123, user_id: 42 });
|
|
56
|
+
} catch (error) {
|
|
57
|
+
if (error instanceof TelegramError) {
|
|
58
|
+
error.kind; // one of the seven kinds above
|
|
59
|
+
error.errorCode; // Telegram error_code (400, 401, 429, …) when present
|
|
60
|
+
error.method; // "getChatMember" — the failing call
|
|
61
|
+
error.payload; // the payload that was sent
|
|
62
|
+
error.retryAfter; // seconds, only for 429 (from parameters.retry_after)
|
|
63
|
+
error.message; // Telegram's description
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## 3. 429 rate limits and the flood gate
|
|
69
|
+
|
|
70
|
+
You normally never see a 429, because the transport handles them for you:
|
|
71
|
+
|
|
72
|
+
1. Telegram answers 429 with `parameters.retry_after`.
|
|
73
|
+
2. The **flood gate** pauses *new* outgoing requests for exactly that window — protecting all in-flight traffic, not just the request that was rejected.
|
|
74
|
+
3. The failed request is retried automatically, then the failure (if it persists) surfaces as `TelegramRateLimitError`.
|
|
75
|
+
|
|
76
|
+
The flood gate is the **only** delay the library ever introduces — it is never a proactive cooldown. Tune or disable it per transport:
|
|
77
|
+
|
|
78
|
+
```ts
|
|
79
|
+
const bot = new Bot({
|
|
80
|
+
token: process.env.TELEGRAM_BOT_TOKEN!,
|
|
81
|
+
transportOptions: {
|
|
82
|
+
floodGate: false, // handle 429s entirely yourself
|
|
83
|
+
retries: 3, // retry count for 429/5xx/network errors
|
|
84
|
+
backoffMs: 250, // exponential backoff base
|
|
85
|
+
maxBackoffMs: 8_000,
|
|
86
|
+
timeoutMs: 30_000, // per-request timeout
|
|
87
|
+
},
|
|
88
|
+
});
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
If you disable the gate, catch `TelegramRateLimitError` and honor `retryAfter` yourself — Telegram's word is final:
|
|
92
|
+
|
|
93
|
+
```ts
|
|
94
|
+
catch (error) {
|
|
95
|
+
if (error instanceof TelegramRateLimitError) {
|
|
96
|
+
await sleep((error.retryAfter ?? 1) * 1000);
|
|
97
|
+
return retry();
|
|
98
|
+
}
|
|
99
|
+
throw error;
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## 4. Handler errors and `bot.catch()`
|
|
104
|
+
|
|
105
|
+
Without an error boundary, a throwing handler rejects `handleUpdate()` (and a webhook answers 500). With `bot.catch()`, failures are routed to one place:
|
|
106
|
+
|
|
107
|
+
```ts
|
|
108
|
+
bot.catch(async (error, ctx) => {
|
|
109
|
+
console.error("handler failed:", error);
|
|
110
|
+
|
|
111
|
+
if (error instanceof TelegramError && error.kind === "authentication") {
|
|
112
|
+
process.exitCode = 1; // unrecoverable — let the supervisor restart
|
|
113
|
+
await bot.stop();
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
await ctx.reply("❌ Terjadi kesalahan. Coba lagi."); // ctx is the failing update's context
|
|
118
|
+
});
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Key behaviors:
|
|
122
|
+
- Only the failing update is affected — other chats keep processing concurrently.
|
|
123
|
+
- Per-chat ordering is preserved: the next update of the same chat still waits for this one.
|
|
124
|
+
- `broadcast()` failures are collected in the report instead of throwing.
|
|
125
|
+
|
|
126
|
+
## 5. handlerTimeout and `UpdateTimeoutError`
|
|
127
|
+
|
|
128
|
+
`handlerTimeout` (default **90 000 ms**, matching Telegraf) protects the pipeline from hung handlers:
|
|
129
|
+
|
|
130
|
+
```ts
|
|
131
|
+
const bot = new Bot({ token, handlerTimeout: 30_000 }); // 0 or Infinity disables
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
- The hung update's `handleUpdate()` promise rejects with `UpdateTimeoutError` and flows through `update:error` → `bot:error` → `bot.catch()`.
|
|
135
|
+
- **The handler keeps running in the background** — sessions and conversations still complete their writes; the timeout only releases the pipeline.
|
|
136
|
+
- Per-chat ordering is unaffected.
|
|
137
|
+
|
|
138
|
+
## 6. Event-based error observation
|
|
139
|
+
|
|
140
|
+
For metrics/ logging independent of the boundary, listen on the event bus:
|
|
141
|
+
|
|
142
|
+
```ts
|
|
143
|
+
bot.events.on("update:error", ({ update, error }) => {
|
|
144
|
+
metrics.increment("handler_errors", { updateId: (update as { update_id?: number }).update_id });
|
|
145
|
+
});
|
|
146
|
+
bot.events.on("bot:error", ({ error }) => log.error("bot error", { error }));
|
|
147
|
+
bot.events.on("api:response", ({ method, durationMs }) => {
|
|
148
|
+
if (durationMs > 3_000) log.warn("slow api call", { method, durationMs });
|
|
149
|
+
});
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## 7. Transport retries and network errors
|
|
153
|
+
|
|
154
|
+
`FetchTransport` retries automatically on: 429 (with Telegram's `retry_after`), 5xx, network errors, and non-JSON responses — with exponential backoff and jitter. After `retries` attempts the last error surfaces as `TelegramNetworkError` (with `status` and a truncated cause). Authentication errors (401) and validation errors (400) are **never** retried — retrying cannot fix them.
|
|
155
|
+
|
|
156
|
+
## 8. Error handling recipes
|
|
157
|
+
|
|
158
|
+
**Retry with backoff around a single call** (beyond transport retries):
|
|
159
|
+
|
|
160
|
+
```ts
|
|
161
|
+
async function withRetry<T>(run: () => Promise<T>, attempts = 3): Promise<T> {
|
|
162
|
+
for (let i = 0; i < attempts; i++) {
|
|
163
|
+
try { return await run(); }
|
|
164
|
+
catch (error) {
|
|
165
|
+
if (error instanceof TelegramError && ["authentication", "validation"].includes(error.kind)) throw error;
|
|
166
|
+
if (i === attempts - 1) throw error;
|
|
167
|
+
await new Promise((r) => setTimeout(r, 500 * 2 ** i));
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
throw new Error("unreachable");
|
|
171
|
+
}
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
**Fail fast on bad tokens** (polling with an invalid token):
|
|
175
|
+
|
|
176
|
+
```ts
|
|
177
|
+
try {
|
|
178
|
+
await bot.start();
|
|
179
|
+
} catch (error) {
|
|
180
|
+
if (error instanceof TelegramError && error.kind === "authentication") {
|
|
181
|
+
console.error("TELEGRAM_BOT_TOKEN is invalid or revoked");
|
|
182
|
+
}
|
|
183
|
+
throw error;
|
|
184
|
+
}
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
**Graceful shutdown** — `stop()` drains in-flight handlers first:
|
|
188
|
+
|
|
189
|
+
```ts
|
|
190
|
+
process.on("SIGINT", () => { void bot.stop().then(() => process.exit(0)); });
|
|
191
|
+
process.on("SIGTERM", () => { void bot.stop().then(() => process.exit(0)); });
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
Bahasa Indonesia: [ERRORS.id.md](ERRORS.id.md) · 简体中文: [ERRORS.zh-CN.md](ERRORS.zh-CN.md)
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
# 错误处理指南(简体中文)
|
|
2
|
+
|
|
3
|
+
telebibz 中每一条错误路径、其含义与处理方式 —— 从 Telegram API 错误到 handler 失败、超时与关机。
|
|
4
|
+
|
|
5
|
+
## 目录
|
|
6
|
+
|
|
7
|
+
1. [错误分类](#1-错误分类)
|
|
8
|
+
2. [TelegramError 解剖](#2-telegramerror-解剖)
|
|
9
|
+
3. [429 限流与 flood gate](#3-429-限流与-flood-gate)
|
|
10
|
+
4. [错误处理器与 `bot.catch()`](#4-错误处理器与-botcatch)
|
|
11
|
+
5. [handlerTimeout 与 `UpdateTimeoutError`](#5-handlertimeout-与-updatetimeouterror)
|
|
12
|
+
6. [基于事件的错误观测](#6-基于事件的错误观测)
|
|
13
|
+
7. [传输层重试与网络错误](#7-传输层重试与网络错误)
|
|
14
|
+
8. [错误处理配方](#8-错误处理配方)
|
|
15
|
+
|
|
16
|
+
## 1. 错误分类
|
|
17
|
+
|
|
18
|
+
每个 Telegram API 失败都是带 `kind` 的 `TelegramError`:
|
|
19
|
+
|
|
20
|
+
| `kind` | 子类 | 含义 | 常见诱因 |
|
|
21
|
+
|---|---|---|---|
|
|
22
|
+
| `rate-limit` | `TelegramRateLimitError` | Telegram 返回 429 | 发送过快;`retryAfter` 有值 |
|
|
23
|
+
| `authentication` | `TelegramAuthError` | token 无效/被吊销(401) | token 错误、bot 被吊销、logout |
|
|
24
|
+
| `validation` | `TelegramValidationError` | 请求参数错误(400) | 未知的 `file_id`、损坏的负载 |
|
|
25
|
+
| `network` | `TelegramNetworkError` | 传输层失败 | DNS、socket、非 JSON 响应、下载失败 |
|
|
26
|
+
| `server` | — | Telegram 服务器错误(5xx) | 瞬时故障;自动重试 |
|
|
27
|
+
| `retryable` | — | 其他可重试的 Telegram 错误 | flood-wait 变体 |
|
|
28
|
+
| `unknown` | — | 其他 | — |
|
|
29
|
+
|
|
30
|
+
当不同错误需要不同反应时,检查 `kind`:
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
import { TelegramError, TelegramRateLimitError } from "@xbibzlibrary/telebibz";
|
|
34
|
+
|
|
35
|
+
try {
|
|
36
|
+
await ctx.reply("hello");
|
|
37
|
+
} catch (error) {
|
|
38
|
+
if (error instanceof TelegramError) {
|
|
39
|
+
switch (error.kind) {
|
|
40
|
+
case "rate-limit": console.log(`放慢 ${error.retryAfter}s`); break;
|
|
41
|
+
case "authentication": console.error("token 问题 —— 停机"); break;
|
|
42
|
+
case "validation": console.warn(error.message); break;
|
|
43
|
+
default: console.error(error.message);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
如果只关心 429,也可以用 `instanceof TelegramRateLimitError`。
|
|
50
|
+
|
|
51
|
+
## 2. TelegramError 解剖
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
try {
|
|
55
|
+
await bot.api.methods.getChatMember({ chat_id: -100123, user_id: 42 });
|
|
56
|
+
} catch (error) {
|
|
57
|
+
if (error instanceof TelegramError) {
|
|
58
|
+
error.kind; // 上表七种 kind 之一
|
|
59
|
+
error.errorCode; // Telegram 的 error_code(400、401、429……),如存在
|
|
60
|
+
error.method; // "getChatMember" —— 失败的调用
|
|
61
|
+
error.payload; // 发出的负载
|
|
62
|
+
error.retryAfter; // 秒数,仅 429(来自 parameters.retry_after)
|
|
63
|
+
error.message; // Telegram 的描述
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## 3. 429 限流与 flood gate
|
|
69
|
+
|
|
70
|
+
通常你根本见不到 429,因为传输层已经处理了:
|
|
71
|
+
|
|
72
|
+
1. Telegram 返回带 `parameters.retry_after` 的 429。
|
|
73
|
+
2. **flood gate** 在该时间窗内暂停*新的*外发请求 —— 保护整个流量,而不只是被拒绝的那个请求。
|
|
74
|
+
3. 被拒请求自动重试;若仍失败,最终以 `TelegramRateLimitError` 抛出。
|
|
75
|
+
|
|
76
|
+
flood gate 是本库引入的唯一暂停机制 —— 从不做主动冷却。可按传输层调整或关闭:
|
|
77
|
+
|
|
78
|
+
```ts
|
|
79
|
+
const bot = new Bot({
|
|
80
|
+
token: process.env.TELEGRAM_BOT_TOKEN!,
|
|
81
|
+
transportOptions: {
|
|
82
|
+
floodGate: false, // 完全自行处理 429
|
|
83
|
+
retries: 3, // 429/5xx/网络错误的重试次数
|
|
84
|
+
backoffMs: 250, // 指数退避基数
|
|
85
|
+
maxBackoffMs: 8_000,
|
|
86
|
+
timeoutMs: 30_000, // 单个请求超时
|
|
87
|
+
},
|
|
88
|
+
});
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
关闭 gate 后,请捕获 `TelegramRateLimitError` 并遵守 `retryAfter` —— Telegram 说了算:
|
|
92
|
+
|
|
93
|
+
```ts
|
|
94
|
+
catch (error) {
|
|
95
|
+
if (error instanceof TelegramRateLimitError) {
|
|
96
|
+
await sleep((error.retryAfter ?? 1) * 1000);
|
|
97
|
+
return retry();
|
|
98
|
+
}
|
|
99
|
+
throw error;
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## 4. 错误处理器与 `bot.catch()`
|
|
104
|
+
|
|
105
|
+
没有错误边界时,抛异常的 handler 会让 `handleUpdate()` 拒绝(webhook 则返回 500)。使用 `bot.catch()` 把失败引到一处:
|
|
106
|
+
|
|
107
|
+
```ts
|
|
108
|
+
bot.catch(async (error, ctx) => {
|
|
109
|
+
console.error("handler 失败:", error);
|
|
110
|
+
|
|
111
|
+
if (error instanceof TelegramError && error.kind === "authentication") {
|
|
112
|
+
process.exitCode = 1; // 不可恢复 —— 交给监督进程重启
|
|
113
|
+
await bot.stop();
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
await ctx.reply("❌ 出错了,请稍后再试。"); // ctx = 失败 update 的上下文
|
|
118
|
+
});
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
关键行为:
|
|
122
|
+
- 只有失败的 update 受影响 —— 其他 chat 继续并发处理。
|
|
123
|
+
- 每 chat 顺序保持不变:同一 chat 的后续 update 仍会排队等待。
|
|
124
|
+
- `broadcast()` 的失败收集在报告中,而不是抛异常。
|
|
125
|
+
|
|
126
|
+
## 5. handlerTimeout 与 `UpdateTimeoutError`
|
|
127
|
+
|
|
128
|
+
`handlerTimeout`(默认 **90 000 毫秒**,与 Telegraf 一致)保护流水线免受挂起 handler 的拖累:
|
|
129
|
+
|
|
130
|
+
```ts
|
|
131
|
+
const bot = new Bot({ token, handlerTimeout: 30_000 }); // 0 或 Infinity 表示禁用
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
- 挂起 update 的 `handleUpdate()` promise 抛出 `UpdateTimeoutError`,并依次流经 `update:error` → `bot:error` → `bot.catch()`。
|
|
135
|
+
- **handler 仍在后台运行** —— session 与 conversation 照常完成写入;超时只释放流水线。
|
|
136
|
+
- 每 chat 顺序不受影响。
|
|
137
|
+
|
|
138
|
+
## 6. 基于事件的错误观测
|
|
139
|
+
|
|
140
|
+
要独立于错误边界的指标/日志,监听事件总线:
|
|
141
|
+
|
|
142
|
+
```ts
|
|
143
|
+
bot.events.on("update:error", ({ update, error }) => {
|
|
144
|
+
metrics.increment("handler_errors", { updateId: (update as { update_id?: number }).update_id });
|
|
145
|
+
});
|
|
146
|
+
bot.events.on("bot:error", ({ error }) => log.error("bot error", { error }));
|
|
147
|
+
bot.events.on("api:response", ({ method, durationMs }) => {
|
|
148
|
+
if (durationMs > 3_000) log.warn("慢速 API 调用", { method, durationMs });
|
|
149
|
+
});
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## 7. 传输层重试与网络错误
|
|
153
|
+
|
|
154
|
+
`FetchTransport` 自动重试:429(遵守 Telegram 的 `retry_after`)、5xx、网络错误、非 JSON 响应 —— 采用指数退避加抖动。重试 `retries` 次后,最后一个错误以 `TelegramNetworkError` 抛出(含 `status` 与截断的 cause)。认证错误(401)和校验错误(400)**从不**重试 —— 重试无济于事。
|
|
155
|
+
|
|
156
|
+
## 8. 错误处理配方
|
|
157
|
+
|
|
158
|
+
**围绕单次调用的退避重试**(在传输层重试之外):
|
|
159
|
+
|
|
160
|
+
```ts
|
|
161
|
+
async function withRetry<T>(run: () => Promise<T>, attempts = 3): Promise<T> {
|
|
162
|
+
for (let i = 0; i < attempts; i++) {
|
|
163
|
+
try { return await run(); }
|
|
164
|
+
catch (error) {
|
|
165
|
+
if (error instanceof TelegramError && ["authentication", "validation"].includes(error.kind)) throw error;
|
|
166
|
+
if (i === attempts - 1) throw error;
|
|
167
|
+
await new Promise((r) => setTimeout(r, 500 * 2 ** i));
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
throw new Error("unreachable");
|
|
171
|
+
}
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
**token 无效时快速失败**(用错误 token 轮询):
|
|
175
|
+
|
|
176
|
+
```ts
|
|
177
|
+
try {
|
|
178
|
+
await bot.start();
|
|
179
|
+
} catch (error) {
|
|
180
|
+
if (error instanceof TelegramError && error.kind === "authentication") {
|
|
181
|
+
console.error("TELEGRAM_BOT_TOKEN 无效或已吊销");
|
|
182
|
+
}
|
|
183
|
+
throw error;
|
|
184
|
+
}
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
**优雅关机** —— `stop()` 先排空在途 handler:
|
|
188
|
+
|
|
189
|
+
```ts
|
|
190
|
+
process.on("SIGINT", () => { void bot.stop().then(() => process.exit(0)); });
|
|
191
|
+
process.on("SIGTERM", () => { void bot.stop().then(() => process.exit(0)); });
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
English: [ERRORS.md](ERRORS.md) · Bahasa Indonesia: [ERRORS.id.md](ERRORS.id.md)
|