gramio 0.10.0 → 0.12.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 CHANGED
@@ -5,8 +5,6 @@ var contexts = require('@gramio/contexts');
5
5
  var files = require('@gramio/files');
6
6
  var format = require('@gramio/format');
7
7
  var keyboards = require('@gramio/keyboards');
8
- var fs = require('node:fs/promises');
9
- var node_stream = require('node:stream');
10
8
  var debug = require('debug');
11
9
  var utils = require('./utils-BKLnNOBm.cjs');
12
10
  var composer = require('@gramio/composer');
@@ -199,9 +197,17 @@ const methods = composer.defineComposerMethods({
199
197
  return macroHandler ? macroHandler(context, composer.noopNext) : handler(context);
200
198
  });
201
199
  }
200
+ if (typeof trigger === "function") {
201
+ return this.on("chosen_inline_result", (context, next) => {
202
+ if (!trigger(context)) return next();
203
+ context.args = null;
204
+ return macroHandler ? macroHandler(context, composer.noopNext) : handler(context);
205
+ });
206
+ }
202
207
  return this.on("chosen_inline_result", (context, next) => {
203
- if (!trigger(context)) return next();
208
+ if (!context.resultId || !trigger.filter(context.resultId)) return next();
204
209
  context.args = null;
210
+ context.queryData = trigger.unpack(context.resultId);
205
211
  return macroHandler ? macroHandler(context, composer.noopNext) : handler(context);
206
212
  });
207
213
  },
@@ -719,6 +725,7 @@ class Updates {
719
725
  })
720
726
  );
721
727
  this.isRequestActive = false;
728
+ if (!this.isStarted) break;
722
729
  const updateId = updates.at(-1)?.update_id;
723
730
  this.offset = updateId ? updateId + 1 : this.offset;
724
731
  this.queue.addBatch(updates);
@@ -938,28 +945,26 @@ class Bot {
938
945
  }
939
946
  return fn();
940
947
  }
