grammy 1.14.0 → 1.15.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/README.md CHANGED
@@ -10,7 +10,7 @@
10
10
 
11
11
  <!-- deno-fmt-ignore-start -->
12
12
 
13
- [![Bot API](https://img.shields.io/badge/Bot%20API-6.4-blue?logo=telegram&style=flat&labelColor=000&color=3b82f6)](https://core.telegram.org/bots/api)
13
+ [![Bot API](https://img.shields.io/badge/Bot%20API-6.6-blue?logo=telegram&style=flat&labelColor=000&color=3b82f6)](https://core.telegram.org/bots/api)
14
14
  [![Deno](https://shield.deno.dev/x/grammy)](https://deno.land/x/grammy)
15
15
  [![npm](https://img.shields.io/npm/v/grammy?logo=npm&style=flat&labelColor=000&color=3b82f6)](https://www.npmjs.org/package/grammy)
16
16
  [![All Contributors](https://img.shields.io/github/all-contributors/grammyjs/grammy?style=flat&labelColor=000&color=3b82f6)](#contributors-)
@@ -245,7 +245,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
245
245
  <tr>
246
246
  <td align="center" valign="top" width="14.28%"><a href="http://ak4zh.com"><img src="https://avatars.githubusercontent.com/u/26350053?v=4?s=100" width="100px;" alt="ak4zh"/><br /><sub><b>ak4zh</b></sub></a><br /><a href="https://github.com/grammyjs/grammY/pulls?q=is%3Apr+reviewed-by%3Aak4zh" title="Reviewed Pull Requests">👀</a> <a href="#ideas-ak4zh" title="Ideas, Planning, & Feedback">🤔</a> <a href="https://github.com/grammyjs/grammY/commits?author=ak4zh" title="Code">💻</a></td>
247
247
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/nlapshin"><img src="https://avatars.githubusercontent.com/u/39495311?v=4?s=100" width="100px;" alt="Nikolay Lapshin"/><br /><sub><b>Nikolay Lapshin</b></sub></a><br /><a href="https://github.com/grammyjs/grammY/commits?author=nlapshin" title="Code">💻</a></td>
248
- <td align="center" valign="top" width="14.28%"><a href="https://github.com/Aquathing"><img src="https://avatars.githubusercontent.com/u/81624781?v=4?s=100" width="100px;" alt="Aquatica"/><br /><sub><b>Aquatica</b></sub></a><br /><a href="https://github.com/grammyjs/grammY/commits?author=Aquathing" title="Documentation">📖</a></td>
248
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/Aquathing"><img src="https://avatars.githubusercontent.com/u/81624781?v=4?s=100" width="100px;" alt="Aquatica"/><br /><sub><b>Aquatica</b></sub></a><br /><a href="https://github.com/grammyjs/grammY/commits?author=Aquathing" title="Documentation">📖</a> <a href="#question-Aquathing" title="Answering Questions">💬</a></td>
249
249
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/fadzikri"><img src="https://avatars.githubusercontent.com/u/109416385?v=4?s=100" width="100px;" alt="Fa Dzikri"/><br /><sub><b>Fa Dzikri</b></sub></a><br /><a href="https://github.com/grammyjs/grammY/pulls?q=is%3Apr+reviewed-by%3Afadzikri" title="Reviewed Pull Requests">👀</a> <a href="https://github.com/grammyjs/grammY/commits?author=fadzikri" title="Documentation">📖</a> <a href="#translation-fadzikri" title="Translation">🌍</a></td>
250
250
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/solidprinciples"><img src="https://avatars.githubusercontent.com/u/7939765?v=4?s=100" width="100px;" alt="Chandler Lattin"/><br /><sub><b>Chandler Lattin</b></sub></a><br /><a href="https://github.com/grammyjs/grammY/commits?author=solidprinciples" title="Code">💻</a> <a href="https://github.com/grammyjs/grammY/pulls?q=is%3Apr+reviewed-by%3Asolidprinciples" title="Reviewed Pull Requests">👀</a> <a href="#plugin-solidprinciples" title="Plugin/utility libraries">🔌</a></td>
251
251
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/sparfenyuk"><img src="https://avatars.githubusercontent.com/u/134065?v=4?s=100" width="100px;" alt="Sergey Parfenyuk"/><br /><sub><b>Sergey Parfenyuk</b></sub></a><br /><a href="https://github.com/grammyjs/grammY/issues?q=author%3Asparfenyuk" title="Bug reports">🐛</a></td>
@@ -256,7 +256,14 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
256
256
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/ByMsx"><img src="https://avatars.githubusercontent.com/u/5565836?v=4?s=100" width="100px;" alt="Maxim Lebedev"/><br /><sub><b>Maxim Lebedev</b></sub></a><br /><a href="#ideas-ByMsx" title="Ideas, Planning, & Feedback">🤔</a> <a href="https://github.com/grammyjs/grammY/commits?author=ByMsx" title="Code">💻</a></td>
257
257
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/Madnex"><img src="https://avatars.githubusercontent.com/u/14137610?v=4?s=100" width="100px;" alt="Madnex"/><br /><sub><b>Madnex</b></sub></a><br /><a href="https://github.com/grammyjs/grammY/commits?author=Madnex" title="Documentation">📖</a></td>
258
258
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/tup4ienko"><img src="https://avatars.githubusercontent.com/u/89318613?v=4?s=100" width="100px;" alt="Svyatoslav Tupchienko"/><br /><sub><b>Svyatoslav Tupchienko</b></sub></a><br /><a href="https://github.com/grammyjs/grammY/commits?author=tup4ienko" title="Code">💻</a></td>
259
- <td align="center" valign="top" width="14.28%"><a href="https://github.com/evermake"><img src="https://avatars.githubusercontent.com/u/53311479?v=4?s=100" width="100px;" alt="Vladislav Deryabkin"/><br /><sub><b>Vladislav Deryabkin</b></sub></a><br /><a href="https://github.com/grammyjs/grammY/issues?q=author%3Aevermake" title="Bug reports">🐛</a> <a href="https://github.com/grammyjs/grammY/commits?author=evermake" title="Code">💻</a></td>
259
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/evermake"><img src="https://avatars.githubusercontent.com/u/53311479?v=4?s=100" width="100px;" alt="Vladislav Deryabkin"/><br /><sub><b>Vladislav Deryabkin</b></sub></a><br /><a href="https://github.com/grammyjs/grammY/issues?q=author%3Aevermake" title="Bug reports">🐛</a> <a href="https://github.com/grammyjs/grammY/commits?author=evermake" title="Code">💻</a> <a href="https://github.com/grammyjs/grammY/pulls?q=is%3Apr+reviewed-by%3Aevermake" title="Reviewed Pull Requests">👀</a></td>
260
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/kashyapndps00"><img src="https://avatars.githubusercontent.com/u/98746601?v=4?s=100" width="100px;" alt="Kashyap Sharma"/><br /><sub><b>Kashyap Sharma</b></sub></a><br /><a href="#example-kashyapndps00" title="Examples">💡</a></td>
261
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/AlexOwl"><img src="https://avatars.githubusercontent.com/u/47189254?v=4?s=100" width="100px;" alt="AlexOwl"/><br /><sub><b>AlexOwl</b></sub></a><br /><a href="https://github.com/grammyjs/grammY/issues?q=author%3AAlexOwl" title="Bug reports">🐛</a> <a href="https://github.com/grammyjs/grammY/commits?author=AlexOwl" title="Code">💻</a></td>
262
+ </tr>
263
+ <tr>
264
+ <td align="center" valign="top" width="14.28%"><a href="https://www.shrimadhavuk.me/"><img src="https://avatars.githubusercontent.com/u/6317196?v=4?s=100" width="100px;" alt="Shrimadhav U K"/><br /><sub><b>Shrimadhav U K</b></sub></a><br /><a href="https://github.com/grammyjs/grammY/commits?author=SpEcHiDe" title="Code">💻</a></td>
265
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/binamralamsal"><img src="https://avatars.githubusercontent.com/u/61900781?v=4?s=100" width="100px;" alt="Binamra Lamsal"/><br /><sub><b>Binamra Lamsal</b></sub></a><br /><a href="#ideas-binamralamsal" title="Ideas, Planning, & Feedback">🤔</a></td>
266
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/gertminov"><img src="https://avatars.githubusercontent.com/u/78727928?v=4?s=100" width="100px;" alt="gertminov"/><br /><sub><b>gertminov</b></sub></a><br /><a href="https://github.com/grammyjs/grammY/commits?author=gertminov" title="Documentation">📖</a> <a href="#tutorial-gertminov" title="Tutorials">✅</a></td>
260
267
  </tr>
261
268
  </tbody>
262
269
  </table>
package/out/bot.d.ts CHANGED
@@ -169,8 +169,10 @@ export declare class Bot<C extends Context = Context, A extends Api = Api> exten
169
169
  * Initializes the bot, i.e. fetches information about the bot itself. This
170
170
  * method is called automatically, you usually don't have to call it
171
171
  * manually.
172
+ *
173
+ * @param signal Optional `AbortSignal` to cancel the initialization
172
174
  */
173
- init(): Promise<void>;
175
+ init(signal?: AbortSignal): Promise<void>;
174
176
  /**
175
177
  * Internal. Do not call. Handles an update batch sequentially by supplying
176
178
  * it one-by-one to the middleware. Handles middleware errors and stores the
@@ -279,3 +281,4 @@ export declare class Bot<C extends Context = Context, A extends Api = Api> exten
279
281
  */
280
282
  private handlePollingError;
281
283
  }
284
+ import { AbortSignal } from "./shim.node.js";
package/out/bot.js CHANGED
@@ -113,12 +113,14 @@ class Bot extends composer_js_1.Composer {
113
113
  * Initializes the bot, i.e. fetches information about the bot itself. This
114
114
  * method is called automatically, you usually don't have to call it
115
115
  * manually.
116
+ *
117
+ * @param signal Optional `AbortSignal` to cancel the initialization
116
118
  */
117
- async init() {
119
+ async init(signal) {
118
120
  var _a;
119
121
  if (!this.isInited()) {
120
122
  debug("Initializing bot");
121
- (_a = this.mePromise) !== null && _a !== void 0 ? _a : (this.mePromise = withRetries(() => this.api.getMe()));
123
+ (_a = this.mePromise) !== null && _a !== void 0 ? _a : (this.mePromise = withRetries(() => this.api.getMe(signal), signal));
122
124
  let me;
123
125
  try {
124
126
  me = await this.mePromise;
@@ -230,19 +232,26 @@ a known bot info object.");
230
232
  * @param options Options to use for simple long polling
231
233
  */
232
234
  async start(options) {
233
- var _a;
235
+ var _a, _b, _c;
234
236
  // Perform setup
235
- if (!this.isInited())
236
- await this.init();
237
+ if (!this.isInited()) {
238
+ await this.init((_a = this.pollingAbortController) === null || _a === void 0 ? void 0 : _a.signal);
239
+ }
237
240
  if (this.pollingRunning) {
238
241
  debug("Simple long polling already running!");
239
242
  return;
240
243
  }
241
- await withRetries(() => this.api.deleteWebhook({
242
- drop_pending_updates: options === null || options === void 0 ? void 0 : options.drop_pending_updates,
243
- }));
244
+ await withRetries(() => {
245
+ var _a;
246
+ return this.api.deleteWebhook({
247
+ drop_pending_updates: options === null || options === void 0 ? void 0 : options.drop_pending_updates,
248
+ }, (_a = this.pollingAbortController) === null || _a === void 0 ? void 0 : _a.signal);
249
+ }, (_b = this.pollingAbortController) === null || _b === void 0 ? void 0 : _b.signal);
244
250
  // All async ops of setup complete, run callback
245
- await ((_a = options === null || options === void 0 ? void 0 : options.onStart) === null || _a === void 0 ? void 0 : _a.call(options, this.botInfo));
251
+ await ((_c = options === null || options === void 0 ? void 0 : options.onStart) === null || _c === void 0 ? void 0 : _c.call(options, this.botInfo));
252
+ // Bot was stopped during `onStart`
253
+ if (!this.pollingRunning)
254
+ return;
246
255
  // Prevent common misuse that causes memory leak
247
256
  this.use = () => {
248
257
  throw new Error(`It looks like you are registering more listeners \
@@ -391,13 +400,26 @@ exports.Bot = Bot;
391
400
  /**
392
401
  * Performs a network call task, retrying upon known errors until success.
393
402
  *
403
+ * If the task errors and a retry_after value can be used, a subsequent retry
404
+ * will be delayed by the specified period of time.
405
+ *
406
+ * Otherwise, if the first attempt at running the task fails, the task is
407
+ * retried immediately. If second attempt fails, too, waits for 100 ms, and then
408
+ * doubles this delay for every subsequent attemt. Never waits longer than 1
409
+ * hour before retrying.
410
+ *
394
411
  * @param task Async task to perform
412
+ * @param signal Optional `AbortSignal` to prevent further retries
395
413
  */
396
- async function withRetries(task) {
414
+ async function withRetries(task, signal) {
397
415
  let result = { ok: false };
416
+ const INITIAL_DELAY = 100; // ms
417
+ let delay = INITIAL_DELAY;
398
418
  while (!result.ok) {
419
+ let mustDelay = true;
399
420
  try {
400
421
  result = { ok: true, value: await task() };
422
+ mustDelay = false;
401
423
  }
402
424
  catch (error) {
403
425
  debugErr(error);
@@ -408,20 +430,52 @@ async function withRetries(task) {
408
430
  continue;
409
431
  if (error.error_code === 429) {
410
432
  const retryAfter = error.parameters.retry_after;
411
- if (retryAfter !== undefined)
412
- await sleep(retryAfter);
433
+ if (retryAfter !== undefined) {
434
+ await sleep(retryAfter, signal);
435
+ mustDelay = false;
436
+ }
413
437
  continue;
414
438
  }
415
439
  }
416
440
  throw error;
417
441
  }
442
+ finally {
443
+ if (mustDelay) {
444
+ if (delay !== INITIAL_DELAY) {
445
+ await sleep(delay, signal);
446
+ }
447
+ // double the next delay but cap it at 1 hour
448
+ delay = Math.min(1 * 60 * 60 * 1000, delay + delay);
449
+ }
450
+ }
418
451
  }
419
452
  return result.value;
420
453
  }
421
454
  /**
422
- * Returns a new promise that resolves after the specified number of seconds.
455
+ * Returns a new promise that resolves after the specified number of seconds, or
456
+ * rejects as soon as the given signal is aborted.
423
457
  */
424
- function sleep(seconds) {
425
- return new Promise((r) => setTimeout(r, 1000 * seconds));
458
+ async function sleep(seconds, signal) {
459
+ let handle;
460
+ let reject;
461
+ function abort() {
462
+ reject === null || reject === void 0 ? void 0 : reject(new Error("Aborted delay"));
463
+ if (handle !== undefined)
464
+ clearTimeout(handle);
465
+ }
466
+ try {
467
+ await new Promise((res, rej) => {
468
+ reject = rej;
469
+ if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
470
+ abort();
471
+ return;
472
+ }
473
+ signal === null || signal === void 0 ? void 0 : signal.addEventListener("abort", abort);
474
+ handle = setTimeout(res, 1000 * seconds);
475
+ });
476
+ }
477
+ finally {
478
+ signal === null || signal === void 0 ? void 0 : signal.removeEventListener("abort", abort);
479
+ }
426
480
  }
427
481
  const shim_node_js_1 = require("./shim.node.js");
package/out/context.d.ts CHANGED
@@ -426,7 +426,9 @@ export declare class Context implements RenamedUpdate {
426
426
  *
427
427
  * **Official reference:** https://core.telegram.org/bots/api#editmessagelivelocation
428
428
  */
429
- editMessageLiveLocation(latitude: number, longitude: number, other?: Other<"editMessageLiveLocation", "chat_id" | "message_id" | "inline_message_id" | "latitude" | "longitude">, signal?: AbortSignal): Promise<true | (Update.Edited & Message.CommonMessage & import("@grammyjs/types/message.js").MsgWith<"location">)>;
429
+ editMessageLiveLocation(latitude: number, longitude: number, other?: Other<"editMessageLiveLocation", "chat_id" | "message_id" | "inline_message_id" | "latitude" | "longitude">, signal?: AbortSignal): Promise<true | (Update.Edited & Message.CommonMessage & {
430
+ location: import("@grammyjs/types/message.js").Location;
431
+ })>;
430
432
  /**
431
433
  * Context-aware alias for `api.stopMessageLiveLocation`. Use this method to stop updating a live location message before live_period expires. On success, if the message is not an inline message, the edited Message is returned, otherwise True is returned.
432
434
  *
@@ -435,7 +437,9 @@ export declare class Context implements RenamedUpdate {
435
437
  *
436
438
  * **Official reference:** https://core.telegram.org/bots/api#stopmessagelivelocation
437
439
  */
438
- stopMessageLiveLocation(other?: Other<"stopMessageLiveLocation", "chat_id" | "message_id" | "inline_message_id">, signal?: AbortSignal): Promise<true | (Update.Edited & Message.CommonMessage & import("@grammyjs/types/message.js").MsgWith<"location">)>;
440
+ stopMessageLiveLocation(other?: Other<"stopMessageLiveLocation", "chat_id" | "message_id" | "inline_message_id">, signal?: AbortSignal): Promise<true | (Update.Edited & Message.CommonMessage & {
441
+ location: import("@grammyjs/types/message.js").Location;
442
+ })>;
439
443
  /**
440
444
  * Context-aware alias for `api.sendVenue`. Use this method to send information about a venue. On success, the sent Message is returned.
441
445
  *
@@ -939,7 +943,7 @@ export declare class Context implements RenamedUpdate {
939
943
  *
940
944
  * **Official reference:** https://core.telegram.org/bots/api#setchatmenubutton
941
945
  */
942
- getChatMenuButton(other?: Other<"getChatMenuButton">, signal?: AbortSignal): Promise<import("@grammyjs/types/menu-button.js").MenuButton>;
946
+ getChatMenuButton(other?: Other<"getChatMenuButton">, signal?: AbortSignal): Promise<import("@grammyjs/types/settings.js").MenuButton>;
943
947
  /**
944
948
  * Context-aware alias for `api.setMyDefaultAdministratorRights`. Use this method to the change the default administrator rights requested by the bot when it's added as an administrator to groups or channels. These rights will be suggested to users, but they are are free to modify the list before adding the bot. Returns True on success.
945
949
  *
@@ -965,7 +969,9 @@ export declare class Context implements RenamedUpdate {
965
969
  *
966
970
  * **Official reference:** https://core.telegram.org/bots/api#editmessagetext
967
971
  */
968
- editMessageText(text: string, other?: Other<"editMessageText", "chat_id" | "message_id" | "inline_message_id" | "text">, signal?: AbortSignal): Promise<true | (Update.Edited & Message.CommonMessage & import("@grammyjs/types/message.js").MsgWith<"text">)>;
972
+ editMessageText(text: string, other?: Other<"editMessageText", "chat_id" | "message_id" | "inline_message_id" | "text">, signal?: AbortSignal): Promise<true | (Update.Edited & Message.CommonMessage & {
973
+ text: string;
974
+ })>;
969
975
  /**
970
976
  * Context-aware alias for `api.editMessageCaption`. Use this method to edit captions of messages. On success, if the edited message is not an inline message, the edited Message is returned, otherwise True is returned.
971
977
  *
@@ -1057,7 +1063,7 @@ export declare class Context implements RenamedUpdate {
1057
1063
  * @param title Product name, 1-32 characters
1058
1064
  * @param description Product description, 1-255 characters
1059
1065
  * @param payload Bot-defined invoice payload, 1-128 bytes. This will not be displayed to the user, use for your internal processes.
1060
- * @param provider_token Payments provider token, obtained via BotFather
1066
+ * @param provider_token Payment provider token, obtained via @BotFather
1061
1067
  * @param currency Three-letter ISO 4217 currency code, see more on currencies
1062
1068
  * @param prices Price breakdown, a list of components (e.g. product price, tax, discount, delivery cost, delivery tax, bonus, etc.)
1063
1069
  * @param other Optional remaining parameters, confer the official reference below
package/out/context.js CHANGED
@@ -1298,7 +1298,7 @@ class Context {
1298
1298
  * @param title Product name, 1-32 characters
1299
1299
  * @param description Product description, 1-255 characters
1300
1300
  * @param payload Bot-defined invoice payload, 1-128 bytes. This will not be displayed to the user, use for your internal processes.
1301
- * @param provider_token Payments provider token, obtained via BotFather
1301
+ * @param provider_token Payment provider token, obtained via @BotFather
1302
1302
  * @param currency Three-letter ISO 4217 currency code, see more on currencies
1303
1303
  * @param prices Price breakdown, a list of components (e.g. product price, tax, discount, delivery cost, delivery tax, bonus, etc.)
1304
1304
  * @param other Optional remaining parameters, confer the official reference below
@@ -0,0 +1,65 @@
1
+ import { type Update } from "../types.js";
2
+ /**
3
+ * HTTP Web frameworks for which grammY provides compatible callback out of the
4
+ * box.
5
+ */
6
+ export type SupportedFrameworks = keyof typeof adapters;
7
+ /**
8
+ * Abstraction over a request-response cycle, providing access to the update, as
9
+ * well as a mechanism for responding to the request and to end it.
10
+ */
11
+ export interface ReqResHandler {
12
+ /**
13
+ * The update object sent from Telegram, usually resolves the request's JSON
14
+ * body
15
+ */
16
+ update: Promise<Update>;
17
+ /**
18
+ * X-Telegram-Bot-Api-Secret-Token header of the request, or undefined if
19
+ * not present
20
+ */
21
+ header?: string;
22
+ /**
23
+ * Ends the request immediately without body, called after every request
24
+ * unless a webhook reply was performed
25
+ */
26
+ end?: () => void;
27
+ /**
28
+ * Sends the specified JSON as a payload in the body, used for webhook
29
+ * replies
30
+ */
31
+ respond: (json: string) => unknown | Promise<unknown>;
32
+ /**
33
+ * Responds that the request is unauthorized due to mismatching
34
+ * X-Telegram-Bot-Api-Secret-Token headers
35
+ */
36
+ unauthorized: () => unknown | Promise<unknown>;
37
+ /**
38
+ * Some frameworks (e.g. Deno's std/http `listenAndServe`) assume that
39
+ * handler returns something
40
+ */
41
+ handlerReturn?: any;
42
+ }
43
+ /**
44
+ * Middleware for a web framework. Creates a request-response handler for a
45
+ * request. The handler will be used to integrate with the compatible framework.
46
+ */
47
+ export type FrameworkAdapter = (...args: any[]) => ReqResHandler;
48
+ export declare const adapters: {
49
+ express: FrameworkAdapter;
50
+ koa: FrameworkAdapter;
51
+ fastify: FrameworkAdapter;
52
+ serveHttp: FrameworkAdapter;
53
+ "std/http": FrameworkAdapter;
54
+ oak: FrameworkAdapter;
55
+ http: FrameworkAdapter;
56
+ https: FrameworkAdapter;
57
+ "aws-lambda": FrameworkAdapter;
58
+ azure: FrameworkAdapter;
59
+ "next-js": FrameworkAdapter;
60
+ sveltekit: FrameworkAdapter;
61
+ cloudflare: FrameworkAdapter;
62
+ "cloudflare-mod": FrameworkAdapter;
63
+ hono: FrameworkAdapter;
64
+ worktop: FrameworkAdapter;
65
+ };
@@ -0,0 +1,264 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.adapters = void 0;
4
+ const SECRET_HEADER = "X-Telegram-Bot-Api-Secret-Token";
5
+ const WRONG_TOKEN_ERROR = "secret token is wrong";
6
+ const ok = () => new Response(null, { status: 200 });
7
+ const okJson = (json) => new Response(json, {
8
+ status: 200,
9
+ headers: { "Content-Type": "application/json" },
10
+ });
11
+ const unauthorized = () => new Response('"unauthorized"', {
12
+ status: 401,
13
+ statusText: WRONG_TOKEN_ERROR,
14
+ });
15
+ /** express web framework */
16
+ const express = (req, res) => ({
17
+ update: Promise.resolve(req.body),
18
+ header: req.header(SECRET_HEADER),
19
+ end: () => res.end(),
20
+ respond: (json) => {
21
+ res.set("Content-Type", "application/json");
22
+ res.send(json);
23
+ },
24
+ unauthorized: () => {
25
+ res.send(401, WRONG_TOKEN_ERROR);
26
+ },
27
+ });
28
+ /** koa web framework */
29
+ const koa = (ctx) => ({
30
+ update: Promise.resolve(ctx.request.body),
31
+ header: ctx.get(SECRET_HEADER),
32
+ end: () => {
33
+ ctx.body = "";
34
+ },
35
+ respond: (json) => {
36
+ ctx.set("Content-Type", "application/json");
37
+ ctx.response.body = json;
38
+ },
39
+ unauthorized: () => {
40
+ ctx.status = 401;
41
+ },
42
+ });
43
+ /** fastify web framework */
44
+ const fastify = (req, reply) => ({
45
+ update: Promise.resolve(req.body),
46
+ header: req.headers[SECRET_HEADER.toLowerCase()],
47
+ end: () => reply.status(200).send(),
48
+ respond: (json) => reply.send(json),
49
+ unauthorized: () => reply.code(401).send(WRONG_TOKEN_ERROR),
50
+ });
51
+ const serveHttp = (requestEvent) => ({
52
+ update: requestEvent.request.json(),
53
+ header: requestEvent.request.headers.get(SECRET_HEADER) || undefined,
54
+ end: () => requestEvent.respondWith(ok()),
55
+ respond: (json) => requestEvent.respondWith(okJson(json)),
56
+ unauthorized: () => requestEvent.respondWith(unauthorized()),
57
+ });
58
+ /** std/http web server */
59
+ const stdHttp = (req) => {
60
+ let resolveResponse;
61
+ return {
62
+ update: req.json(),
63
+ header: req.headers.get(SECRET_HEADER) || undefined,
64
+ end: () => {
65
+ if (resolveResponse)
66
+ resolveResponse(ok());
67
+ },
68
+ respond: (json) => {
69
+ if (resolveResponse)
70
+ resolveResponse(okJson(json));
71
+ },
72
+ unauthorized: () => {
73
+ if (resolveResponse)
74
+ resolveResponse(unauthorized());
75
+ },
76
+ handlerReturn: new Promise((resolve) => {
77
+ resolveResponse = resolve;
78
+ }),
79
+ };
80
+ };
81
+ /** oak web framework */
82
+ const oak = (ctx) => ({
83
+ update: ctx.request.body({ type: "json" }).value,
84
+ header: ctx.request.headers.get(SECRET_HEADER) || undefined,
85
+ end: () => {
86
+ ctx.response.status = 200;
87
+ },
88
+ respond: (json) => {
89
+ ctx.response.type = "json";
90
+ ctx.response.body = json;
91
+ },
92
+ unauthorized: () => {
93
+ ctx.response.status = 401;
94
+ },
95
+ });
96
+ /** Node.js native 'http' and 'https' modules */
97
+ const http = (req, res) => {
98
+ const secretHeaderFromRequest = req.headers[SECRET_HEADER.toLowerCase()];
99
+ return {
100
+ update: new Promise((resolve, reject) => {
101
+ const chunks = [];
102
+ req.on("data", (chunk) => chunks.push(chunk))
103
+ .once("end", () => {
104
+ // @ts-ignore `Buffer` is Node-only
105
+ const raw = Buffer.concat(chunks).toString("utf-8");
106
+ resolve(JSON.parse(raw));
107
+ })
108
+ .once("error", reject);
109
+ }),
110
+ header: Array.isArray(secretHeaderFromRequest)
111
+ ? secretHeaderFromRequest[0]
112
+ : secretHeaderFromRequest,
113
+ end: () => res.end(),
114
+ respond: (json) => res
115
+ .writeHead(200, { "Content-Type": "application/json" })
116
+ .end(json),
117
+ unauthorized: () => res.writeHead(401).end(WRONG_TOKEN_ERROR),
118
+ };
119
+ };
120
+ /** AWS lambda serverless functions */
121
+ const awsLambda = (event, _context, callback) => ({
122
+ update: JSON.parse(event.body),
123
+ header: event.headers[SECRET_HEADER],
124
+ end: () => callback(null, { statusCode: 200 }),
125
+ respond: (json) => callback(null, { statusCode: 200, body: json }),
126
+ unauthorized: () => callback(null, { statusCode: 401 }),
127
+ });
128
+ /** Azure Functions */
129
+ const azure = (context, req) => ({
130
+ update: Promise.resolve(req.body),
131
+ header: context.res.headers[SECRET_HEADER],
132
+ end: () => (context.res = {
133
+ status: 200,
134
+ body: "",
135
+ }),
136
+ respond: (json) => {
137
+ context.res.set("Content-Type", "application/json");
138
+ context.res.send(json);
139
+ },
140
+ unauthorized: () => {
141
+ context.res.send(401, WRONG_TOKEN_ERROR);
142
+ },
143
+ });
144
+ /** Next.js Serverless Functions */
145
+ const nextJs = (req, res) => ({
146
+ update: Promise.resolve(req.body),
147
+ header: req.headers[SECRET_HEADER],
148
+ end: () => res.end(),
149
+ respond: (json) => res.status(200).json(json),
150
+ unauthorized: () => res.status(401).send(WRONG_TOKEN_ERROR),
151
+ });
152
+ /** Sveltekit Serverless Functions */
153
+ const sveltekit = ({ request }) => {
154
+ let resolveResponse;
155
+ return {
156
+ update: Promise.resolve(request.json()),
157
+ header: request.headers.get(SECRET_HEADER) || undefined,
158
+ end: () => {
159
+ if (resolveResponse)
160
+ resolveResponse(ok());
161
+ },
162
+ respond: (json) => {
163
+ if (resolveResponse)
164
+ resolveResponse(okJson(json));
165
+ },
166
+ unauthorized: () => {
167
+ if (resolveResponse)
168
+ resolveResponse(unauthorized());
169
+ },
170
+ handlerReturn: new Promise((resolve) => {
171
+ resolveResponse = resolve;
172
+ }),
173
+ };
174
+ };
175
+ /** Native CloudFlare workers (service worker) */
176
+ const cloudflare = (event) => {
177
+ let resolveResponse;
178
+ event.respondWith(new Promise((resolve) => {
179
+ resolveResponse = resolve;
180
+ }));
181
+ return {
182
+ update: event.request.json(),
183
+ header: event.request.headers.get(SECRET_HEADER) || undefined,
184
+ end: () => {
185
+ resolveResponse(ok());
186
+ },
187
+ respond: (json) => {
188
+ resolveResponse(okJson(json));
189
+ },
190
+ unauthorized: () => {
191
+ resolveResponse(unauthorized());
192
+ },
193
+ };
194
+ };
195
+ /** Native CloudFlare workers (module worker) */
196
+ const cloudflareModule = (request) => {
197
+ let resolveResponse;
198
+ return {
199
+ update: request.json(),
200
+ header: request.headers.get(SECRET_HEADER) || undefined,
201
+ end: () => {
202
+ resolveResponse(ok());
203
+ },
204
+ respond: (json) => {
205
+ resolveResponse(okJson(json));
206
+ },
207
+ unauthorized: () => {
208
+ resolveResponse(unauthorized());
209
+ },
210
+ handlerReturn: new Promise((resolve) => {
211
+ resolveResponse = resolve;
212
+ }),
213
+ };
214
+ };
215
+ /** hono web framework */
216
+ const hono = (ctx) => {
217
+ let resolveResponse;
218
+ return {
219
+ update: ctx.req.json(),
220
+ header: ctx.req.headers.get(SECRET_HEADER) || undefined,
221
+ end: () => {
222
+ resolveResponse(ctx.body());
223
+ },
224
+ respond: (json) => {
225
+ ctx.header('Content-Type", "application/json');
226
+ resolveResponse(ctx.body(json));
227
+ },
228
+ unauthorized: () => {
229
+ ctx.status(401);
230
+ ctx.statusText(WRONG_TOKEN_ERROR);
231
+ resolveResponse(ctx.body());
232
+ },
233
+ handlerReturn: new Promise((resolve) => {
234
+ resolveResponse = resolve;
235
+ }),
236
+ };
237
+ };
238
+ /** worktop CloudFlare workers framework */
239
+ const worktop = (req, res) => ({
240
+ update: Promise.resolve(req.body.json()),
241
+ header: req.headers.get(SECRET_HEADER),
242
+ end: () => res.end(),
243
+ respond: (json) => res.send(200, json),
244
+ unauthorized: () => res.send(401, WRONG_TOKEN_ERROR),
245
+ });
246
+ // Please open a PR if you want to add another adapter
247
+ exports.adapters = {
248
+ express,
249
+ koa,
250
+ fastify,
251
+ serveHttp,
252
+ "std/http": stdHttp,
253
+ oak,
254
+ http,
255
+ https: http,
256
+ "aws-lambda": awsLambda,
257
+ azure,
258
+ "next-js": nextJs,
259
+ sveltekit,
260
+ cloudflare,
261
+ "cloudflare-mod": cloudflareModule,
262
+ hono,
263
+ worktop,
264
+ };
@@ -79,6 +79,22 @@ export interface StorageAdapter<T> {
79
79
  * Deletes a value for the given key from the storage.
80
80
  */
81
81
  delete: (key: string) => MaybePromise<void>;
82
+ /**
83
+ * Checks whether a key exists in the storage.
84
+ */
85
+ has?: (key: string) => MaybePromise<boolean>;
86
+ /**
87
+ * Lists all keys.
88
+ */
89
+ readAllKeys?: () => Iterable<string> | AsyncIterable<string>;
90
+ /**
91
+ * Lists all values.
92
+ */
93
+ readAllValues?: () => Iterable<T> | AsyncIterable<T>;
94
+ /**
95
+ * Lists all keys with their values.
96
+ */
97
+ readAllEntries?: () => Iterable<[key: string, value: T]> | AsyncIterable<[key: string, value: T]>;
82
98
  }
83
99
  /**
84
100
  * Options for session middleware.
@@ -309,10 +325,13 @@ export declare class MemorySessionStorage<S> implements StorageAdapter<S> {
309
325
  constructor(timeToLive?: number | undefined);
310
326
  read(key: string): S | undefined;
311
327
  /**
312
- * Reads the values for all keys of the session storage, and returns them as
313
- * an array.
328
+ * @deprecated Use {@link readAllValues} instead
314
329
  */
315
330
  readAll(): S[];
331
+ readAllKeys(): string[];
332
+ readAllValues(): S[];
333
+ readAllEntries(): [string, S][];
334
+ has(key: string): boolean;
316
335
  write(key: string, value: S): void;
317
336
  delete(key: string): void;
318
337
  }
@@ -406,15 +406,28 @@ class MemorySessionStorage {
406
406
  return value.session;
407
407
  }
408
408
  /**
409
- * Reads the values for all keys of the session storage, and returns them as
410
- * an array.
409
+ * @deprecated Use {@link readAllValues} instead
411
410
  */
412
411
  readAll() {
412
+ return this.readAllValues();
413
+ }
414
+ readAllKeys() {
415
+ return Array.from(this.storage.keys());
416
+ }
417
+ readAllValues() {
413
418
  return Array
414
419
  .from(this.storage.keys())
415
420
  .map((key) => this.read(key))
416
421
  .filter((value) => value !== undefined);
417
422
  }
423
+ readAllEntries() {
424
+ return Array.from(this.storage.keys())
425
+ .map((key) => [key, this.read(key)])
426
+ .filter((pair) => pair[1] !== undefined);
427
+ }
428
+ has(key) {
429
+ return this.storage.has(key);
430
+ }
418
431
  write(key, value) {
419
432
  this.storage.set(key, addExpiryDate(value, this.timeToLive));
420
433
  }