@xbibzlibrary/telebibz 0.4.5 → 3.0.1
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 +35 -138
- package/LICENSE +1 -1
- package/NOTICE.md +9 -4
- package/README.md +173 -256
- package/examples/01-quickstart.js +13 -0
- package/examples/02-menu-tombol.js +22 -0
- package/examples/03-wizard.js +29 -0
- package/examples/04-broadcast.js +25 -0
- package/examples/05-kirim-file.js +19 -0
- package/examples/06-menu.js +35 -0
- package/examples/07-inline-query.js +18 -0
- package/index.d.ts +81 -0
- package/index.js +42 -0
- package/lib/api.js +154 -0
- package/lib/broadcast.js +35 -0
- package/lib/composer.js +174 -0
- package/lib/context.js +190 -0
- package/lib/errors.js +35 -0
- package/lib/file.js +41 -0
- package/lib/inline-query.js +28 -0
- package/lib/keyboard.js +83 -0
- package/lib/logger.js +38 -0
- package/lib/menus.js +90 -0
- package/lib/net.js +115 -0
- package/lib/ratelimit.js +61 -0
- package/lib/runner.js +45 -0
- package/lib/session.js +36 -0
- package/lib/telebibz.js +190 -0
- package/lib/wizard.js +81 -0
- package/package.json +35 -97
- package/test/all.test.js +334 -0
- package/CODE_OF_CONDUCT.md +0 -37
- package/CONTRIBUTING.md +0 -59
- package/CONTRIBUTION_RULES.md +0 -41
- package/GOVERNANCE.md +0 -47
- package/README.id.md +0 -306
- package/README.zh-CN.md +0 -306
- package/RELEASE_AUTOMATION.md +0 -78
- package/RELEASE_POLICY.md +0 -32
- package/SECURITY.md +0 -47
- package/SHOWCASE.md +0 -29
- package/SUPPORT.md +0 -30
- package/assets/readme-preview.html +0 -75
- package/assets/telebibz-logo.png +0 -0
- package/assets/telebibz-readme-preview.png +0 -0
- package/bin/telebibz.mjs +0 -3
- package/dist/generated/api.d.ts +0 -13
- package/dist/generated/api.d.ts.map +0 -1
- package/dist/generated/api.js +0 -192
- package/dist/generated/api.js.map +0 -1
- package/dist/src/api/client.d.ts +0 -62
- package/dist/src/api/client.d.ts.map +0 -1
- package/dist/src/api/client.js +0 -104
- package/dist/src/api/client.js.map +0 -1
- package/dist/src/api/errors.d.ts +0 -45
- package/dist/src/api/errors.d.ts.map +0 -1
- package/dist/src/api/errors.js +0 -65
- package/dist/src/api/errors.js.map +0 -1
- package/dist/src/api/index.d.ts +0 -6
- package/dist/src/api/index.d.ts.map +0 -1
- package/dist/src/api/index.js +0 -6
- package/dist/src/api/index.js.map +0 -1
- package/dist/src/api/telegram-types/LICENSE +0 -21
- package/dist/src/api/telegram-types/api.d.ts +0 -22
- package/dist/src/api/telegram-types/checklist.d.ts +0 -72
- package/dist/src/api/telegram-types/inline.d.ts +0 -692
- package/dist/src/api/telegram-types/langs.d.ts +0 -193
- package/dist/src/api/telegram-types/manage.d.ts +0 -1144
- package/dist/src/api/telegram-types/markup.d.ts +0 -268
- package/dist/src/api/telegram-types/message.d.ts +0 -1537
- package/dist/src/api/telegram-types/methods.d.ts +0 -2870
- package/dist/src/api/telegram-types/mod.d.ts +0 -14
- package/dist/src/api/telegram-types/passport.d.ts +0 -163
- package/dist/src/api/telegram-types/payment.d.ts +0 -570
- package/dist/src/api/telegram-types/rich.d.ts +0 -1010
- package/dist/src/api/telegram-types/settings.d.ts +0 -120
- package/dist/src/api/telegram-types/story.d.ts +0 -89
- package/dist/src/api/telegram-types/update.d.ts +0 -84
- package/dist/src/api/telegram.d.ts +0 -7
- package/dist/src/api/telegram.d.ts.map +0 -1
- package/dist/src/api/telegram.js +0 -2
- package/dist/src/api/telegram.js.map +0 -1
- package/dist/src/api/transport.d.ts +0 -68
- package/dist/src/api/transport.d.ts.map +0 -1
- package/dist/src/api/transport.js +0 -264
- package/dist/src/api/transport.js.map +0 -1
- package/dist/src/api/types.d.ts +0 -466
- package/dist/src/api/types.d.ts.map +0 -1
- package/dist/src/api/types.js +0 -2
- package/dist/src/api/types.js.map +0 -1
- package/dist/src/branding/terminal.d.ts +0 -77
- package/dist/src/branding/terminal.d.ts.map +0 -1
- package/dist/src/branding/terminal.js +0 -328
- package/dist/src/branding/terminal.js.map +0 -1
- package/dist/src/broadcast/broadcast.d.ts +0 -50
- package/dist/src/broadcast/broadcast.d.ts.map +0 -1
- package/dist/src/broadcast/broadcast.js +0 -56
- package/dist/src/broadcast/broadcast.js.map +0 -1
- package/dist/src/cache/cache.d.ts +0 -34
- package/dist/src/cache/cache.d.ts.map +0 -1
- package/dist/src/cache/cache.js +0 -41
- package/dist/src/cache/cache.js.map +0 -1
- package/dist/src/cli.d.ts +0 -2
- package/dist/src/cli.d.ts.map +0 -1
- package/dist/src/cli.js +0 -84
- package/dist/src/cli.js.map +0 -1
- package/dist/src/context/context.d.ts +0 -124
- package/dist/src/context/context.d.ts.map +0 -1
- package/dist/src/context/context.js +0 -302
- package/dist/src/context/context.js.map +0 -1
- package/dist/src/core/bot.d.ts +0 -204
- package/dist/src/core/bot.d.ts.map +0 -1
- package/dist/src/core/bot.js +0 -506
- package/dist/src/core/bot.js.map +0 -1
- package/dist/src/core/events.d.ts +0 -75
- package/dist/src/core/events.d.ts.map +0 -1
- package/dist/src/core/events.js +0 -35
- package/dist/src/core/events.js.map +0 -1
- package/dist/src/core/webhook-reply.d.ts +0 -34
- package/dist/src/core/webhook-reply.d.ts.map +0 -1
- package/dist/src/core/webhook-reply.js +0 -37
- package/dist/src/core/webhook-reply.js.map +0 -1
- package/dist/src/index.d.ts +0 -24
- package/dist/src/index.d.ts.map +0 -1
- package/dist/src/index.js +0 -24
- package/dist/src/index.js.map +0 -1
- package/dist/src/keyboard/index.d.ts +0 -46
- package/dist/src/keyboard/index.d.ts.map +0 -1
- package/dist/src/keyboard/index.js +0 -55
- package/dist/src/keyboard/index.js.map +0 -1
- package/dist/src/middleware/compose.d.ts +0 -5
- package/dist/src/middleware/compose.d.ts.map +0 -1
- package/dist/src/middleware/compose.js +0 -17
- package/dist/src/middleware/compose.js.map +0 -1
- package/dist/src/observability/logger.d.ts +0 -78
- package/dist/src/observability/logger.d.ts.map +0 -1
- package/dist/src/observability/logger.js +0 -285
- package/dist/src/observability/logger.js.map +0 -1
- package/dist/src/plugins/plugin.d.ts +0 -38
- package/dist/src/plugins/plugin.d.ts.map +0 -1
- package/dist/src/plugins/plugin.js +0 -59
- package/dist/src/plugins/plugin.js.map +0 -1
- package/dist/src/queue/queue.d.ts +0 -77
- package/dist/src/queue/queue.d.ts.map +0 -1
- package/dist/src/queue/queue.js +0 -213
- package/dist/src/queue/queue.js.map +0 -1
- package/dist/src/router/router.d.ts +0 -61
- package/dist/src/router/router.d.ts.map +0 -1
- package/dist/src/router/router.js +0 -183
- package/dist/src/router/router.js.map +0 -1
- package/dist/src/state/conversation.d.ts +0 -56
- package/dist/src/state/conversation.d.ts.map +0 -1
- package/dist/src/state/conversation.js +0 -133
- package/dist/src/state/conversation.js.map +0 -1
- package/dist/src/state/forms.d.ts +0 -34
- package/dist/src/state/forms.d.ts.map +0 -1
- package/dist/src/state/forms.js +0 -44
- package/dist/src/state/forms.js.map +0 -1
- package/dist/src/state/menu.d.ts +0 -78
- package/dist/src/state/menu.d.ts.map +0 -1
- package/dist/src/state/menu.js +0 -127
- package/dist/src/state/menu.js.map +0 -1
- package/dist/src/storage/storage.d.ts +0 -146
- package/dist/src/storage/storage.d.ts.map +0 -1
- package/dist/src/storage/storage.js +0 -195
- package/dist/src/storage/storage.js.map +0 -1
- package/dist/src/telegram-features.d.ts +0 -33
- package/dist/src/telegram-features.d.ts.map +0 -1
- package/dist/src/telegram-features.js +0 -71
- package/dist/src/telegram-features.js.map +0 -1
- package/dist/src/testing.d.ts +0 -24
- package/dist/src/testing.d.ts.map +0 -1
- package/dist/src/testing.js +0 -38
- package/dist/src/testing.js.map +0 -1
- package/dist/src/utils/concurrency.d.ts +0 -25
- package/dist/src/utils/concurrency.d.ts.map +0 -1
- package/dist/src/utils/concurrency.js +0 -52
- package/dist/src/utils/concurrency.js.map +0 -1
- package/dist/src/utils/files.d.ts +0 -45
- package/dist/src/utils/files.d.ts.map +0 -1
- package/dist/src/utils/files.js +0 -53
- package/dist/src/utils/files.js.map +0 -1
- package/dist/src/utils/text.d.ts +0 -39
- package/dist/src/utils/text.d.ts.map +0 -1
- package/dist/src/utils/text.js +0 -56
- package/dist/src/utils/text.js.map +0 -1
- package/dist/src/webhook/handler.d.ts +0 -19
- package/dist/src/webhook/handler.d.ts.map +0 -1
- package/dist/src/webhook/handler.js +0 -141
- package/dist/src/webhook/handler.js.map +0 -1
- package/dist-cjs/generated/api.js +0 -194
- package/dist-cjs/package.json +0 -3
- package/dist-cjs/src/api/client.js +0 -107
- package/dist-cjs/src/api/errors.js +0 -74
- package/dist-cjs/src/api/index.js +0 -21
- package/dist-cjs/src/api/telegram-types/LICENSE +0 -21
- package/dist-cjs/src/api/telegram-types/api.d.ts +0 -22
- package/dist-cjs/src/api/telegram-types/checklist.d.ts +0 -72
- package/dist-cjs/src/api/telegram-types/inline.d.ts +0 -692
- package/dist-cjs/src/api/telegram-types/langs.d.ts +0 -193
- package/dist-cjs/src/api/telegram-types/manage.d.ts +0 -1144
- package/dist-cjs/src/api/telegram-types/markup.d.ts +0 -268
- package/dist-cjs/src/api/telegram-types/message.d.ts +0 -1537
- package/dist-cjs/src/api/telegram-types/methods.d.ts +0 -2870
- package/dist-cjs/src/api/telegram-types/mod.d.ts +0 -14
- package/dist-cjs/src/api/telegram-types/passport.d.ts +0 -163
- package/dist-cjs/src/api/telegram-types/payment.d.ts +0 -570
- package/dist-cjs/src/api/telegram-types/rich.d.ts +0 -1010
- package/dist-cjs/src/api/telegram-types/settings.d.ts +0 -120
- package/dist-cjs/src/api/telegram-types/story.d.ts +0 -89
- package/dist-cjs/src/api/telegram-types/update.d.ts +0 -84
- package/dist-cjs/src/api/telegram.js +0 -2
- package/dist-cjs/src/api/transport.js +0 -267
- package/dist-cjs/src/api/types.js +0 -2
- package/dist-cjs/src/branding/terminal.js +0 -338
- package/dist-cjs/src/broadcast/broadcast.js +0 -58
- package/dist-cjs/src/cache/cache.js +0 -45
- package/dist-cjs/src/cli.js +0 -86
- package/dist-cjs/src/context/context.js +0 -305
- package/dist-cjs/src/core/bot.js +0 -510
- package/dist-cjs/src/core/events.js +0 -38
- package/dist-cjs/src/core/webhook-reply.js +0 -42
- package/dist-cjs/src/index.js +0 -47
- package/dist-cjs/src/keyboard/index.js +0 -61
- package/dist-cjs/src/middleware/compose.js +0 -20
- package/dist-cjs/src/observability/logger.js +0 -293
- package/dist-cjs/src/plugins/plugin.js +0 -63
- package/dist-cjs/src/queue/queue.js +0 -219
- package/dist-cjs/src/router/router.js +0 -186
- package/dist-cjs/src/state/conversation.js +0 -139
- package/dist-cjs/src/state/forms.js +0 -47
- package/dist-cjs/src/state/menu.js +0 -133
- package/dist-cjs/src/storage/storage.js +0 -202
- package/dist-cjs/src/telegram-features.js +0 -76
- package/dist-cjs/src/testing.js +0 -45
- package/dist-cjs/src/utils/concurrency.js +0 -57
- package/dist-cjs/src/utils/files.js +0 -58
- package/dist-cjs/src/utils/text.js +0 -63
- package/dist-cjs/src/webhook/handler.js +0 -144
- package/docs/API.id.md +0 -1935
- package/docs/API.md +0 -1969
- package/docs/API.zh-CN.md +0 -1929
- package/docs/COOKBOOK.id.md +0 -321
- package/docs/COOKBOOK.md +0 -321
- package/docs/COOKBOOK.zh-CN.md +0 -321
- package/docs/ERRORS.id.md +0 -194
- package/docs/ERRORS.md +0 -194
- package/docs/ERRORS.zh-CN.md +0 -194
- package/docs/FILES.id.md +0 -243
- package/docs/FILES.md +0 -243
- package/docs/FILES.zh-CN.md +0 -243
- package/docs/GETTING_STARTED.id.md +0 -89
- package/docs/GETTING_STARTED.md +0 -89
- package/docs/GETTING_STARTED.zh-CN.md +0 -89
- package/docs/GITHUB_PACKAGES.id.md +0 -82
- package/docs/GITHUB_PACKAGES.md +0 -82
- package/docs/GITHUB_PACKAGES.zh-CN.md +0 -82
- package/docs/MIGRATION_TELEGRAF.id.md +0 -147
- package/docs/MIGRATION_TELEGRAF.md +0 -154
- package/docs/MIGRATION_TELEGRAF.zh-CN.md +0 -147
- package/docs/README.md +0 -67
- package/docs/STORAGE.id.md +0 -105
- package/docs/STORAGE.md +0 -105
- package/docs/STORAGE.zh-CN.md +0 -105
- package/docs/TESTING.id.md +0 -203
- package/docs/TESTING.md +0 -203
- package/docs/TESTING.zh-CN.md +0 -203
- package/docs/WEBHOOK.id.md +0 -212
- package/docs/WEBHOOK.md +0 -215
- package/docs/WEBHOOK.zh-CN.md +0 -212
- package/examples/README.md +0 -37
- package/examples/files.ts +0 -35
- package/examples/minimal.ts +0 -12
- package/examples/tsconfig.json +0 -9
- package/examples/webhook.ts +0 -42
- package/examples/wizard-registration.ts +0 -42
package/docs/ERRORS.md
DELETED
|
@@ -1,194 +0,0 @@
|
|
|
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)
|
package/docs/ERRORS.zh-CN.md
DELETED
|
@@ -1,194 +0,0 @@
|
|
|
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)
|
package/docs/FILES.id.md
DELETED
|
@@ -1,243 +0,0 @@
|
|
|
1
|
-
# Bekerja dengan file: upload, download, validasi (Bahasa Indonesia)
|
|
2
|
-
|
|
3
|
-
Panduan lengkap setiap alur file di telebibz: mengunduh file yang dikirim user, mengunggah file ke Telegram, mengirim ulang via `file_id`, memvalidasi unggahan, serta batasan dan jebakan Telegram Bot API.
|
|
4
|
-
|
|
5
|
-
## Daftar isi
|
|
6
|
-
|
|
7
|
-
1. [Download: satu panggilan dengan `downloadFile()`](#1-download-satu-panggilan-dengan-downloadfile)
|
|
8
|
-
2. [Download: alur manual dengan `getFile()`](#2-download-alur-manual-dengan-getfile)
|
|
9
|
-
3. [Penamaan property: `file_path` vs `filePath`](#3-penamaan-property-file_path-vs-filepath)
|
|
10
|
-
4. [Upload: semua tipe sumber](#4-upload-semua-tipe-sumber)
|
|
11
|
-
5. [Upload: validasi sebelum mengirim](#5-upload-validasi-sebelum-mengirim)
|
|
12
|
-
6. [Media group dengan `attach://`](#6-media-group-dengan-attach)
|
|
13
|
-
7. [Batasan dan masa berlaku](#7-batasan-dan-masa-berlaku)
|
|
14
|
-
8. [Local Bot API server](#8-local-bot-api-server)
|
|
15
|
-
9. [Menguji alur file tanpa jaringan](#9-menguji-alur-file-tanpa-jaringan)
|
|
16
|
-
10. [Troubleshooting](#10-troubleshooting)
|
|
17
|
-
|
|
18
|
-
## 1. Download: satu panggilan dengan `downloadFile()`
|
|
19
|
-
|
|
20
|
-
`bot.downloadFile()` / `ctx.downloadFile()` me-resolve `file_id` lewat `getFile` lalu mengunduh byte mentahnya dalam satu panggilan:
|
|
21
|
-
|
|
22
|
-
```ts
|
|
23
|
-
bot.on("message:document", async (ctx) => {
|
|
24
|
-
const fileId = ctx.message.document.file_id;
|
|
25
|
-
|
|
26
|
-
// Unduh ke memori…
|
|
27
|
-
const file = await ctx.downloadFile(fileId);
|
|
28
|
-
console.log(file.fileName, file.sizeBytes, file.url);
|
|
29
|
-
// file.bytes adalah Uint8Array
|
|
30
|
-
|
|
31
|
-
// …atau langsung ke disk
|
|
32
|
-
const saved = await ctx.downloadFile(fileId, { destination: "downloads/report.pdf" });
|
|
33
|
-
console.log(`Tersimpan di ${saved.savedTo}`);
|
|
34
|
-
});
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
Hasilnya (`DownloadedFile`) membawa semua yang dibutuhkan:
|
|
38
|
-
|
|
39
|
-
| Field | Arti |
|
|
40
|
-
|---|---|
|
|
41
|
-
| `file` | Objek `File` Telegram yang dikembalikan `getFile` |
|
|
42
|
-
| `bytes` | Byte mentah file (`Uint8Array`) |
|
|
43
|
-
| `filePath` | `file_path` yang dipakai untuk unduhan |
|
|
44
|
-
| `url` | URL unduhan langsung — valid **minimal 1 jam** |
|
|
45
|
-
| `fileName` | Segmen path terakhir `filePath` (mis. `report.pdf`) |
|
|
46
|
-
| `sizeBytes` | Panjang byte `bytes` |
|
|
47
|
-
| `savedTo` | Path lokal, terisi saat Anda memberi `destination` |
|
|
48
|
-
|
|
49
|
-
Error-nya presisi:
|
|
50
|
-
- Telegram tidak mengembalikan `file_path` → `TelegramError` dengan `kind: "validation"`
|
|
51
|
-
- Unduhan HTTP-nya sendiri gagal → `TelegramNetworkError` (lengkap dengan status)
|
|
52
|
-
- `getFile` gagal (file_id salah, file terlalu besar) → `TelegramError` asli dari Telegram
|
|
53
|
-
|
|
54
|
-
Keduanya menerima `AbortSignal`:
|
|
55
|
-
|
|
56
|
-
```ts
|
|
57
|
-
const controller = new AbortController();
|
|
58
|
-
setTimeout(() => controller.abort(), 10_000);
|
|
59
|
-
const file = await bot.downloadFile(fileId, { signal: controller.signal });
|
|
60
|
-
```
|
|
61
|
-
|
|
62
|
-
## 2. Download: alur manual dengan `getFile()`
|
|
63
|
-
|
|
64
|
-
Kalau ingin membangun URL sendiri (mis. untuk HTTP client lain):
|
|
65
|
-
|
|
66
|
-
```ts
|
|
67
|
-
const file = await ctx.getFile(fileId); // objek File Telegram
|
|
68
|
-
if (!file.file_path) throw new Error("File tidak tersedia (di atas 20 MB atau kedaluwarsa)");
|
|
69
|
-
|
|
70
|
-
const url = `https://api.telegram.org/file/bot${process.env.TELEGRAM_BOT_TOKEN}/${file.file_path}`;
|
|
71
|
-
const response = await fetch(url); // ← fetch — JANGAN createReadStream (tidak bisa membuka URL)
|
|
72
|
-
if (!response.ok) throw new Error(`Unduhan gagal: HTTP ${response.status}`);
|
|
73
|
-
const bytes = new Uint8Array(await response.arrayBuffer());
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
Tiga aturan yang terkandung di snippet ini:
|
|
77
|
-
1. Prefix URL-nya `/file/bot<TOKEN>/` — **kata `bot` wajib ada**. Lupa menuliskannya adalah kesalahan paling umum dan hasilnya `404 Not Found`.
|
|
78
|
-
2. `fetch` yang mengunduh byte; `fs.createReadStream()` hanya membuka **path lokal** — diberi URL ia akan crash dengan `ENOENT` pada stream error yang tidak tertangani.
|
|
79
|
-
3. URL dijamin valid **minimal 1 jam**. Jangan pernah cache lama; panggil `getFile` lagi setelah kedaluwarsa.
|
|
80
|
-
|
|
81
|
-
## 3. Penamaan property: `file_path` vs `filePath`
|
|
82
|
-
|
|
83
|
-
Ini menjegal semua orang minimal sekali. Dua gaya penamaan ini milik layer yang berbeda:
|
|
84
|
-
|
|
85
|
-
| Penamaan | Milik | Contoh |
|
|
86
|
-
|---|---|---|
|
|
87
|
-
| `snake_case` (`file_path`) | **Objek mentah Telegram** — hasil `getFile()`, `ctx.message.document`, `ctx.message.photo` | `file.file_path`, `document.file_id`, `photo.file_unique_id` |
|
|
88
|
-
| `camelCase` (`filePath`) | **Tipe hasil telebibz** — `DownloadedFile` dan opsi library | `downloaded.filePath`, `downloaded.fileName`, `downloaded.sizeBytes` |
|
|
89
|
-
|
|
90
|
-
```ts
|
|
91
|
-
const file = await ctx.getFile(fileId);
|
|
92
|
-
file.file_path; // ✅ objek Telegram → snake_case
|
|
93
|
-
file.filePath; // ❌ undefined — itu nama milik DownloadedFile
|
|
94
|
-
|
|
95
|
-
const downloaded = await ctx.downloadFile(fileId);
|
|
96
|
-
downloaded.filePath; // ✅ hasil library → camelCase
|
|
97
|
-
downloaded.file_path; // ❌ undefined
|
|
98
|
-
```
|
|
99
|
-
|
|
100
|
-
Kalau cek "tidak ada path" Anda gagal padahal response yang di-log jelas memuat `file_path`, berarti Anda membaca property camelCase dari objek snake_case.
|
|
101
|
-
|
|
102
|
-
## 4. Upload: semua tipe sumber
|
|
103
|
-
|
|
104
|
-
Semua pengirim `replyWith*` dan panggilan API mentah menerima tipe `InputFile`. Kirim unggahan sebagai `{ source, filename? }`:
|
|
105
|
-
|
|
106
|
-
```ts
|
|
107
|
-
// Dari path di disk (absolut, ./, atau ../)
|
|
108
|
-
await ctx.replyWithDocument({ source: "reports/q3.pdf", filename: "Q3-report.pdf" });
|
|
109
|
-
|
|
110
|
-
// Dari byte mentah
|
|
111
|
-
const bytes = new Uint8Array(await someFile.bytes());
|
|
112
|
-
await ctx.replyWithDocument({ source: bytes, filename: "data.bin" });
|
|
113
|
-
|
|
114
|
-
// Dari Blob atau File (File membawa namanya sendiri)
|
|
115
|
-
await ctx.replyWithDocument({ source: new File([bytes], "photo.png") });
|
|
116
|
-
|
|
117
|
-
// Dari web ReadableStream atau stream Node (dikuras otomatis)
|
|
118
|
-
import { createReadStream } from "node:fs";
|
|
119
|
-
await ctx.replyWithVideo({ source: createReadStream("clip.mp4"), filename: "clip.mp4" });
|
|
120
|
-
```
|
|
121
|
-
|
|
122
|
-
Catatan:
|
|
123
|
-
- `filename` menimpa nama apa pun yang dimiliki sumber (untuk path, basename dipakai secara default).
|
|
124
|
-
- **Anda tidak pernah merakit `FormData` sendiri.** Transport mendeteksi payload unggahan dan beralih ke multipart otomatis. `FormData` buatan tangan dengan stream Node pasti gagal (`append` butuh `Blob`) — selalu serahkan stream/byte ke library.
|
|
125
|
-
- Nilai telanjang juga bisa: `await ctx.replyWithDocument(bytes)` (tanpa nama) atau file id Telegram yang sudah ada:
|
|
126
|
-
|
|
127
|
-
```ts
|
|
128
|
-
// Kirim ulang via file_id — tanpa unduh, tanpa upload, tanpa batas ukuran
|
|
129
|
-
await ctx.replyWithDocument(ctx.message.document.file_id);
|
|
130
|
-
```
|
|
131
|
-
|
|
132
|
-
## 5. Upload: validasi sebelum mengirim
|
|
133
|
-
|
|
134
|
-
`validateUpload()` / `assertValidUpload()` menegakkan aturan Anda sendiri sebelum satu byte pun keluar dari proses:
|
|
135
|
-
|
|
136
|
-
```ts
|
|
137
|
-
import { assertValidUpload, UploadValidationError } from "@xbibzlibrary/telebibz";
|
|
138
|
-
|
|
139
|
-
bot.command("doc", async (ctx) => {
|
|
140
|
-
const filePath = ctx.message?.text?.split(/\s+/)[1];
|
|
141
|
-
if (!filePath) return void (await ctx.reply("Penggunaan: /doc <path>"));
|
|
142
|
-
|
|
143
|
-
const info = await stat(filePath);
|
|
144
|
-
try {
|
|
145
|
-
assertValidUpload(
|
|
146
|
-
{ sizeBytes: info.size, fileName: filePath },
|
|
147
|
-
{
|
|
148
|
-
maxBytes: 50 * 1024 * 1024, // batas dokumen Telegram
|
|
149
|
-
allowedExtensions: [".pdf", ".docx", ".pptx"], // case-insensitive
|
|
150
|
-
},
|
|
151
|
-
);
|
|
152
|
-
} catch (error) {
|
|
153
|
-
if (error instanceof UploadValidationError) {
|
|
154
|
-
return void (await ctx.reply(`❌ ${error.message}`)); // mencantumkan semua pelanggaran
|
|
155
|
-
}
|
|
156
|
-
throw error;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
await ctx.replyWithDocument({ source: filePath });
|
|
160
|
-
});
|
|
161
|
-
```
|
|
162
|
-
|
|
163
|
-
`validateUpload()` mengembalikan daftar issue alih-alih melempar (array kosong = valid). Aturan MIME mendukung wildcard:
|
|
164
|
-
|
|
165
|
-
```ts
|
|
166
|
-
validateUpload({ mimeType: "image/png" }, { allowedMimeTypes: ["image/*"] }); // []
|
|
167
|
-
```
|
|
168
|
-
|
|
169
|
-
Aturan yang tersedia: `maxBytes`, `allowedMimeTypes` (persis atau wildcard `image/*`), `allowedExtensions` (titik opsional, case-insensitive).
|
|
170
|
-
|
|
171
|
-
## 6. Media group dengan `attach://`
|
|
172
|
-
|
|
173
|
-
`sendMediaGroup` menerima array JSON input media; file biner ikut sebagai **bagian form terpisah** yang direferensikan lewat `attach://<nama>`:
|
|
174
|
-
|
|
175
|
-
```ts
|
|
176
|
-
await ctx.replyWithMediaGroup([
|
|
177
|
-
{ type: "photo", media: "attach://pic1" },
|
|
178
|
-
{ type: "photo", media: "attach://pic2" },
|
|
179
|
-
], { pic1: bytes1, pic2: bytes2 } as never);
|
|
180
|
-
```
|
|
181
|
-
|
|
182
|
-
Transport mendeteksi bagian biner dan beralih ke multipart otomatis; array `media` sendiri diserialisasi sebagai satu field JSON — persis kontrak Telegram.
|
|
183
|
-
|
|
184
|
-
## 7. Batasan dan masa berlaku
|
|
185
|
-
|
|
186
|
-
| Batasan | Nilai | Catatan |
|
|
187
|
-
|---|---|---|
|
|
188
|
-
| Unduhan via `getFile` | **20 MB** | File lebih besar: `getFile` gagal (HTTP 400 "file is too big") — bukan `file_path` kosong |
|
|
189
|
-
| Upload foto | 10 MB | |
|
|
190
|
-
| Upload file lain | 50 MB | |
|
|
191
|
-
| Kirim ulang via `file_id` | **Tanpa batas** | File sudah ada di Telegram |
|
|
192
|
-
| Kirim via URL | 5 MB foto / 20 MB lainnya | Telegram yang mengambil URL-nya |
|
|
193
|
-
| Validitas URL unduhan | **≥ 1 jam** | Jalankan `getFile` lagi setelah kedaluwarsa |
|
|
194
|
-
| Kehadiran `file_path` | Opsional di skema | Selalu cek sebelum dipakai |
|
|
195
|
-
|
|
196
|
-
Batas upload milik Telegram, bukan library — `validateUpload()` adalah cara Anda menolak lebih awal dengan pesan yang ramah.
|
|
197
|
-
|
|
198
|
-
## 8. Local Bot API server
|
|
199
|
-
|
|
200
|
-
Menjalankan [local Bot API server](https://core.telegram.org/bots/api#using-a-local-bot-api-server) sendiri menghapus batas unduhan 20 MB dan mengizinkan upload hingga 2000 MB:
|
|
201
|
-
|
|
202
|
-
```ts
|
|
203
|
-
const bot = new Bot({
|
|
204
|
-
token: process.env.TELEGRAM_BOT_TOKEN!,
|
|
205
|
-
apiBaseUrl: "http://localhost:8081", // opsi Bot API
|
|
206
|
-
transportOptions: { timeoutMs: 600_000 },
|
|
207
|
-
});
|
|
208
|
-
```
|
|
209
|
-
|
|
210
|
-
`downloadFile()` dan `fileUrl()` memetakan `/bot<token>` ke `/file/bot<token>` di base URL mana pun, jadi unduhan juga bekerja lewat server lokal. Catatan: pada server lokal, `file_path` berupa **path absolut di disk server** — fetch URL-nya hanya bila server jarak jauh; baca path-nya langsung bila bot berjalan di mesin yang sama.
|
|
211
|
-
|
|
212
|
-
## 9. Menguji alur file tanpa jaringan
|
|
213
|
-
|
|
214
|
-
`MockTransport` (dari `@xbibzlibrary/telebibz/testing`) mengimplementasikan member download:
|
|
215
|
-
|
|
216
|
-
```ts
|
|
217
|
-
import { createTestBot } from "@xbibzlibrary/telebibz/testing";
|
|
218
|
-
|
|
219
|
-
const { bot, transport } = createTestBot();
|
|
220
|
-
transport.respond("getFile", { ok: true, result: { file_id: "F1", file_unique_id: "U1", file_path: "documents/a.pdf" } });
|
|
221
|
-
transport.downloadBytes = new TextEncoder().encode("pdf-content");
|
|
222
|
-
|
|
223
|
-
const file = await bot.downloadFile("F1");
|
|
224
|
-
file.fileName; // "a.pdf"
|
|
225
|
-
new TextDecoder().decode(file.bytes); // "pdf-content"
|
|
226
|
-
transport.downloads; // ["documents/a.pdf"] — unduhan yang tercatat
|
|
227
|
-
```
|
|
228
|
-
|
|
229
|
-
Lihat [TESTING.id.md](TESTING.id.md) untuk panduan testing lengkap.
|
|
230
|
-
|
|
231
|
-
## 10. Troubleshooting
|
|
232
|
-
|
|
233
|
-
| Gejala | Penyebab | Perbaikan |
|
|
234
|
-
|---|---|---|
|
|
235
|
-
| "Gagal mendapatkan path file" padahal response yang di-log memuat `file_path` | Membaca `file.filePath`/`file.path` dari objek mentah Telegram | Pakai `file.file_path` (snake_case) — atau lewati manual sepenuhnya dengan `ctx.downloadFile()` |
|
|
236
|
-
| `ENOENT … open 'https://…'` pada ReadStream | `createReadStream()` hanya membuka path lokal | Pakai `fetch(url)` untuk URL, atau `ctx.downloadFile()` |
|
|
237
|
-
| URL unduhan mengembalikan 404 | Prefix `bot` hilang di `/file/bot<TOKEN>/` | Pakai field `url` dari `downloadFile()` — selalu dibangun dengan benar |
|
|
238
|
-
| `getFile` mengembalikan HTTP 400 "file is too big" | File di atas 20 MB | Pakai local Bot API server, atau kirim ulang via `file_id` |
|
|
239
|
-
| `FormData append: parameter 2 is not of type 'Blob'` | `FormData` buatan tangan berisi stream Node | Serahkan `{ source: stream, filename }` ke `replyWith*`; library yang mengurus multipart |
|
|
240
|
-
| `file_path` tadinya ada, sekarang hilang | URL kedaluwarsa (>1 jam) | Panggil `getFile` lagi |
|
|
241
|
-
| File terunduh 0 byte / salah | `file_id` milik bot lain | `file_id` bersifat per-bot; pakai id dari update bot Anda sendiri |
|
|
242
|
-
|
|
243
|
-
English: [FILES.md](FILES.md) · 简体中文: [FILES.zh-CN.md](FILES.zh-CN.md)
|