941
- async downloadFile(attachment, path) {
942
- function getFileId(attachment2) {
943
- if (attachment2 instanceof contexts.PhotoAttachment) {
944
- return attachment2.bigSize.fileId;
945
- }
946
- if ("fileId" in attachment2 && typeof attachment2.fileId === "string")
947
- return attachment2.fileId;
948
- if ("file_id" in attachment2) return attachment2.file_id;
949
- throw new Error("Invalid attachment");
950
- }
951
- const fileId = typeof attachment === "string" ? attachment : getFileId(attachment);
952
- const file = await this.api.getFile({ file_id: fileId });
953
- const url = `${this.options.api.baseURL.replace("/bot", "/file/bot")}${this.options.token}/${file.file_path}`;
954
- const res = await fetch(url);
955
- if (path) {
956
- if (!res.body)
957
- throw new Error("Response without body (should be never throw)");
958
- await fs.writeFile(path, node_stream.Readable.fromWeb(res.body));
959
- return path;
960
- }
961
- const buffer = await res.arrayBuffer();
962
- return buffer;
948
+ downloadFile(attachment, path) {
949
+ const input = attachment;
950
+ return path ? files.downloadFile(this, input, path) : files.downloadFile(this, input);
951
+ }
952
+ /**
953
+ * Get a shareable download link for a file.
954
+ *
955
+ * When {@link BotOptions.files | `files.baseURL`} is set (e.g. a local Bot API
956
+ * server with the bundled file server), the link is **token-less and path-based**
957
+ * — safe to hand to users. Otherwise it falls back to the classic
958
+ * `…/file/bot<token>/<path>` URL (which contains the bot token).
959
+ *
960
+ * @example
961
+ * ```ts
962
+ * const link = await bot.getFileLink(ctx.document.fileId);
963
+ * await ctx.reply(`Download: ${link}`);
964
+ * ```
965
+ */
966
+ getFileLink(attachment) {
967
+ return files.downloadFile(this, attachment).link();
963
968
  }
964
969
  /**
965
970
  * Register custom class-error for type-safe catch in `onError` hook
@@ -1303,7 +1308,32 @@ class Bot {
1303
1308
  );
1304
1309
  return this;
1305
1310
  }
1306
- /** Register handler to `chosen_inline_result` update */
1311
+ /**
1312
+ * Register handler to `chosen_inline_result` update
1313
+ *
1314
+ * Accepts a `CallbackData` schema for type-safe filtering on `result_id`:
1315
+ *
1316
+ * @example
1317
+ * ```ts
1318
+ * const trackRef = new CallbackData("track").string("src").string("id");
1319
+ *
1320
+ * new Bot()
1321
+ * .on("inline_query", async (ctx) => {
1322
+ * await ctx.answer(tracks.map((t) => ({
1323
+ * type: "audio",
1324
+ * id: trackRef.pack({ src: t.source, id: t.id }),
1325
+ * audio_url: t.url,
1326
+ * title: t.title,
1327
+ * })));
1328
+ * })
1329
+ * .chosenInlineResult(trackRef, (ctx) => {
1330
+ * ctx.queryData; // { src: string; id: string }
1331
+ * });
1332
+ * ```
1333
+ *
1334
+ * String/RegExp/predicate triggers filter on `context.query` (the user's
1335
+ * typed text); the `CallbackData` schema filters on `context.resultId`.
1336
+ */
1307
1337
  chosenInlineResult(trigger, handler, options) {
1308
1338
  this.updates.composer.chosenInlineResult(
1309
1339
  trigger,
package/dist/index.d.cts CHANGED
@@ -4,6 +4,7 @@ export * from '@gramio/callback-data';
4
4
  import * as _gramio_contexts from '@gramio/contexts';
5
5
  import { UpdateName, MessageEventName, CustomEventName, Context, ContextsMapping, BotLike, ContextType, Attachment, AttachmentsMapping, Dice, MessageOriginUser, MessageOriginChat, MessageOriginChannel, MessageOriginHiddenUser, Message, MessageEntity, TextQuote, User, LinkPreviewOptions, ExternalReplyInfo, Chat, Giveaway, PaidMediaInfo, Game, StoryAttachment, Venue, MessageContext } from '@gramio/contexts';
6
6
  export * from '@gramio/contexts';
7
+ import { FileSource, TelegramFileDownload } from '@gramio/files';
7
8
  export * from '@gramio/files';
8
9
  export * from '@gramio/format';
9
10
  export * from '@gramio/keyboards';
@@ -12,7 +13,7 @@ import { APIMethods, TelegramResponseParameters, TelegramAPIResponseError, Teleg
12
13
  export * from '@gramio/types';
13
14
  import * as _gramio_composer from '@gramio/composer';
14
15
  import { ComposerLike, MacroDefinitions, EventContextOf, EventComposer, MacroDef, Next, EventQueue, HandlerOptions, DeriveFromOptions } from '@gramio/composer';
15
- export { ContextCallback, DeriveFromOptions, EventComposer, EventQueue, HandlerOptions, MacroDef, MacroDefinitions, MacroDeriveType, MacroHooks, MacroOptionType, Middleware, Next, WithCtx, buildFromOptions, compose, noopNext, skip, stop } from '@gramio/composer';
16
+ export { ContextCallback, DeriveFromOptions, DeriveHandler, EventComposer, EventQueue, HandlerOptions, MacroDef, MacroDefinitions, MacroDeriveType, MacroHooks, MacroOptionType, Middleware, Next, WithCtx, WithDecorate, WithDerives, WithEventDerive, WithExtend, buildFromOptions, compose, noopNext, skip, stop } from '@gramio/composer';
16
17
 
17
18
  /**
18
19
  * Telegram Bot API top-level update type name.
@@ -184,8 +185,9 @@ declare const methods: {
184
185
  callbackQuery<TThis extends GramIOLike<TThis>, Trigger extends CallbackData | string | RegExp>(this: TThis, trigger: Trigger, handler: (context: Ctx<"callback_query"> & {
185
186
  queryData: Trigger extends CallbackData ? ReturnType<Trigger["unpack"]> : Trigger extends RegExp ? RegExpMatchArray : never;
186
187
  } & EventContextOf<TThis, "callback_query">) => unknown, macroOptions?: Record<string, unknown>): TThis;
187
- chosenInlineResult<TThis extends GramIOLike<TThis>>(this: TThis, trigger: RegExp | string | ((context: Ctx<"chosen_inline_result">) => boolean), handler: (context: Ctx<"chosen_inline_result"> & {
188
+ chosenInlineResult<TThis extends GramIOLike<TThis>, Trigger extends CallbackData | RegExp | string | ((context: Ctx<"chosen_inline_result">) => boolean)>(this: TThis, trigger: Trigger, handler: (context: Ctx<"chosen_inline_result"> & {
188
189
  args: RegExpMatchArray | null;
190
+ queryData: Trigger extends CallbackData ? ReturnType<Trigger["unpack"]> : never;
189
191
  } & EventContextOf<TThis, "chosen_inline_result">) => unknown, macroOptions?: Record<string, unknown>): TThis;
190
192
  inlineQuery<TThis extends GramIOLike<TThis>>(this: TThis, triggerOrHandler: RegExp | string | ((context: Ctx<"inline_query">) => boolean) | ((context: Ctx<"inline_query"> & {
191
193
  args: RegExpMatchArray | null;
@@ -233,8 +235,9 @@ declare const Composer: _gramio_composer.EventComposerConstructor<Context<AnyBot
233
235
  callbackQuery<TThis extends GramIOLike<TThis>, Trigger extends CallbackData | string | RegExp>(this: TThis, trigger: Trigger, handler: (context: Ctx<"callback_query"> & {
234
236
  queryData: Trigger extends CallbackData ? ReturnType<Trigger["unpack"]> : Trigger extends RegExp ? RegExpMatchArray : never;
235
237
  } & EventContextOf<TThis, "callback_query">) => unknown, macroOptions?: Record<string, unknown>): TThis;
236
- chosenInlineResult<TThis extends GramIOLike<TThis>>(this: TThis, trigger: RegExp | string | ((context: Ctx<"chosen_inline_result">) => boolean), handler: (context: Ctx<"chosen_inline_result"> & {
238
+ chosenInlineResult<TThis extends GramIOLike<TThis>, Trigger extends CallbackData | RegExp | string | ((context: Ctx<"chosen_inline_result">) => boolean)>(this: TThis, trigger: Trigger, handler: (context: Ctx<"chosen_inline_result"> & {
237
239
  args: RegExpMatchArray | null;
240
+ queryData: Trigger extends CallbackData ? ReturnType<Trigger["unpack"]> : never;
238
241
  } & EventContextOf<TThis, "chosen_inline_result">) => unknown, macroOptions?: Record<string, unknown>): TThis;
239
242
  inlineQuery<TThis extends GramIOLike<TThis>>(this: TThis, triggerOrHandler: RegExp | string | ((context: Ctx<"inline_query">) => boolean) | ((context: Ctx<"inline_query"> & {
240
243
  args: RegExpMatchArray | null;
@@ -424,8 +427,9 @@ declare class Plugin<Errors extends ErrorDefinitions = {}, Derives extends Deriv
424
427
  Derives?: Record<string, object>;
425
428
  };
426
429
  chosenInlineResult(trigger: any, handler: any, macroOptions?: any): TThis;
427
- }>(this: TThis, trigger: RegExp | string | ((context: _gramio_contexts.ChosenInlineResultContext<AnyBot>) => boolean), handler: (context: _gramio_contexts.ChosenInlineResultContext<AnyBot> & {
430
+ }, Trigger extends _gramio_callback_data.CallbackData | RegExp | string | ((context: _gramio_contexts.ChosenInlineResultContext<AnyBot>) => boolean)>(this: TThis, trigger: Trigger, handler: (context: _gramio_contexts.ChosenInlineResultContext<AnyBot> & {
428
431
  args: RegExpMatchArray | null;
432
+ queryData: Trigger extends _gramio_callback_data.CallbackData ? ReturnType<Trigger["unpack"]> : never;
429
433
  } & _gramio_composer.EventContextOf<TThis, "chosen_inline_result">) => unknown, macroOptions?: Record<string, unknown>): TThis;
430
434
  inlineQuery<TThis extends _gramio_composer.ComposerLike<TThis> & {
431
435
  "~": {
@@ -513,8 +517,9 @@ declare class Plugin<Errors extends ErrorDefinitions = {}, Derives extends Deriv
513
517
  Derives?: Record<string, object>;
514
518
  };
515
519
  chosenInlineResult(trigger: any, handler: any, macroOptions?: any): TThis;
516
- }>(this: TThis, trigger: RegExp | string | ((context: _gramio_contexts.ChosenInlineResultContext<AnyBot>) => boolean), handler: (context: _gramio_contexts.ChosenInlineResultContext<AnyBot> & {
520
+ }, Trigger extends _gramio_callback_data.CallbackData | RegExp | string | ((context: _gramio_contexts.ChosenInlineResultContext<AnyBot>) => boolean)>(this: TThis, trigger: Trigger, handler: (context: _gramio_contexts.ChosenInlineResultContext<AnyBot> & {
517
521
  args: RegExpMatchArray | null;
522
+ queryData: Trigger extends _gramio_callback_data.CallbackData ? ReturnType<Trigger["unpack"]> : never;
518
523
  } & _gramio_composer.EventContextOf<TThis, "chosen_inline_result">) => unknown, macroOptions?: Record<string, unknown>): TThis;
519
524
  inlineQuery<TThis extends _gramio_composer.ComposerLike<TThis> & {
520
525
  "~": {
@@ -840,6 +845,30 @@ interface BotOptions {
840
845
  */
841
846
  retryGetUpdatesWait?: number;
842
847
  };
848
+ /**
849
+ * File download/serving options — mainly for a **local Bot API server**.
850
+ *
851
+ * @example
852
+ * ```ts
853
+ * const bot = new Bot(token, {
854
+ * api: { baseURL: "http://telegram-bot-api:8081/bot" },
855
+ * files: {
856
+ * // bot.getFileLink() and ctx.download() resolve to this (token-less) URL
857
+ * baseURL: "http://telegram-bot-api:8080",
858
+ * },
859
+ * });
860
+ * ```
861
+ */
862
+ files?: {
863
+ /** How to fetch file bytes. @default "auto" */
864
+ source?: FileSource;
865
+ /** Working directory of the local Bot API server — the prefix of the absolute `file_path` it returns. @default "/var/lib/telegram-bot-api" */
866
+ localDir?: string;
867
+ /** Where that working dir is mounted on the bot's side (for `source: "disk"` when bot & server share a volume at a different path). Defaults to `localDir`. */
868
+ mountDir?: string;
869
+ /** Public base URL where the working dir is served (e.g. the bundled file server / nginx). Enables token-less {@link Bot.getFileLink} and `source: "rewrite"`. */
870
+ baseURL?: string;
871
+ };
843
872
  }
844
873
  /**
845
874
  * Handler is a function with context and next function arguments
@@ -1248,10 +1277,27 @@ declare class Bot<Errors extends ErrorDefinitions = {}, Derives extends DeriveDe
1248
1277
  */
1249
1278
  downloadFile(attachment: Attachment | {
1250
1279
  file_id: string;
1251
- } | string): Promise<ArrayBuffer>;
1280
+ } | string): TelegramFileDownload;
1252
1281
  downloadFile(attachment: Attachment | {
1253
1282
  file_id: string;
1254
1283
  } | string, path: string): Promise<string>;
