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