@xbibzlibrary/telebibz 0.3.2 → 0.4.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 +16 -1
- package/README.id.md +13 -0
- package/README.md +13 -0
- package/README.zh-CN.md +13 -0
- package/dist/src/api/client.d.ts.map +1 -1
- package/dist/src/api/client.js +12 -0
- package/dist/src/api/client.js.map +1 -1
- package/dist/src/context/context.d.ts +46 -1
- package/dist/src/context/context.d.ts.map +1 -1
- package/dist/src/context/context.js +107 -0
- package/dist/src/context/context.js.map +1 -1
- package/dist/src/core/bot.d.ts +38 -3
- package/dist/src/core/bot.d.ts.map +1 -1
- package/dist/src/core/bot.js +70 -7
- package/dist/src/core/bot.js.map +1 -1
- package/dist/src/core/webhook-reply.d.ts +34 -0
- package/dist/src/core/webhook-reply.d.ts.map +1 -0
- package/dist/src/core/webhook-reply.js +37 -0
- package/dist/src/core/webhook-reply.js.map +1 -0
- 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/webhook/handler.d.ts +8 -0
- package/dist/src/webhook/handler.d.ts.map +1 -1
- package/dist/src/webhook/handler.js +26 -6
- package/dist/src/webhook/handler.js.map +1 -1
- package/dist-cjs/src/api/client.js +12 -0
- package/dist-cjs/src/context/context.js +107 -0
- package/dist-cjs/src/core/bot.js +72 -8
- package/dist-cjs/src/core/webhook-reply.js +42 -0
- package/dist-cjs/src/index.js +1 -0
- package/dist-cjs/src/webhook/handler.js +26 -6
- package/docs/API.id.md +48 -4
- package/docs/API.md +48 -4
- package/docs/API.zh-CN.md +48 -4
- package/package.json +1 -1
package/dist-cjs/src/core/bot.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.Bot = void 0;
|
|
3
|
+
exports.Bot = exports.UpdateTimeoutError = void 0;
|
|
4
4
|
const client_js_1 = require("../api/client.js");
|
|
5
5
|
const transport_js_1 = require("../api/transport.js");
|
|
6
6
|
const context_js_1 = require("../context/context.js");
|
|
@@ -14,6 +14,17 @@ const terminal_js_1 = require("../branding/terminal.js");
|
|
|
14
14
|
const conversation_js_1 = require("../state/conversation.js");
|
|
15
15
|
const broadcast_js_1 = require("../broadcast/broadcast.js");
|
|
16
16
|
const concurrency_js_1 = require("../utils/concurrency.js");
|
|
17
|
+
const webhook_reply_js_1 = require("./webhook-reply.js");
|
|
18
|
+
/** Thrown when a single update exceeds `handlerTimeout`; the handler keeps running in the background. */
|
|
19
|
+
class UpdateTimeoutError extends Error {
|
|
20
|
+
name = "UpdateTimeoutError";
|
|
21
|
+
updateId;
|
|
22
|
+
constructor(updateId, timeoutMs) {
|
|
23
|
+
super(`Update ${updateId} handler timed out after ${timeoutMs}ms`);
|
|
24
|
+
this.updateId = updateId;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
exports.UpdateTimeoutError = UpdateTimeoutError;
|
|
17
28
|
class Bot {
|
|
18
29
|
api;
|
|
19
30
|
router;
|
|
@@ -31,6 +42,10 @@ class Bot {
|
|
|
31
42
|
me;
|
|
32
43
|
/** Caps how many updates run at once (default: unlimited). */
|
|
33
44
|
updateLimiter;
|
|
45
|
+
/** Per-update timeout in ms; `Infinity` disables. */
|
|
46
|
+
handlerTimeoutMs;
|
|
47
|
+
/** Context class instantiated per update (default `Context`). */
|
|
48
|
+
contextType;
|
|
34
49
|
/** Per-chat processing chains: parallel across chats, ordered within a chat. */
|
|
35
50
|
chatChains = new Map();
|
|
36
51
|
/** Memoized init so a burst of updates triggers exactly one getMe call. */
|
|
@@ -62,6 +77,8 @@ class Bot {
|
|
|
62
77
|
});
|
|
63
78
|
this.plugins = new plugin_js_1.PluginManager(this);
|
|
64
79
|
this.updateLimiter = new concurrency_js_1.Limiter(config.updates?.concurrency ?? Infinity);
|
|
80
|
+
this.handlerTimeoutMs = config.handlerTimeout ?? 90_000;
|
|
81
|
+
this.contextType = config.contextType ?? context_js_1.Context;
|
|
65
82
|
this.pollingOptions = {
|
|
66
83
|
allowedUpdates: config.polling?.allowedUpdates ?? [],
|
|
67
84
|
limit: config.polling?.limit ?? 100,
|
|
@@ -155,7 +172,7 @@ class Bot {
|
|
|
155
172
|
throw error;
|
|
156
173
|
}
|
|
157
174
|
}
|
|
158
|
-
async start() { await this.launch({ mode: "polling" }); }
|
|
175
|
+
async start(options = {}) { await this.launch({ mode: "polling", ...options }); }
|
|
159
176
|
async launch(options = { mode: "polling" }) {
|
|
160
177
|
if (options.mode !== "polling")
|
|
161
178
|
throw new Error("Use createWebhookHandler() for webhook mode.");
|
|
@@ -180,6 +197,11 @@ class Bot {
|
|
|
180
197
|
}
|
|
181
198
|
if (this.statusValue === "running")
|
|
182
199
|
return;
|
|
200
|
+
if (options.dropPendingUpdates) {
|
|
201
|
+
// Same mechanism Telegraf uses: drop everything Telegram is holding for
|
|
202
|
+
// this bot before the first getUpdates call.
|
|
203
|
+
await this.api.call("deleteWebhook", { drop_pending_updates: true });
|
|
204
|
+
}
|
|
183
205
|
this.statusValue = "starting";
|
|
184
206
|
this.startupLog("bot.starting", { mode: options.mode });
|
|
185
207
|
await this.events.emit("bot:starting", { bot: this });
|
|
@@ -224,18 +246,60 @@ class Bot {
|
|
|
224
246
|
* updates for the same chat are processed strictly in arrival order so
|
|
225
247
|
* sessions, wizards, and conversations never interleave. Rejects for this
|
|
226
248
|
* update's failure (as before) without affecting other updates.
|
|
249
|
+
*
|
|
250
|
+
* `options.webhookReply` installs a Telegraf-style responder: the first
|
|
251
|
+
* outgoing API call during this update is answered through the webhook HTTP
|
|
252
|
+
* response instead of a separate request, and resolves with `true` because
|
|
253
|
+
* Telegram never sends the method result back to a webhook response.
|
|
227
254
|
*/
|
|
228
|
-
async handleUpdate(update) {
|
|
255
|
+
async handleUpdate(update, options = {}) {
|
|
229
256
|
const key = this.conversationKey(update);
|
|
230
257
|
const previous = this.chatChains.get(key);
|
|
231
|
-
const
|
|
258
|
+
const execute = options.webhookReply === undefined
|
|
259
|
+
? () => this.processUpdate(update)
|
|
260
|
+
: () => (0, webhook_reply_js_1.runWithWebhookReply)(options.webhookReply, () => this.processUpdate(update));
|
|
261
|
+
// The chain waits for the real completion so same-chat ordering holds
|
|
262
|
+
// even when the caller-facing await below is released by a timeout.
|
|
263
|
+
const run = (previous ?? Promise.resolve()).catch(() => undefined).then(execute);
|
|
232
264
|
const tail = run.then(() => undefined, () => undefined);
|
|
233
265
|
this.chatChains.set(key, tail);
|
|
234
266
|
void tail.then(() => {
|
|
235
267
|
if (this.chatChains.get(key) === tail)
|
|
236
268
|
this.chatChains.delete(key);
|
|
237
269
|
});
|
|
238
|
-
|
|
270
|
+
try {
|
|
271
|
+
await this.withTimeout(run, this.handlerTimeoutMs, update.update_id);
|
|
272
|
+
}
|
|
273
|
+
catch (error) {
|
|
274
|
+
if (!(error instanceof UpdateTimeoutError))
|
|
275
|
+
throw error;
|
|
276
|
+
// A timed-out update follows the same error flow as a failed handler;
|
|
277
|
+
// the handler itself keeps running to completion in the background.
|
|
278
|
+
this.logger.error("update.handler_timeout", { updateId: update.update_id, timeoutMs: this.handlerTimeoutMs });
|
|
279
|
+
await this.events.emit("update:error", { update, error });
|
|
280
|
+
await this.events.emit("bot:error", { bot: this, error });
|
|
281
|
+
if (this.errorHandler) {
|
|
282
|
+
await this.errorHandler(error, new context_js_1.Context({ update, api: this.api, session: {}, services: this.services, me: this.me }));
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
throw error;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
/** Rejects with `UpdateTimeoutError` after `timeoutMs` unless `promise` settles first. */
|
|
289
|
+
async withTimeout(promise, timeoutMs, updateId) {
|
|
290
|
+
if (!Number.isFinite(timeoutMs))
|
|
291
|
+
return promise;
|
|
292
|
+
let timer;
|
|
293
|
+
const timeout = new Promise((_, reject) => {
|
|
294
|
+
timer = setTimeout(() => reject(new UpdateTimeoutError(updateId, timeoutMs)), timeoutMs);
|
|
295
|
+
});
|
|
296
|
+
try {
|
|
297
|
+
return await Promise.race([promise, timeout]);
|
|
298
|
+
}
|
|
299
|
+
finally {
|
|
300
|
+
if (timer !== undefined)
|
|
301
|
+
clearTimeout(timer);
|
|
302
|
+
}
|
|
239
303
|
}
|
|
240
304
|
/**
|
|
241
305
|
* Handles a whole batch of updates at once: every chat in the batch is
|
|
@@ -265,12 +329,12 @@ class Bot {
|
|
|
265
329
|
async broadcast(chatIds, send, options) {
|
|
266
330
|
return (0, broadcast_js_1.runBroadcast)(chatIds, send, options);
|
|
267
331
|
}
|
|
268
|
-
/** Runs init() once even when many updates arrive concurrently. */
|
|
332
|
+
/** Runs init() once even when many updates arrive concurrently; never claims a webhook reply slot. */
|
|
269
333
|
ensureInitialized() {
|
|
270
334
|
if (this.me)
|
|
271
335
|
return Promise.resolve();
|
|
272
336
|
if (!this.initOnce) {
|
|
273
|
-
this.initOnce = this.init().then(() => { this.initOnce = undefined; }, (error) => {
|
|
337
|
+
this.initOnce = (0, webhook_reply_js_1.runWithoutWebhookReply)(() => this.init()).then(() => { this.initOnce = undefined; }, (error) => {
|
|
274
338
|
this.initOnce = undefined;
|
|
275
339
|
throw error;
|
|
276
340
|
});
|
|
@@ -296,7 +360,7 @@ class Bot {
|
|
|
296
360
|
await this.ensureInitialized();
|
|
297
361
|
if (!this.me)
|
|
298
362
|
return;
|
|
299
|
-
const ctx = new
|
|
363
|
+
const ctx = new this.contextType({ update, api: this.api, session, services: this.services, me: this.me });
|
|
300
364
|
await this.events.emit("update", { update });
|
|
301
365
|
if (message) {
|
|
302
366
|
await this.events.emit("message", { message });
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.runWithWebhookReply = runWithWebhookReply;
|
|
4
|
+
exports.runWithoutWebhookReply = runWithoutWebhookReply;
|
|
5
|
+
exports.claimWebhookReply = claimWebhookReply;
|
|
6
|
+
exports.hasWebhookReply = hasWebhookReply;
|
|
7
|
+
const node_async_hooks_1 = require("node:async_hooks");
|
|
8
|
+
const storage = new node_async_hooks_1.AsyncLocalStorage();
|
|
9
|
+
/** Runs `fn` with a webhook reply sink active for every API call inside it. */
|
|
10
|
+
function runWithWebhookReply(sink, fn) {
|
|
11
|
+
return storage.run({ sink, claimed: false, suppressed: false }, fn);
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Runs `fn` with webhook replies suppressed (library-internal calls such as
|
|
15
|
+
* the lazy `getMe` initialization must never claim the response slot).
|
|
16
|
+
*/
|
|
17
|
+
function runWithoutWebhookReply(fn) {
|
|
18
|
+
const state = storage.getStore();
|
|
19
|
+
if (!state)
|
|
20
|
+
return fn();
|
|
21
|
+
return storage.run({ ...state, suppressed: true }, fn);
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Claims the webhook reply for `method`/`payload` if a sink is active and not
|
|
25
|
+
* yet used. Returns the synthesized transport response the caller should
|
|
26
|
+
* resolve with, or `undefined` when the call must go through the transport.
|
|
27
|
+
*/
|
|
28
|
+
function claimWebhookReply(method, payload) {
|
|
29
|
+
const state = storage.getStore();
|
|
30
|
+
if (!state || state.claimed || state.suppressed)
|
|
31
|
+
return undefined;
|
|
32
|
+
state.claimed = true;
|
|
33
|
+
state.sink({ method, ...(payload ?? {}) });
|
|
34
|
+
// Telegram never sends the method result back to the webhook response, so
|
|
35
|
+
// the caller resolves with a synthetic success (same as Telegraf).
|
|
36
|
+
return { status: 200, data: { ok: true, result: true } };
|
|
37
|
+
}
|
|
38
|
+
/** True while an unclaimed webhook reply is available (diagnostics/testing). */
|
|
39
|
+
function hasWebhookReply() {
|
|
40
|
+
const state = storage.getStore();
|
|
41
|
+
return state !== undefined && !state.claimed;
|
|
42
|
+
}
|
package/dist-cjs/src/index.js
CHANGED
|
@@ -16,6 +16,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
17
|
exports.printTerminalBranding = exports.buildTerminalBranding = void 0;
|
|
18
18
|
__exportStar(require("./core/bot.js"), exports);
|
|
19
|
+
__exportStar(require("./core/webhook-reply.js"), exports);
|
|
19
20
|
__exportStar(require("./core/events.js"), exports);
|
|
20
21
|
__exportStar(require("./api/index.js"), exports);
|
|
21
22
|
__exportStar(require("./context/context.js"), exports);
|
|
@@ -3,6 +3,19 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.createWebhookHandler = createWebhookHandler;
|
|
4
4
|
exports.webhookCallback = webhookCallback;
|
|
5
5
|
const node_crypto_1 = require("node:crypto");
|
|
6
|
+
/** Runs the update, optionally claiming the webhook response for the first API call. */
|
|
7
|
+
async function processWithOptionalReply(bot, update, sink) {
|
|
8
|
+
if (sink === undefined) {
|
|
9
|
+
await bot.handleUpdate(update);
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
await bot.handleUpdate(update, { webhookReply: sink });
|
|
13
|
+
}
|
|
14
|
+
function replyResponse(payload) {
|
|
15
|
+
if (payload !== undefined)
|
|
16
|
+
return new Response(JSON.stringify(payload), { status: 200, headers: { "content-type": "application/json" } });
|
|
17
|
+
return new Response("OK", { status: 200 });
|
|
18
|
+
}
|
|
6
19
|
function createWebhookHandler(bot, options = {}) {
|
|
7
20
|
const maxBodyBytes = options.maxBodyBytes ?? 1_048_576;
|
|
8
21
|
return async (request) => {
|
|
@@ -23,8 +36,10 @@ function createWebhookHandler(bot, options = {}) {
|
|
|
23
36
|
const update = JSON.parse(new TextDecoder().decode(raw));
|
|
24
37
|
if (!Number.isInteger(update.update_id))
|
|
25
38
|
return new Response("Bad Request", { status: 400 });
|
|
26
|
-
|
|
27
|
-
|
|
39
|
+
let replyPayload;
|
|
40
|
+
const sink = options.webhookReply === true ? (payload) => { replyPayload = payload; } : undefined;
|
|
41
|
+
await processWithOptionalReply(bot, update, sink);
|
|
42
|
+
return replyResponse(replyPayload);
|
|
28
43
|
}
|
|
29
44
|
catch (error) {
|
|
30
45
|
await options.onError?.(error);
|
|
@@ -37,9 +52,9 @@ function webhookCallback(bot, _framework = "express", options = {}) {
|
|
|
37
52
|
return async (req, res) => {
|
|
38
53
|
const rawReq = req;
|
|
39
54
|
const rawRes = res;
|
|
40
|
-
const sendResponse = (status, message) => {
|
|
55
|
+
const sendResponse = (status, message, contentType = "text/plain") => {
|
|
41
56
|
if (rawRes && typeof rawRes.writeHead === "function" && typeof rawRes.end === "function") {
|
|
42
|
-
rawRes.writeHead(status, { "Content-Type":
|
|
57
|
+
rawRes.writeHead(status, { "Content-Type": contentType });
|
|
43
58
|
rawRes.end(message);
|
|
44
59
|
return;
|
|
45
60
|
}
|
|
@@ -99,8 +114,13 @@ function webhookCallback(bot, _framework = "express", options = {}) {
|
|
|
99
114
|
sendResponse(400, "Bad Request");
|
|
100
115
|
return;
|
|
101
116
|
}
|
|
102
|
-
|
|
103
|
-
|
|
117
|
+
let replyPayload;
|
|
118
|
+
const sink = options.webhookReply === true ? (payload) => { replyPayload = payload; } : undefined;
|
|
119
|
+
await processWithOptionalReply(bot, update, sink);
|
|
120
|
+
if (replyPayload !== undefined)
|
|
121
|
+
sendResponse(200, JSON.stringify(replyPayload), "application/json");
|
|
122
|
+
else
|
|
123
|
+
sendResponse(200, "OK");
|
|
104
124
|
}
|
|
105
125
|
catch (error) {
|
|
106
126
|
await options.onError?.(error);
|
package/docs/API.id.md
CHANGED
|
@@ -75,6 +75,8 @@ type BotStatus =
|
|
|
75
75
|
| `polling.retryDelayMs` | `number` | `500` | Delay awal ketika polling gagal. |
|
|
76
76
|
| `polling.maxRetryDelayMs` | `number` | `30000` | Batas maksimum delay reconnect. |
|
|
77
77
|
| `updates.concurrency` | `number` | `Infinity` | Batas jumlah update yang diproses bersamaan. Update selalu berjalan paralel antar chat dan tetap berurutan di dalam satu chat, sehingga burst 1000+ pesan tertangani sekaligus. |
|
|
78
|
+
| `handlerTimeout` | `number` | `90000` | Timeout pemrosesan per update dalam ms (`Infinity` untuk menonaktifkan). Saat timeout, alur error update berjalan (`update:error`, `bot:error`, boundary `catch()`) dan `handleUpdate()` melempar `UpdateTimeoutError`, sementara handler tetap berjalan sampai selesai di background. |
|
|
79
|
+
| `contextType` | `new (options: ContextOptions<S>) => Context<S>` | `Context` | Subclass `Context` kustom yang diinstansiasi untuk setiap update (`contextType` milik Telegraf). |
|
|
78
80
|
|
|
79
81
|
### Konstruktor `Bot`
|
|
80
82
|
|
|
@@ -199,10 +201,11 @@ launch(options?: {
|
|
|
199
201
|
mode: "polling";
|
|
200
202
|
timeout?: number;
|
|
201
203
|
allowedUpdates?: string[];
|
|
204
|
+
dropPendingUpdates?: boolean;
|
|
202
205
|
}): Promise<void>
|
|
203
206
|
```
|
|
204
207
|
|
|
205
|
-
Menjalankan bot dalam mode polling. Saat mulai, lifecycle berpindah melalui `starting` lalu `running`, kemudian loop `getUpdates()` memproses setiap batch update secara konkuren: update dari chat berbeda berjalan paralel, sedangkan update dari chat yang sama menjaga urutan kedatangannya. Kegagalan polling memancarkan `polling:reconnect` dan menggunakan backoff eksponensial.
|
|
208
|
+
Menjalankan bot dalam mode polling. Saat mulai, lifecycle berpindah melalui `starting` lalu `running`, kemudian loop `getUpdates()` memproses setiap batch update secara konkuren: update dari chat berbeda berjalan paralel, sedangkan update dari chat yang sama menjaga urutan kedatangannya. Kegagalan polling memancarkan `polling:reconnect` dan menggunakan backoff eksponensial. `dropPendingUpdates: true` (juga tersedia di `bot.start()`) membuang semua update yang ditahan Telegram sebelum panggilan `getUpdates` pertama, memakai mekanisme `deleteWebhook({ drop_pending_updates: true })` yang sama dengan Telegraf.
|
|
206
209
|
|
|
207
210
|
Mode selain `"polling"` melempar error dan menyarankan penggunaan `createWebhookHandler()` untuk webhook.
|
|
208
211
|
|
|
@@ -274,12 +277,14 @@ Jalan pintas ke `deleteMyCommands`.
|
|
|
274
277
|
### `bot.handleUpdate(update)`
|
|
275
278
|
|
|
276
279
|
```ts
|
|
277
|
-
handleUpdate(update: Update): Promise<void>
|
|
280
|
+
handleUpdate(update: Update, options?: { webhookReply?: WebhookReplySink }): Promise<void>
|
|
278
281
|
```
|
|
279
282
|
|
|
280
|
-
Memproses satu update secara manual. Method menentukan kunci session dari `chat.id` dan `from.id`, membuat `Context
|
|
283
|
+
Memproses satu update secara manual. Method menentukan kunci session dari `chat.id` dan `from.id`, membuat `Context` (dari `contextType` yang dikonfigurasi), memancarkan event `update` dan `message`, menjalankan middleware lalu router, dan menyimpan session setelah pipeline selesai.
|
|
281
284
|
|
|
282
|
-
Update dari chat berbeda diproses paralel; update dari chat yang sama diserialisasi sesuai urutan kedatangan, sehingga session, wizard, dan conversation tidak pernah saling tumpang tindih dan penulisan session tidak pernah hilang. Burst update konkuren hanya memicu satu inisialisasi `getMe`.
|
|
285
|
+
Update dari chat berbeda diproses paralel; update dari chat yang sama diserialisasi sesuai urutan kedatangan, sehingga session, wizard, dan conversation tidak pernah saling tumpang tindih dan penulisan session tidak pernah hilang. Burst update konkuren hanya memicu satu inisialisasi `getMe`. Seluruh proses per update dijaga `handlerTimeout` (default 90 detik, sama dengan Telegraf): saat timeout, error mengalir lewat `update:error`/`bot:error` dan boundary `catch()`, dan `handleUpdate()` melempar `UpdateTimeoutError` sementara handler tetap berjalan di background.
|
|
286
|
+
|
|
287
|
+
`options.webhookReply` memasang responder ala Telegraf: panggilan API keluar pertama selama update ini dijawab lewat respons HTTP webhook, bukan request terpisah, dan resolve dengan `true` (Telegram tidak pernah mengirim hasil method kembali ke respons webhook).
|
|
283
288
|
|
|
284
289
|
Error pipeline mengubah status bot menjadi `error`, memancarkan `bot:error`, lalu dilempar kembali.
|
|
285
290
|
|
|
@@ -327,6 +332,25 @@ for (const failure of report.failures) console.warn(`Gagal: ${failure.chatId}
|
|
|
327
332
|
| `BroadcastReport.durationMs` | `number` | — | Durasi total sesi broadcast. |
|
|
328
333
|
| `BroadcastReport.failures` | `BroadcastFailure[]` | — | Catatan per chat `{ chatId, attempts, error, errorKind }`. |
|
|
329
334
|
|
|
335
|
+
### `UpdateTimeoutError` dan helper webhook-reply
|
|
336
|
+
|
|
337
|
+
```ts
|
|
338
|
+
class UpdateTimeoutError extends Error {
|
|
339
|
+
readonly name = "UpdateTimeoutError";
|
|
340
|
+
readonly updateId: number;
|
|
341
|
+
}
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
Dilempar oleh `handleUpdate()` ketika satu update melebihi `handlerTimeout`. Handler itu sendiri tetap berjalan; error juga mengalir lewat `update:error`, `bot:error`, dan boundary `catch()`.
|
|
345
|
+
|
|
346
|
+
```ts
|
|
347
|
+
type WebhookReplySink = (payload: Record<string, unknown>) => void;
|
|
348
|
+
runWithWebhookReply(sink, fn): Promise<T> // memasang responder untuk semua panggilan API di dalam fn
|
|
349
|
+
runWithoutWebhookReply(fn): Promise<T> // panggilan internal library yang tidak pernah mengklaim slot
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
Diekspor agar webhook server kustom bisa memasang webhook reply dengan cara yang sama seperti `createWebhookHandler`.
|
|
353
|
+
|
|
330
354
|
### Contoh bot minimal
|
|
331
355
|
|
|
332
356
|
```ts
|
|
@@ -736,6 +760,23 @@ new Context<S>(options: ContextOptions<S>): Context<S>
|
|
|
736
760
|
|
|
737
761
|
Semua pengirim `replyWith*` menerima parameter native Telegram sebagai `extra` dan otomatis me-quote message yang masuk. `reply_parameters` pada `extra` digabung dengan `message_id` otomatis, bukan menggantikannya. `reply`, `send`, `getChat`, dan beberapa helper lain melempar error ketika update tidak memiliki chat yang diperlukan. `edit` dan `delete` membutuhkan chat serta message.
|
|
738
762
|
|
|
763
|
+
### Method admin, chat, dan forum pada Context (paritas penuh Telegraf)
|
|
764
|
+
|
|
765
|
+
Semua method di bawah beraksi pada chat update (`ctx.chat`) dan menerima parameter native Telegram lewat `extra`; semuanya melempar error jelas bila update tidak memiliki chat. Gunakan `ctx.api.methods.*` untuk menargetkan chat lain.
|
|
766
|
+
|
|
767
|
+
| Grup | Method |
|
|
768
|
+
|---|---|
|
|
769
|
+
| Moderasi | `banChatMember(userId, untilDate?, extra?)`, `unbanChatMember(userId, onlyIfBanned?, extra?)`, `restrictChatMember(userId, permissions, untilDate?, extra?)`, `promoteChatMember(userId, extra?)`, `banChatSenderChat(senderChatId, extra?)`, `unbanChatSenderChat(senderChatId, extra?)` |
|
|
770
|
+
| Manajemen chat | `setChatTitle(title)`, `setChatDescription(description?)`, `setChatPhoto(photo)`, `deleteChatPhoto()`, `setChatPermissions(permissions, extra?)`, `leaveChat()`, `unpinAllChatMessages(extra?)`, `setChatStickerSet(name)`, `deleteChatStickerSet()` |
|
|
771
|
+
| Info chat & member | `getChatAdministrators(): Promise<ChatMember[]>`, `getChatMemberCount(): Promise<number>`, `getChatMember(userId): Promise<ChatMember>` |
|
|
772
|
+
| Invite link | `exportChatInviteLink(): Promise<string>`, `createChatInviteLink(extra?)`, `editChatInviteLink(inviteLink, extra?)`, `revokeChatInviteLink(inviteLink)` |
|
|
773
|
+
| Join request | `approveChatJoinRequest(userId)`, `declineChatJoinRequest(userId)` |
|
|
774
|
+
| Poll & live location | `replyWithQuiz(question, options, extra?)` (sendPoll dengan `type: "quiz"`), `stopPoll(messageId?, extra?)`, `editMessageLiveLocation(latitude?, longitude?, extra?)`, `stopMessageLiveLocation(extra?)` |
|
|
775
|
+
| Game & pembayaran | `replyWithGame(gameShortName, extra?)`, `setGameScore(userId, score, extra?)`, `getGameHighScores(userId?, extra?)`, `replyWithInvoice(title, description, payload, providerToken, currency, prices, extra?)` |
|
|
776
|
+
| Forum topic | `createForumTopic(name, extra?)`, `editForumTopic(extra?)`, `closeForumTopic(threadId?)`, `reopenForumTopic(threadId?)`, `deleteForumTopic(threadId?)`, `unpinAllForumTopicMessages(threadId?)`, `getForumTopicIconStickers()`, `editGeneralForumTopic(name)`, `closeGeneralForumTopic()`, `reopenGeneralForumTopic()`, `hideGeneralForumTopic()`, `unhideGeneralForumTopic()` |
|
|
777
|
+
|
|
778
|
+
`threadId` default ke `message_thread_id` message context. `replyWithQuiz`, `replyWithGame`, dan `replyWithInvoice` me-quote message masuk seperti semua pengirim `replyWith*`.
|
|
779
|
+
|
|
739
780
|
---
|
|
740
781
|
|
|
741
782
|
## 5. Middleware dan router
|
|
@@ -1131,9 +1172,12 @@ interface WebhookOptions {
|
|
|
1131
1172
|
secretToken?: string;
|
|
1132
1173
|
maxBodyBytes?: number;
|
|
1133
1174
|
onError?: (error: unknown) => void | Promise<void>;
|
|
1175
|
+
webhookReply?: boolean;
|
|
1134
1176
|
}
|
|
1135
1177
|
```
|
|
1136
1178
|
|
|
1179
|
+
`webhookReply` (default `false`) mengaktifkan webhook reply ala Telegraf: saat memproses update, panggilan API keluar pertama dijawab lewat respons HTTP webhook itu sendiri (`{"method":"sendMessage", ...}`), sehingga Telegram mengeksekusi method tanpa request kedua. Panggilan itu resolve dengan `true` karena Telegram tidak pernah mengirim hasil method kembali ke respons webhook; setiap panggilan berikutnya tetap lewat transport seperti biasa. Inisialisasi `getMe` malas tidak pernah mengklaim slot tersebut. Berbeda dengan Telegraf, fitur ini opt-in agar deployment webhook yang sudah ada mempertahankan perilakunya persis.
|
|
1180
|
+
|
|
1137
1181
|
### `createWebhookHandler(bot, options?)`
|
|
1138
1182
|
|
|
1139
1183
|
```ts
|
package/docs/API.md
CHANGED
|
@@ -74,6 +74,8 @@ type BotStatus =
|
|
|
74
74
|
| `polling.retryDelayMs` | `number` | `500` | Initial delay when polling fails. |
|
|
75
75
|
| `polling.maxRetryDelayMs` | `number` | `30000` | Maximum reconnect delay. |
|
|
76
76
|
| `updates.concurrency` | `number` | `Infinity` | Cap on how many updates are processed at the same time. Updates always run in parallel across chats and stay ordered within a single chat, so bursts of 1000+ messages are handled at once. |
|
|
77
|
+
| `handlerTimeout` | `number` | `90000` | Per-update processing timeout in ms (`Infinity` disables). On timeout the update error flow runs (`update:error`, `bot:error`, `catch()` boundary) and `handleUpdate()` rejects with `UpdateTimeoutError`, while the handler keeps running to completion in the background. |
|
|
78
|
+
| `contextType` | `new (options: ContextOptions<S>) => Context<S>` | `Context` | Custom `Context` subclass instantiated for every update (Telegraf's `contextType`). |
|
|
77
79
|
|
|
78
80
|
### Constructor `Bot`
|
|
79
81
|
|
|
@@ -216,10 +218,11 @@ launch(options?: {
|
|
|
216
218
|
mode: "polling";
|
|
217
219
|
timeout?: number;
|
|
218
220
|
allowedUpdates?: string[];
|
|
221
|
+
dropPendingUpdates?: boolean;
|
|
219
222
|
}): Promise<void>
|
|
220
223
|
```
|
|
221
224
|
|
|
222
|
-
Runs the bot in polling mode. On start, the lifecycle moves through `starting` to `running`, then the `getUpdates()` loop processes each batch of updates concurrently: updates for different chats run in parallel while updates for the same chat keep their arrival order. Polling failures emit `polling:reconnect` and use exponential backoff.
|
|
225
|
+
Runs the bot in polling mode. On start, the lifecycle moves through `starting` to `running`, then the `getUpdates()` loop processes each batch of updates concurrently: updates for different chats run in parallel while updates for the same chat keep their arrival order. Polling failures emit `polling:reconnect` and use exponential backoff. `dropPendingUpdates: true` (also on `bot.start()`) drops everything Telegram is holding for the bot before the first `getUpdates` call, using the same `deleteWebhook({ drop_pending_updates: true })` mechanism Telegraf uses.
|
|
223
226
|
|
|
224
227
|
Modes other than `"polling"` throw an error and suggest using `createWebhookHandler()` for webhooks.
|
|
225
228
|
|
|
@@ -291,12 +294,14 @@ Shortcut to `deleteMyCommands`.
|
|
|
291
294
|
### `bot.handleUpdate(update)`
|
|
292
295
|
|
|
293
296
|
```ts
|
|
294
|
-
handleUpdate(update: Update): Promise<void>
|
|
297
|
+
handleUpdate(update: Update, options?: { webhookReply?: WebhookReplySink }): Promise<void>
|
|
295
298
|
```
|
|
296
299
|
|
|
297
|
-
Processes a single update manually. The method determines the session key from `chat.id` and `from.id`, creates a `Context
|
|
300
|
+
Processes a single update manually. The method determines the session key from `chat.id` and `from.id`, creates a `Context` (of the configured `contextType`), emits `update` and `message` events, runs middleware then the router, and saves the session after the pipeline completes.
|
|
298
301
|
|
|
299
|
-
Updates for different chats are processed in parallel; updates for the same chat are serialized in arrival order, so sessions, wizards, and conversations never interleave and session writes are never lost. A burst of concurrent updates triggers exactly one `getMe` initialization.
|
|
302
|
+
Updates for different chats are processed in parallel; updates for the same chat are serialized in arrival order, so sessions, wizards, and conversations never interleave and session writes are never lost. A burst of concurrent updates triggers exactly one `getMe` initialization. The whole per-update run is guarded by `handlerTimeout` (default 90s, matching Telegraf): on timeout the error flows through `update:error`/`bot:error` and the `catch()` boundary, and `handleUpdate()` rejects with `UpdateTimeoutError` while the handler keeps running in the background.
|
|
303
|
+
|
|
304
|
+
`options.webhookReply` installs a Telegraf-style responder: the first outgoing API call during this update is answered through the webhook HTTP response instead of a separate request, and resolves with `true` (Telegram never sends the method result back to a webhook response).
|
|
300
305
|
|
|
301
306
|
Pipeline errors set the bot status to `error`, emit `bot:error`, and then rethrow the error.
|
|
302
307
|
|
|
@@ -344,6 +349,25 @@ for (const failure of report.failures) console.warn(`Failed: ${failure.chatId}
|
|
|
344
349
|
| `BroadcastReport.durationMs` | `number` | — | Wall-clock duration of the run. |
|
|
345
350
|
| `BroadcastReport.failures` | `BroadcastFailure[]` | — | Per-chat `{ chatId, attempts, error, errorKind }` records. |
|
|
346
351
|
|
|
352
|
+
### `UpdateTimeoutError` and webhook-reply helpers
|
|
353
|
+
|
|
354
|
+
```ts
|
|
355
|
+
class UpdateTimeoutError extends Error {
|
|
356
|
+
readonly name = "UpdateTimeoutError";
|
|
357
|
+
readonly updateId: number;
|
|
358
|
+
}
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
Rejected by `handleUpdate()` when a single update exceeds `handlerTimeout`. The handler itself keeps running; the error also flows through `update:error`, `bot:error`, and the `catch()` boundary.
|
|
362
|
+
|
|
363
|
+
```ts
|
|
364
|
+
type WebhookReplySink = (payload: Record<string, unknown>) => void;
|
|
365
|
+
runWithWebhookReply(sink, fn): Promise<T> // sets the responder for every API call inside fn
|
|
366
|
+
runWithoutWebhookReply(fn): Promise<T> // library-internal calls that never claim the slot
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
Exported so custom webhook servers can wire webhook replies the same way `createWebhookHandler` does.
|
|
370
|
+
|
|
347
371
|
### Minimal bot example
|
|
348
372
|
|
|
349
373
|
```ts
|
|
@@ -754,6 +778,23 @@ new Context<S>(options: ContextOptions<S>): Context<S>
|
|
|
754
778
|
|
|
755
779
|
All `replyWith*` senders accept the native Telegram parameters as `extra` and automatically quote the incoming message. Passing `reply_parameters` in `extra` merges with the automatic `message_id` instead of replacing it. `reply`, `send`, `getChat`, and some other helpers throw an error when the update does not have the required chat. `edit` and `delete` require both chat and message.
|
|
756
780
|
|
|
781
|
+
### Context admin, chat, and forum methods (full Telegraf parity)
|
|
782
|
+
|
|
783
|
+
Every method below acts on the update's chat (`ctx.chat`) and accepts native Telegram parameters through `extra`; they throw a clear error when the update has no chat. Use `ctx.api.methods.*` to target a different chat.
|
|
784
|
+
|
|
785
|
+
| Group | Methods |
|
|
786
|
+
|---|---|
|
|
787
|
+
| Moderation | `banChatMember(userId, untilDate?, extra?)`, `unbanChatMember(userId, onlyIfBanned?, extra?)`, `restrictChatMember(userId, permissions, untilDate?, extra?)`, `promoteChatMember(userId, extra?)`, `banChatSenderChat(senderChatId, extra?)`, `unbanChatSenderChat(senderChatId, extra?)` |
|
|
788
|
+
| Chat management | `setChatTitle(title)`, `setChatDescription(description?)`, `setChatPhoto(photo)`, `deleteChatPhoto()`, `setChatPermissions(permissions, extra?)`, `leaveChat()`, `unpinAllChatMessages(extra?)`, `setChatStickerSet(name)`, `deleteChatStickerSet()` |
|
|
789
|
+
| Chat & member info | `getChatAdministrators(): Promise<ChatMember[]>`, `getChatMemberCount(): Promise<number>`, `getChatMember(userId): Promise<ChatMember>` |
|
|
790
|
+
| Invite links | `exportChatInviteLink(): Promise<string>`, `createChatInviteLink(extra?)`, `editChatInviteLink(inviteLink, extra?)`, `revokeChatInviteLink(inviteLink)` |
|
|
791
|
+
| Join requests | `approveChatJoinRequest(userId)`, `declineChatJoinRequest(userId)` |
|
|
792
|
+
| Polls & live location | `replyWithQuiz(question, options, extra?)` (sendPoll with `type: "quiz"`), `stopPoll(messageId?, extra?)`, `editMessageLiveLocation(latitude?, longitude?, extra?)`, `stopMessageLiveLocation(extra?)` |
|
|
793
|
+
| Games & payments | `replyWithGame(gameShortName, extra?)`, `setGameScore(userId, score, extra?)`, `getGameHighScores(userId?, extra?)`, `replyWithInvoice(title, description, payload, providerToken, currency, prices, extra?)` |
|
|
794
|
+
| Forum topics | `createForumTopic(name, extra?)`, `editForumTopic(extra?)`, `closeForumTopic(threadId?)`, `reopenForumTopic(threadId?)`, `deleteForumTopic(threadId?)`, `unpinAllForumTopicMessages(threadId?)`, `getForumTopicIconStickers()`, `editGeneralForumTopic(name)`, `closeGeneralForumTopic()`, `reopenGeneralForumTopic()`, `hideGeneralForumTopic()`, `unhideGeneralForumTopic()` |
|
|
795
|
+
|
|
796
|
+
`threadId` defaults to the context message's `message_thread_id`. `replyWithQuiz`, `replyWithGame`, and `replyWithInvoice` quote the incoming message like every `replyWith*` sender.
|
|
797
|
+
|
|
757
798
|
---
|
|
758
799
|
|
|
759
800
|
## 5. Middleware and router
|
|
@@ -1149,9 +1190,12 @@ interface WebhookOptions {
|
|
|
1149
1190
|
secretToken?: string;
|
|
1150
1191
|
maxBodyBytes?: number;
|
|
1151
1192
|
onError?: (error: unknown) => void | Promise<void>;
|
|
1193
|
+
webhookReply?: boolean;
|
|
1152
1194
|
}
|
|
1153
1195
|
```
|
|
1154
1196
|
|
|
1197
|
+
`webhookReply` (default `false`) enables Telegraf-style webhook replies: while handling an update, the first outgoing API call is answered through the webhook HTTP response itself (`{"method":"sendMessage", ...}`), so Telegram executes the method without a second request. That call resolves with `true` because Telegram never sends the method result back to a webhook response; every later call goes through the transport as usual. The lazy `getMe` initialization never claims the slot. Unlike Telegraf, this is opt-in so existing webhook deployments keep their exact behavior.
|
|
1198
|
+
|
|
1155
1199
|
### `createWebhookHandler(bot, options?)`
|
|
1156
1200
|
|
|
1157
1201
|
```ts
|
package/docs/API.zh-CN.md
CHANGED
|
@@ -75,6 +75,8 @@ type BotStatus =
|
|
|
75
75
|
| `polling.retryDelayMs` | `number` | `500` | 轮询失败时的初始延迟(毫秒)。 |
|
|
76
76
|
| `polling.maxRetryDelayMs` | `number` | `30000` | 重连延迟的最大值(毫秒)。 |
|
|
77
77
|
| `updates.concurrency` | `number` | `Infinity` | 同时处理的 update 数量上限。不同 chat 的 update 始终并行,同一 chat 内保持顺序,因此 1000+ 条消息的突发可一次性处理。 |
|
|
78
|
+
| `handlerTimeout` | `number` | `90000` | 单个 update 的处理超时(毫秒,`Infinity` 表示禁用)。超时后走 update 错误流程(`update:error`、`bot:error`、`catch()` 边界),`handleUpdate()` 以 `UpdateTimeoutError` 拒绝,而 handler 仍在后台继续运行直至完成。 |
|
|
79
|
+
| `contextType` | `new (options: ContextOptions<S>) => Context<S>` | `Context` | 为每个 update 实例化的自定义 `Context` 子类(Telegraf 的 `contextType`)。 |
|
|
78
80
|
|
|
79
81
|
### `Bot` constructor
|
|
80
82
|
|
|
@@ -199,10 +201,11 @@ launch(options?: {
|
|
|
199
201
|
mode: "polling";
|
|
200
202
|
timeout?: number;
|
|
201
203
|
allowedUpdates?: string[];
|
|
204
|
+
dropPendingUpdates?: boolean;
|
|
202
205
|
}): Promise<void>
|
|
203
206
|
```
|
|
204
207
|
|
|
205
|
-
以 polling 模式运行 bot。启动时生命周期依次变为 `starting` 然后 `running`,之后 `getUpdates()` 循环并发处理每一批 update:不同 chat 的 update 并行执行,同一 chat 的 update 保持到达顺序。轮询失败会触发 `polling:reconnect`
|
|
208
|
+
以 polling 模式运行 bot。启动时生命周期依次变为 `starting` 然后 `running`,之后 `getUpdates()` 循环并发处理每一批 update:不同 chat 的 update 并行执行,同一 chat 的 update 保持到达顺序。轮询失败会触发 `polling:reconnect` 并使用指数退避。`dropPendingUpdates: true`(`bot.start()` 同样支持)会在第一次 `getUpdates` 之前丢弃 Telegram 为该 bot 持有的全部更新,使用与 Telegraf 相同的 `deleteWebhook({ drop_pending_updates: true })` 机制。
|
|
206
209
|
|
|
207
210
|
除 `"polling"` 外的模式会抛出错误,并建议对 webhook 使用 `createWebhookHandler()`。
|
|
208
211
|
|
|
@@ -274,12 +277,14 @@ deleteCommands(
|
|
|
274
277
|
### `bot.handleUpdate(update)`
|
|
275
278
|
|
|
276
279
|
```ts
|
|
277
|
-
handleUpdate(update: Update): Promise<void>
|
|
280
|
+
handleUpdate(update: Update, options?: { webhookReply?: WebhookReplySink }): Promise<void>
|
|
278
281
|
```
|
|
279
282
|
|
|
280
|
-
手动处理单个 update。该方法根据 `chat.id` 和 `from.id` 确定会话 key,创建 `Context
|
|
283
|
+
手动处理单个 update。该方法根据 `chat.id` 和 `from.id` 确定会话 key,创建 `Context`(由配置的 `contextType` 实例化),触发 `update` 和 `message` 事件,执行 middleware 然后路由器,并在流水线完成后保存会话。
|
|
281
284
|
|
|
282
|
-
不同 chat 的 update 并行处理;同一 chat 的 update 按到达顺序串行处理,因此会话、wizard 和 conversation 永远不会交错,会话写入也不会丢失。并发的 update 突发只会触发一次 `getMe`
|
|
285
|
+
不同 chat 的 update 并行处理;同一 chat 的 update 按到达顺序串行处理,因此会话、wizard 和 conversation 永远不会交错,会话写入也不会丢失。并发的 update 突发只会触发一次 `getMe` 初始化。整个单 update 流程受 `handlerTimeout` 保护(默认 90 秒,与 Telegraf 一致):超时后错误会流经 `update:error`/`bot:error` 和 `catch()` 边界,`handleUpdate()` 以 `UpdateTimeoutError` 拒绝,而 handler 仍在后台继续运行。
|
|
286
|
+
|
|
287
|
+
`options.webhookReply` 安装一个 Telegraf 风格的响应器:该 update 期间第一个外发 API 调用通过 webhook HTTP 响应本身来应答(而不是单独发请求),并且以 `true` resolve(Telegram 从不把方法结果发回 webhook 响应)。
|
|
283
288
|
|
|
284
289
|
流水线错误会将 bot 状态置为 `error`,触发 `bot:error`,然后重新抛出错误。
|
|
285
290
|
|
|
@@ -327,6 +332,25 @@ for (const failure of report.failures) console.warn(`Failed: ${failure.chatId}
|
|
|
327
332
|
| `BroadcastReport.durationMs` | `number` | — | 本次广播的实际耗时(毫秒)。 |
|
|
328
333
|
| `BroadcastReport.failures` | `BroadcastFailure[]` | — | 按 chat 记录的 `{ chatId, attempts, error, errorKind }`。 |
|
|
329
334
|
|
|
335
|
+
### `UpdateTimeoutError` 与 webhook-reply 辅助函数
|
|
336
|
+
|
|
337
|
+
```ts
|
|
338
|
+
class UpdateTimeoutError extends Error {
|
|
339
|
+
readonly name = "UpdateTimeoutError";
|
|
340
|
+
readonly updateId: number;
|
|
341
|
+
}
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
当单个 update 超过 `handlerTimeout` 时由 `handleUpdate()` 拒绝抛出。handler 本身继续运行;错误同样会流经 `update:error`、`bot:error` 和 `catch()` 边界。
|
|
345
|
+
|
|
346
|
+
```ts
|
|
347
|
+
type WebhookReplySink = (payload: Record<string, unknown>) => void;
|
|
348
|
+
runWithWebhookReply(sink, fn): Promise<T> // 为 fn 内的所有 API 调用设置响应器
|
|
349
|
+
runWithoutWebhookReply(fn): Promise<T> // 永不占用槽位的库内部调用
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
导出这些函数,让自定义 webhook 服务器能够以与 `createWebhookHandler` 相同的方式接入 webhook 应答。
|
|
353
|
+
|
|
330
354
|
### 最小 bot 示例
|
|
331
355
|
|
|
332
356
|
```ts
|
|
@@ -736,6 +760,23 @@ new Context<S>(options: ContextOptions<S>): Context<S>
|
|
|
736
760
|
|
|
737
761
|
`reply`、`send`、`getChat` 以及其他一些辅助方法在更新缺少所需聊天时会抛出错误。`edit` 和 `delete` 需要同时有聊天和消息。
|
|
738
762
|
|
|
763
|
+
### Context 管理员、聊天与论坛方法(与 Telegraf 完全对齐)
|
|
764
|
+
|
|
765
|
+
以下方法均作用于本次更新的聊天(`ctx.chat`),并通过 `extra` 接受原生 Telegram 参数;当更新没有聊天时都会抛出清晰的错误。要操作其他聊天请使用 `ctx.api.methods.*`。
|
|
766
|
+
|
|
767
|
+
| 分组 | 方法 |
|
|
768
|
+
|---|---|
|
|
769
|
+
| 管理/封禁 | `banChatMember(userId, untilDate?, extra?)`、`unbanChatMember(userId, onlyIfBanned?, extra?)`、`restrictChatMember(userId, permissions, untilDate?, extra?)`、`promoteChatMember(userId, extra?)`、`banChatSenderChat(senderChatId, extra?)`、`unbanChatSenderChat(senderChatId, extra?)` |
|
|
770
|
+
| 聊天管理 | `setChatTitle(title)`、`setChatDescription(description?)`、`setChatPhoto(photo)`、`deleteChatPhoto()`、`setChatPermissions(permissions, extra?)`、`leaveChat()`、`unpinAllChatMessages(extra?)`、`setChatStickerSet(name)`、`deleteChatStickerSet()` |
|
|
771
|
+
| 聊天与成员信息 | `getChatAdministrators(): Promise<ChatMember[]>`、`getChatMemberCount(): Promise<number>`、`getChatMember(userId): Promise<ChatMember>` |
|
|
772
|
+
| 邀请链接 | `exportChatInviteLink(): Promise<string>`、`createChatInviteLink(extra?)`、`editChatInviteLink(inviteLink, extra?)`、`revokeChatInviteLink(inviteLink)` |
|
|
773
|
+
| 加群申请 | `approveChatJoinRequest(userId)`、`declineChatJoinRequest(userId)` |
|
|
774
|
+
| 投票与实时位置 | `replyWithQuiz(question, options, extra?)`(`type: "quiz"` 的 sendPoll)、`stopPoll(messageId?, extra?)`、`editMessageLiveLocation(latitude?, longitude?, extra?)`、`stopMessageLiveLocation(extra?)` |
|
|
775
|
+
| 游戏与支付 | `replyWithGame(gameShortName, extra?)`、`setGameScore(userId, score, extra?)`、`getGameHighScores(userId?, extra?)`、`replyWithInvoice(title, description, payload, providerToken, currency, prices, extra?)` |
|
|
776
|
+
| 论坛主题 | `createForumTopic(name, extra?)`、`editForumTopic(extra?)`、`closeForumTopic(threadId?)`、`reopenForumTopic(threadId?)`、`deleteForumTopic(threadId?)`、`unpinAllForumTopicMessages(threadId?)`、`getForumTopicIconStickers()`、`editGeneralForumTopic(name)`、`closeGeneralForumTopic()`、`reopenGeneralForumTopic()`、`hideGeneralForumTopic()`、`unhideGeneralForumTopic()` |
|
|
777
|
+
|
|
778
|
+
`threadId` 默认取上下文消息的 `message_thread_id`。`replyWithQuiz`、`replyWithGame` 和 `replyWithInvoice` 与所有 `replyWith*` 发送者一样自动引用回复。
|
|
779
|
+
|
|
739
780
|
---
|
|
740
781
|
|
|
741
782
|
## 5. 中间件与路由器
|
|
@@ -1125,9 +1166,12 @@ interface WebhookOptions {
|
|
|
1125
1166
|
secretToken?: string;
|
|
1126
1167
|
maxBodyBytes?: number;
|
|
1127
1168
|
onError?: (error: unknown) => void | Promise<void>;
|
|
1169
|
+
webhookReply?: boolean;
|
|
1128
1170
|
}
|
|
1129
1171
|
```
|
|
1130
1172
|
|
|
1173
|
+
`webhookReply`(默认 `false`)启用 Telegraf 风格的 webhook 应答:处理 update 期间,第一个外发 API 调用直接通过 webhook HTTP 响应本身应答(`{"method":"sendMessage", ...}`),Telegram 因此无需第二次请求即可执行该方法。该调用以 `true` resolve,因为 Telegram 从不把方法结果发回 webhook 响应;之后的每个调用都照常走 transport。懒加载的 `getMe` 初始化永远不会占用该槽位。与 Telegraf 不同,此功能为 opt-in,已有的 webhook 部署行为保持完全不变。
|
|
1174
|
+
|
|
1131
1175
|
### `createWebhookHandler(bot, options?)`
|
|
1132
1176
|
|
|
1133
1177
|
```ts
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xbibzlibrary/telebibz",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.1",
|
|
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",
|