1284
+ /**
1285
+ * Get a shareable download link for a file.
1286
+ *
1287
+ * When {@link BotOptions.files | `files.baseURL`} is set (e.g. a local Bot API
1288
+ * server with the bundled file server), the link is **token-less and path-based**
1289
+ * — safe to hand to users. Otherwise it falls back to the classic
1290
+ * `…/file/bot<token>/<path>` URL (which contains the bot token).
1291
+ *
1292
+ * @example
1293
+ * ```ts
1294
+ * const link = await bot.getFileLink(ctx.document.fileId);
1295
+ * await ctx.reply(`Download: ${link}`);
1296
+ * ```
1297
+ */
1298
+ getFileLink(attachment: Attachment | {
1299
+ file_id: string;
1300
+ } | string): Promise<string>;
1255
1301
  /**
1256
1302
  * Register custom class-error for type-safe catch in `onError` hook
1257
1303
  *
@@ -1524,9 +1570,35 @@ declare class Bot<Errors extends ErrorDefinitions = {}, Derives extends DeriveDe
1524
1570
  * ```
1525
1571
  */
1526
1572
  callbackQuery<Trigger extends CallbackData | string | RegExp, TOptions extends HandlerOptions<CallbackQueryShorthandContext<typeof this, Trigger>, Macros> = {}>(trigger: Trigger, handler: (context: CallbackQueryShorthandContext<typeof this, Trigger> & DeriveFromOptions<Macros, TOptions>) => unknown, options?: TOptions): this;
