@xbibzlibrary/telebibz 0.1.19 → 0.2.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 +28 -8
- package/CONTRIBUTING.md +2 -2
- package/README.id.md +25 -5
- package/README.md +26 -6
- package/README.zh-CN.md +25 -5
- package/RELEASE_AUTOMATION.md +17 -5
- package/bin/telebibz.mjs +1 -1
- package/dist/src/api/client.d.ts +3 -1
- package/dist/src/api/client.d.ts.map +1 -1
- package/dist/src/api/client.js +3 -1
- package/dist/src/api/client.js.map +1 -1
- package/dist/src/api/transport.d.ts +3 -1
- package/dist/src/api/transport.d.ts.map +1 -1
- package/dist/src/api/transport.js +3 -2
- package/dist/src/api/transport.js.map +1 -1
- package/dist/src/branding/terminal.d.ts +62 -0
- package/dist/src/branding/terminal.d.ts.map +1 -1
- package/dist/src/branding/terminal.js +258 -0
- package/dist/src/branding/terminal.js.map +1 -1
- package/dist/src/cli.d.ts.map +1 -1
- package/dist/src/cli.js +7 -3
- package/dist/src/cli.js.map +1 -1
- package/dist/src/context/context.d.ts +24 -1
- package/dist/src/context/context.d.ts.map +1 -1
- package/dist/src/context/context.js +102 -8
- package/dist/src/context/context.js.map +1 -1
- package/dist/src/core/bot.d.ts +18 -1
- package/dist/src/core/bot.d.ts.map +1 -1
- package/dist/src/core/bot.js +106 -23
- package/dist/src/core/bot.js.map +1 -1
- package/dist/src/keyboard/index.d.ts +12 -0
- package/dist/src/keyboard/index.d.ts.map +1 -1
- package/dist/src/keyboard/index.js +13 -1
- package/dist/src/keyboard/index.js.map +1 -1
- package/dist/src/observability/logger.d.ts +28 -1
- package/dist/src/observability/logger.d.ts.map +1 -1
- package/dist/src/observability/logger.js +110 -0
- package/dist/src/observability/logger.js.map +1 -1
- package/dist/src/plugins/plugin.d.ts +2 -0
- package/dist/src/plugins/plugin.d.ts.map +1 -1
- package/dist/src/plugins/plugin.js +11 -4
- package/dist/src/plugins/plugin.js.map +1 -1
- package/dist/src/router/router.d.ts +19 -0
- package/dist/src/router/router.d.ts.map +1 -1
- package/dist/src/router/router.js +125 -23
- package/dist/src/router/router.js.map +1 -1
- package/dist/src/state/forms.d.ts +0 -1
- package/dist/src/state/forms.d.ts.map +1 -1
- package/dist/src/state/forms.js +27 -24
- package/dist/src/state/forms.js.map +1 -1
- package/dist/src/storage/storage.d.ts +10 -0
- package/dist/src/storage/storage.d.ts.map +1 -1
- package/dist/src/storage/storage.js +20 -2
- package/dist/src/storage/storage.js.map +1 -1
- package/dist/src/utils/text.d.ts +22 -0
- package/dist/src/utils/text.d.ts.map +1 -1
- package/dist/src/utils/text.js +0 -0
- package/dist/src/utils/text.js.map +1 -1
- package/dist/src/webhook/handler.d.ts +3 -0
- package/dist/src/webhook/handler.d.ts.map +1 -1
- package/dist/src/webhook/handler.js +85 -0
- package/dist/src/webhook/handler.js.map +1 -1
- package/dist-cjs/src/api/client.js +3 -1
- package/dist-cjs/src/api/transport.js +3 -2
- package/dist-cjs/src/branding/terminal.js +264 -1
- package/dist-cjs/src/cli.js +7 -3
- package/dist-cjs/src/context/context.js +102 -8
- package/dist-cjs/src/core/bot.js +104 -21
- package/dist-cjs/src/keyboard/index.js +13 -1
- package/dist-cjs/src/observability/logger.js +113 -1
- package/dist-cjs/src/plugins/plugin.js +11 -4
- package/dist-cjs/src/router/router.js +126 -24
- package/dist-cjs/src/state/forms.js +27 -24
- package/dist-cjs/src/storage/storage.js +20 -2
- package/dist-cjs/src/utils/text.js +0 -0
- package/dist-cjs/src/webhook/handler.js +86 -0
- package/docs/API.id.md +57 -3
- package/docs/API.md +59 -3
- package/docs/API.zh-CN.md +56 -2
- package/package.json +3 -3
|
@@ -3,32 +3,35 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.validators = exports.Form = void 0;
|
|
4
4
|
class Form {
|
|
5
5
|
fields = new Map();
|
|
6
|
-
values = {};
|
|
7
6
|
field(definition) { this.fields.set(definition.name, definition); return this; }
|
|
8
|
-
async parse(input) {
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
7
|
+
async parse(input) {
|
|
8
|
+
const issues = [];
|
|
9
|
+
const values = {};
|
|
10
|
+
for (const [name, field] of this.fields) {
|
|
11
|
+
const raw = input[name];
|
|
12
|
+
if ((raw === undefined || raw === null || raw === "") && field.required) {
|
|
13
|
+
issues.push({ path: name, message: "Field is required", code: "required" });
|
|
14
|
+
continue;
|
|
15
|
+
}
|
|
16
|
+
if (raw === undefined || raw === null || raw === "")
|
|
17
|
+
continue;
|
|
18
|
+
try {
|
|
19
|
+
let value = field.parse(raw);
|
|
20
|
+
if (field.transform)
|
|
21
|
+
value = await field.transform(value);
|
|
22
|
+
const error = await field.validate?.(value);
|
|
23
|
+
if (error)
|
|
24
|
+
issues.push({ path: name, message: error, code: "invalid" });
|
|
25
|
+
else
|
|
26
|
+
values[name] = value;
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
issues.push({ path: name, message: error instanceof Error ? error.message : "Invalid value", code: "parse" });
|
|
30
|
+
}
|
|
13
31
|
}
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
let value = field.parse(raw);
|
|
18
|
-
if (field.transform)
|
|
19
|
-
value = await field.transform(value);
|
|
20
|
-
const error = await field.validate?.(value);
|
|
21
|
-
if (error)
|
|
22
|
-
issues.push({ path: name, message: error, code: "invalid" });
|
|
23
|
-
else
|
|
24
|
-
this.values[name] = value;
|
|
25
|
-
}
|
|
26
|
-
catch (error) {
|
|
27
|
-
issues.push({ path: name, message: error instanceof Error ? error.message : "Invalid value", code: "parse" });
|
|
28
|
-
}
|
|
29
|
-
} return issues.length ? { success: false, issues } : { success: true, data: this.values }; }
|
|
30
|
-
reset() { for (const key of Object.keys(this.values))
|
|
31
|
-
delete this.values[key]; }
|
|
32
|
+
return issues.length ? { success: false, issues } : { success: true, data: values };
|
|
33
|
+
}
|
|
34
|
+
reset() { }
|
|
32
35
|
}
|
|
33
36
|
exports.Form = Form;
|
|
34
37
|
exports.validators = { string: (value) => { if (typeof value !== "string")
|
|
@@ -23,6 +23,20 @@ class MemoryStorage {
|
|
|
23
23
|
}
|
|
24
24
|
return entry.value;
|
|
25
25
|
}
|
|
26
|
+
/**
|
|
27
|
+
* Reads the raw entry including expiry metadata. Intended for storage adapters
|
|
28
|
+
* (such as `JsonFileStorage`) that need to persist TTL information across restarts.
|
|
29
|
+
*/
|
|
30
|
+
async readEntry(key) {
|
|
31
|
+
const entry = this.valuesMap.get(key);
|
|
32
|
+
if (!entry)
|
|
33
|
+
return undefined;
|
|
34
|
+
if (entry.expiresAt !== undefined && entry.expiresAt <= Date.now()) {
|
|
35
|
+
this.valuesMap.delete(key);
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
return entry.expiresAt === undefined ? { value: entry.value } : { value: entry.value, expiresAt: entry.expiresAt };
|
|
39
|
+
}
|
|
26
40
|
async set(key, value, options = {}) {
|
|
27
41
|
const expiresAt = expiration(options.ttlMs);
|
|
28
42
|
this.valuesMap.set(key, expiresAt === undefined ? { value } : { value, expiresAt });
|
|
@@ -84,8 +98,12 @@ class JsonFileStorage {
|
|
|
84
98
|
await this.ready;
|
|
85
99
|
this.writeChain = this.writeChain.then(async () => {
|
|
86
100
|
const output = {};
|
|
87
|
-
for await (const
|
|
88
|
-
|
|
101
|
+
for await (const key of this.memory.keys()) {
|
|
102
|
+
const entry = await this.memory.readEntry(key);
|
|
103
|
+
if (entry === undefined)
|
|
104
|
+
continue;
|
|
105
|
+
output[key] = entry.expiresAt === undefined ? { value: entry.value } : { value: entry.value, expiresAt: entry.expiresAt };
|
|
106
|
+
}
|
|
89
107
|
await (0, promises_1.mkdir)((0, node_path_1.dirname)(this.filePath), { recursive: true });
|
|
90
108
|
const temporary = `${this.filePath}.${process.pid}.tmp`;
|
|
91
109
|
await (0, promises_1.writeFile)(temporary, JSON.stringify(output, null, 2), "utf8");
|
|
Binary file
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.createWebhookHandler = createWebhookHandler;
|
|
4
|
+
exports.webhookCallback = webhookCallback;
|
|
4
5
|
const node_crypto_1 = require("node:crypto");
|
|
5
6
|
function createWebhookHandler(bot, options = {}) {
|
|
6
7
|
const maxBodyBytes = options.maxBodyBytes ?? 1_048_576;
|
|
@@ -31,6 +32,91 @@ function createWebhookHandler(bot, options = {}) {
|
|
|
31
32
|
}
|
|
32
33
|
};
|
|
33
34
|
}
|
|
35
|
+
function webhookCallback(bot, _framework = "express", options = {}) {
|
|
36
|
+
const maxBodyBytes = options.maxBodyBytes ?? 1_048_576;
|
|
37
|
+
return async (req, res) => {
|
|
38
|
+
const rawReq = req;
|
|
39
|
+
const rawRes = res;
|
|
40
|
+
const sendResponse = (status, message) => {
|
|
41
|
+
if (rawRes && typeof rawRes.writeHead === "function" && typeof rawRes.end === "function") {
|
|
42
|
+
rawRes.writeHead(status, { "Content-Type": "text/plain" });
|
|
43
|
+
rawRes.end(message);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
const expressLike = rawRes;
|
|
47
|
+
if (typeof expressLike?.status === "function" && typeof expressLike.send === "function") {
|
|
48
|
+
expressLike.status(status).send(message);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
const koaLike = rawRes;
|
|
52
|
+
if (koaLike !== undefined) {
|
|
53
|
+
koaLike.status = status;
|
|
54
|
+
koaLike.body = message;
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
if (rawReq.method !== "POST") {
|
|
58
|
+
sendResponse(405, "Method Not Allowed");
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
if (options.secretToken) {
|
|
62
|
+
if (!secureEqual(readHeader(rawReq, "x-telegram-bot-api-secret-token"), options.secretToken)) {
|
|
63
|
+
sendResponse(401, "Unauthorized");
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
try {
|
|
68
|
+
let update;
|
|
69
|
+
if (typeof rawReq.arrayBuffer === "function") {
|
|
70
|
+
// Web-standard Request objects (e.g. Deno, Bun, WinterCG runtimes, or
|
|
71
|
+
// a converted fetch Request) expose the body through arrayBuffer().
|
|
72
|
+
// Checked before `body` because a Request's `body` is a ReadableStream.
|
|
73
|
+
const raw = await rawReq.arrayBuffer();
|
|
74
|
+
if (raw.byteLength > maxBodyBytes) {
|
|
75
|
+
sendResponse(413, "Payload Too Large");
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
update = JSON.parse(new TextDecoder().decode(raw));
|
|
79
|
+
}
|
|
80
|
+
else if (rawReq.body && typeof rawReq.body === "object") {
|
|
81
|
+
update = rawReq.body;
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
const chunks = [];
|
|
85
|
+
let totalBytes = 0;
|
|
86
|
+
for await (const chunk of rawReq) {
|
|
87
|
+
const buffer = typeof chunk === "string" ? Buffer.from(chunk) : chunk;
|
|
88
|
+
totalBytes += buffer.length;
|
|
89
|
+
if (totalBytes > maxBodyBytes) {
|
|
90
|
+
sendResponse(413, "Payload Too Large");
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
chunks.push(buffer);
|
|
94
|
+
}
|
|
95
|
+
const raw = Buffer.concat(chunks).toString("utf8");
|
|
96
|
+
update = JSON.parse(raw);
|
|
97
|
+
}
|
|
98
|
+
if (!Number.isInteger(update?.update_id)) {
|
|
99
|
+
sendResponse(400, "Bad Request");
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
await bot.handleUpdate(update);
|
|
103
|
+
sendResponse(200, "OK");
|
|
104
|
+
}
|
|
105
|
+
catch (error) {
|
|
106
|
+
await options.onError?.(error);
|
|
107
|
+
sendResponse(500, "Internal Server Error");
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
function readHeader(req, name) {
|
|
112
|
+
const headers = req.headers;
|
|
113
|
+
if (!headers || typeof headers !== "object")
|
|
114
|
+
return "";
|
|
115
|
+
if (typeof headers.get === "function")
|
|
116
|
+
return headers.get(name) ?? "";
|
|
117
|
+
const value = headers[name];
|
|
118
|
+
return Array.isArray(value) ? value[0] ?? "" : value ?? "";
|
|
119
|
+
}
|
|
34
120
|
function secureEqual(left, right) {
|
|
35
121
|
const leftBuffer = Buffer.from(left, "utf8");
|
|
36
122
|
const rightBuffer = Buffer.from(right, "utf8");
|
package/docs/API.id.md
CHANGED
|
@@ -68,6 +68,7 @@ type BotStatus =
|
|
|
68
68
|
| `transportOptions` | `Omit<FetchTransportOptions, "baseUrl">` | `{}` | Timeout, retry, backoff, jitter, headers, dan fetch implementation. |
|
|
69
69
|
| `session` | `Storage<string, S>` | storage baru | Penyimpanan session berdasarkan kunci chat/user; dapat memakai adapter persistent. |
|
|
70
70
|
| `services` | `Record<string, unknown>` | `{}` | Dependency/service yang tersedia melalui `ctx.services`. |
|
|
71
|
+
| `branding` | `boolean` | `true` | Pengalaman startup terminal: efek ketik, glass progress bar, banner rainbow animasi `Tele Bibz`, dan baris update yang mudah dibaca. Hanya dirender pada TTY interaktif. |
|
|
71
72
|
| `polling.timeout` | `number` | `30` | Long-poll timeout dalam detik untuk `getUpdates`. |
|
|
72
73
|
| `polling.limit` | `number` | `100` | Jumlah maksimum update per request polling. |
|
|
73
74
|
| `polling.allowedUpdates` | `string[]` | `[]` | Filter update Telegram. |
|
|
@@ -140,6 +141,30 @@ onRegex(expression: RegExp, handler: Middleware<Context<S>>): this
|
|
|
140
141
|
|
|
141
142
|
Menangani message text menggunakan `RegExp`. Parameter route tidak diekstrak otomatis ke `ctx.params`; gunakan predicate atau middleware custom jika memerlukan ekstraksi.
|
|
142
143
|
|
|
144
|
+
### `bot.on(filter, handler)`
|
|
145
|
+
|
|
146
|
+
```ts
|
|
147
|
+
on(filter: UpdateFilter | UpdateFilter[], handler: Middleware<Context<S>>): this
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Mendaftarkan handler untuk tipe update, opsional dipersempit dengan field payload. Contoh: `"message"`, `"message:text"`, `"message:photo"`, `"edited_message"`, `"channel_post"`, `"callback_query"`, `"callback_query:data"`, `"inline_query"`, `"chat_member"`, `"message_reaction"`, atau array seperti `["message:text", "callback_query:data"]`. Tipe update tidak valid melempar `TypeError` saat registrasi.
|
|
151
|
+
|
|
152
|
+
### `bot.hears(trigger, handler)`
|
|
153
|
+
|
|
154
|
+
```ts
|
|
155
|
+
hears(trigger: string | RegExp, handler: Middleware<Context<S>>): this
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Menangani message text yang sama persis (string) atau yang cocok dengan `RegExp`.
|
|
159
|
+
|
|
160
|
+
### `bot.catch(handler)`
|
|
161
|
+
|
|
162
|
+
```ts
|
|
163
|
+
catch(handler: (error: unknown, ctx: Context<S>) => void | Promise<void>): this
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
Mendaftarkan error boundary untuk handler update. Jika dipasang, kegagalan handler dicatat, dipancarkan sebagai `update:error`/`bot:error`, dan diteruskan ke handler ini alih-alih menolak `handleUpdate()` — webhook menjawab `200` dan polling berlanjut. Tanpa boundary, error dilempar ulang.
|
|
167
|
+
|
|
143
168
|
### `bot.usePlugin(plugin)`
|
|
144
169
|
|
|
145
170
|
```ts
|
|
@@ -633,6 +658,22 @@ new Context<S>(options: ContextOptions<S>): Context<S>
|
|
|
633
658
|
| `send` | `send(text, extra?): Promise<Message>` | Mengirim message ke chat update tanpa reply reference. |
|
|
634
659
|
| `edit` | `edit(text, extra?): Promise<Message \| true>` | Mengedit message update menggunakan `editMessageText`. |
|
|
635
660
|
| `delete` | `delete(): Promise<true>` | Menghapus message update. |
|
|
661
|
+
| `replyWithHTML` | `replyWithHTML(text, extra?): Promise<Message>` | Membalas dengan `parse_mode: "HTML"`. |
|
|
662
|
+
| `replyWithMarkdown` | `replyWithMarkdown(text, extra?): Promise<Message>` | Membalas dengan `parse_mode: "MarkdownV2"`. |
|
|
663
|
+
| `replyWithPhoto` | `replyWithPhoto(photo, extra?): Promise<Message>` | Mengirim `sendPhoto` dengan quote-reply otomatis. |
|
|
664
|
+
| `replyWithDocument` | `replyWithDocument(document, extra?): Promise<Message>` | Mengirim `sendDocument` dengan quote-reply otomatis. |
|
|
665
|
+
| `replyWithAudio` | `replyWithAudio(audio, extra?): Promise<Message>` | Mengirim `sendAudio` dengan quote-reply otomatis. |
|
|
666
|
+
| `replyWithVideo` | `replyWithVideo(video, extra?): Promise<Message>` | Mengirim `sendVideo` dengan quote-reply otomatis. |
|
|
667
|
+
| `replyWithVoice` | `replyWithVoice(voice, extra?): Promise<Message>` | Mengirim `sendVoice` dengan quote-reply otomatis. |
|
|
668
|
+
| `replyWithAnimation` | `replyWithAnimation(animation, extra?): Promise<Message>` | Mengirim `sendAnimation` dengan quote-reply otomatis. |
|
|
669
|
+
| `replyWithVideoNote` | `replyWithVideoNote(videoNote, extra?): Promise<Message>` | Mengirim `sendVideoNote` dengan quote-reply otomatis. |
|
|
670
|
+
| `replyWithSticker` | `replyWithSticker(sticker, extra?): Promise<Message>` | Mengirim `sendSticker` dengan quote-reply otomatis. |
|
|
671
|
+
| `replyWithMediaGroup` | `replyWithMediaGroup(media, extra?): Promise<Message[]>` | Mengirim album via `sendMediaGroup` dengan quote-reply otomatis. |
|
|
672
|
+
| `replyWithLocation` | `replyWithLocation(latitude, longitude, extra?): Promise<Message>` | Mengirim `sendLocation` dengan quote-reply otomatis. |
|
|
673
|
+
| `replyWithVenue` | `replyWithVenue(latitude, longitude, title, address, extra?): Promise<Message>` | Mengirim `sendVenue` dengan quote-reply otomatis. |
|
|
674
|
+
| `replyWithContact` | `replyWithContact(phoneNumber, firstName, extra?): Promise<Message>` | Mengirim `sendContact` dengan quote-reply otomatis. |
|
|
675
|
+
| `replyWithPoll` | `replyWithPoll(question, options, extra?): Promise<Message>` | Mengirim `sendPoll` dengan quote-reply otomatis. |
|
|
676
|
+
| `replyWithDice` | `replyWithDice(emoji?, extra?): Promise<Message>` | Mengirim `sendDice` dengan quote-reply otomatis. |
|
|
636
677
|
| `copy` | `copy(fromChatId, messageId, extra?): Promise<unknown>` | Memanggil `copyMessage` ke chat context. |
|
|
637
678
|
| `forward` | `forward(fromChatId, messageId, extra?): Promise<Message>` | Memanggil `forwardMessage` ke chat context. |
|
|
638
679
|
| `pin` | `pin(messageId?, extra?): Promise<true>` | Memanggil `pinChatMessage`, default message id dari context. |
|
|
@@ -645,7 +686,7 @@ new Context<S>(options: ContextOptions<S>): Context<S>
|
|
|
645
686
|
| `getFile` | `getFile(fileId): Promise<unknown>` | Mengambil file berdasarkan id. |
|
|
646
687
|
| `withReplyMarkup` | `withReplyMarkup(markup): this` | Menyimpan markup di `ctx.state.reply_markup` dan mengembalikan context. Metode ini tidak otomatis mengirim message. |
|
|
647
688
|
|
|
648
|
-
`reply`, `send`, `getChat`, dan beberapa helper lain melempar error ketika update tidak memiliki chat yang diperlukan. `edit` dan `delete` membutuhkan chat serta message.
|
|
689
|
+
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.
|
|
649
690
|
|
|
650
691
|
---
|
|
651
692
|
|
|
@@ -1263,7 +1304,20 @@ new Menu(id: string): Menu
|
|
|
1263
1304
|
|
|
1264
1305
|
## 12. Logging Terminal
|
|
1265
1306
|
|
|
1266
|
-
|
|
1307
|
+
Saat stdout adalah TTY interaktif, setiap `bot.start()` / `bot.launch()` memainkan urutan startup: efek ketik `Installing Dependencies......`, glass progress bar dengan kilau menyapu, dan banner ASCII rainbow animasi `Tele Bibz` (font figlet `Speed`) yang terus mengalir sampai bot terhubung, lalu diam dengan `✓ Connected as @<username>`.
|
|
1308
|
+
|
|
1309
|
+
Setiap update yang ditangani bot dicatat dalam baris yang mudah dibaca:
|
|
1310
|
+
|
|
1311
|
+
```text
|
|
1312
|
+
[ => ] Message From 123456789 John Doe 29/08/2026 15:04:05
|
|
1313
|
+
↳ Text: /start
|
|
1314
|
+
[ => ] Callback From 123456789 John Doe 29/08/2026 15:04:07
|
|
1315
|
+
↳ Data: menu:open
|
|
1316
|
+
```
|
|
1317
|
+
|
|
1318
|
+
Teks pesan/command dibatasi 50 karakter; data tombol callback ditampilkan penuh. Error dicetak merah lengkap dengan stack. Nonaktifkan dengan `branding: false` pada `Bot`, atau set `logger.format: "json"` untuk log terstruktur — pada mode itu update masuk dikeluarkan sebagai entry `update.received`. Stdout non-interaktif (pipe, Docker, CI) otomatis fallback ke teks polos tanpa animasi.
|
|
1319
|
+
|
|
1320
|
+
Helper branding tambahan yang diekspor untuk aplikasi: `runStartupSequence()`, `startTeleBibzBanner()`, `printTeleBibzBanner()`, `paintRainbow()`, dan `printStatusLine()`.
|
|
1267
1321
|
|
|
1268
1322
|
## 13. Utilitas Teks
|
|
1269
1323
|
|
|
@@ -1691,7 +1745,7 @@ Package memvendorkan declaration Telegram berlisensi MIT dan mengeksposnya sebag
|
|
|
1691
1745
|
|
|
1692
1746
|
## 19. Kompatibilitas dan batasan yang perlu diketahui
|
|
1693
1747
|
|
|
1694
|
-
Perpustakaan menargetkan Node.js `>=
|
|
1748
|
+
Perpustakaan menargetkan Node.js `>=22`, menggunakan ESM sebagai module utama, serta menyediakan build CommonJS. Webhook membutuhkan runtime yang menyediakan Web `Request`, `Response`, `Headers`, `FormData`, `Blob`, dan `AbortController`; Node.js modern menyediakannya secara native.
|
|
1695
1749
|
|
|
1696
1750
|
Daftar method yang dihasilkan API dan peta method API bukanlah hal yang sama. `TelegramMethodName` mencakup 184 nama runtime, tetapi `TelegramMethodMap` hanya memiliki parameter/hasil yang bertipe khusus untuk subset yang tercantum pada bagian API client. Untuk method lain, gunakan `api.raw()` atau tambahkan deklarasi tipe di sisi aplikasi.
|
|
1697
1751
|
|
package/docs/API.md
CHANGED
|
@@ -67,6 +67,7 @@ type BotStatus =
|
|
|
67
67
|
| `transportOptions` | `Omit<FetchTransportOptions, "baseUrl">` | `{}` | Timeout, retry, backoff, jitter, headers, and fetch implementation. |
|
|
68
68
|
| `session` | `Storage<string, S>` | new storage | Session storage keyed by chat/user; any storage adapter may be used. |
|
|
69
69
|
| `services` | `Record<string, unknown>` | `{}` | Dependencies/services available via `ctx.services`. |
|
|
70
|
+
| `branding` | `boolean` | `true` | Terminal startup experience: typing effect, glass progress bar, animated rainbow "Tele Bibz" banner, and human-readable update lines. Only renders on an interactive TTY. |
|
|
70
71
|
| `polling.timeout` | `number` | `30` | Long-poll timeout in seconds for `getUpdates`. |
|
|
71
72
|
| `polling.limit` | `number` | `100` | Maximum number of updates per polling request. |
|
|
72
73
|
| `polling.allowedUpdates` | `string[]` | `[]` | Telegram update filters. |
|
|
@@ -139,6 +140,30 @@ onRegex(expression: RegExp, handler: Middleware<Context<S>>): this
|
|
|
139
140
|
|
|
140
141
|
Handles message text using a `RegExp`. Route parameters are not automatically extracted into `ctx.params`; use a predicate or custom middleware if extraction is needed.
|
|
141
142
|
|
|
143
|
+
### `bot.on(filter, handler)`
|
|
144
|
+
|
|
145
|
+
```ts
|
|
146
|
+
on(filter: UpdateFilter | UpdateFilter[], handler: Middleware<Context<S>>): this
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Registers a handler for update types, optionally narrowed by a payload field. Examples: `"message"`, `"message:text"`, `"message:photo"`, `"edited_message"`, `"channel_post"`, `"callback_query"`, `"callback_query:data"`, `"inline_query"`, `"chat_member"`, `"message_reaction"`, or an array such as `["message:text", "callback_query:data"]`. Invalid update types throw a `TypeError` at registration time.
|
|
150
|
+
|
|
151
|
+
### `bot.hears(trigger, handler)`
|
|
152
|
+
|
|
153
|
+
```ts
|
|
154
|
+
hears(trigger: string | RegExp, handler: Middleware<Context<S>>): this
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
Handles exact message text (string) or message text matching a `RegExp`.
|
|
158
|
+
|
|
159
|
+
### `bot.catch(handler)`
|
|
160
|
+
|
|
161
|
+
```ts
|
|
162
|
+
catch(handler: (error: unknown, ctx: Context<S>) => void | Promise<void>): this
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
Registers the error boundary for update handlers. When set, a handler failure is logged, emitted as `update:error`/`bot:error`, and passed to this handler instead of rejecting `handleUpdate()` — webhook requests answer `200` and polling continues. Without a boundary, the error is rethrown.
|
|
166
|
+
|
|
142
167
|
### `bot.usePlugin(plugin)`
|
|
143
168
|
|
|
144
169
|
```ts
|
|
@@ -650,6 +675,23 @@ new Context<S>(options: ContextOptions<S>): Context<S>
|
|
|
650
675
|
| `send` | `send(text, extra?): Promise<Message>` | Sends a message to the update chat without a reply reference. |
|
|
651
676
|
| `edit` | `edit(text, extra?): Promise<Message \| true>` | Edits the update message using `editMessageText`. |
|
|
652
677
|
| `delete` | `delete(): Promise<true>` | Deletes the update message. |
|
|
678
|
+
| `replyWithHTML` | `replyWithHTML(text, extra?): Promise<Message>` | Replies with `parse_mode: "HTML"`. |
|
|
679
|
+
| `replyWithMarkdown` | `replyWithMarkdown(text, extra?): Promise<Message>` | Replies with `parse_mode: "MarkdownV2"`. |
|
|
680
|
+
| `replyWithPhoto` | `replyWithPhoto(photo, extra?): Promise<Message>` | Sends `sendPhoto` with automatic quote-reply. |
|
|
681
|
+
| `replyWithDocument` | `replyWithDocument(document, extra?): Promise<Message>` | Sends `sendDocument` with automatic quote-reply. |
|
|
682
|
+
| `replyWithAudio` | `replyWithAudio(audio, extra?): Promise<Message>` | Sends `sendAudio` with automatic quote-reply. |
|
|
683
|
+
| `replyWithVideo` | `replyWithVideo(video, extra?): Promise<Message>` | Sends `sendVideo` with automatic quote-reply. |
|
|
684
|
+
| `replyWithVoice` | `replyWithVoice(voice, extra?): Promise<Message>` | Sends `sendVoice` with automatic quote-reply. |
|
|
685
|
+
| `replyWithAnimation` | `replyWithAnimation(animation, extra?): Promise<Message>` | Sends `sendAnimation` with automatic quote-reply. |
|
|
686
|
+
| `replyWithVideoNote` | `replyWithVideoNote(videoNote, extra?): Promise<Message>` | Sends `sendVideoNote` with automatic quote-reply. |
|
|
687
|
+
| `replyWithSticker` | `replyWithSticker(sticker, extra?): Promise<Message>` | Sends `sendSticker` with automatic quote-reply. |
|
|
688
|
+
| `replyWithMediaGroup` | `replyWithMediaGroup(media, extra?): Promise<Message[]>` | Sends an album via `sendMediaGroup` with automatic quote-reply. |
|
|
689
|
+
| `replyWithLocation` | `replyWithLocation(latitude, longitude, extra?): Promise<Message>` | Sends `sendLocation` with automatic quote-reply. |
|
|
690
|
+
| `replyWithVenue` | `replyWithVenue(latitude, longitude, title, address, extra?): Promise<Message>` | Sends `sendVenue` with automatic quote-reply. |
|
|
691
|
+
| `replyWithContact` | `replyWithContact(phoneNumber, firstName, extra?): Promise<Message>` | Sends `sendContact` with automatic quote-reply. |
|
|
692
|
+
| `replyWithPoll` | `replyWithPoll(question, options, extra?): Promise<Message>` | Sends `sendPoll` with automatic quote-reply. |
|
|
693
|
+
| `replyWithDice` | `replyWithDice(emoji?, extra?): Promise<Message>` | Sends `sendDice` with automatic quote-reply. |
|
|
694
|
+
| `sendChatAction` | `sendChatAction(action, extra?): Promise<true>` | Sends a chat action such as `typing`. |
|
|
653
695
|
| `copy` | `copy(fromChatId, messageId, extra?): Promise<unknown>` | Calls `copyMessage` to the context chat. |
|
|
654
696
|
| `forward` | `forward(fromChatId, messageId, extra?): Promise<Message>` | Calls `forwardMessage` to the context chat. |
|
|
655
697
|
| `pin` | `pin(messageId?, extra?): Promise<true>` | Calls `pinChatMessage`; defaults to the context message id. |
|
|
@@ -662,7 +704,7 @@ new Context<S>(options: ContextOptions<S>): Context<S>
|
|
|
662
704
|
| `getFile` | `getFile(fileId): Promise<unknown>` | Fetches a file by id. |
|
|
663
705
|
| `withReplyMarkup` | `withReplyMarkup(markup): this` | Stores markup in `ctx.state.reply_markup` and returns the context. This method does not automatically send a message. |
|
|
664
706
|
|
|
665
|
-
`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.
|
|
707
|
+
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.
|
|
666
708
|
|
|
667
709
|
---
|
|
668
710
|
|
|
@@ -1281,11 +1323,23 @@ new Menu(id: string): Menu
|
|
|
1281
1323
|
|
|
1282
1324
|
## 12. Terminal Logging
|
|
1283
1325
|
|
|
1284
|
-
|
|
1326
|
+
When stdout is an interactive TTY, every `bot.start()` / `bot.launch()` plays a startup sequence: a typing effect for `Installing Dependencies......`, a glass progress bar with a sweeping highlight, and the animated rainbow ASCII banner `Tele Bibz` (figlet `Speed` font) that keeps flowing until the bot connects, then freezes with `✓ Connected as @<username>`.
|
|
1327
|
+
|
|
1328
|
+
Every update the bot handles is logged on a human-readable line:
|
|
1329
|
+
|
|
1330
|
+
```text
|
|
1331
|
+
[ => ] Message From 123456789 John Doe 29/08/2026 15:04:05
|
|
1332
|
+
↳ Text: /start
|
|
1333
|
+
[ => ] Callback From 123456789 John Doe 29/08/2026 15:04:07
|
|
1334
|
+
↳ Data: menu:open
|
|
1335
|
+
```
|
|
1336
|
+
|
|
1337
|
+
Message and command text is truncated to 50 characters; callback button data is shown in full. Errors are printed in red and include the full stack. Pass `branding: false` to `Bot` to disable the startup sequence, and set `logger.format: "json"` for machine ingestion — in that mode incoming updates are emitted as structured `update.received` entries. Non-interactive stdout (pipes, Docker, CI) automatically falls back to plain, uncolored output without animations.
|
|
1285
1338
|
|
|
1286
1339
|
```ts
|
|
1287
1340
|
const bot = new Bot({
|
|
1288
1341
|
token: process.env.TELEGRAM_BOT_TOKEN!,
|
|
1342
|
+
branding: false, // turn off the startup sequence
|
|
1289
1343
|
logger: {
|
|
1290
1344
|
level: "debug",
|
|
1291
1345
|
format: "pretty",
|
|
@@ -1295,6 +1349,8 @@ const bot = new Bot({
|
|
|
1295
1349
|
});
|
|
1296
1350
|
```
|
|
1297
1351
|
|
|
1352
|
+
Additional branding helpers exported for applications: `runStartupSequence()`, `startTeleBibzBanner()`, `printTeleBibzBanner()`, `paintRainbow()`, and `printStatusLine()`.
|
|
1353
|
+
|
|
1298
1354
|
---
|
|
1299
1355
|
|
|
1300
1356
|
## 13. Text Utilities
|
|
@@ -1723,7 +1779,7 @@ The package vendors MIT-licensed Telegram declarations and exposes them as type-
|
|
|
1723
1779
|
---
|
|
1724
1780
|
## 19. Compatibility and limitations to be aware of
|
|
1725
1781
|
|
|
1726
|
-
The library targets Node.js `>=
|
|
1782
|
+
The library targets Node.js `>=22`, uses ESM as the primary module, and also provides a CommonJS build. Webhooks require a runtime that provides Web `Request`, `Response`, `Headers`, `FormData`, `Blob`, and `AbortController`; modern Node.js provides these natively.
|
|
1727
1783
|
|
|
1728
1784
|
The list of generated API methods and the API method map are not the same. `TelegramMethodName` includes 184 runtime names, but `TelegramMethodMap` only has specially-typed parameters/results for the subset listed in the API client section. For other methods, use `api.raw()` or add a type declaration on the application side.
|
|
1729
1785
|
|
package/docs/API.zh-CN.md
CHANGED
|
@@ -68,6 +68,7 @@ type BotStatus =
|
|
|
68
68
|
| `transportOptions` | `Omit<FetchTransportOptions, "baseUrl">` | `{}` | 超时、重试、退避、jitter、headers 和 fetch 实现。 |
|
|
69
69
|
| `session` | `Storage<string, S>` | 新的存储 | 基于 chat/user key 的会话存储,可使用持久化适配器。 |
|
|
70
70
|
| `services` | `Record<string, unknown>` | `{}` | 通过 `ctx.services` 可用的依赖/服务。 |
|
|
71
|
+
| `branding` | `boolean` | `true` | 终端启动体验:打字效果、glass 进度条、动画彩虹 `Tele Bibz` 横幅以及易读的 update 日志行。仅在交互式 TTY 上渲染。 |
|
|
71
72
|
| `polling.timeout` | `number` | `30` | 用于 `getUpdates` 的长轮询超时(秒)。 |
|
|
72
73
|
| `polling.limit` | `number` | `100` | 每次轮询请求的最大 update 数量。 |
|
|
73
74
|
| `polling.allowedUpdates` | `string[]` | `[]` | Telegram 更新过滤器。 |
|
|
@@ -140,6 +141,30 @@ onRegex(expression: RegExp, handler: Middleware<Context<S>>): this
|
|
|
140
141
|
|
|
141
142
|
使用 `RegExp` 处理消息文本。路由参数不会自动提取到 `ctx.params`;如需提取请使用 predicate 或自定义 middleware。
|
|
142
143
|
|
|
144
|
+
### `bot.on(filter, handler)`
|
|
145
|
+
|
|
146
|
+
```ts
|
|
147
|
+
on(filter: UpdateFilter | UpdateFilter[], handler: Middleware<Context<S>>): this
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
按更新类型注册处理器,并可用 payload 字段收窄。示例:`"message"`、`"message:text"`、`"message:photo"`、`"edited_message"`、`"channel_post"`、`"callback_query"`、`"callback_query:data"`、`"inline_query"`、`"chat_member"`、`"message_reaction"`,或数组如 `["message:text", "callback_query:data"]`。无效的更新类型会在注册时抛出 `TypeError`。
|
|
151
|
+
|
|
152
|
+
### `bot.hears(trigger, handler)`
|
|
153
|
+
|
|
154
|
+
```ts
|
|
155
|
+
hears(trigger: string | RegExp, handler: Middleware<Context<S>>): this
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
处理完全匹配的文本(string)或匹配 `RegExp` 的消息文本。
|
|
159
|
+
|
|
160
|
+
### `bot.catch(handler)`
|
|
161
|
+
|
|
162
|
+
```ts
|
|
163
|
+
catch(handler: (error: unknown, ctx: Context<S>) => void | Promise<void>): this
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
注册更新处理器的错误边界。设置后,处理器失败会被记录、以 `update:error`/`bot:error` 事件发出,并转发给该 handler,而不是让 `handleUpdate()` 拒绝 —— webhook 返回 `200`,轮询继续。未设置边界时错误会被重新抛出。
|
|
167
|
+
|
|
143
168
|
### `bot.usePlugin(plugin)`
|
|
144
169
|
|
|
145
170
|
```ts
|
|
@@ -633,6 +658,22 @@ new Context<S>(options: ContextOptions<S>): Context<S>
|
|
|
633
658
|
| `send` | `send(text, extra?): Promise<Message>` | 向更新的聊天发送消息,不带回复引用. |
|
|
634
659
|
| `edit` | `edit(text, extra?): Promise<Message \| true>` | 使用 `editMessageText` 编辑更新的消息. |
|
|
635
660
|
| `delete` | `delete(): Promise<true>` | 删除更新的消息. |
|
|
661
|
+
| `replyWithHTML` | `replyWithHTML(text, extra?): Promise<Message>` | 以 `parse_mode: "HTML"` 回复. |
|
|
662
|
+
| `replyWithMarkdown` | `replyWithMarkdown(text, extra?): Promise<Message>` | 以 `parse_mode: "MarkdownV2"` 回复. |
|
|
663
|
+
| `replyWithPhoto` | `replyWithPhoto(photo, extra?): Promise<Message>` | 发送 `sendPhoto`,自动引用回复. |
|
|
664
|
+
| `replyWithDocument` | `replyWithDocument(document, extra?): Promise<Message>` | 发送 `sendDocument`,自动引用回复. |
|
|
665
|
+
| `replyWithAudio` | `replyWithAudio(audio, extra?): Promise<Message>` | 发送 `sendAudio`,自动引用回复. |
|
|
666
|
+
| `replyWithVideo` | `replyWithVideo(video, extra?): Promise<Message>` | 发送 `sendVideo`,自动引用回复. |
|
|
667
|
+
| `replyWithVoice` | `replyWithVoice(voice, extra?): Promise<Message>` | 发送 `sendVoice`,自动引用回复. |
|
|
668
|
+
| `replyWithAnimation` | `replyWithAnimation(animation, extra?): Promise<Message>` | 发送 `sendAnimation`,自动引用回复. |
|
|
669
|
+
| `replyWithVideoNote` | `replyWithVideoNote(videoNote, extra?): Promise<Message>` | 发送 `sendVideoNote`,自动引用回复. |
|
|
670
|
+
| `replyWithSticker` | `replyWithSticker(sticker, extra?): Promise<Message>` | 发送 `sendSticker`,自动引用回复. |
|
|
671
|
+
| `replyWithMediaGroup` | `replyWithMediaGroup(media, extra?): Promise<Message[]>` | 通过 `sendMediaGroup` 发送相册,自动引用回复. |
|
|
672
|
+
| `replyWithLocation` | `replyWithLocation(latitude, longitude, extra?): Promise<Message>` | 发送 `sendLocation`,自动引用回复. |
|
|
673
|
+
| `replyWithVenue` | `replyWithVenue(latitude, longitude, title, address, extra?): Promise<Message>` | 发送 `sendVenue`,自动引用回复. |
|
|
674
|
+
| `replyWithContact` | `replyWithContact(phoneNumber, firstName, extra?): Promise<Message>` | 发送 `sendContact`,自动引用回复. |
|
|
675
|
+
| `replyWithPoll` | `replyWithPoll(question, options, extra?): Promise<Message>` | 发送 `sendPoll`,自动引用回复. |
|
|
676
|
+
| `replyWithDice` | `replyWithDice(emoji?, extra?): Promise<Message>` | 发送 `sendDice`,自动引用回复. |
|
|
636
677
|
| `copy` | `copy(fromChatId, messageId, extra?): Promise<unknown>` | 向上下文聊天调用 `copyMessage`. |
|
|
637
678
|
| `forward` | `forward(fromChatId, messageId, extra?): Promise<Message>` | 向上下文聊天调用 `forwardMessage`. |
|
|
638
679
|
| `pin` | `pin(messageId?, extra?): Promise<true>` | 调用 `pinChatMessage`,默认消息 ID 来自上下文. |
|
|
@@ -1257,7 +1298,20 @@ new Menu(id: string): Menu
|
|
|
1257
1298
|
|
|
1258
1299
|
## 12. Terminal Logging
|
|
1259
1300
|
|
|
1260
|
-
|
|
1301
|
+
当 stdout 是交互式 TTY 时,每次 `bot.start()` / `bot.launch()` 都会播放启动序列:`Installing Dependencies......` 打字效果、带扫过高光的 glass 进度条,以及动画彩虹 ASCII 横幅 `Tele Bibz`(figlet `Speed` 字体)——持续流动直到 bot 连接成功,随后定格为 `✓ Connected as @<username>`。
|
|
1302
|
+
|
|
1303
|
+
bot 处理的每条 update 都会以易读的行格式记录:
|
|
1304
|
+
|
|
1305
|
+
```text
|
|
1306
|
+
[ => ] Message From 123456789 John Doe 29/08/2026 15:04:05
|
|
1307
|
+
↳ Text: /start
|
|
1308
|
+
[ => ] Callback From 123456789 John Doe 29/08/2026 15:04:07
|
|
1309
|
+
↳ Data: menu:open
|
|
1310
|
+
```
|
|
1311
|
+
|
|
1312
|
+
普通消息与命令文本截断为 50 个字符;回调按钮数据完整显示。错误以红色打印并附带完整堆栈。向 `Bot` 传入 `branding: false` 可关闭启动序列;设置 `logger.format: "json"` 时,进入的 update 会作为结构化 `update.received` entry 输出。非交互 stdout(管道、Docker、CI)自动回退为无动画的纯文本。
|
|
1313
|
+
|
|
1314
|
+
面向应用导出的附加 branding helper:`runStartupSequence()`、`startTeleBibzBanner()`、`printTeleBibzBanner()`、`paintRainbow()` 和 `printStatusLine()`。
|
|
1261
1315
|
|
|
1262
1316
|
## 13. 文本工具
|
|
1263
1317
|
|
|
@@ -1685,7 +1739,7 @@ Package 内置 MIT 许可的 Telegram declaration,并通过 type-only export
|
|
|
1685
1739
|
|
|
1686
1740
|
## 19. 兼容性和需要注意的限制
|
|
1687
1741
|
|
|
1688
|
-
|
|
1742
|
+
本库面向 Node.js `>=22`,使用 ESM 作为主要模块,并提供 CommonJS 构建。Webhook 需要运行时提供 Web `Request`、`Response`、`Headers`、`FormData`、`Blob` 和 `AbortController`;现代 Node.js 原生提供了这些。
|
|
1689
1743
|
|
|
1690
1744
|
API 生成的方法列表(generated method list)和 API 方法映射(API method map)并不相同。`TelegramMethodName` 包含 184 个运行时名称,但 `TelegramMethodMap` 仅对 API 客户端部分列出的子集提供了带类型的参数/结果。对于其他方法,使用 `api.raw()` 或在应用端添加类型声明。
|
|
1691
1745
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xbibzlibrary/telebibz",
|
|
3
|
-
"version": "0.1
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.2.1",
|
|
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",
|
|
7
7
|
"telegram-bot",
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"url": "https://github.com/XbibzOfficial777/telebibz/issues"
|
|
33
33
|
},
|
|
34
34
|
"engines": {
|
|
35
|
-
"node": ">=
|
|
35
|
+
"node": ">=22"
|
|
36
36
|
},
|
|
37
37
|
"scripts": {
|
|
38
38
|
"generate": "node scripts/generate-api.mjs",
|