gramio 0.0.50 → 0.1.0

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/dist/index.js CHANGED
@@ -1,33 +1,1159 @@
1
- "use strict";
2
- /**
3
- * @module
4
- *
5
- * Powerful Telegram Bot API framework
6
- */
7
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
8
- if (k2 === undefined) k2 = k;
9
- var desc = Object.getOwnPropertyDescriptor(m, k);
10
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
11
- desc = { enumerable: true, get: function() { return m[k]; } };
12
- }
13
- Object.defineProperty(o, k2, desc);
14
- }) : (function(o, m, k, k2) {
15
- if (k2 === undefined) k2 = k;
16
- o[k2] = m[k];
17
- }));
18
- var __exportStar = (this && this.__exportStar) || function(m, exports) {
19
- for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
1
+ import fs from 'node:fs/promises';
2
+ import { Readable } from 'node:stream';
3
+ import { CallbackData } from '@gramio/callback-data';
4
+ export * from '@gramio/callback-data';
5
+ import { contextsMappings, PhotoAttachment } from '@gramio/contexts';
6
+ export * from '@gramio/contexts';
7
+ import { isMediaUpload, convertJsonToFormData } from '@gramio/files';
8
+ export * from '@gramio/files';
9
+ import { FormattableMap } from '@gramio/format';
10
+ export * from '@gramio/format';
11
+ import debug from 'debug';
12
+ import { Inspectable } from 'inspectable';
13
+ import { Composer as Composer$1, noopNext } from 'middleware-io';
14
+ export * from '@gramio/keyboards';
15
+
16
+ const ErrorKind = Symbol("ErrorKind");
17
+ class TelegramError extends Error {
18
+ /** Name of the API Method */
19
+ method;
20
+ /** Params that were sent */
21
+ params;
22
+ /** See {@link TelegramAPIResponseError.error_code}*/
23
+ code;
24
+ /** Describes why a request was unsuccessful. */
25
+ payload;
26
+ /** Construct new TelegramError */
27
+ constructor(error, method, params) {
28
+ super(error.description);
29
+ this.name = method;
30
+ this.method = method;
31
+ this.params = params;
32
+ this.code = error.error_code;
33
+ if (error.parameters) this.payload = error.parameters;
34
+ }
35
+ }
36
+ TelegramError.constructor[ErrorKind] = "TELEGRAM";
37
+
38
+ class Composer {
39
+ composer = Composer$1.builder();
40
+ length = 0;
41
+ composed;
42
+ onError;
43
+ constructor(onError) {
44
+ this.onError = onError || ((_, error) => {
45
+ throw error;
46
+ });
47
+ this.recompose();
48
+ }
49
+ /** Register handler to one or many Updates */
50
+ on(updateName, handler) {
51
+ return this.use(async (context, next) => {
52
+ if (context.is(updateName)) return await handler(context, next);
53
+ return await next();
54
+ });
55
+ }
56
+ /** Register handler to any Update */
57
+ use(handler) {
58
+ this.composer.caught(this.onError).use(handler);
59
+ return this.recompose();
60
+ }
61
+ /**
62
+ * Derive some data to handlers
63
+ *
64
+ * @example
65
+ * ```ts
66
+ * new Bot("token").derive((context) => {
67
+ * return {
68
+ * superSend: () => context.send("Derived method")
69
+ * }
70
+ * })
71
+ * ```
72
+ */
73
+ derive(updateNameOrHandler, handler) {
74
+ if (typeof updateNameOrHandler === "function")
75
+ this.use(async (context, next) => {
76
+ for (const [key, value] of Object.entries(
77
+ await updateNameOrHandler(context)
78
+ )) {
79
+ context[key] = value;
80
+ }
81
+ next();
82
+ });
83
+ else if (handler)
84
+ this.on(updateNameOrHandler, async (context, next) => {
85
+ for (const [key, value] of Object.entries(await handler(context))) {
86
+ context[key] = value;
87
+ }
88
+ next();
89
+ });
90
+ return this;
91
+ }
92
+ recompose() {
93
+ this.composed = this.composer.compose();
94
+ this.length = this.composer.length;
95
+ return this;
96
+ }
97
+ compose(context, next = noopNext) {
98
+ this.composed(context, next);
99
+ }
100
+ }
101
+
102
+ var __create$1 = Object.create;
103
+ var __defProp$1 = Object.defineProperty;
104
+ var __getOwnPropDesc$1 = Object.getOwnPropertyDescriptor;
105
+ var __knownSymbol$1 = (name, symbol) => (symbol = Symbol[name]) ? symbol : Symbol.for("Symbol." + name);
106
+ var __typeError$1 = (msg) => {
107
+ throw TypeError(msg);
20
108
  };
21
- Object.defineProperty(exports, "__esModule", { value: true });
22
- // INFO: Temp polyfill, more info https://github.com/microsoft/TypeScript/issues/55453#issuecomment-1687496648
109
+ var __defNormalProp$1 = (obj, key, value) => key in obj ? __defProp$1(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
110
+ var __name$1 = (target, value) => __defProp$1(target, "name", { value, configurable: true });
111
+ var __decoratorStart$1 = (base) => [, , , __create$1(null)];
112
+ var __decoratorStrings$1 = ["class", "method", "getter", "setter", "accessor", "field", "value", "get", "set"];
113
+ var __expectFn$1 = (fn) => fn !== void 0 && typeof fn !== "function" ? __typeError$1("Function expected") : fn;
114
+ var __decoratorContext$1 = (kind, name, done, metadata, fns) => ({ kind: __decoratorStrings$1[kind], name, metadata, addInitializer: (fn) => done._ ? __typeError$1("Already initialized") : fns.push(__expectFn$1(fn || null)) });
115
+ var __decoratorMetadata$1 = (array, target) => __defNormalProp$1(target, __knownSymbol$1("metadata"), array[3]);
116
+ var __runInitializers$1 = (array, flags, self, value) => {
117
+ for (var i = 0, fns = array[flags >> 1], n = fns && fns.length; i < n; i++) fns[i].call(self) ;
118
+ return value;
119
+ };
120
+ var __decorateElement$1 = (array, flags, name, decorators, target, extra) => {
121
+ var it, done, ctx, k = flags & 7, p = !!(flags & 16);
122
+ var j = 0;
123
+ var extraInitializers = array[j] || (array[j] = []);
124
+ var desc = k && ((target = target.prototype), k < 5 && (k > 3 || !p) && __getOwnPropDesc$1(target , name));
125
+ __name$1(target, name);
126
+ for (var i = decorators.length - 1; i >= 0; i--) {
127
+ ctx = __decoratorContext$1(k, name, done = {}, array[3], extraInitializers);
128
+ it = (0, decorators[i])(target, ctx), done._ = 1;
129
+ __expectFn$1(it) && (target = it);
130
+ }
131
+ return __decoratorMetadata$1(array, target), desc && __defProp$1(target, name, desc), p ? k ^ 4 ? extra : desc : target;
132
+ };
133
+ var _Plugin_decorators, _init$1;
134
+ _Plugin_decorators = [Inspectable({
135
+ serialize: (plugin) => ({
136
+ name: plugin._.name,
137
+ dependencies: plugin._.dependencies
138
+ })
139
+ })];
140
+ class Plugin {
141
+ /**
142
+ * @internal
143
+ * Set of Plugin data
144
+ *
145
+ *
146
+ */
147
+ _ = {
148
+ /** Name of plugin */
149
+ name: "",
150
+ /** List of plugin dependencies. If user does't extend from listed there dependencies it throw a error */
151
+ dependencies: [],
152
+ /** remap generic type. {} in runtime */
153
+ Errors: {},
154
+ /** remap generic type. {} in runtime */
155
+ Derives: {},
156
+ /** Composer */
157
+ composer: new Composer(),
158
+ /** Store plugin preRequests hooks */
159
+ preRequests: [],
160
+ /** Store plugin onResponses hooks */
161
+ onResponses: [],
162
+ /** Store plugin onResponseErrors hooks */
163
+ onResponseErrors: [],
164
+ /**
165
+ * Store plugin groups
166
+ *
167
+ * If you use `on` or `use` in group and on plugin-level groups handlers are registered after plugin-level handlers
168
+ * */
169
+ groups: [],
170
+ /** Store plugin onStarts hooks */
171
+ onStarts: [],
172
+ /** Store plugin onStops hooks */
173
+ onStops: [],
174
+ /** Store plugin onErrors hooks */
175
+ onErrors: [],
176
+ /** Map of plugin errors */
177
+ errorsDefinitions: {},
178
+ decorators: {}
179
+ };
180
+ /** Create new Plugin. Please provide `name` */
181
+ constructor(name, { dependencies } = {}) {
182
+ this._.name = name;
183
+ if (dependencies) this._.dependencies = dependencies;
184
+ }
185
+ /** Currently not isolated!!!
186
+ *
187
+ * > [!WARNING]
188
+ * > If you use `on` or `use` in a `group` and at the plugin level, the group handlers are registered **after** the handlers at the plugin level
189
+ */
190
+ group(grouped) {
191
+ this._.groups.push(grouped);
192
+ return this;
193
+ }
194
+ /**
195
+ * Register custom class-error in plugin
196
+ **/
197
+ error(kind, error) {
198
+ error[ErrorKind] = kind;
199
+ this._.errorsDefinitions[kind] = error;
200
+ return this;
201
+ }
202
+ derive(updateNameOrHandler, handler) {
203
+ this._.composer.derive(updateNameOrHandler, handler);
204
+ return this;
205
+ }
206
+ decorate(nameOrValue, value) {
207
+ if (typeof nameOrValue === "string") this._.decorators[nameOrValue] = value;
208
+ else {
209
+ for (const [name, value2] of Object.entries(nameOrValue)) {
210
+ this._.decorators[name] = value2;
211
+ }
212
+ }
213
+ return this;
214
+ }
215
+ /** Register handler to one or many Updates */
216
+ on(updateName, handler) {
217
+ this._.composer.on(updateName, handler);
218
+ return this;
219
+ }
220
+ /** Register handler to any Updates */
221
+ use(handler) {
222
+ this._.composer.use(handler);
223
+ return this;
224
+ }
225
+ preRequest(methodsOrHandler, handler) {
226
+ if ((typeof methodsOrHandler === "string" || Array.isArray(methodsOrHandler)) && handler)
227
+ this._.preRequests.push([handler, methodsOrHandler]);
228
+ else if (typeof methodsOrHandler === "function")
229
+ this._.preRequests.push([methodsOrHandler, void 0]);
230
+ return this;
231
+ }
232
+ onResponse(methodsOrHandler, handler) {
233
+ if ((typeof methodsOrHandler === "string" || Array.isArray(methodsOrHandler)) && handler)
234
+ this._.onResponses.push([handler, methodsOrHandler]);
235
+ else if (typeof methodsOrHandler === "function")
236
+ this._.onResponses.push([methodsOrHandler, void 0]);
237
+ return this;
238
+ }
239
+ onResponseError(methodsOrHandler, handler) {
240
+ if ((typeof methodsOrHandler === "string" || Array.isArray(methodsOrHandler)) && handler)
241
+ this._.onResponseErrors.push([handler, methodsOrHandler]);
242
+ else if (typeof methodsOrHandler === "function")
243
+ this._.onResponseErrors.push([methodsOrHandler, void 0]);
244
+ return this;
245
+ }
246
+ /**
247
+ * This hook called when the bot is `started`.
248
+ *
249
+ * @example
250
+ * ```typescript
251
+ * import { Bot } from "gramio";
252
+ *
253
+ * const bot = new Bot(process.env.TOKEN!).onStart(
254
+ * ({ plugins, info, updatesFrom }) => {
255
+ * console.log(`plugin list - ${plugins.join(", ")}`);
256
+ * console.log(`bot username is @${info.username}`);
257
+ * console.log(`updates from ${updatesFrom}`);
258
+ * }
259
+ * );
260
+ *
261
+ * bot.start();
262
+ * ```
263
+ *
264
+ * [Documentation](https://gramio.dev/hooks/on-start.html)
265
+ * */
266
+ onStart(handler) {
267
+ this._.onStarts.push(handler);
268
+ return this;
269
+ }
270
+ /**
271
+ * This hook called when the bot stops.
272
+ *
273
+ * @example
274
+ * ```typescript
275
+ * import { Bot } from "gramio";
276
+ *
277
+ * const bot = new Bot(process.env.TOKEN!).onStop(
278
+ * ({ plugins, info, updatesFrom }) => {
279
+ * console.log(`plugin list - ${plugins.join(", ")}`);
280
+ * console.log(`bot username is @${info.username}`);
281
+ * }
282
+ * );
283
+ *
284
+ * bot.start();
285
+ * bot.stop();
286
+ * ```
287
+ *
288
+ * [Documentation](https://gramio.dev/hooks/on-stop.html)
289
+ * */
290
+ onStop(handler) {
291
+ this._.onStops.push(handler);
292
+ return this;
293
+ }
294
+ onError(updateNameOrHandler, handler) {
295
+ if (typeof updateNameOrHandler === "function") {
296
+ this._.onErrors.push(updateNameOrHandler);
297
+ return this;
298
+ }
299
+ if (handler) {
300
+ this._.onErrors.push(async (errContext) => {
301
+ if (errContext.context.is(updateNameOrHandler))
302
+ await handler(errContext);
303
+ });
304
+ }
305
+ return this;
306
+ }
307
+ }
308
+ _init$1 = __decoratorStart$1();
309
+ Plugin = __decorateElement$1(_init$1, 0, "Plugin", _Plugin_decorators, Plugin);
310
+ __runInitializers$1(_init$1, 1, Plugin);
311
+
312
+ const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
313
+ class Updates {
314
+ bot;
315
+ isStarted = false;
316
+ offset = 0;
317
+ composer;
318
+ constructor(bot, onError) {
319
+ this.bot = bot;
320
+ this.composer = new Composer(onError);
321
+ }
322
+ async handleUpdate(data) {
323
+ const updateType = Object.keys(data).at(1);
324
+ this.offset = data.update_id + 1;
325
+ const UpdateContext = contextsMappings[updateType];
326
+ if (!UpdateContext) throw new Error(updateType);
327
+ const updatePayload = data[updateType];
328
+ if (!updatePayload) throw new Error("");
329
+ try {
330
+ let context = new UpdateContext({
331
+ bot: this.bot,
332
+ update: data,
333
+ // @ts-expect-error
334
+ payload: updatePayload,
335
+ type: updateType,
336
+ updateId: data.update_id
337
+ });
338
+ if ("isEvent" in context && context.isEvent() && context.eventType) {
339
+ const payload = data.message ?? data.edited_message ?? data.channel_post ?? data.edited_channel_post ?? data.business_message;
340
+ if (!payload) throw new Error("Unsupported event??");
341
+ context = new contextsMappings[context.eventType]({
342
+ bot: this.bot,
343
+ update: data,
344
+ payload,
345
+ // @ts-expect-error
346
+ type: context.eventType,
347
+ updateId: data.update_id
348
+ });
349
+ }
350
+ this.composer.compose(context);
351
+ } catch (error) {
352
+ throw new Error(`[UPDATES] Update type ${updateType} not supported.`);
353
+ }
354
+ }
355
+ /** @deprecated use bot.start instead @internal */
356
+ async startPolling(params = {}) {
357
+ if (this.isStarted) throw new Error("[UPDATES] Polling already started!");
358
+ this.isStarted = true;
359
+ this.startFetchLoop(params);
360
+ return;
361
+ }
362
+ async startFetchLoop(params = {}) {
363
+ while (this.isStarted) {
364
+ try {
365
+ const updates = await this.bot.api.getUpdates({
366
+ ...params,
367
+ offset: this.offset
368
+ });
369
+ for await (const update of updates) {
370
+ await this.handleUpdate(update).catch(console.error);
371
+ }
372
+ } catch (error) {
373
+ console.error("Error received when fetching updates", error);
374
+ await sleep(this.bot.options.api.retryGetUpdatesWait ?? 1e3);
375
+ }
376
+ }
377
+ }
378
+ stopPolling() {
379
+ this.isStarted = false;
380
+ }
381
+ }
382
+
383
+ var __create = Object.create;
384
+ var __defProp = Object.defineProperty;
385
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
386
+ var __knownSymbol = (name2, symbol) => (symbol = Symbol[name2]) ? symbol : Symbol.for("Symbol." + name2);
387
+ var __typeError = (msg) => {
388
+ throw TypeError(msg);
389
+ };
390
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
391
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
392
+ var __decoratorStart = (base) => [, , , __create(null)];
393
+ var __decoratorStrings = ["class", "method", "getter", "setter", "accessor", "field", "value", "get", "set"];
394
+ var __expectFn = (fn) => fn !== void 0 && typeof fn !== "function" ? __typeError("Function expected") : fn;
395
+ var __decoratorContext = (kind, name2, done, metadata, fns) => ({ kind: __decoratorStrings[kind], name: name2, metadata, addInitializer: (fn) => done._ ? __typeError("Already initialized") : fns.push(__expectFn(fn || null)) });
396
+ var __decoratorMetadata = (array, target) => __defNormalProp(target, __knownSymbol("metadata"), array[3]);
397
+ var __runInitializers = (array, flags, self, value) => {
398
+ for (var i = 0, fns = array[flags >> 1], n = fns && fns.length; i < n; i++) fns[i].call(self) ;
399
+ return value;
400
+ };
401
+ var __decorateElement = (array, flags, name2, decorators, target, extra) => {
402
+ var it, done, ctx, k = flags & 7, p = !!(flags & 16);
403
+ var j = 0;
404
+ var extraInitializers = array[j] || (array[j] = []);
405
+ var desc = k && ((target = target.prototype), k < 5 && (k > 3 || !p) && __getOwnPropDesc(target , name2));
406
+ __name(target, name2);
407
+ for (var i = decorators.length - 1; i >= 0; i--) {
408
+ ctx = __decoratorContext(k, name2, done = {}, array[3], extraInitializers);
409
+ it = (0, decorators[i])(target, ctx), done._ = 1;
410
+ __expectFn(it) && (target = it);
411
+ }
412
+ return __decoratorMetadata(array, target), desc && __defProp(target, name2, desc), p ? k ^ 4 ? extra : desc : target;
413
+ };
414
+ var _Bot_decorators, _init;
415
+ const $debugger = debug("gramio");
416
+ const debug$api = $debugger.extend("api");
417
+ _Bot_decorators = [Inspectable({
418
+ serialize: () => ({})
419
+ })];
420
+ class Bot {
421
+ _ = {
422
+ /** @internal. Remap generic */
423
+ derives: {}
424
+ };
425
+ /** @internal. Remap generic */
426
+ __Derives;
427
+ filters = {
428
+ context: (name2) => (context) => context.is(name2)
429
+ };
430
+ /** Options provided to instance */
431
+ options;
432
+ /** Bot data (filled in when calling bot.init/bot.start) */
433
+ info;
434
+ /**
435
+ * Send API Request to Telegram Bot API
436
+ *
437
+ * @example
438
+ * ```ts
439
+ * const response = await bot.api.sendMessage({
440
+ * chat_id: "@gramio_forum",
441
+ * text: "some text",
442
+ * });
443
+ * ```
444
+ *
445
+ * [Documentation](https://gramio.dev/bot-api.html)
446
+ */
447
+ api = new Proxy({}, {
448
+ get: (_target, method) => (
449
+ // @ts-expect-error
450
+ // biome-ignore lint/suspicious/noAssignInExpressions: <explanation>
451
+ _target[method] ??= (args) => this._callApi(method, args)
452
+ )
453
+ });
454
+ lazyloadPlugins = [];
455
+ dependencies = [];
456
+ errorsDefinitions = {
457
+ TELEGRAM: TelegramError
458
+ };
459
+ errorHandler(context, error) {
460
+ if (!this.hooks.onError.length)
461
+ return console.error("[Default Error Handler]", context, error);
462
+ return this.runImmutableHooks("onError", {
463
+ context,
464
+ //@ts-expect-error ErrorKind exists if user register error-class with .error("kind", SomeError);
465
+ kind: error.constructor[ErrorKind] ?? "UNKNOWN",
466
+ error
467
+ });
468
+ }
469
+ /** This instance handle updates */
470
+ updates = new Updates(this, this.errorHandler.bind(this));
471
+ hooks = {
472
+ preRequest: [],
473
+ onResponse: [],
474
+ onResponseError: [],
475
+ onError: [],
476
+ onStart: [],
477
+ onStop: []
478
+ };
479
+ constructor(tokenOrOptions, optionsRaw) {
480
+ const token = typeof tokenOrOptions === "string" ? tokenOrOptions : tokenOrOptions?.token;
481
+ const options = typeof tokenOrOptions === "object" ? tokenOrOptions : optionsRaw;
482
+ if (!token || typeof token !== "string")
483
+ throw new Error(`Token is ${typeof token} but it should be a string!`);
484
+ this.options = {
485
+ ...options,
486
+ token,
487
+ api: {
488
+ baseURL: "https://api.telegram.org/bot",
489
+ retryGetUpdatesWait: 1e3,
490
+ ...options?.api
491
+ }
492
+ };
493
+ if (options?.info) this.info = options.info;
494
+ if (!(optionsRaw?.plugins && "format" in optionsRaw.plugins && !optionsRaw.plugins.format))
495
+ this.extend(
496
+ new Plugin("@gramio/format").preRequest((context) => {
497
+ if (!context.params) return context;
498
+ const formattable = FormattableMap[context.method];
499
+ if (formattable) context.params = formattable(context.params);
500
+ return context;
501
+ })
502
+ );
503
+ }
504
+ async runHooks(type, context) {
505
+ let data = context;
506
+ for await (const hook of this.hooks[type]) {
507
+ data = await hook(data);
508
+ }
509
+ return data;
510
+ }
511
+ async runImmutableHooks(type, ...context) {
512
+ for await (const hook of this.hooks[type]) {
513
+ await hook(...context);
514
+ }
515
+ }
516
+ async _callApi(method, params = {}) {
517
+ const debug$api$method = debug$api.extend(method);
518
+ const url = `${this.options.api.baseURL}${this.options.token}/${method}`;
519
+ const reqOptions = {
520
+ method: "POST",
521
+ ...this.options.api.fetchOptions,
522
+ // @ts-ignore types node/bun and global missmatch
523
+ headers: new Headers(this.options.api.fetchOptions?.headers)
524
+ };
525
+ const context = await this.runHooks(
526
+ "preRequest",
527
+ // TODO: fix type error
528
+ // @ts-expect-error
529
+ {
530
+ method,
531
+ params
532
+ }
533
+ );
534
+ params = context.params;
535
+ if (params && isMediaUpload(method, params)) {
536
+ const formData = await convertJsonToFormData(method, params);
537
+ reqOptions.body = formData;
538
+ } else {
539
+ reqOptions.headers.set("Content-Type", "application/json");
540
+ reqOptions.body = JSON.stringify(params);
541
+ }
542
+ debug$api$method("options: %j", reqOptions);
543
+ const response = await fetch(url, reqOptions);
544
+ const data = await response.json();
545
+ debug$api$method("response: %j", data);
546
+ if (!data.ok) {
547
+ const err = new TelegramError(data, method, params);
548
+ this.runImmutableHooks("onResponseError", err, this.api);
549
+ if (!params?.suppress) throw err;
550
+ return err;
551
+ }
552
+ this.runImmutableHooks(
553
+ "onResponse",
554
+ // TODO: fix type error
555
+ // @ts-expect-error
556
+ {
557
+ method,
558
+ params,
559
+ response: data.result
560
+ }
561
+ );
562
+ return data.result;
563
+ }
564
+ async downloadFile(attachment, path) {
565
+ function getFileId(attachment2) {
566
+ if (attachment2 instanceof PhotoAttachment) {
567
+ return attachment2.bigSize.fileId;
568
+ }
569
+ if ("fileId" in attachment2 && typeof attachment2.fileId === "string")
570
+ return attachment2.fileId;
571
+ if ("file_id" in attachment2) return attachment2.file_id;
572
+ throw new Error("Invalid attachment");
573
+ }
574
+ const fileId = typeof attachment === "string" ? attachment : getFileId(attachment);
575
+ const file = await this.api.getFile({ file_id: fileId });
576
+ const url = `${this.options.api.baseURL.replace("/bot", "/file/bot")}${this.options.token}/${file.file_path}`;
577
+ const res = await fetch(url);
578
+ if (path) {
579
+ if (!res.body)
580
+ throw new Error("Response without body (should be never throw)");
581
+ await fs.writeFile(path, Readable.fromWeb(res.body));
582
+ return path;
583
+ }
584
+ const buffer = await res.arrayBuffer();
585
+ return buffer;
586
+ }
587
+ /**
588
+ * Register custom class-error for type-safe catch in `onError` hook
589
+ *
590
+ * @example
591
+ * ```ts
592
+ * export class NoRights extends Error {
593
+ * needRole: "admin" | "moderator";
594
+ *
595
+ * constructor(role: "admin" | "moderator") {
596
+ * super();
597
+ * this.needRole = role;
598
+ * }
599
+ * }
600
+ *
601
+ * const bot = new Bot(process.env.TOKEN!)
602
+ * .error("NO_RIGHTS", NoRights)
603
+ * .onError(({ context, kind, error }) => {
604
+ * if (context.is("message") && kind === "NO_RIGHTS")
605
+ * return context.send(
606
+ * format`You don't have enough rights! You need to have an «${bold(
607
+ * error.needRole
608
+ * )}» role.`
609
+ * );
610
+ * });
611
+ *
612
+ * bot.updates.on("message", (context) => {
613
+ * if (context.text === "bun") throw new NoRights("admin");
614
+ * });
615
+ * ```
616
+ */
617
+ error(kind, error) {
618
+ error[ErrorKind] = kind;
619
+ this.errorsDefinitions[kind] = error;
620
+ return this;
621
+ }
622
+ onError(updateNameOrHandler, handler) {
623
+ if (typeof updateNameOrHandler === "function") {
624
+ this.hooks.onError.push(updateNameOrHandler);
625
+ return this;
626
+ }
627
+ if (handler) {
628
+ this.hooks.onError.push(async (errContext) => {
629
+ if (errContext.context.is(updateNameOrHandler))
630
+ await handler(errContext);
631
+ });
632
+ }
633
+ return this;
634
+ }
635
+ derive(updateNameOrHandler, handler) {
636
+ this.updates.composer.derive(updateNameOrHandler, handler);
637
+ return this;
638
+ }
639
+ decorate(nameOrRecordValue, value) {
640
+ for (const contextName of Object.keys(contextsMappings)) {
641
+ if (typeof nameOrRecordValue === "string")
642
+ Object.defineProperty(contextsMappings[contextName].prototype, name, {
643
+ value
644
+ });
645
+ else
646
+ Object.defineProperties(
647
+ // @ts-expect-error
648
+ contextsMappings[contextName].prototype,
649
+ Object.keys(nameOrRecordValue).reduce(
650
+ (acc, key) => {
651
+ acc[key] = { value: nameOrRecordValue[key] };
652
+ return acc;
653
+ },
654
+ {}
655
+ )
656
+ );
657
+ }
658
+ return this;
659
+ }
660
+ /**
661
+ * This hook called when the bot is `started`.
662
+ *
663
+ * @example
664
+ * ```typescript
665
+ * import { Bot } from "gramio";
666
+ *
667
+ * const bot = new Bot(process.env.TOKEN!).onStart(
668
+ * ({ plugins, info, updatesFrom }) => {
669
+ * console.log(`plugin list - ${plugins.join(", ")}`);
670
+ * console.log(`bot username is @${info.username}`);
671
+ * console.log(`updates from ${updatesFrom}`);
672
+ * }
673
+ * );
674
+ *
675
+ * bot.start();
676
+ * ```
677
+ *
678
+ * [Documentation](https://gramio.dev/hooks/on-start.html)
679
+ * */
680
+ onStart(handler) {
681
+ this.hooks.onStart.push(handler);
682
+ return this;
683
+ }
684
+ /**
685
+ * This hook called when the bot stops.
686
+ *
687
+ * @example
688
+ * ```typescript
689
+ * import { Bot } from "gramio";
690
+ *
691
+ * const bot = new Bot(process.env.TOKEN!).onStop(
692
+ * ({ plugins, info, updatesFrom }) => {
693
+ * console.log(`plugin list - ${plugins.join(", ")}`);
694
+ * console.log(`bot username is @${info.username}`);
695
+ * }
696
+ * );
697
+ *
698
+ * bot.start();
699
+ * bot.stop();
700
+ * ```
701
+ *
702
+ * [Documentation](https://gramio.dev/hooks/on-stop.html)
703
+ * */
704
+ onStop(handler) {
705
+ this.hooks.onStop.push(handler);
706
+ return this;
707
+ }
708
+ preRequest(methodsOrHandler, handler) {
709
+ if (typeof methodsOrHandler === "string" || Array.isArray(methodsOrHandler)) {
710
+ if (!handler) throw new Error("TODO:");
711
+ const methods = typeof methodsOrHandler === "string" ? [methodsOrHandler] : methodsOrHandler;
712
+ this.hooks.preRequest.push(async (context) => {
713
+ if (methods.includes(context.method)) return handler(context);
714
+ return context;
715
+ });
716
+ } else this.hooks.preRequest.push(methodsOrHandler);
717
+ return this;
718
+ }
719
+ onResponse(methodsOrHandler, handler) {
720
+ if (typeof methodsOrHandler === "string" || Array.isArray(methodsOrHandler)) {
721
+ if (!handler) throw new Error("TODO:");
722
+ const methods = typeof methodsOrHandler === "string" ? [methodsOrHandler] : methodsOrHandler;
723
+ this.hooks.onResponse.push(async (context) => {
724
+ if (methods.includes(context.method)) return handler(context);
725
+ return context;
726
+ });
727
+ } else this.hooks.onResponse.push(methodsOrHandler);
728
+ return this;
729
+ }
730
+ onResponseError(methodsOrHandler, handler) {
731
+ if (typeof methodsOrHandler === "string" || Array.isArray(methodsOrHandler)) {
732
+ if (!handler) throw new Error("TODO:");
733
+ const methods = typeof methodsOrHandler === "string" ? [methodsOrHandler] : methodsOrHandler;
734
+ this.hooks.onResponseError.push(async (context) => {
735
+ if (methods.includes(context.method)) return handler(context);
736
+ return context;
737
+ });
738
+ } else this.hooks.onResponseError.push(methodsOrHandler);
739
+ return this;
740
+ }
741
+ // onExperimental(
742
+ // // filter: Filters,
743
+ // filter: (
744
+ // f: Filters<
745
+ // Context<typeof this> & Derives["global"],
746
+ // [{ equal: { prop: number }; addition: { some: () => 2 } }]
747
+ // >,
748
+ // ) => Filters,
749
+ // handler: Handler<Context<typeof this> & Derives["global"]>,
750
+ // ) {}
751
+ /** Register handler to one or many Updates */
752
+ on(updateName, handler) {
753
+ this.updates.composer.on(updateName, handler);
754
+ return this;
755
+ }
756
+ /** Register handler to any Updates */
757
+ use(handler) {
758
+ this.updates.composer.use(handler);
759
+ return this;
760
+ }
761
+ /**
762
+ * Extend {@link Plugin} logic and types
763
+ *
764
+ * @example
765
+ * ```ts
766
+ * import { Plugin, Bot } from "gramio";
767
+ *
768
+ * export class PluginError extends Error {
769
+ * wow: "type" | "safe" = "type";
770
+ * }
771
+ *
772
+ * const plugin = new Plugin("gramio-example")
773
+ * .error("PLUGIN", PluginError)
774
+ * .derive(() => {
775
+ * return {
776
+ * some: ["derived", "props"] as const,
777
+ * };
778
+ * });
779
+ *
780
+ * const bot = new Bot(process.env.TOKEN!)
781
+ * .extend(plugin)
782
+ * .onError(({ context, kind, error }) => {
783
+ * if (context.is("message") && kind === "PLUGIN") {
784
+ * console.log(error.wow);
785
+ * }
786
+ * })
787
+ * .use((context) => {
788
+ * console.log(context.some);
789
+ * });
790
+ * ```
791
+ */
792
+ extend(plugin) {
793
+ if (plugin instanceof Promise) {
794
+ this.lazyloadPlugins.push(plugin);
795
+ return this;
796
+ }
797
+ if (plugin._.dependencies.some((dep) => !this.dependencies.includes(dep)))
798
+ throw new Error(
799
+ `The \xAB${plugin._.name}\xBB plugin needs dependencies registered before: ${plugin._.dependencies.filter((dep) => !this.dependencies.includes(dep)).join(", ")}`
800
+ );
801
+ if (plugin._.composer.length) {
802
+ this.use(plugin._.composer.composed);
803
+ }
804
+ this.decorate(plugin._.decorators);
805
+ for (const [key, value] of Object.entries(plugin._.errorsDefinitions)) {
806
+ if (this.errorsDefinitions[key]) this.errorsDefinitions[key] = value;
807
+ }
808
+ for (const value of plugin._.preRequests) {
809
+ const [preRequest, updateName] = value;
810
+ if (!updateName) this.preRequest(preRequest);
811
+ else this.preRequest(updateName, preRequest);
812
+ }
813
+ for (const value of plugin._.onResponses) {
814
+ const [onResponse, updateName] = value;
815
+ if (!updateName) this.onResponse(onResponse);
816
+ else this.onResponse(updateName, onResponse);
817
+ }
818
+ for (const value of plugin._.onResponseErrors) {
819
+ const [onResponseError, updateName] = value;
820
+ if (!updateName) this.onResponseError(onResponseError);
821
+ else this.onResponseError(updateName, onResponseError);
822
+ }
823
+ for (const handler of plugin._.groups) {
824
+ this.group(handler);
825
+ }
826
+ this.dependencies.push(plugin._.name);
827
+ return this;
828
+ }
829
+ /**
830
+ * Register handler to reaction (`message_reaction` update)
831
+ *
832
+ * @example
833
+ * ```ts
834
+ * new Bot().reaction("👍", async (context) => {
835
+ * await context.reply(`Thank you!`);
836
+ * });
837
+ * ```
838
+ * */
839
+ reaction(trigger, handler) {
840
+ const reactions = Array.isArray(trigger) ? trigger : [trigger];
841
+ return this.on("message_reaction", (context, next) => {
842
+ const newReactions = [];
843
+ for (const reaction of context.newReactions) {
844
+ if (reaction.type !== "emoji") continue;
845
+ const foundIndex = context.oldReactions.findIndex(
846
+ (oldReaction) => oldReaction.type === "emoji" && oldReaction.emoji === reaction.emoji
847
+ );
848
+ if (foundIndex === -1) {
849
+ newReactions.push(reaction);
850
+ } else {
851
+ context.oldReactions.splice(foundIndex, 1);
852
+ }
853
+ }
854
+ if (!newReactions.some(
855
+ (x) => x.type === "emoji" && reactions.includes(x.emoji)
856
+ ))
857
+ return next();
858
+ return handler(context);
859
+ });
860
+ }
861
+ /**
862
+ * Register handler to `callback_query` event
863
+ *
864
+ * @example
865
+ * ```ts
866
+ * const someData = new CallbackData("example").number("id");
867
+ *
868
+ * new Bot()
869
+ * .command("start", (context) =>
870
+ * context.send("some", {
871
+ * reply_markup: new InlineKeyboard().text(
872
+ * "example",
873
+ * someData.pack({
874
+ * id: 1,
875
+ * })
876
+ * ),
877
+ * })
878
+ * )
879
+ * .callbackQuery(someData, (context) => {
880
+ * context.queryData; // is type-safe
881
+ * });
882
+ * ```
883
+ */
884
+ callbackQuery(trigger, handler) {
885
+ return this.on("callback_query", (context, next) => {
886
+ if (!context.data) return next();
887
+ if (typeof trigger === "string" && context.data !== trigger)
888
+ return next();
889
+ if (trigger instanceof CallbackData && !trigger.regexp().test(context.data))
890
+ return next();
891
+ if (trigger instanceof RegExp && !trigger.test(context.data))
892
+ return next();
893
+ if (trigger instanceof CallbackData)
894
+ context.queryData = trigger.unpack(context.data);
895
+ return handler(context);
896
+ });
897
+ }
898
+ /** Register handler to `chosen_inline_result` update */
899
+ chosenInlineResult(trigger, handler) {
900
+ return this.on("chosen_inline_result", (context, next) => {
901
+ if (typeof trigger === "string" && context.query === trigger || // @ts-expect-error
902
+ typeof trigger === "function" && trigger(context) || trigger instanceof RegExp && trigger.test(context.query)) {
903
+ context.args = trigger instanceof RegExp ? context.query?.match(trigger) : null;
904
+ return handler(context);
905
+ }
906
+ return next();
907
+ });
908
+ }
909
+ /**
910
+ * Register handler to `inline_query` update
911
+ *
912
+ * @example
913
+ * ```ts
914
+ * new Bot().inlineQuery(
915
+ * /regular expression with (.*)/i,
916
+ * async (context) => {
917
+ * if (context.args) {
918
+ * await context.answer(
919
+ * [
920
+ * InlineQueryResult.article(
921
+ * "id-1",
922
+ * context.args[1],
923
+ * InputMessageContent.text("some"),
924
+ * {
925
+ * reply_markup: new InlineKeyboard().text(
926
+ * "some",
927
+ * "callback-data"
928
+ * ),
929
+ * }
930
+ * ),
931
+ * ],
932
+ * {
933
+ * cache_time: 0,
934
+ * }
935
+ * );
936
+ * }
937
+ * },
938
+ * {
939
+ * onResult: (context) => context.editText("Message edited!"),
940
+ * }
941
+ * );
942
+ * ```
943
+ * */
944
+ inlineQuery(trigger, handler, options = {}) {
945
+ if (options.onResult) this.chosenInlineResult(trigger, options.onResult);
946
+ return this.on("inline_query", (context, next) => {
947
+ if (typeof trigger === "string" && context.query === trigger || // @ts-expect-error
948
+ typeof trigger === "function" && trigger(context) || trigger instanceof RegExp && trigger.test(context.query)) {
949
+ context.args = trigger instanceof RegExp ? context.query?.match(trigger) : null;
950
+ return handler(context);
951
+ }
952
+ return next();
953
+ });
954
+ }
955
+ /**
956
+ * Register handler to `message` and `business_message` event
957
+ *
958
+ * new Bot().hears(/regular expression with (.*)/i, async (context) => {
959
+ * if (context.args) await context.send(`Params ${context.args[1]}`);
960
+ * });
961
+ */
962
+ hears(trigger, handler) {
963
+ return this.on("message", (context, next) => {
964
+ const text = context.text ?? context.caption;
965
+ if (typeof trigger === "string" && text === trigger || // @ts-expect-error
966
+ typeof trigger === "function" && trigger(context) || trigger instanceof RegExp && text && trigger.test(text)) {
967
+ context.args = trigger instanceof RegExp ? text?.match(trigger) : null;
968
+ return handler(context);
969
+ }
970
+ return next();
971
+ });
972
+ }
973
+ /**
974
+ * Register handler to `message` and `business_message` event when entities contains a command
975
+ *
976
+ * new Bot().command("start", async (context) => {
977
+ * return context.send(`You message is /start ${context.args}`);
978
+ * });
979
+ */
980
+ command(command, handler, options) {
981
+ if (command.startsWith("/"))
982
+ throw new Error("Do not use / in command name");
983
+ return this.on(["message", "business_message"], (context, next) => {
984
+ if (context.entities?.some((entity) => {
985
+ if (entity.type !== "bot_command" || entity.offset > 0) return false;
986
+ const cmd = context.text?.slice(1, entity.length)?.replace(`@${this.info.username}`, "");
987
+ context.args = context.text?.slice(entity.length).trim() || null;
988
+ return cmd === command;
989
+ }))
990
+ return handler(context);
991
+ return next();
992
+ });
993
+ }
994
+ /** Currently not isolated!!! */
995
+ group(grouped) {
996
+ return grouped(this);
997
+ }
998
+ /**
999
+ * Init bot. Call it manually only if you doesn't use {@link Bot.start}
1000
+ */
1001
+ async init() {
1002
+ await Promise.all(
1003
+ this.lazyloadPlugins.map(async (plugin) => this.extend(await plugin))
1004
+ );
1005
+ if (!this.info) {
1006
+ const info = await this.api.getMe({
1007
+ suppress: true
1008
+ });
1009
+ if (info instanceof TelegramError) {
1010
+ if (info.code === 404)
1011
+ info.message = "The bot token is incorrect. Check it in BotFather.";
1012
+ throw info;
1013
+ }
1014
+ this.info = info;
1015
+ }
1016
+ }
1017
+ /**
1018
+ * Start receive updates via long-polling or webhook
1019
+ *
1020
+ * @example
1021
+ * ```ts
1022
+ * import { Bot } from "gramio";
1023
+ *
1024
+ * const bot = new Bot("") // put you token here
1025
+ * .command("start", (context) => context.send("Hi!"))
1026
+ * .onStart(console.log);
1027
+ *
1028
+ * bot.start();
1029
+ * ```
1030
+ */
1031
+ async start({
1032
+ webhook,
1033
+ dropPendingUpdates,
1034
+ allowedUpdates
1035
+ } = {}) {
1036
+ await this.init();
1037
+ if (!webhook) {
1038
+ await this.api.deleteWebhook({
1039
+ drop_pending_updates: dropPendingUpdates
1040
+ });
1041
+ await this.updates.startPolling({
1042
+ allowed_updates: allowedUpdates
1043
+ });
1044
+ this.runImmutableHooks("onStart", {
1045
+ plugins: this.dependencies,
1046
+ // biome-ignore lint/style/noNonNullAssertion: bot.init() guarantees this.info
1047
+ info: this.info,
1048
+ updatesFrom: "long-polling"
1049
+ });
1050
+ return this.info;
1051
+ }
1052
+ if (this.updates.isStarted) this.updates.stopPolling();
1053
+ await this.api.setWebhook({
1054
+ ...webhook,
1055
+ drop_pending_updates: dropPendingUpdates,
1056
+ allowed_updates: allowedUpdates
1057
+ });
1058
+ this.runImmutableHooks("onStart", {
1059
+ plugins: this.dependencies,
1060
+ // biome-ignore lint/style/noNonNullAssertion: bot.init() guarantees this.info
1061
+ info: this.info,
1062
+ updatesFrom: "webhook"
1063
+ });
1064
+ return this.info;
1065
+ }
1066
+ /**
1067
+ * Stops receiving events via long-polling or webhook
1068
+ * Currently does not implement graceful shutdown
1069
+ * */
1070
+ async stop() {
1071
+ if (this.updates.isStarted) this.updates.stopPolling();
1072
+ else await this.api.deleteWebhook();
1073
+ await this.runImmutableHooks("onStop", {
1074
+ plugins: this.dependencies,
1075
+ // biome-ignore lint/style/noNonNullAssertion: bot.init() guarantees this.info
1076
+ info: this.info
1077
+ });
1078
+ }
1079
+ }
1080
+ _init = __decoratorStart();
1081
+ Bot = __decorateElement(_init, 0, "Bot", _Bot_decorators, Bot);
1082
+ __runInitializers(_init, 1, Bot);
1083
+
1084
+ const SECRET_TOKEN_HEADER = "X-Telegram-Bot-Api-Secret-Token";
1085
+ const WRONG_TOKEN_ERROR = "secret token is invalid";
1086
+ const responseOK = new Response("ok!");
1087
+ const responseUnauthorized = new Response(WRONG_TOKEN_ERROR, {
1088
+ status: 401
1089
+ // @ts-ignore
1090
+ });
1091
+ const frameworks = {
1092
+ elysia: ({ body, headers }) => ({
1093
+ update: body,
1094
+ header: headers[SECRET_TOKEN_HEADER],
1095
+ unauthorized: () => responseUnauthorized
1096
+ }),
1097
+ fastify: (request, reply) => ({
1098
+ update: request.body,
1099
+ header: request.headers[SECRET_TOKEN_HEADER],
1100
+ unauthorized: () => reply.code(401).send(WRONG_TOKEN_ERROR)
1101
+ }),
1102
+ hono: (c) => ({
1103
+ update: c.req.json(),
1104
+ header: c.req.header(SECRET_TOKEN_HEADER),
1105
+ unauthorized: () => c.text(WRONG_TOKEN_ERROR, 401)
1106
+ }),
1107
+ express: (req, res) => ({
1108
+ update: req.body,
1109
+ header: req.header(SECRET_TOKEN_HEADER),
1110
+ unauthorized: () => res.status(401).send(WRONG_TOKEN_ERROR)
1111
+ }),
1112
+ koa: (ctx) => ({
1113
+ update: ctx.request.body,
1114
+ header: ctx.get(SECRET_TOKEN_HEADER),
1115
+ unauthorized: () => {
1116
+ ctx.status === 401;
1117
+ ctx.body = WRONG_TOKEN_ERROR;
1118
+ }
1119
+ }),
1120
+ http: (req, res) => ({
1121
+ update: new Promise((resolve) => {
1122
+ let body = "";
1123
+ req.on("data", (chunk) => {
1124
+ body += chunk.toString();
1125
+ });
1126
+ req.on("end", () => resolve(JSON.parse(body)));
1127
+ }),
1128
+ header: req.headers[SECRET_TOKEN_HEADER.toLowerCase()],
1129
+ unauthorized: () => res.writeHead(401).end(WRONG_TOKEN_ERROR)
1130
+ }),
1131
+ "std/http": (req) => ({
1132
+ update: req.json(),
1133
+ header: req.headers.get(SECRET_TOKEN_HEADER),
1134
+ response: () => responseOK,
1135
+ unauthorized: () => responseUnauthorized
1136
+ }),
1137
+ "Bun.serve": (req) => ({
1138
+ update: req.json(),
1139
+ header: req.headers.get(SECRET_TOKEN_HEADER),
1140
+ response: () => responseOK,
1141
+ unauthorized: () => responseUnauthorized
1142
+ })
1143
+ };
1144
+
1145
+ function webhookHandler(bot, framework, secretToken) {
1146
+ const frameworkAdapter = frameworks[framework];
1147
+ return async (...args) => {
1148
+ const { update, response, header, unauthorized } = frameworkAdapter(
1149
+ ...args
1150
+ );
1151
+ if (secretToken && header !== secretToken) return unauthorized();
1152
+ await bot.updates.handleUpdate(await update);
1153
+ if (response) return response();
1154
+ };
1155
+ }
1156
+
23
1157
  Symbol.metadata ??= Symbol("Symbol.metadata");
24
- __exportStar(require("./bot"), exports);
25
- __exportStar(require("./errors"), exports);
26
- __exportStar(require("./types"), exports);
27
- __exportStar(require("./plugin"), exports);
28
- __exportStar(require("./webhook/index"), exports);
29
- __exportStar(require("@gramio/contexts"), exports);
30
- __exportStar(require("@gramio/files"), exports);
31
- __exportStar(require("@gramio/keyboards"), exports);
32
- __exportStar(require("@gramio/format"), exports);
33
- __exportStar(require("@gramio/callback-data"), exports);
1158
+
1159
+ export { Bot, ErrorKind, Plugin, TelegramError, webhookHandler };