1527
- /** Register handler to `chosen_inline_result` update */
1528
- chosenInlineResult<Ctx = ContextType<typeof this, "chosen_inline_result">, TOptions extends HandlerOptions<Ctx, Macros> = {}>(trigger: RegExp | string | ((context: Ctx) => boolean), handler: (context: Ctx & {
1573
+ /**
1574
+ * Register handler to `chosen_inline_result` update
1575
+ *
1576
+ * Accepts a `CallbackData` schema for type-safe filtering on `result_id`:
1577
+ *
1578
+ * @example
1579
+ * ```ts
1580
+ * const trackRef = new CallbackData("track").string("src").string("id");
1581
+ *
1582
+ * new Bot()
1583
+ * .on("inline_query", async (ctx) => {
1584
+ * await ctx.answer(tracks.map((t) => ({
1585
+ * type: "audio",
1586
+ * id: trackRef.pack({ src: t.source, id: t.id }),
1587
+ * audio_url: t.url,
1588
+ * title: t.title,
1589
+ * })));
1590
+ * })
1591
+ * .chosenInlineResult(trackRef, (ctx) => {
1592
+ * ctx.queryData; // { src: string; id: string }
1593
+ * });
1594
+ * ```
1595
+ *
1596
+ * String/RegExp/predicate triggers filter on `context.query` (the user's
1597
+ * typed text); the `CallbackData` schema filters on `context.resultId`.
1598
+ */
1599
+ chosenInlineResult<Trigger extends CallbackData | RegExp | string | ((context: ContextType<typeof this, "chosen_inline_result">) => boolean), Ctx = ContextType<typeof this, "chosen_inline_result">, TOptions extends HandlerOptions<Ctx, Macros> = {}>(trigger: Trigger, handler: (context: Ctx & {
1529
1600
  args: RegExpMatchArray | null;
1601
+ queryData: Trigger extends CallbackData ? ReturnType<Trigger["unpack"]> : never;
1530
1602
  } & DeriveFromOptions<Macros, TOptions>) => unknown, options?: TOptions): this;
1531
1603
  /**
1532
1604
  * Register handler to `inline_query` update
package/dist/index.d.ts CHANGED
@@ -4,6 +4,7 @@ export * from '@gramio/callback-data';
4
4
  import * as _gramio_contexts from '@gramio/contexts';
5
5
  import { UpdateName, MessageEventName, CustomEventName, Context, ContextsMapping, BotLike, ContextType, Attachment, AttachmentsMapping, Dice, MessageOriginUser, MessageOriginChat, MessageOriginChannel, MessageOriginHiddenUser, Message, MessageEntity, TextQuote, User, LinkPreviewOptions, ExternalReplyInfo, Chat, Giveaway, PaidMediaInfo, Game, StoryAttachment, Venue, MessageContext } from '@gramio/contexts';
6
6
  export * from '@gramio/contexts';
7
+ import { FileSource, TelegramFileDownload } from '@gramio/files';
7
8
  export * from '@gramio/files';
8
9
  export * from '@gramio/format';
9
10
  export * from '@gramio/keyboards';
@@ -12,7 +13,7 @@ import { APIMethods, TelegramResponseParameters, TelegramAPIResponseError, Teleg
12
13
  export * from '@gramio/types';
13
14
  import * as _gramio_composer from '@gramio/composer';
14
15
  import { ComposerLike, MacroDefinitions, EventContextOf, EventComposer, MacroDef, Next, EventQueue, HandlerOptions, DeriveFromOptions } from '@gramio/composer';
15
- export { ContextCallback, DeriveFromOptions, EventComposer, EventQueue, HandlerOptions, MacroDef, MacroDefinitions, MacroDeriveType, MacroHooks, MacroOptionType, Middleware, Next, WithCtx, buildFromOptions, compose, noopNext, skip, stop } from '@gramio/composer';
16
+ export { ContextCallback, DeriveFromOptions, DeriveHandler, EventComposer, EventQueue, HandlerOptions, MacroDef, MacroDefinitions, MacroDeriveType, MacroHooks, MacroOptionType, Middleware, Next, WithCtx, WithDecorate, WithDerives, WithEventDerive, WithExtend, buildFromOptions, compose, noopNext, skip, stop } from '@gramio/composer';
16
17
 
17
18
  /**
18
19
  * Telegram Bot API top-level update type name.
@@ -184,8 +185,9 @@ declare const methods: {
184
185
  callbackQuery<TThis extends GramIOLike<TThis>, Trigger extends CallbackData | string | RegExp>(this: TThis, trigger: Trigger, handler: (context: Ctx<"callback_query"> & {
185
186
  queryData: Trigger extends CallbackData ? ReturnType<Trigger["unpack"]> : Trigger extends RegExp ? RegExpMatchArray : never;
186
187
  } & EventContextOf<TThis, "callback_query">) => unknown, macroOptions?: Record<string, unknown>): TThis;
187
- chosenInlineResult<TThis extends GramIOLike<TThis>>(this: TThis, trigger: RegExp | string | ((context: Ctx<"chosen_inline_result">) => boolean), handler: (context: Ctx<"chosen_inline_result"> & {
188
+ chosenInlineResult<TThis extends GramIOLike<TThis>, Trigger extends CallbackData | RegExp | string | ((context: Ctx<"chosen_inline_result">) => boolean)>(this: TThis, trigger: Trigger, handler: (context: Ctx<"chosen_inline_result"> & {
188
189
  args: RegExpMatchArray | null;
190
+ queryData: Trigger extends CallbackData ? ReturnType<Trigger["unpack"]> : never;
189
191
  } & EventContextOf<TThis, "chosen_inline_result">) => unknown, macroOptions?: Record<string, unknown>): TThis;
190
192
  inlineQuery<TThis extends GramIOLike<TThis>>(this: TThis, triggerOrHandler: RegExp | string | ((context: Ctx<"inline_query">) => boolean) | ((context: Ctx<"inline_query"> & {
191
193
  args: RegExpMatchArray | null;
@@ -233,8 +235,9 @@ declare const Composer: _gramio_composer.EventComposerConstructor<Context<AnyBot
233
235
  callbackQuery<TThis extends GramIOLike<TThis>, Trigger extends CallbackData | string | RegExp>(this: TThis, trigger: Trigger, handler: (context: Ctx<"callback_query"> & {
234
236
  queryData: Trigger extends CallbackData ? ReturnType<Trigger["unpack"]> : Trigger extends RegExp ? RegExpMatchArray : never;
235
237
  } & EventContextOf<TThis, "callback_query">) => unknown, macroOptions?: Record<string, unknown>): TThis;
236
- chosenInlineResult<TThis extends GramIOLike<TThis>>(this: TThis, trigger: RegExp | string | ((context: Ctx<"chosen_inline_result">) => boolean), handler: (context: Ctx<"chosen_inline_result"> & {
238
+ chosenInlineResult<TThis extends GramIOLike<TThis>, Trigger extends CallbackData | RegExp | string | ((context: Ctx<"chosen_inline_result">) => boolean)>(this: TThis, trigger: Trigger, handler: (context: Ctx<"chosen_inline_result"> & {
237
239
  args: RegExpMatchArray | null;
240
+ queryData: Trigger extends CallbackData ? ReturnType<Trigger["unpack"]> : never;
238
241
  } & EventContextOf<TThis, "chosen_inline_result">) => unknown, macroOptions?: Record<string, unknown>): TThis;
239
242
  inlineQuery<TThis extends GramIOLike<TThis>>(this: TThis, triggerOrHandler: RegExp | string | ((context: Ctx<"inline_query">) => boolean) | ((context: Ctx<"inline_query"> & {
240
243
  args: RegExpMatchArray | null;
@@ -424,8 +427,9 @@ declare class Plugin<Errors extends ErrorDefinitions = {}, Derives extends Deriv
424
427
  Derives?: Record<string, object>;
425
428
  };
426
429
  chosenInlineResult(trigger: any, handler: any, macroOptions?: any): TThis;
427
- }>(this: TThis, trigger: RegExp | string | ((context: _gramio_contexts.ChosenInlineResultContext<AnyBot>) => boolean), handler: (context: _gramio_contexts.ChosenInlineResultContext<AnyBot> & {
430
+ }, Trigger extends _gramio_callback_data.CallbackData | RegExp | string | ((context: _gramio_contexts.ChosenInlineResultContext<AnyBot>) => boolean)>(this: TThis, trigger: Trigger, handler: (context: _gramio_contexts.ChosenInlineResultContext<AnyBot> & {
428
431
  args: RegExpMatchArray | null;
432
+ queryData: Trigger extends _gramio_callback_data.CallbackData ? ReturnType<Trigger["unpack"]> : never;
429
433
  } & _gramio_composer.EventContextOf<TThis, "chosen_inline_result">) => unknown, macroOptions?: Record<string, unknown>): TThis;
430
434
  inlineQuery<TThis extends _gramio_composer.ComposerLike<TThis> & {
431
435
  "~": {
@@ -513,8 +517,9 @@ declare class Plugin<Errors extends ErrorDefinitions = {}, Derives extends Deriv
513
517
  Derives?: Record<string, object>;
514
518
  };
515
519
  chosenInlineResult(trigger: any, handler: any, macroOptions?: any): TThis;
516
- }>(this: TThis, trigger: RegExp | string | ((context: _gramio_contexts.ChosenInlineResultContext<AnyBot>) => boolean), handler: (context: _gramio_contexts.ChosenInlineResultContext<AnyBot> & {
520
+ }, Trigger extends _gramio_callback_data.CallbackData | RegExp | string | ((context: _gramio_contexts.ChosenInlineResultContext<AnyBot>) => boolean)>(this: TThis, trigger: Trigger, handler: (context: _gramio_contexts.ChosenInlineResultContext<AnyBot> & {
517
521
  args: RegExpMatchArray | null;
522
+ queryData: Trigger extends _gramio_callback_data.CallbackData ? ReturnType<Trigger["unpack"]> : never;
518
523
  } & _gramio_composer.EventContextOf<TThis, "chosen_inline_result">) => unknown, macroOptions?: Record<string, unknown>): TThis;
519
524
  inlineQuery<TThis extends _gramio_composer.ComposerLike<TThis> & {
520
525
  "~": {
@@ -840,6 +845,30 @@ interface BotOptions {
840
845
  */
841
846
  retryGetUpdatesWait?: number;
842
847
  };
848
+ /**
849
+ * File download/serving options — mainly for a **local Bot API server**.
850
+ *
851
+ * @example
852
+ * ```ts
853
+ * const bot = new Bot(token, {
854
+ * api: { baseURL: "http://telegram-bot-api:8081/bot" },
855
+ * files: {
856
+ * // bot.getFileLink() and ctx.download() resolve to this (token-less) URL
857
+ * baseURL: "http://telegram-bot-api:8080",
858
+ * },
859
+ * });
860
+ * ```
861
+ */
862
+ files?: {
863
+ /** How to fetch file bytes. @default "auto" */
864
+ source?: FileSource;
865
+ /** Working directory of the local Bot API server — the prefix of the absolute `file_path` it returns. @default "/var/lib/telegram-bot-api" */
866
+ localDir?: string;
867
+ /** Where that working dir is mounted on the bot's side (for `source: "disk"` when bot & server share a volume at a different path). Defaults to `localDir`. */
868
+ mountDir?: string;
869
+ /** Public base URL where the working dir is served (e.g. the bundled file server / nginx). Enables token-less {@link Bot.getFileLink} and `source: "rewrite"`. */
870
+ baseURL?: string;
871
+ };
843
872
  }
844
873
  /**
845
874
  * Handler is a function with context and next function arguments
@@ -1248,10 +1277,27 @@ declare class Bot<Errors extends ErrorDefinitions = {}, Derives extends DeriveDe
1248
1277
  */
1249
1278
  downloadFile(attachment: Attachment | {
1250
1279
  file_id: string;
1251
- } | string): Promise<ArrayBuffer>;
1280
+ } | string): TelegramFileDownload;
1252
1281
  downloadFile(attachment: Attachment | {
1253
1282
  file_id: string;
1254
1283
  } | string, path: string): Promise<string>;
1284
+ /**
1285
+ * Get a shareable download link for a file.
1286
+ *
1287
+ * When {@link BotOptions.files | `files.baseURL`} is set (e.g. a local Bot API
1288
+ * server with the bundled file server), the link is **token-less and path-based**
1289
+ * — safe to hand to users. Otherwise it falls back to the classic
1290
+ * `…/file/bot<token>/<path>` URL (which contains the bot token).
1291
+ *
1292
+ * @example
1293
+ * ```ts
1294
+ * const link = await bot.getFileLink(ctx.document.fileId);
1295
+ * await ctx.reply(`Download: ${link}`);
1296
+ * ```
1297
+ */
1298
+ getFileLink(attachment: Attachment | {
1299
+ file_id: string;
1300
+ } | string): Promise<string>;
1255
1301
  /**
1256
1302
  * Register custom class-error for type-safe catch in `onError` hook
1257
1303
  *
@@ -1524,9 +1570,35 @@ declare class Bot<Errors extends ErrorDefinitions = {}, Derives extends DeriveDe
1524
1570
  * ```
1525
1571
  */
1526
1572
  callbackQuery<Trigger extends CallbackData | string | RegExp, TOptions extends HandlerOptions<CallbackQueryShorthandContext<typeof this, Trigger>, Macros> = {}>(trigger: Trigger, handler: (context: CallbackQueryShorthandContext<typeof this, Trigger> & DeriveFromOptions<Macros, TOptions>) => unknown, options?: TOptions): this;
1527
- /** Register handler to `chosen_inline_result` update */
1528
- chosenInlineResult<Ctx = ContextType<typeof this, "chosen_inline_result">, TOptions extends HandlerOptions<Ctx, Macros> = {}>(trigger: RegExp | string | ((context: Ctx) => boolean), handler: (context: Ctx & {
1573
+ /**
1574
+ * Register handler to `chosen_inline_result` update
1575
+ *
1576
+ * Accepts a `CallbackData` schema for type-safe filtering on `result_id`:
1577
+ *
1578
+ * @example
1579
+ * ```ts
1580
+ * const trackRef = new CallbackData("track").string("src").string("id");
1581
+ *
1582
+ * new Bot()
1583
+ * .on("inline_query", async (ctx) => {
1584
+ * await ctx.answer(tracks.map((t) => ({
1585
+ * type: "audio",
1586
+ * id: trackRef.pack({ src: t.source, id: t.id }),
1587
+ * audio_url: t.url,
1588
+ * title: t.title,
1589
+ * })));
1590
+ * })
1591
+ * .chosenInlineResult(trackRef, (ctx) => {
1592
+ * ctx.queryData; // { src: string; id: string }
1593
+ * });
1594
+ * ```
1595
+ *
1596
+ * String/RegExp/predicate triggers filter on `context.query` (the user's
1597
+ * typed text); the `CallbackData` schema filters on `context.resultId`.
1598
+ */
1599
+ chosenInlineResult<Trigger extends CallbackData | RegExp | string | ((context: ContextType<typeof this, "chosen_inline_result">) => boolean), Ctx = ContextType<typeof this, "chosen_inline_result">, TOptions extends HandlerOptions<Ctx, Macros> = {}>(trigger: Trigger, handler: (context: Ctx & {
1529
1600
  args: RegExpMatchArray | null;
1601
+ queryData: Trigger extends CallbackData ? ReturnType<Trigger["unpack"]> : never;
1530
1602
  } & DeriveFromOptions<Macros, TOptions>) => unknown, options?: TOptions): this;
1531
1603
  /**
1532
1604
  * Register handler to `inline_query` update
package/dist/index.js CHANGED
@@ -1,13 +1,11 @@
1
1
  export * from '@gramio/callback-data';
2
- import { contextsMappings, PhotoAttachment } from '@gramio/contexts';
2
+ import { contextsMappings } from '@gramio/contexts';
3
3
  export * from '@gramio/contexts';
4
- import { isMediaUpload, convertJsonToFormData, extractFilesToFormData } from '@gramio/files';
4
+ import { downloadFile, isMediaUpload, convertJsonToFormData, extractFilesToFormData } from '@gramio/files';
5
5
  export * from '@gramio/files';
6
6
  import { FormattableMap } from '@gramio/format';
7
7
  export * from '@gramio/format';
8
8
  export * from '@gramio/keyboards';
9
- import fs from 'node:fs/promises';
10
- import { Readable } from 'node:stream';
11
9
  import debug from 'debug';
12
10
  import { E as ErrorKind, w as withRetries, T as TelegramError, s as sleep, d as debug$updates, a as simpleHash, I as IS_BUN, b as simplifyObject, t as timeoutWebhook } from './utils-1ejkKLIB.js';
13
11
  import { defineComposerMethods, buildFromOptions, noopNext, createComposer, eventTypes, EventQueue } from '@gramio/composer';
@@ -201,9 +199,17 @@ const methods = defineComposerMethods({
201
199
  return macroHandler ? macroHandler(context, noopNext) : handler(context);
202
200
  });
203
201
  }
202
+ if (typeof trigger === "function") {
203
+ return this.on("chosen_inline_result", (context, next) => {
204
+ if (!trigger(context)) return next();
205
+ context.args = null;
206
+ return macroHandler ? macroHandler(context, noopNext) : handler(context);
207
+ });
208
+ }
204
209
  return this.on("chosen_inline_result", (context, next) => {
205
- if (!trigger(context)) return next();
210
+ if (!context.resultId || !trigger.filter(context.resultId)) return next();
206
211
  context.args = null;
212
+ context.queryData = trigger.unpack(context.resultId);
207
213
  return macroHandler ? macroHandler(context, noopNext) : handler(context);
208
214
  });
209
215
  },
@@ -721,6 +727,7 @@ class Updates {
721
727
  })
722
728
  );
723
729
  this.isRequestActive = false;
730
+ if (!this.isStarted) break;
724
731
  const updateId = updates.at(-1)?.update_id;
725
732
  this.offset = updateId ? updateId + 1 : this.offset;
726
733
  this.queue.addBatch(updates);
@@ -940,28 +947,26 @@ class Bot {
940
947
  }
941
948
  return fn();
942
949
  }
943
- async downloadFile(attachment, path) {
944
- function getFileId(attachment2) {
945
- if (attachment2 instanceof PhotoAttachment) {
946
- return attachment2.bigSize.fileId;
947
- }
948
- if ("fileId" in attachment2 && typeof attachment2.fileId === "string")
949
- return attachment2.fileId;
950
- if ("file_id" in attachment2) return attachment2.file_id;
951
- throw new Error("Invalid attachment");
952
- }
953
- const fileId = typeof attachment === "string" ? attachment : getFileId(attachment);
954
- const file = await this.api.getFile({ file_id: fileId });
955
- const url = `${this.options.api.baseURL.replace("/bot", "/file/bot")}${this.options.token}/${file.file_path}`;
956
- const res = await fetch(url);
957
- if (path) {
958
- if (!res.body)
959
- throw new Error("Response without body (should be never throw)");
960
- await fs.writeFile(path, Readable.fromWeb(res.body));
961
- return path;
962
- }
963
- const buffer = await res.arrayBuffer();
964
- return buffer;
950
+ downloadFile(attachment, path) {
951
+ const input = attachment;
952
+ return path ? downloadFile(this, input, path) : downloadFile(this, input);
953
+ }
954
+ /**
955
+ * Get a shareable download link for a file.
956
+ *
957
+ * When {@link BotOptions.files | `files.baseURL`} is set (e.g. a local Bot API
958
+ * server with the bundled file server), the link is **token-less and path-based**
959
+ * — safe to hand to users. Otherwise it falls back to the classic
960
+ * `…/file/bot<token>/<path>` URL (which contains the bot token).
961
+ *
962
+ * @example
963
+ * ```ts
964
+ * const link = await bot.getFileLink(ctx.document.fileId);
965
+ * await ctx.reply(`Download: ${link}`);
966
+ * ```
967
+ */
968
+ getFileLink(attachment) {
969
+ return downloadFile(this, attachment).link();
965
970
  }
966
971
  /**
967
972
  * Register custom class-error for type-safe catch in `onError` hook
@@ -1305,7 +1310,32 @@ class Bot {
1305
1310
  );
1306
1311
  return this;
1307
1312
  }
1308
- /** Register handler to `chosen_inline_result` update */
1313
+ /**
1314
+ * Register handler to `chosen_inline_result` update
1315
+ *
1316
+ * Accepts a `CallbackData` schema for type-safe filtering on `result_id`:
1317
+ *
1318
+ * @example
1319
+ * ```ts
1320
+ * const trackRef = new CallbackData("track").string("src").string("id");
1321
+ *
1322
+ * new Bot()
1323
+ * .on("inline_query", async (ctx) => {
1324
+ * await ctx.answer(tracks.map((t) => ({
1325
+ * type: "audio",
1326
+ * id: trackRef.pack({ src: t.source, id: t.id }),
1327
+ * audio_url: t.url,
1328
+ * title: t.title,
1329
+ * })));
1330
+ * })
1331
+ * .chosenInlineResult(trackRef, (ctx) => {
1332
+ * ctx.queryData; // { src: string; id: string }
1333
+ * });
1334
+ * ```
1335
+ *
1336
+ * String/RegExp/predicate triggers filter on `context.query` (the user's
1337
+ * typed text); the `CallbackData` schema filters on `context.resultId`.
1338
+ */
1309
1339
  chosenInlineResult(trigger, handler, options) {
1310
1340
  this.updates.composer.chosenInlineResult(
1311
1341
  trigger,
package/dist/rich.cjs ADDED
@@ -0,0 +1,12 @@
1
+ 'use strict';
2
+
3
+ var rich = require('@gramio/format/rich');
4
+
5
+
6
+
7
+ Object.keys(rich).forEach(function (k) {
8
+ if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
9
+ enumerable: true,
10
+ get: function () { return rich[k]; }
11
+ });
12
+ });
@@ -0,0 +1 @@
1
+ export * from '@gramio/format/rich';
package/dist/rich.d.ts ADDED
@@ -0,0 +1 @@
1
+ export * from '@gramio/format/rich';
package/dist/rich.js ADDED
@@ -0,0 +1 @@
1
+ export * from '@gramio/format/rich';
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "gramio",
3
3
  "type": "module",
4
- "version": "0.10.0",
4
+ "version": "0.12.0",
5
5
  "description": "Powerful, extensible and really type-safe Telegram Bot API framework",
6
6
  "main": "dist/index.cjs",
7
7
  "module": "dist/index.js",
@@ -26,6 +26,16 @@
26
26
  "types": "./dist/utils.d.cts",
27
27
  "default": "./dist/utils.cjs"
28
28
  }
29
+ },
30
+ "./rich": {
31
+ "import": {
32
+ "types": "./dist/rich.d.ts",
33
+ "default": "./dist/rich.js"
34
+ },
35
+ "require": {
36
+ "types": "./dist/rich.d.cts",
37
+ "default": "./dist/rich.cjs"
38
+ }
29
39
  }
30
40
  },
31
41
  "keywords": [
@@ -65,12 +75,12 @@
65
75
  },
66
76
  "dependencies": {
67
77
  "@gramio/callback-data": "^0.1.0",
68
- "@gramio/composer": "^0.4.1",
69
- "@gramio/contexts": "^0.7.0",
70
- "@gramio/files": "^0.5.0",
71
- "@gramio/format": "^0.8.0",
78
+ "@gramio/composer": "^0.5.0",
79
+ "@gramio/contexts": "^0.9.0",
80
+ "@gramio/files": "^0.6.1",
81
+ "@gramio/format": "^0.9.0",
72
82
  "@gramio/keyboards": "^1.4.0",
73
- "@gramio/types": "^10.0.0",
83
+ "@gramio/types": "^10.1.0",
74
84
  "debug": "^4.4.3"
75
85
  },
76
86
  "files": [