wiki search people tested pipeline

This commit is contained in:
Alvis
2026-03-05 11:22:34 +00:00
parent 09a93c661e
commit a30936f120
152 changed files with 47694 additions and 263 deletions

View File

@@ -0,0 +1,98 @@
import { DEFAULT_UPDATE_TYPES } from "../bot.js";
declare const ALL_UPDATE_TYPES: readonly ["message", "edited_message", "channel_post", "edited_channel_post", "business_connection", "business_message", "edited_business_message", "deleted_business_messages", "inline_query", "chosen_inline_result", "callback_query", "shipping_query", "pre_checkout_query", "purchased_paid_media", "poll", "poll_answer", "my_chat_member", "chat_join_request", "chat_boost", "removed_chat_boost", "chat_member", "message_reaction", "message_reaction_count"];
declare const ALL_CHAT_PERMISSIONS: {
readonly is_anonymous: true;
readonly can_manage_chat: true;
readonly can_delete_messages: true;
readonly can_manage_video_chats: true;
readonly can_restrict_members: true;
readonly can_promote_members: true;
readonly can_change_info: true;
readonly can_invite_users: true;
readonly can_post_stories: true;
readonly can_edit_stories: true;
readonly can_delete_stories: true;
readonly can_post_messages: true;
readonly can_edit_messages: true;
readonly can_pin_messages: true;
readonly can_manage_topics: true;
};
/**
* Types of the constants used in the Telegram Bot API. Currently holds all
* available update types as well as all chat permissions.
*/
export interface ApiConstants {
/**
* List of update types a bot receives by default. Useful if you want to
* receive all update types but `chat_member`, `message_reaction`, and
* `message_reaction_count`.
*
* ```ts
* // Built-in polling:
* bot.start({ allowed_updates: DEFAULT_UPDATE_TYPES });
* // grammY runner:
* run(bot, { runner: { fetch: { allowed_updates: DEFAULT_UPDATE_TYPES } } });
* // Webhooks:
* await bot.api.setWebhook(url, { allowed_updates: DEFAULT_UPDATE_TYPES });
* ```
*
* See the [Bot API reference](https://core.telegram.org/bots/api#update)
* for more information.
*/
DEFAULT_UPDATE_TYPES: typeof DEFAULT_UPDATE_TYPES[number];
/**
* List of all available update types. Useful if you want to receive all
* updates from the Bot API, rather than just those that are delivered by
* default.
*
* The main use case for this is when you want to receive `chat_member`,
* `message_reaction`, and `message_reaction_count` updates, as they need to
* be enabled first. Use it like so:
*
* ```ts
* // Built-in polling:
* bot.start({ allowed_updates: ALL_UPDATE_TYPES });
* // grammY runner:
* run(bot, { runner: { fetch: { allowed_updates: ALL_UPDATE_TYPES } } });
* // Webhooks:
* await bot.api.setWebhook(url, { allowed_updates: ALL_UPDATE_TYPES });
* ```
*
* See the [Bot API reference](https://core.telegram.org/bots/api#update)
* for more information.
*/
ALL_UPDATE_TYPES: typeof ALL_UPDATE_TYPES[number];
/**
* An object containing all available chat permissions. Useful if you want
* to lift restrictions from a user, as this action requires you to pass
* `true` for all permissions. Use it like so:
*
* ```ts
* // On `Bot`:
* await bot.api.restrictChatMember(chat_id, user_id, ALL_CHAT_PERMISSIONS);
* // On `Api`:
* await ctx.api.restrictChatMember(chat_id, user_id, ALL_CHAT_PERMISSIONS);
* // On `Context`:
* await ctx.restrictChatMember(user_id, ALL_CHAT_PERMISSIONS);
* await ctx.restrictAuthor(ALL_CHAT_PERMISSIONS);
* ```
*
* See the [Bot API reference](https://core.telegram.org/bots/api#update)
* for more information.
*/
ALL_CHAT_PERMISSIONS: keyof typeof ALL_CHAT_PERMISSIONS;
}
interface TypeOf {
DEFAULT_UPDATE_TYPES: typeof DEFAULT_UPDATE_TYPES;
ALL_UPDATE_TYPES: typeof ALL_UPDATE_TYPES;
ALL_CHAT_PERMISSIONS: typeof ALL_CHAT_PERMISSIONS;
}
type ValuesFor<T> = {
[K in keyof T]: K extends keyof TypeOf ? Readonly<TypeOf[K]> : never;
};
/**
* A container for constants used in the Telegram Bot API. Currently holds all
* available update types as well as all chat permissions.
*/
export declare const API_CONSTANTS: ValuesFor<ApiConstants>;
export {};

View File

@@ -0,0 +1,37 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.API_CONSTANTS = void 0;
const bot_js_1 = require("../bot.js");
const ALL_UPDATE_TYPES = [
...bot_js_1.DEFAULT_UPDATE_TYPES,
"chat_member",
"message_reaction",
"message_reaction_count",
];
const ALL_CHAT_PERMISSIONS = {
is_anonymous: true,
can_manage_chat: true,
can_delete_messages: true,
can_manage_video_chats: true,
can_restrict_members: true,
can_promote_members: true,
can_change_info: true,
can_invite_users: true,
can_post_stories: true,
can_edit_stories: true,
can_delete_stories: true,
can_post_messages: true,
can_edit_messages: true,
can_pin_messages: true,
can_manage_topics: true,
};
/**
* A container for constants used in the Telegram Bot API. Currently holds all
* available update types as well as all chat permissions.
*/
exports.API_CONSTANTS = {
DEFAULT_UPDATE_TYPES: bot_js_1.DEFAULT_UPDATE_TYPES,
ALL_UPDATE_TYPES,
ALL_CHAT_PERMISSIONS,
};
Object.freeze(exports.API_CONSTANTS);

View File

@@ -0,0 +1,226 @@
import { type Update } from "../types.js";
type MaybePromise<T> = T | Promise<T>;
/**
* Abstraction over a request-response cycle, providing access to the update, as
* well as a mechanism for responding to the request and to end it.
*/
export interface ReqResHandler<T = void> {
/**
* The update object sent from Telegram, usually resolves the request's JSON
* body
*/
update: MaybePromise<Update>;
/**
* X-Telegram-Bot-Api-Secret-Token header of the request, or undefined if
* not present
*/
header?: string;
/**
* Ends the request immediately without body, called after every request
* unless a webhook reply was performed
*/
end?: () => void;
/**
* Sends the specified JSON as a payload in the body, used for webhook
* replies
*/
respond: (json: string) => unknown | Promise<unknown>;
/**
* Responds that the request is unauthorized due to mismatching
* X-Telegram-Bot-Api-Secret-Token headers
*/
unauthorized: () => unknown | Promise<unknown>;
/**
* Some frameworks (e.g. Deno's std/http `listenAndServe`) assume that
* handler returns something
*/
handlerReturn?: Promise<T>;
}
/**
* Middleware for a web framework. Creates a request-response handler for a
* request. The handler will be used to integrate with the compatible framework.
*/
export type FrameworkAdapter = (...args: any[]) => ReqResHandler<any>;
export type LambdaAdapter = (event: {
body?: string;
headers: Record<string, string | undefined>;
}, _context: unknown, callback: (arg0: unknown, arg1: Record<string, unknown>) => Promise<unknown>) => ReqResHandler;
export type LambdaAsyncAdapter = (event: {
body?: string;
headers: Record<string, string | undefined>;
}, _context: unknown) => ReqResHandler;
export type AzureAdapter = (context: {
res?: {
[key: string]: any;
};
}, request: {
body?: unknown;
}) => ReqResHandler;
export type AzureAdapterV4 = (request: {
headers: {
get(name: string): string | null;
};
json(): Promise<unknown>;
}) => ReqResHandler<{
status: number;
body?: string;
} | {
jsonBody: string;
}>;
export type BunAdapter = (request: {
headers: Headers;
json: () => Promise<unknown>;
}) => ReqResHandler<Response>;
export type CloudflareAdapter = (event: {
request: Body & {
method: string;
url: string;
headers: Headers;
};
respondWith: (response: Promise<Response>) => void;
}) => ReqResHandler;
export type CloudflareModuleAdapter = (request: Body & {
method: string;
url: string;
headers: Headers;
}) => ReqResHandler<Response>;
export type ElysiaAdapter = (ctx: {
body: unknown;
headers: Record<string, string | undefined>;
set: {
headers: Record<string, string | number>;
status?: string | number;
};
}) => ReqResHandler<string>;
export type ExpressAdapter = (req: {
body: Update;
header: (header: string) => string | undefined;
}, res: {
end: (cb?: () => void) => typeof res;
set: (field: string, value?: string | string[]) => typeof res;
send: (json: string) => typeof res;
status: (code: number) => typeof res;
}) => ReqResHandler;
export type FastifyAdapter = (request: {
body: unknown;
headers: any;
}, reply: {
status: (code: number) => typeof reply;
headers: (headers: Record<string, string>) => typeof reply;
code: (code: number) => typeof reply;
send: {
(): typeof reply;
(json: string): typeof reply;
};
}) => ReqResHandler;
export type HonoAdapter = (c: {
req: {
json: <T>() => Promise<T>;
header: (header: string) => string | undefined;
};
body(data: string): Response;
body(data: null, status: 204): Response;
status: (status: any) => void;
json: (json: string) => Response;
}) => ReqResHandler<Response>;
export type HttpAdapter = (req: {
headers: Record<string, string | string[] | undefined>;
on: (event: string, listener: (chunk: unknown) => void) => typeof req;
once: (event: string, listener: () => void) => typeof req;
}, res: {
writeHead: {
(status: number): typeof res;
(status: number, headers: Record<string, string>): typeof res;
};
end: (json?: string) => void;
}) => ReqResHandler;
export type KoaAdapter = (ctx: {
get: (header: string) => string | undefined;
set: (key: string, value: string) => void;
status: number;
body: string;
request: {
body?: unknown;
};
response: {
body: unknown;
status: number;
};
}) => ReqResHandler;
export type NextAdapter = (req: {
body: Update;
headers: Record<string, string | string[] | undefined>;
}, res: {
end: (cb?: () => void) => typeof res;
status: (code: number) => typeof res;
json: (json: string) => any;
send: (json: string) => any;
}) => ReqResHandler;
export type NHttpAdapter = (rev: {
body: unknown;
headers: {
get: (header: string) => string | null;
};
response: {
sendStatus: (status: number) => void;
status: (status: number) => {
send: (json: string) => void;
};
};
}) => ReqResHandler;
export type OakAdapter = (ctx: {
request: {
body: {
json: () => Promise<Update>;
};
headers: {
get: (header: string) => string | null;
};
};
response: {
status: number;
type: string | undefined;
body: unknown;
};
}) => ReqResHandler;
export type ServeHttpAdapter = (requestEvent: {
request: Request;
respondWith: (response: Response) => void;
}) => ReqResHandler;
export type StdHttpAdapter = (req: Request) => ReqResHandler<Response>;
export type SveltekitAdapter = ({ request }: {
request: Request;
}) => ReqResHandler<unknown>;
export type WorktopAdapter = (req: {
json: () => Promise<Update>;
headers: {
get: (header: string) => string | null;
};
}, res: {
end: (data: BodyInit | null) => void;
send: (status: number, json: string) => void;
}) => ReqResHandler;
export declare const adapters: {
"aws-lambda": LambdaAdapter;
"aws-lambda-async": LambdaAsyncAdapter;
azure: AzureAdapter;
"azure-v4": AzureAdapterV4;
bun: BunAdapter;
cloudflare: CloudflareAdapter;
"cloudflare-mod": CloudflareModuleAdapter;
elysia: ElysiaAdapter;
express: ExpressAdapter;
fastify: FastifyAdapter;
hono: HonoAdapter;
http: HttpAdapter;
https: HttpAdapter;
koa: KoaAdapter;
"next-js": NextAdapter;
nhttp: NHttpAdapter;
oak: OakAdapter;
serveHttp: ServeHttpAdapter;
"std/http": StdHttpAdapter;
sveltekit: SveltekitAdapter;
worktop: WorktopAdapter;
};
export {};

View File

@@ -0,0 +1,393 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.adapters = void 0;
const SECRET_HEADER = "X-Telegram-Bot-Api-Secret-Token";
const SECRET_HEADER_LOWERCASE = SECRET_HEADER.toLowerCase();
const WRONG_TOKEN_ERROR = "secret token is wrong";
const ok = () => new Response(null, { status: 200 });
const okJson = (json) => new Response(json, {
status: 200,
headers: { "Content-Type": "application/json" },
});
const unauthorized = () => new Response('"unauthorized"', {
status: 401,
statusText: WRONG_TOKEN_ERROR,
});
/** AWS lambda serverless functions */
const awsLambda = (event, _context, callback) => ({
get update() {
var _a;
return JSON.parse((_a = event.body) !== null && _a !== void 0 ? _a : "{}");
},
header: event.headers[SECRET_HEADER],
end: () => callback(null, { statusCode: 200 }),
respond: (json) => callback(null, {
statusCode: 200,
headers: { "Content-Type": "application/json" },
body: json,
}),
unauthorized: () => callback(null, { statusCode: 401 }),
});
/** AWS lambda async/await serverless functions */
const awsLambdaAsync = (event, _context) => {
// deno-lint-ignore no-explicit-any
let resolveResponse;
return {
get update() {
var _a;
return JSON.parse((_a = event.body) !== null && _a !== void 0 ? _a : "{}");
},
header: event.headers[SECRET_HEADER],
end: () => resolveResponse({ statusCode: 200 }),
respond: (json) => resolveResponse({
statusCode: 200,
headers: { "Content-Type": "application/json" },
body: json,
}),
unauthorized: () => resolveResponse({ statusCode: 401 }),
handlerReturn: new Promise((res) => resolveResponse = res),
};
};
/** Azure Functions v3 and v4 */
const azure = (context, request) => {
var _a, _b;
return ({
get update() {
return request.body;
},
header: (_b = (_a = context.res) === null || _a === void 0 ? void 0 : _a.headers) === null || _b === void 0 ? void 0 : _b[SECRET_HEADER],
end: () => (context.res = {
status: 200,
body: "",
}),
respond: (json) => {
var _a, _b, _c, _d;
(_b = (_a = context.res) === null || _a === void 0 ? void 0 : _a.set) === null || _b === void 0 ? void 0 : _b.call(_a, "Content-Type", "application/json");
(_d = (_c = context.res) === null || _c === void 0 ? void 0 : _c.send) === null || _d === void 0 ? void 0 : _d.call(_c, json);
},
unauthorized: () => {
var _a, _b;
(_b = (_a = context.res) === null || _a === void 0 ? void 0 : _a.send) === null || _b === void 0 ? void 0 : _b.call(_a, 401, WRONG_TOKEN_ERROR);
},
});
};
const azureV4 = (request) => {
let resolveResponse;
return {
get update() {
return request.json();
},
header: request.headers.get(SECRET_HEADER) || undefined,
end: () => resolveResponse({ status: 204 }),
respond: (json) => resolveResponse({ jsonBody: json }),
unauthorized: () => resolveResponse({ status: 401, body: WRONG_TOKEN_ERROR }),
handlerReturn: new Promise((resolve) => resolveResponse = resolve),
};
};
/** Bun.serve */
const bun = (request) => {
let resolveResponse;
return {
get update() {
return request.json();
},
header: request.headers.get(SECRET_HEADER) || undefined,
end: () => {
resolveResponse(ok());
},
respond: (json) => {
resolveResponse(okJson(json));
},
unauthorized: () => {
resolveResponse(unauthorized());
},
handlerReturn: new Promise((res) => resolveResponse = res),
};
};
/** Native CloudFlare workers (service worker) */
const cloudflare = (event) => {
let resolveResponse;
event.respondWith(new Promise((resolve) => {
resolveResponse = resolve;
}));
return {
get update() {
return event.request.json();
},
header: event.request.headers.get(SECRET_HEADER) || undefined,
end: () => {
resolveResponse(ok());
},
respond: (json) => {
resolveResponse(okJson(json));
},
unauthorized: () => {
resolveResponse(unauthorized());
},
};
};
/** Native CloudFlare workers (module worker) */
const cloudflareModule = (request) => {
let resolveResponse;
return {
get update() {
return request.json();
},
header: request.headers.get(SECRET_HEADER) || undefined,
end: () => {
resolveResponse(ok());
},
respond: (json) => {
resolveResponse(okJson(json));
},
unauthorized: () => {
resolveResponse(unauthorized());
},
handlerReturn: new Promise((res) => resolveResponse = res),
};
};
/** express web framework */
const express = (req, res) => ({
get update() {
return req.body;
},
header: req.header(SECRET_HEADER),
end: () => res.end(),
respond: (json) => {
res.set("Content-Type", "application/json");
res.send(json);
},
unauthorized: () => {
res.status(401).send(WRONG_TOKEN_ERROR);
},
});
/** fastify web framework */
const fastify = (request, reply) => ({
get update() {
return request.body;
},
header: request.headers[SECRET_HEADER_LOWERCASE],
end: () => reply.send(""),
respond: (json) => reply.headers({ "Content-Type": "application/json" }).send(json),
unauthorized: () => reply.code(401).send(WRONG_TOKEN_ERROR),
});
/** hono web framework */
const hono = (c) => {
let resolveResponse;
return {
get update() {
return c.req.json();
},
header: c.req.header(SECRET_HEADER),
end: () => {
resolveResponse(c.body(""));
},
respond: (json) => {
resolveResponse(c.json(json));
},
unauthorized: () => {
c.status(401);
resolveResponse(c.body(""));
},
handlerReturn: new Promise((res) => resolveResponse = res),
};
};
/** Node.js native 'http' and 'https' modules */
const http = (req, res) => {
const secretHeaderFromRequest = req.headers[SECRET_HEADER_LOWERCASE];
return {
get update() {
return new Promise((resolve, reject) => {
const chunks = [];
req.on("data", (chunk) => chunks.push(chunk))
.once("end", () => {
// @ts-ignore `Buffer` is Node-only
// deno-lint-ignore no-node-globals
const raw = Buffer.concat(chunks).toString("utf-8");
resolve(JSON.parse(raw));
})
.once("error", reject);
});
},
header: Array.isArray(secretHeaderFromRequest)
? secretHeaderFromRequest[0]
: secretHeaderFromRequest,
end: () => res.end(),
respond: (json) => res
.writeHead(200, { "Content-Type": "application/json" })
.end(json),
unauthorized: () => res.writeHead(401).end(WRONG_TOKEN_ERROR),
};
};
/** koa web framework */
const koa = (ctx) => ({
get update() {
return ctx.request.body;
},
header: ctx.get(SECRET_HEADER) || undefined,
end: () => {
ctx.body = "";
},
respond: (json) => {
ctx.set("Content-Type", "application/json");
ctx.response.body = json;
},
unauthorized: () => {
ctx.status = 401;
},
});
/** Next.js Serverless Functions */
const nextJs = (request, response) => ({
get update() {
return request.body;
},
header: request.headers[SECRET_HEADER_LOWERCASE],
end: () => response.end(),
respond: (json) => response.status(200).json(json),
unauthorized: () => response.status(401).send(WRONG_TOKEN_ERROR),
});
/** nhttp web framework */
const nhttp = (rev) => ({
get update() {
return rev.body;
},
header: rev.headers.get(SECRET_HEADER) || undefined,
end: () => rev.response.sendStatus(200),
respond: (json) => rev.response.status(200).send(json),
unauthorized: () => rev.response.status(401).send(WRONG_TOKEN_ERROR),
});
/** oak web framework */
const oak = (ctx) => ({
get update() {
return ctx.request.body.json();
},
header: ctx.request.headers.get(SECRET_HEADER) || undefined,
end: () => {
ctx.response.status = 200;
},
respond: (json) => {
ctx.response.type = "json";
ctx.response.body = json;
},
unauthorized: () => {
ctx.response.status = 401;
},
});
/** Deno.serve */
const serveHttp = (requestEvent) => ({
get update() {
return requestEvent.request.json();
},
header: requestEvent.request.headers.get(SECRET_HEADER) || undefined,
end: () => requestEvent.respondWith(ok()),
respond: (json) => requestEvent.respondWith(okJson(json)),
unauthorized: () => requestEvent.respondWith(unauthorized()),
});
/** std/http web server */
const stdHttp = (req) => {
let resolveResponse;
return {
get update() {
return req.json();
},
header: req.headers.get(SECRET_HEADER) || undefined,
end: () => {
if (resolveResponse)
resolveResponse(ok());
},
respond: (json) => {
if (resolveResponse)
resolveResponse(okJson(json));
},
unauthorized: () => {
if (resolveResponse)
resolveResponse(unauthorized());
},
handlerReturn: new Promise((res) => resolveResponse = res),
};
};
/** Sveltekit Serverless Functions */
const sveltekit = ({ request }) => {
let resolveResponse;
return {
get update() {
return request.json();
},
header: request.headers.get(SECRET_HEADER) || undefined,
end: () => {
if (resolveResponse)
resolveResponse(ok());
},
respond: (json) => {
if (resolveResponse)
resolveResponse(okJson(json));
},
unauthorized: () => {
if (resolveResponse)
resolveResponse(unauthorized());
},
handlerReturn: new Promise((res) => resolveResponse = res),
};
};
/** worktop Cloudflare workers framework */
const worktop = (req, res) => {
var _a;
return ({
get update() {
return req.json();
},
header: (_a = req.headers.get(SECRET_HEADER)) !== null && _a !== void 0 ? _a : undefined,
end: () => res.end(null),
respond: (json) => res.send(200, json),
unauthorized: () => res.send(401, WRONG_TOKEN_ERROR),
});
};
const elysia = (ctx) => {
// @note upgrade target to use modern code?
// const { promise, resolve } = Promise.withResolvers<string>();
let resolveResponse;
return {
// @note technically the type shouldn't be limited to Promise, because it's fine to await plain values as well
get update() {
return ctx.body;
},
header: ctx.headers[SECRET_HEADER_LOWERCASE],
end() {
resolveResponse("");
},
respond(json) {
// @note since json is passed as string here, we gotta define proper content-type
ctx.set.headers["content-type"] = "application/json";
resolveResponse(json);
},
unauthorized() {
ctx.set.status = 401;
resolveResponse("");
},
handlerReturn: new Promise((res) => resolveResponse = res),
};
};
// Please open a pull request if you want to add another adapter
exports.adapters = {
"aws-lambda": awsLambda,
"aws-lambda-async": awsLambdaAsync,
azure,
"azure-v4": azureV4,
bun,
cloudflare,
"cloudflare-mod": cloudflareModule,
elysia,
express,
fastify,
hono,
http,
https: http,
koa,
"next-js": nextJs,
nhttp,
oak,
serveHttp,
"std/http": stdHttp,
sveltekit,
worktop,
};

View File

@@ -0,0 +1,401 @@
import { type InlineQueryResultArticle, type InlineQueryResultAudio, type InlineQueryResultCachedAudio, type InlineQueryResultCachedDocument, type InlineQueryResultCachedGif, type InlineQueryResultCachedMpeg4Gif, type InlineQueryResultCachedPhoto, type InlineQueryResultCachedSticker, type InlineQueryResultCachedVideo, type InlineQueryResultCachedVoice, type InlineQueryResultContact, type InlineQueryResultDocument, type InlineQueryResultGame, type InlineQueryResultGif, type InlineQueryResultLocation, type InlineQueryResultMpeg4Gif, type InlineQueryResultPhoto, type InlineQueryResultVenue, type InlineQueryResultVideo, type InlineQueryResultVoice, type InputContactMessageContent, type InputInvoiceMessageContent, type InputLocationMessageContent, type InputTextMessageContent, type InputVenueMessageContent, type LabeledPrice } from "../types.js";
type InlineQueryResultOptions<T, K extends keyof T> = Omit<T, "type" | "id" | "input_message_content" | K>;
type OptionalKeys<T> = {
[K in keyof T]-?: undefined extends T[K] ? K : never;
};
type OptionalFields<T> = Pick<T, OptionalKeys<T>[keyof T]>;
/**
* Holds a number of helper methods for building `InlineQueryResult*` objects.
*
* For example, letting the user pick one out of three photos can be done like
* this.
*
* ```ts
* const results = [
* InlineQueryResultBuilder.photo('id0', 'https://grammy.dev/images/Y.png'),
* InlineQueryResultBuilder.photo('id1', 'https://grammy.dev/images/Y.png'),
* InlineQueryResultBuilder.photo('id2', 'https://grammy.dev/images/Y.png'),
* ];
* await ctx.answerInlineQuery(results)
* ```
*
* If you want the message content to be different from the content in the
* inline query result, you can perform another method call on the resulting
* objects.
*
* ```ts
* const results = [
* InlineQueryResultBuilder.photo("id0", "https://grammy.dev/images/Y.png")
* .text("Picked photo 0!"),
* InlineQueryResultBuilder.photo("id1", "https://grammy.dev/images/Y.png")
* .text("Picked photo 1!"),
* InlineQueryResultBuilder.photo("id2", "https://grammy.dev/images/Y.png")
* .text("Picked photo 2!"),
* ];
* await ctx.answerInlineQuery(results)
* ```
*
* Be sure to check the
* [documentation](https://core.telegram.org/bots/api#inline-mode) on inline
* mode.
*/
export declare const InlineQueryResultBuilder: {
/**
* Builds an InlineQueryResultArticle object as specified by
* https://core.telegram.org/bots/api#inlinequeryresultarticle. Requires you
* to specify the actual message content by calling another function on the
* object returned from this method.
*
* @param id Unique identifier for this result, 1-64 Bytes
* @param title Title of the result
* @param options Remaining options
*/
article(id: string, title: string, options?: InlineQueryResultOptions<InlineQueryResultArticle, "title">): {
text(message_text: string, options?: OptionalFields<InputTextMessageContent>): InlineQueryResultArticle;
location(latitude: number, longitude: number, options?: OptionalFields<InputLocationMessageContent>): InlineQueryResultArticle;
venue(title: string, latitude: number, longitude: number, address: string, options: OptionalFields<InputVenueMessageContent>): InlineQueryResultArticle;
contact(first_name: string, phone_number: string, options?: OptionalFields<InputContactMessageContent>): InlineQueryResultArticle;
invoice(title: string, description: string, payload: string, provider_token: string, currency: string, prices: LabeledPrice[], options?: OptionalFields<InputInvoiceMessageContent>): InlineQueryResultArticle;
};
/**
* Builds an InlineQueryResultAudio object as specified by
* https://core.telegram.org/bots/api#inlinequeryresultaudio.
*
* @param id Unique identifier for this result, 1-64 bytes
* @param title Title
* @param audio_url A valid URL for the audio file
* @param options Remaining options
*/
audio(id: string, title: string, audio_url: string | URL, options?: InlineQueryResultOptions<InlineQueryResultAudio, "title" | "audio_url">): InlineQueryResultAudio & {
text(message_text: string, options?: OptionalFields<InputTextMessageContent>): InlineQueryResultAudio;
location(latitude: number, longitude: number, options?: OptionalFields<InputLocationMessageContent>): InlineQueryResultAudio;
venue(title: string, latitude: number, longitude: number, address: string, options: OptionalFields<InputVenueMessageContent>): InlineQueryResultAudio;
contact(first_name: string, phone_number: string, options?: OptionalFields<InputContactMessageContent>): InlineQueryResultAudio;
invoice(title: string, description: string, payload: string, provider_token: string, currency: string, prices: LabeledPrice[], options?: OptionalFields<InputInvoiceMessageContent>): InlineQueryResultAudio;
};
/**
* Builds an InlineQueryResultCachedAudio object as specified by
* https://core.telegram.org/bots/api#inlinequeryresultcachedaudio.
*
* @param id Unique identifier for this result, 1-64 bytes
* @param audio_file_id A valid file identifier for the audio file
* @param options Remaining options
*/
audioCached(id: string, audio_file_id: string, options?: InlineQueryResultOptions<InlineQueryResultCachedAudio, "audio_file_id">): InlineQueryResultCachedAudio & {
text(message_text: string, options?: OptionalFields<InputTextMessageContent>): InlineQueryResultCachedAudio;
location(latitude: number, longitude: number, options?: OptionalFields<InputLocationMessageContent>): InlineQueryResultCachedAudio;
venue(title: string, latitude: number, longitude: number, address: string, options: OptionalFields<InputVenueMessageContent>): InlineQueryResultCachedAudio;
contact(first_name: string, phone_number: string, options?: OptionalFields<InputContactMessageContent>): InlineQueryResultCachedAudio;
invoice(title: string, description: string, payload: string, provider_token: string, currency: string, prices: LabeledPrice[], options?: OptionalFields<InputInvoiceMessageContent>): InlineQueryResultCachedAudio;
};
/**
* Builds an InlineQueryResultContact object as specified by
* https://core.telegram.org/bots/api#inlinequeryresultcontact.
*
* @param id Unique identifier for this result, 1-64 Bytes
* @param phone_number Contact's phone number
* @param first_name Contact's first name
* @param options Remaining options
*/
contact(id: string, phone_number: string, first_name: string, options?: InlineQueryResultOptions<InlineQueryResultContact, "phone_number" | "first_name">): InlineQueryResultContact & {
text(message_text: string, options?: OptionalFields<InputTextMessageContent>): InlineQueryResultContact;
location(latitude: number, longitude: number, options?: OptionalFields<InputLocationMessageContent>): InlineQueryResultContact;
venue(title: string, latitude: number, longitude: number, address: string, options: OptionalFields<InputVenueMessageContent>): InlineQueryResultContact;
contact(first_name: string, phone_number: string, options?: OptionalFields<InputContactMessageContent>): InlineQueryResultContact;
invoice(title: string, description: string, payload: string, provider_token: string, currency: string, prices: LabeledPrice[], options?: OptionalFields<InputInvoiceMessageContent>): InlineQueryResultContact;
};
/**
* Builds an InlineQueryResultDocument object as specified by
* https://core.telegram.org/bots/api#inlinequeryresultdocument with
* mime_type set to "application/pdf".
*
* @param id Unique identifier for this result, 1-64 bytes
* @param title Title for the result
* @param document_url A valid URL for the file
* @param options Remaining options
*/
documentPdf(id: string, title: string, document_url: string | URL, options?: InlineQueryResultOptions<InlineQueryResultDocument, "mime_type" | "title" | "document_url">): InlineQueryResultDocument & {
text(message_text: string, options?: OptionalFields<InputTextMessageContent>): InlineQueryResultDocument;
location(latitude: number, longitude: number, options?: OptionalFields<InputLocationMessageContent>): InlineQueryResultDocument;
venue(title: string, latitude: number, longitude: number, address: string, options: OptionalFields<InputVenueMessageContent>): InlineQueryResultDocument;
contact(first_name: string, phone_number: string, options?: OptionalFields<InputContactMessageContent>): InlineQueryResultDocument;
invoice(title: string, description: string, payload: string, provider_token: string, currency: string, prices: LabeledPrice[], options?: OptionalFields<InputInvoiceMessageContent>): InlineQueryResultDocument;
};
/**
* Builds an InlineQueryResultDocument object as specified by
* https://core.telegram.org/bots/api#inlinequeryresultdocument with
* mime_type set to "application/zip".
*
* @param id Unique identifier for this result, 1-64 bytes
* @param title Title for the result
* @param document_url A valid URL for the file
* @param options Remaining options
*/
documentZip(id: string, title: string, document_url: string | URL, options?: InlineQueryResultOptions<InlineQueryResultDocument, "mime_type" | "title" | "document_url">): InlineQueryResultDocument & {
text(message_text: string, options?: OptionalFields<InputTextMessageContent>): InlineQueryResultDocument;
location(latitude: number, longitude: number, options?: OptionalFields<InputLocationMessageContent>): InlineQueryResultDocument;
venue(title: string, latitude: number, longitude: number, address: string, options: OptionalFields<InputVenueMessageContent>): InlineQueryResultDocument;
contact(first_name: string, phone_number: string, options?: OptionalFields<InputContactMessageContent>): InlineQueryResultDocument;
invoice(title: string, description: string, payload: string, provider_token: string, currency: string, prices: LabeledPrice[], options?: OptionalFields<InputInvoiceMessageContent>): InlineQueryResultDocument;
};
/**
* Builds an InlineQueryResultCachedDocument object as specified by
* https://core.telegram.org/bots/api#inlinequeryresultcacheddocument.
*
* @param id Unique identifier for this result, 1-64 bytes
* @param title Title for the result
* @param document_file_id A valid file identifier for the file
* @param options Remaining options
*/
documentCached(id: string, title: string, document_file_id: string, options?: InlineQueryResultOptions<InlineQueryResultCachedDocument, "title" | "document_file_id">): InlineQueryResultCachedDocument & {
text(message_text: string, options?: OptionalFields<InputTextMessageContent>): InlineQueryResultCachedDocument;
location(latitude: number, longitude: number, options?: OptionalFields<InputLocationMessageContent>): InlineQueryResultCachedDocument;
venue(title: string, latitude: number, longitude: number, address: string, options: OptionalFields<InputVenueMessageContent>): InlineQueryResultCachedDocument;
contact(first_name: string, phone_number: string, options?: OptionalFields<InputContactMessageContent>): InlineQueryResultCachedDocument;
invoice(title: string, description: string, payload: string, provider_token: string, currency: string, prices: LabeledPrice[], options?: OptionalFields<InputInvoiceMessageContent>): InlineQueryResultCachedDocument;
};
/**
* Builds an InlineQueryResultGame object as specified by
* https://core.telegram.org/bots/api#inlinequeryresultgame.
*
* @param id Unique identifier for this result, 1-64 bytes
* @param game_short_name Short name of the game
* @param options Remaining options
*/
game(id: string, game_short_name: string, options?: InlineQueryResultOptions<InlineQueryResultGame, "game_short_name">): {
reply_markup?: import("@grammyjs/types/markup.js").InlineKeyboardMarkup | undefined;
type: string;
id: string;
game_short_name: string;
};
/**
* Builds an InlineQueryResultGif object as specified by
* https://core.telegram.org/bots/api#inlinequeryresultgif.
*
* @param id Unique identifier for this result, 1-64 bytes
* @param gif_url A valid URL for the GIF file. File size must not exceed 1MB
* @param thumbnail_url URL of the static (JPEG or GIF) or animated (MPEG4) thumbnail for the result
* @param options Remaining options
*/
gif(id: string, gif_url: string | URL, thumbnail_url: string | URL, options?: InlineQueryResultOptions<InlineQueryResultGif, "gif_url" | "thumbnail_url">): InlineQueryResultGif & {
text(message_text: string, options?: OptionalFields<InputTextMessageContent>): InlineQueryResultGif;
location(latitude: number, longitude: number, options?: OptionalFields<InputLocationMessageContent>): InlineQueryResultGif;
venue(title: string, latitude: number, longitude: number, address: string, options: OptionalFields<InputVenueMessageContent>): InlineQueryResultGif;
contact(first_name: string, phone_number: string, options?: OptionalFields<InputContactMessageContent>): InlineQueryResultGif;
invoice(title: string, description: string, payload: string, provider_token: string, currency: string, prices: LabeledPrice[], options?: OptionalFields<InputInvoiceMessageContent>): InlineQueryResultGif;
};
/**
* Builds an InlineQueryResultCachedGif object as specified by
* https://core.telegram.org/bots/api#inlinequeryresultcachedgif.
*
* @param id Unique identifier for this result, 1-64 bytes
* @param gif_file_id A valid file identifier for the GIF file
* @param options Remaining options
*/
gifCached(id: string, gif_file_id: string, options?: InlineQueryResultOptions<InlineQueryResultCachedGif, "gif_file_id">): InlineQueryResultCachedGif & {
text(message_text: string, options?: OptionalFields<InputTextMessageContent>): InlineQueryResultCachedGif;
location(latitude: number, longitude: number, options?: OptionalFields<InputLocationMessageContent>): InlineQueryResultCachedGif;
venue(title: string, latitude: number, longitude: number, address: string, options: OptionalFields<InputVenueMessageContent>): InlineQueryResultCachedGif;
contact(first_name: string, phone_number: string, options?: OptionalFields<InputContactMessageContent>): InlineQueryResultCachedGif;
invoice(title: string, description: string, payload: string, provider_token: string, currency: string, prices: LabeledPrice[], options?: OptionalFields<InputInvoiceMessageContent>): InlineQueryResultCachedGif;
};
/**
* Builds an InlineQueryResultLocation object as specified by
* https://core.telegram.org/bots/api#inlinequeryresultlocation.
*
* @param id Unique identifier for this result, 1-64 Bytes
* @param title Location title
* @param latitude Location latitude in degrees
* @param longitude Location longitude in degrees
* @param options Remaining options
*/
location(id: string, title: string, latitude: number, longitude: number, options?: InlineQueryResultOptions<InlineQueryResultLocation, "title" | "latitude" | "longitude">): InlineQueryResultLocation & {
text(message_text: string, options?: OptionalFields<InputTextMessageContent>): InlineQueryResultLocation;
location(latitude: number, longitude: number, options?: OptionalFields<InputLocationMessageContent>): InlineQueryResultLocation;
venue(title: string, latitude: number, longitude: number, address: string, options: OptionalFields<InputVenueMessageContent>): InlineQueryResultLocation;
contact(first_name: string, phone_number: string, options?: OptionalFields<InputContactMessageContent>): InlineQueryResultLocation;
invoice(title: string, description: string, payload: string, provider_token: string, currency: string, prices: LabeledPrice[], options?: OptionalFields<InputInvoiceMessageContent>): InlineQueryResultLocation;
};
/**
* Builds an InlineQueryResultMpeg4Gif object as specified by
* https://core.telegram.org/bots/api#inlinequeryresultmpeg4gif.
*
* @param id Unique identifier for this result, 1-64 bytes
* @param mpeg4_url A valid URL for the MPEG4 file. File size must not exceed 1MB
* @param thumbnail_url URL of the static (JPEG or GIF) or animated (MPEG4) thumbnail for the result
* @param options Remaining options
*/
mpeg4gif(id: string, mpeg4_url: string | URL, thumbnail_url: string | URL, options?: InlineQueryResultOptions<InlineQueryResultMpeg4Gif, "mpeg4_url" | "thumbnail_url">): InlineQueryResultMpeg4Gif & {
text(message_text: string, options?: OptionalFields<InputTextMessageContent>): InlineQueryResultMpeg4Gif;
location(latitude: number, longitude: number, options?: OptionalFields<InputLocationMessageContent>): InlineQueryResultMpeg4Gif;
venue(title: string, latitude: number, longitude: number, address: string, options: OptionalFields<InputVenueMessageContent>): InlineQueryResultMpeg4Gif;
contact(first_name: string, phone_number: string, options?: OptionalFields<InputContactMessageContent>): InlineQueryResultMpeg4Gif;
invoice(title: string, description: string, payload: string, provider_token: string, currency: string, prices: LabeledPrice[], options?: OptionalFields<InputInvoiceMessageContent>): InlineQueryResultMpeg4Gif;
};
/**
* Builds an InlineQueryResultCachedMpeg4Gif object as specified by
* https://core.telegram.org/bots/api#inlinequeryresultcachedmpeg4gif.
*
* @param id Unique identifier for this result, 1-64 bytes
* @param mpeg4_file_id A valid file identifier for the MPEG4 file
* @param options Remaining options
*/
mpeg4gifCached(id: string, mpeg4_file_id: string, options?: InlineQueryResultOptions<InlineQueryResultCachedMpeg4Gif, "mpeg4_file_id">): InlineQueryResultCachedMpeg4Gif & {
text(message_text: string, options?: OptionalFields<InputTextMessageContent>): InlineQueryResultCachedMpeg4Gif;
location(latitude: number, longitude: number, options?: OptionalFields<InputLocationMessageContent>): InlineQueryResultCachedMpeg4Gif;
venue(title: string, latitude: number, longitude: number, address: string, options: OptionalFields<InputVenueMessageContent>): InlineQueryResultCachedMpeg4Gif;
contact(first_name: string, phone_number: string, options?: OptionalFields<InputContactMessageContent>): InlineQueryResultCachedMpeg4Gif;
invoice(title: string, description: string, payload: string, provider_token: string, currency: string, prices: LabeledPrice[], options?: OptionalFields<InputInvoiceMessageContent>): InlineQueryResultCachedMpeg4Gif;
};
/**
* Builds an InlineQueryResultPhoto object as specified by
* https://core.telegram.org/bots/api#inlinequeryresultphoto with the
* thumbnail defaulting to the photo itself.
*
* @param id Unique identifier for this result, 1-64 bytes
* @param photo_url A valid URL of the photo. Photo must be in JPEG format. Photo size must not exceed 5MB
* @param options Remaining options
*/
photo(id: string, photo_url: string | URL, options?: InlineQueryResultOptions<InlineQueryResultPhoto, "photo_url">): InlineQueryResultPhoto & {
text(message_text: string, options?: OptionalFields<InputTextMessageContent>): InlineQueryResultPhoto;
location(latitude: number, longitude: number, options?: OptionalFields<InputLocationMessageContent>): InlineQueryResultPhoto;
venue(title: string, latitude: number, longitude: number, address: string, options: OptionalFields<InputVenueMessageContent>): InlineQueryResultPhoto;
contact(first_name: string, phone_number: string, options?: OptionalFields<InputContactMessageContent>): InlineQueryResultPhoto;
invoice(title: string, description: string, payload: string, provider_token: string, currency: string, prices: LabeledPrice[], options?: OptionalFields<InputInvoiceMessageContent>): InlineQueryResultPhoto;
};
/**
* Builds an InlineQueryResultCachedPhoto object as specified by
* https://core.telegram.org/bots/api#inlinequeryresultcachedphoto.
*
* @param id Unique identifier for this result, 1-64 bytes
* @param photo_file_id A valid file identifier of the photo
* @param options Remaining options
*/
photoCached(id: string, photo_file_id: string, options?: InlineQueryResultOptions<InlineQueryResultCachedPhoto, "photo_file_id">): InlineQueryResultCachedPhoto & {
text(message_text: string, options?: OptionalFields<InputTextMessageContent>): InlineQueryResultCachedPhoto;
location(latitude: number, longitude: number, options?: OptionalFields<InputLocationMessageContent>): InlineQueryResultCachedPhoto;
venue(title: string, latitude: number, longitude: number, address: string, options: OptionalFields<InputVenueMessageContent>): InlineQueryResultCachedPhoto;
contact(first_name: string, phone_number: string, options?: OptionalFields<InputContactMessageContent>): InlineQueryResultCachedPhoto;
invoice(title: string, description: string, payload: string, provider_token: string, currency: string, prices: LabeledPrice[], options?: OptionalFields<InputInvoiceMessageContent>): InlineQueryResultCachedPhoto;
};
/**
* Builds an InlineQueryResultCachedSticker object as specified by
* https://core.telegram.org/bots/api#inlinequeryresultcachedsticker.
*
* @param id Unique identifier for this result, 1-64 bytes
* @param sticker_file_id A valid file identifier of the sticker
* @param options Remaining options
*/
stickerCached(id: string, sticker_file_id: string, options?: InlineQueryResultOptions<InlineQueryResultCachedSticker, "sticker_file_id">): InlineQueryResultCachedSticker & {
text(message_text: string, options?: OptionalFields<InputTextMessageContent>): InlineQueryResultCachedSticker;
location(latitude: number, longitude: number, options?: OptionalFields<InputLocationMessageContent>): InlineQueryResultCachedSticker;
venue(title: string, latitude: number, longitude: number, address: string, options: OptionalFields<InputVenueMessageContent>): InlineQueryResultCachedSticker;
contact(first_name: string, phone_number: string, options?: OptionalFields<InputContactMessageContent>): InlineQueryResultCachedSticker;
invoice(title: string, description: string, payload: string, provider_token: string, currency: string, prices: LabeledPrice[], options?: OptionalFields<InputInvoiceMessageContent>): InlineQueryResultCachedSticker;
};
/**
* Builds an InlineQueryResultVenue object as specified by
* https://core.telegram.org/bots/api#inlinequeryresultvenue.
*
* @param id Unique identifier for this result, 1-64 Bytes
* @param title Title of the venue
* @param latitude Latitude of the venue location in degrees
* @param longitude Longitude of the venue location in degrees
* @param address Address of the venue
* @param options Remaining options
*/
venue(id: string, title: string, latitude: number, longitude: number, address: string, options?: InlineQueryResultOptions<InlineQueryResultVenue, "title" | "latitude" | "longitude" | "address">): InlineQueryResultVenue & {
text(message_text: string, options?: OptionalFields<InputTextMessageContent>): InlineQueryResultVenue;
location(latitude: number, longitude: number, options?: OptionalFields<InputLocationMessageContent>): InlineQueryResultVenue;
venue(title: string, latitude: number, longitude: number, address: string, options: OptionalFields<InputVenueMessageContent>): InlineQueryResultVenue;
contact(first_name: string, phone_number: string, options?: OptionalFields<InputContactMessageContent>): InlineQueryResultVenue;
invoice(title: string, description: string, payload: string, provider_token: string, currency: string, prices: LabeledPrice[], options?: OptionalFields<InputInvoiceMessageContent>): InlineQueryResultVenue;
};
/**
* Builds an InlineQueryResultVideo object as specified by
* https://core.telegram.org/bots/api#inlinequeryresultvideo with mime_type
* set to "text/html". This will send an embedded video player. Requires you
* to specify the actual message content by calling another function on the
* object returned from this method.
*
* @param id Unique identifier for this result, 1-64 bytes
* @param title Title for the result
* @param video_url A valid URL for the embedded video player
* @param thumbnail_url URL of the thumbnail (JPEG only) for the video
* @param options Remaining options
*/
videoHtml(id: string, title: string, video_url: string | URL, thumbnail_url: string | URL, options?: InlineQueryResultOptions<InlineQueryResultVideo, "mime_type" | "title" | "video_url" | "thumbnail_url">): {
text(message_text: string, options?: OptionalFields<InputTextMessageContent>): InlineQueryResultVideo;
location(latitude: number, longitude: number, options?: OptionalFields<InputLocationMessageContent>): InlineQueryResultVideo;
venue(title: string, latitude: number, longitude: number, address: string, options: OptionalFields<InputVenueMessageContent>): InlineQueryResultVideo;
contact(first_name: string, phone_number: string, options?: OptionalFields<InputContactMessageContent>): InlineQueryResultVideo;
invoice(title: string, description: string, payload: string, provider_token: string, currency: string, prices: LabeledPrice[], options?: OptionalFields<InputInvoiceMessageContent>): InlineQueryResultVideo;
};
/**
* Builds an InlineQueryResultVideo object as specified by
* https://core.telegram.org/bots/api#inlinequeryresultvideo with mime_type
* set to "video/mp4".
*
* @param id Unique identifier for this result, 1-64 bytes
* @param title Title for the result
* @param video_url A valid URL for the video file
* @param thumbnail_url URL of the thumbnail (JPEG only) for the video
* @param options Remaining options
*/
videoMp4(id: string, title: string, video_url: string | URL, thumbnail_url: string | URL, options?: InlineQueryResultOptions<InlineQueryResultVideo, "mime_type" | "title" | "video_url" | "thumbnail_url">): InlineQueryResultVideo & {
text(message_text: string, options?: OptionalFields<InputTextMessageContent>): InlineQueryResultVideo;
location(latitude: number, longitude: number, options?: OptionalFields<InputLocationMessageContent>): InlineQueryResultVideo;
venue(title: string, latitude: number, longitude: number, address: string, options: OptionalFields<InputVenueMessageContent>): InlineQueryResultVideo;
contact(first_name: string, phone_number: string, options?: OptionalFields<InputContactMessageContent>): InlineQueryResultVideo;
invoice(title: string, description: string, payload: string, provider_token: string, currency: string, prices: LabeledPrice[], options?: OptionalFields<InputInvoiceMessageContent>): InlineQueryResultVideo;
};
/**
* Builds an InlineQueryResultCachedVideo object as specified by
* https://core.telegram.org/bots/api#inlinequeryresultcachedvideo.
*
* @param id Unique identifier for this result, 1-64 bytes
* @param title Title for the result
* @param video_file_id A valid file identifier for the video file
* @param options Remaining options
*/
videoCached(id: string, title: string, video_file_id: string, options?: InlineQueryResultOptions<InlineQueryResultCachedVideo, "title" | "video_file_id">): InlineQueryResultCachedVideo & {
text(message_text: string, options?: OptionalFields<InputTextMessageContent>): InlineQueryResultCachedVideo;
location(latitude: number, longitude: number, options?: OptionalFields<InputLocationMessageContent>): InlineQueryResultCachedVideo;
venue(title: string, latitude: number, longitude: number, address: string, options: OptionalFields<InputVenueMessageContent>): InlineQueryResultCachedVideo;
contact(first_name: string, phone_number: string, options?: OptionalFields<InputContactMessageContent>): InlineQueryResultCachedVideo;
invoice(title: string, description: string, payload: string, provider_token: string, currency: string, prices: LabeledPrice[], options?: OptionalFields<InputInvoiceMessageContent>): InlineQueryResultCachedVideo;
};
/**
* Builds an InlineQueryResultVoice object as specified by
* https://core.telegram.org/bots/api#inlinequeryresultvoice.
*
* @param id Unique identifier for this result, 1-64 bytes
* @param title Voice message title
* @param voice_url A valid URL for the voice recording
* @param options Remaining options
*/
voice(id: string, title: string, voice_url: string | URL, options?: InlineQueryResultOptions<InlineQueryResultVoice, "title" | "voice_url">): InlineQueryResultVoice & {
text(message_text: string, options?: OptionalFields<InputTextMessageContent>): InlineQueryResultVoice;
location(latitude: number, longitude: number, options?: OptionalFields<InputLocationMessageContent>): InlineQueryResultVoice;
venue(title: string, latitude: number, longitude: number, address: string, options: OptionalFields<InputVenueMessageContent>): InlineQueryResultVoice;
contact(first_name: string, phone_number: string, options?: OptionalFields<InputContactMessageContent>): InlineQueryResultVoice;
invoice(title: string, description: string, payload: string, provider_token: string, currency: string, prices: LabeledPrice[], options?: OptionalFields<InputInvoiceMessageContent>): InlineQueryResultVoice;
};
/**
* Builds an InlineQueryResultCachedVoice object as specified by
* https://core.telegram.org/bots/api#inlinequeryresultcachedvoice.
*
* @param id Unique identifier for this result, 1-64 bytes
* @param title Voice message title
* @param voice_file_id A valid file identifier for the voice message
* @param options Remaining options
*/
voiceCached(id: string, title: string, voice_file_id: string, options?: InlineQueryResultOptions<InlineQueryResultCachedVoice, "title" | "voice_file_id">): InlineQueryResultCachedVoice & {
text(message_text: string, options?: OptionalFields<InputTextMessageContent>): InlineQueryResultCachedVoice;
location(latitude: number, longitude: number, options?: OptionalFields<InputLocationMessageContent>): InlineQueryResultCachedVoice;
venue(title: string, latitude: number, longitude: number, address: string, options: OptionalFields<InputVenueMessageContent>): InlineQueryResultCachedVoice;
contact(first_name: string, phone_number: string, options?: OptionalFields<InputContactMessageContent>): InlineQueryResultCachedVoice;
invoice(title: string, description: string, payload: string, provider_token: string, currency: string, prices: LabeledPrice[], options?: OptionalFields<InputInvoiceMessageContent>): InlineQueryResultCachedVoice;
};
};
export {};

View File

@@ -0,0 +1,461 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.InlineQueryResultBuilder = void 0;
function inputMessage(queryTemplate) {
return {
...queryTemplate,
...inputMessageMethods(queryTemplate),
};
}
function inputMessageMethods(queryTemplate) {
return {
text(message_text, options = {}) {
const content = {
message_text,
...options,
};
return { ...queryTemplate, input_message_content: content };
},
location(latitude, longitude, options = {}) {
const content = {
latitude,
longitude,
...options,
};
return { ...queryTemplate, input_message_content: content };
},
venue(title, latitude, longitude, address, options) {
const content = {
title,
latitude,
longitude,
address,
...options,
};
return { ...queryTemplate, input_message_content: content };
},
contact(first_name, phone_number, options = {}) {
const content = {
first_name,
phone_number,
...options,
};
return { ...queryTemplate, input_message_content: content };
},
invoice(title, description, payload, provider_token, currency, prices, options = {}) {
const content = {
title,
description,
payload,
provider_token,
currency,
prices,
...options,
};
return { ...queryTemplate, input_message_content: content };
},
};
}
/**
* Holds a number of helper methods for building `InlineQueryResult*` objects.
*
* For example, letting the user pick one out of three photos can be done like
* this.
*
* ```ts
* const results = [
* InlineQueryResultBuilder.photo('id0', 'https://grammy.dev/images/Y.png'),
* InlineQueryResultBuilder.photo('id1', 'https://grammy.dev/images/Y.png'),
* InlineQueryResultBuilder.photo('id2', 'https://grammy.dev/images/Y.png'),
* ];
* await ctx.answerInlineQuery(results)
* ```
*
* If you want the message content to be different from the content in the
* inline query result, you can perform another method call on the resulting
* objects.
*
* ```ts
* const results = [
* InlineQueryResultBuilder.photo("id0", "https://grammy.dev/images/Y.png")
* .text("Picked photo 0!"),
* InlineQueryResultBuilder.photo("id1", "https://grammy.dev/images/Y.png")
* .text("Picked photo 1!"),
* InlineQueryResultBuilder.photo("id2", "https://grammy.dev/images/Y.png")
* .text("Picked photo 2!"),
* ];
* await ctx.answerInlineQuery(results)
* ```
*
* Be sure to check the
* [documentation](https://core.telegram.org/bots/api#inline-mode) on inline
* mode.
*/
exports.InlineQueryResultBuilder = {
/**
* Builds an InlineQueryResultArticle object as specified by
* https://core.telegram.org/bots/api#inlinequeryresultarticle. Requires you
* to specify the actual message content by calling another function on the
* object returned from this method.
*
* @param id Unique identifier for this result, 1-64 Bytes
* @param title Title of the result
* @param options Remaining options
*/
article(id, title, options = {}) {
return inputMessageMethods({ type: "article", id, title, ...options });
},
/**
* Builds an InlineQueryResultAudio object as specified by
* https://core.telegram.org/bots/api#inlinequeryresultaudio.
*
* @param id Unique identifier for this result, 1-64 bytes
* @param title Title
* @param audio_url A valid URL for the audio file
* @param options Remaining options
*/
audio(id, title, audio_url, options = {}) {
return inputMessage({
type: "audio",
id,
title,
audio_url: typeof audio_url === "string"
? audio_url
: audio_url.href,
...options,
});
},
/**
* Builds an InlineQueryResultCachedAudio object as specified by
* https://core.telegram.org/bots/api#inlinequeryresultcachedaudio.
*
* @param id Unique identifier for this result, 1-64 bytes
* @param audio_file_id A valid file identifier for the audio file
* @param options Remaining options
*/
audioCached(id, audio_file_id, options = {}) {
return inputMessage({ type: "audio", id, audio_file_id, ...options });
},
/**
* Builds an InlineQueryResultContact object as specified by
* https://core.telegram.org/bots/api#inlinequeryresultcontact.
*
* @param id Unique identifier for this result, 1-64 Bytes
* @param phone_number Contact's phone number
* @param first_name Contact's first name
* @param options Remaining options
*/
contact(id, phone_number, first_name, options = {}) {
return inputMessage({ type: "contact", id, phone_number, first_name, ...options });
},
/**
* Builds an InlineQueryResultDocument object as specified by
* https://core.telegram.org/bots/api#inlinequeryresultdocument with
* mime_type set to "application/pdf".
*
* @param id Unique identifier for this result, 1-64 bytes
* @param title Title for the result
* @param document_url A valid URL for the file
* @param options Remaining options
*/
documentPdf(id, title, document_url, options = {}) {
return inputMessage({
type: "document",
mime_type: "application/pdf",
id,
title,
document_url: typeof document_url === "string"
? document_url
: document_url.href,
...options,
});
},
/**
* Builds an InlineQueryResultDocument object as specified by
* https://core.telegram.org/bots/api#inlinequeryresultdocument with
* mime_type set to "application/zip".
*
* @param id Unique identifier for this result, 1-64 bytes
* @param title Title for the result
* @param document_url A valid URL for the file
* @param options Remaining options
*/
documentZip(id, title, document_url, options = {}) {
return inputMessage({
type: "document",
mime_type: "application/zip",
id,
title,
document_url: typeof document_url === "string"
? document_url
: document_url.href,
...options,
});
},
/**
* Builds an InlineQueryResultCachedDocument object as specified by
* https://core.telegram.org/bots/api#inlinequeryresultcacheddocument.
*
* @param id Unique identifier for this result, 1-64 bytes
* @param title Title for the result
* @param document_file_id A valid file identifier for the file
* @param options Remaining options
*/
documentCached(id, title, document_file_id, options = {}) {
return inputMessage({ type: "document", id, title, document_file_id, ...options });
},
/**
* Builds an InlineQueryResultGame object as specified by
* https://core.telegram.org/bots/api#inlinequeryresultgame.
*
* @param id Unique identifier for this result, 1-64 bytes
* @param game_short_name Short name of the game
* @param options Remaining options
*/
game(id, game_short_name, options = {}) {
return { type: "game", id, game_short_name, ...options };
},
/**
* Builds an InlineQueryResultGif object as specified by
* https://core.telegram.org/bots/api#inlinequeryresultgif.
*
* @param id Unique identifier for this result, 1-64 bytes
* @param gif_url A valid URL for the GIF file. File size must not exceed 1MB
* @param thumbnail_url URL of the static (JPEG or GIF) or animated (MPEG4) thumbnail for the result
* @param options Remaining options
*/
gif(id, gif_url, thumbnail_url, options = {}) {
return inputMessage({
type: "gif",
id,
gif_url: typeof gif_url === "string" ? gif_url : gif_url.href,
thumbnail_url: typeof thumbnail_url === "string"
? thumbnail_url
: thumbnail_url.href,
...options,
});
},
/**
* Builds an InlineQueryResultCachedGif object as specified by
* https://core.telegram.org/bots/api#inlinequeryresultcachedgif.
*
* @param id Unique identifier for this result, 1-64 bytes
* @param gif_file_id A valid file identifier for the GIF file
* @param options Remaining options
*/
gifCached(id, gif_file_id, options = {}) {
return inputMessage({ type: "gif", id, gif_file_id, ...options });
},
/**
* Builds an InlineQueryResultLocation object as specified by
* https://core.telegram.org/bots/api#inlinequeryresultlocation.
*
* @param id Unique identifier for this result, 1-64 Bytes
* @param title Location title
* @param latitude Location latitude in degrees
* @param longitude Location longitude in degrees
* @param options Remaining options
*/
location(id, title, latitude, longitude, options = {}) {
return inputMessage({ type: "location", id, title, latitude, longitude, ...options });
},
/**
* Builds an InlineQueryResultMpeg4Gif object as specified by
* https://core.telegram.org/bots/api#inlinequeryresultmpeg4gif.
*
* @param id Unique identifier for this result, 1-64 bytes
* @param mpeg4_url A valid URL for the MPEG4 file. File size must not exceed 1MB
* @param thumbnail_url URL of the static (JPEG or GIF) or animated (MPEG4) thumbnail for the result
* @param options Remaining options
*/
mpeg4gif(id, mpeg4_url, thumbnail_url, options = {}) {
return inputMessage({
type: "mpeg4_gif",
id,
mpeg4_url: typeof mpeg4_url === "string"
? mpeg4_url
: mpeg4_url.href,
thumbnail_url: typeof thumbnail_url === "string"
? thumbnail_url
: thumbnail_url.href,
...options,
});
},
/**
* Builds an InlineQueryResultCachedMpeg4Gif object as specified by
* https://core.telegram.org/bots/api#inlinequeryresultcachedmpeg4gif.
*
* @param id Unique identifier for this result, 1-64 bytes
* @param mpeg4_file_id A valid file identifier for the MPEG4 file
* @param options Remaining options
*/
mpeg4gifCached(id, mpeg4_file_id, options = {}) {
return inputMessage({ type: "mpeg4_gif", id, mpeg4_file_id, ...options });
},
/**
* Builds an InlineQueryResultPhoto object as specified by
* https://core.telegram.org/bots/api#inlinequeryresultphoto with the
* thumbnail defaulting to the photo itself.
*
* @param id Unique identifier for this result, 1-64 bytes
* @param photo_url A valid URL of the photo. Photo must be in JPEG format. Photo size must not exceed 5MB
* @param options Remaining options
*/
photo(id, photo_url, options = {
thumbnail_url: typeof photo_url === "string"
? photo_url
: photo_url.href,
}) {
return inputMessage({
type: "photo",
id,
photo_url: typeof photo_url === "string"
? photo_url
: photo_url.href,
...options,
});
},
/**
* Builds an InlineQueryResultCachedPhoto object as specified by
* https://core.telegram.org/bots/api#inlinequeryresultcachedphoto.
*
* @param id Unique identifier for this result, 1-64 bytes
* @param photo_file_id A valid file identifier of the photo
* @param options Remaining options
*/
photoCached(id, photo_file_id, options = {}) {
return inputMessage({ type: "photo", id, photo_file_id, ...options });
},
/**
* Builds an InlineQueryResultCachedSticker object as specified by
* https://core.telegram.org/bots/api#inlinequeryresultcachedsticker.
*
* @param id Unique identifier for this result, 1-64 bytes
* @param sticker_file_id A valid file identifier of the sticker
* @param options Remaining options
*/
stickerCached(id, sticker_file_id, options = {}) {
return inputMessage({ type: "sticker", id, sticker_file_id, ...options });
},
/**
* Builds an InlineQueryResultVenue object as specified by
* https://core.telegram.org/bots/api#inlinequeryresultvenue.
*
* @param id Unique identifier for this result, 1-64 Bytes
* @param title Title of the venue
* @param latitude Latitude of the venue location in degrees
* @param longitude Longitude of the venue location in degrees
* @param address Address of the venue
* @param options Remaining options
*/
venue(id, title, latitude, longitude, address, options = {}) {
return inputMessage({
type: "venue",
id,
title,
latitude,
longitude,
address,
...options,
});
},
/**
* Builds an InlineQueryResultVideo object as specified by
* https://core.telegram.org/bots/api#inlinequeryresultvideo with mime_type
* set to "text/html". This will send an embedded video player. Requires you
* to specify the actual message content by calling another function on the
* object returned from this method.
*
* @param id Unique identifier for this result, 1-64 bytes
* @param title Title for the result
* @param video_url A valid URL for the embedded video player
* @param thumbnail_url URL of the thumbnail (JPEG only) for the video
* @param options Remaining options
*/
videoHtml(id, title, video_url, thumbnail_url, options = {}) {
// require input message content by only returning methods
return inputMessageMethods({
type: "video",
mime_type: "text/html",
id,
title,
video_url: typeof video_url === "string"
? video_url
: video_url.href,
thumbnail_url: typeof thumbnail_url === "string"
? thumbnail_url
: thumbnail_url.href,
...options,
});
},
/**
* Builds an InlineQueryResultVideo object as specified by
* https://core.telegram.org/bots/api#inlinequeryresultvideo with mime_type
* set to "video/mp4".
*
* @param id Unique identifier for this result, 1-64 bytes
* @param title Title for the result
* @param video_url A valid URL for the video file
* @param thumbnail_url URL of the thumbnail (JPEG only) for the video
* @param options Remaining options
*/
videoMp4(id, title, video_url, thumbnail_url, options = {}) {
return inputMessage({
type: "video",
mime_type: "video/mp4",
id,
title,
video_url: typeof video_url === "string"
? video_url
: video_url.href,
thumbnail_url: typeof thumbnail_url === "string"
? thumbnail_url
: thumbnail_url.href,
...options,
});
},
/**
* Builds an InlineQueryResultCachedVideo object as specified by
* https://core.telegram.org/bots/api#inlinequeryresultcachedvideo.
*
* @param id Unique identifier for this result, 1-64 bytes
* @param title Title for the result
* @param video_file_id A valid file identifier for the video file
* @param options Remaining options
*/
videoCached(id, title, video_file_id, options = {}) {
return inputMessage({ type: "video", id, title, video_file_id, ...options });
},
/**
* Builds an InlineQueryResultVoice object as specified by
* https://core.telegram.org/bots/api#inlinequeryresultvoice.
*
* @param id Unique identifier for this result, 1-64 bytes
* @param title Voice message title
* @param voice_url A valid URL for the voice recording
* @param options Remaining options
*/
voice(id, title, voice_url, options = {}) {
return inputMessage({
type: "voice",
id,
title,
voice_url: typeof voice_url === "string"
? voice_url
: voice_url.href,
...options,
});
},
/**
* Builds an InlineQueryResultCachedVoice object as specified by
* https://core.telegram.org/bots/api#inlinequeryresultcachedvoice.
*
* @param id Unique identifier for this result, 1-64 bytes
* @param title Voice message title
* @param voice_file_id A valid file identifier for the voice message
* @param options Remaining options
*/
voiceCached(id, title, voice_file_id, options = {}) {
return inputMessage({ type: "voice", id, title, voice_file_id, ...options });
},
};

View File

@@ -0,0 +1,72 @@
import { type InputFile, type InputMediaAnimation, type InputMediaAudio, type InputMediaDocument, type InputMediaPhoto, type InputMediaVideo } from "../types.js";
type InputMediaOptions<T> = Omit<T, "type" | "media">;
/**
* Holds a number of helper methods for building `InputMedia*` objects. They are
* useful when sending media groups and when editing media messages.
*
* For example, media groups can be sent like this.
*
* ```ts
* const paths = [
* '/tmp/pic0.jpg',
* '/tmp/pic1.jpg',
* '/tmp/pic2.jpg',
* ]
* const files = paths.map((path) => new InputFile(path))
* const media = files.map((file) => InputMediaBuilder.photo(file))
* await bot.api.sendMediaGroup(chatId, media)
* ```
*
* Media can be edited like this.
*
* ```ts
* const file = new InputFile('/tmp/pic0.jpg')
* const media = InputMediaBuilder.photo(file, {
* caption: 'new caption'
* })
* await bot.api.editMessageMedia(chatId, messageId, media)
* ```
*/
export declare const InputMediaBuilder: {
/**
* Creates a new `InputMediaPhoto` object as specified by
* https://core.telegram.org/bots/api#inputmediaphoto.
*
* @param media An `InputFile` instance or a file identifier
* @param options Remaining optional options
*/
photo(media: string | InputFile, options?: InputMediaOptions<InputMediaPhoto>): InputMediaPhoto;
/**
* Creates a new `InputMediaVideo` object as specified by
* https://core.telegram.org/bots/api#inputmediavideo.
*
* @param media An `InputFile` instance or a file identifier
* @param options Remaining optional options
*/
video(media: string | InputFile, options?: InputMediaOptions<InputMediaVideo>): InputMediaVideo;
/**
* Creates a new `InputMediaAnimation` object as specified by
* https://core.telegram.org/bots/api#inputmediaanimation.
*
* @param media An `InputFile` instance or a file identifier
* @param options Remaining optional options
*/
animation(media: string | InputFile, options?: InputMediaOptions<InputMediaAnimation>): InputMediaAnimation;
/**
* Creates a new `InputMediaAudio` object as specified by
* https://core.telegram.org/bots/api#inputmediaaudio.
*
* @param media An `InputFile` instance or a file identifier
* @param options Remaining optional options
*/
audio(media: string | InputFile, options?: InputMediaOptions<InputMediaAudio>): InputMediaAudio;
/**
* Creates a new `InputMediaDocument` object as specified by
* https://core.telegram.org/bots/api#inputmediadocument.
*
* @param media An `InputFile` instance or a file identifier
* @param options Remaining optional options
*/
document(media: string | InputFile, options?: InputMediaOptions<InputMediaDocument>): InputMediaDocument;
};
export {};

View File

@@ -0,0 +1,82 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.InputMediaBuilder = void 0;
/**
* Holds a number of helper methods for building `InputMedia*` objects. They are
* useful when sending media groups and when editing media messages.
*
* For example, media groups can be sent like this.
*
* ```ts
* const paths = [
* '/tmp/pic0.jpg',
* '/tmp/pic1.jpg',
* '/tmp/pic2.jpg',
* ]
* const files = paths.map((path) => new InputFile(path))
* const media = files.map((file) => InputMediaBuilder.photo(file))
* await bot.api.sendMediaGroup(chatId, media)
* ```
*
* Media can be edited like this.
*
* ```ts
* const file = new InputFile('/tmp/pic0.jpg')
* const media = InputMediaBuilder.photo(file, {
* caption: 'new caption'
* })
* await bot.api.editMessageMedia(chatId, messageId, media)
* ```
*/
exports.InputMediaBuilder = {
/**
* Creates a new `InputMediaPhoto` object as specified by
* https://core.telegram.org/bots/api#inputmediaphoto.
*
* @param media An `InputFile` instance or a file identifier
* @param options Remaining optional options
*/
photo(media, options = {}) {
return { type: "photo", media, ...options };
},
/**
* Creates a new `InputMediaVideo` object as specified by
* https://core.telegram.org/bots/api#inputmediavideo.
*
* @param media An `InputFile` instance or a file identifier
* @param options Remaining optional options
*/
video(media, options = {}) {
return { type: "video", media, ...options };
},
/**
* Creates a new `InputMediaAnimation` object as specified by
* https://core.telegram.org/bots/api#inputmediaanimation.
*
* @param media An `InputFile` instance or a file identifier
* @param options Remaining optional options
*/
animation(media, options = {}) {
return { type: "animation", media, ...options };
},
/**
* Creates a new `InputMediaAudio` object as specified by
* https://core.telegram.org/bots/api#inputmediaaudio.
*
* @param media An `InputFile` instance or a file identifier
* @param options Remaining optional options
*/
audio(media, options = {}) {
return { type: "audio", media, ...options };
},
/**
* Creates a new `InputMediaDocument` object as specified by
* https://core.telegram.org/bots/api#inputmediadocument.
*
* @param media An `InputFile` instance or a file identifier
* @param options Remaining optional options
*/
document(media, options = {}) {
return { type: "document", media, ...options };
},
};

View File

@@ -0,0 +1,879 @@
import { type CopyTextButton, type InlineKeyboardButton, type KeyboardButton, type KeyboardButtonPollType, type KeyboardButtonRequestChat, type KeyboardButtonRequestUsers, type LoginUrl, type SwitchInlineQueryChosenChat, type WebAppInfo } from "../types.js";
type KeyboardButtonSource = string | KeyboardButton;
type KeyboardSource = KeyboardButtonSource[][] | Keyboard;
/**
* Use this class to simplify building a custom keyboard (something like this:
* https://core.telegram.org/bots/features#keyboards).
*
* ```ts
* // Build a custom keyboard:
* const keyboard = new Keyboard()
* .text('A').text('B').row()
* .text('C').text('D')
*
* // Now you can send it like so:
* await ctx.reply('Here is your custom keyboard!', {
* reply_markup: keyboard
* })
* ```
*
* If you already have some source data which you would like to turn into a
* keyboard button object, you can use the static equivalents which every button
* has. You can use them to create a two-dimensional keyboard button array. The
* resulting array can be turned into a keyboard instance.
*
* ```ts
* const button = Keyboard.text('push my buttons')
* const array = [[button]]
* const keyboard = Keyboard.from(array)
* ```
*
* If you want to create text buttons only, you can directly use a
* two-dimensional string array and turn it into a keyboard.
*
* ```ts
* const data = [['A', 'B'], ['C', 'D']]
* const keyboard = Keyboard.from(data)
* ```
*
* Be sure to check out the
* [documentation](https://grammy.dev/plugins/keyboard#custom-keyboards) on
* custom keyboards in grammY.
*/
export declare class Keyboard {
readonly keyboard: KeyboardButton[][];
/**
* Requests clients to always show the keyboard when the regular keyboard is
* hidden. Defaults to false, in which case the custom keyboard can be
* hidden and opened with a keyboard icon.
*/
is_persistent?: boolean;
/**
* Show the current keyboard only to those users that are mentioned in the
* text of the message object.
*/
selective?: boolean;
/**
* Hide the keyboard after a button is pressed.
*/
one_time_keyboard?: boolean;
/**
* Resize the current keyboard according to its buttons. Usually, this will
* make the keyboard smaller.
*/
resize_keyboard?: boolean;
/**
* Placeholder to be shown in the input field when the keyboard is active.
*/
input_field_placeholder?: string;
/**
* Initialize a new `Keyboard` with an optional two-dimensional array of
* `KeyboardButton` objects. This is the nested array that holds the custom
* keyboard. It will be extended every time you call one of the provided
* methods.
*
* @param keyboard An optional initial two-dimensional button array
*/
constructor(keyboard?: KeyboardButton[][]);
/**
* Allows you to add your own `KeyboardButton` objects if you already have
* them for some reason. You most likely want to call one of the other
* methods.
*
* @param buttons The buttons to add
*/
add(...buttons: KeyboardButton[]): this;
/**
* Adds a 'line break'. Call this method to make sure that the next added
* buttons will be on a new row.
*
* You may pass a number of `KeyboardButton` objects if you already have the
* instances for some reason. You most likely don't want to pass any
* arguments to `row`.
*
* @param buttons A number of buttons to add to the next row
*/
row(...buttons: KeyboardButton[]): this;
/**
* Adds a new text button. This button will simply send the given text as a
* text message back to your bot if a user clicks on it.
*
* @param text The text to display, and optional styling information
* @param options Optional styling information
*/
text(text: string, options?: KeyboardButton.CommonButton["style"] | Omit<KeyboardButton.CommonButton, "text">): this;
/**
* Creates a new text button. This button will simply send the given text as
* a text message back to your bot if a user clicks on it.
*
* @param text The text to display, and optional styling information
* @param options Optional styling information
*/
static text(text: string, options?: KeyboardButton.CommonButton["style"] | Omit<KeyboardButton.CommonButton, "text">): KeyboardButton.CommonButton;
/**
* Adds a new request users button. When the user presses the button, a list
* of suitable users will be opened. Tapping on any number of users will
* send their identifiers to the bot in a “users_shared” service message.
* Available in private chats only.
*
* @param text The text to display, and optional styling information
* @param requestId A signed 32-bit identifier of the request
* @param options Options object for further requirements
*/
requestUsers(text: string | KeyboardButton.CommonButton, requestId: number, options?: Omit<KeyboardButtonRequestUsers, "request_id">): this;
/**
* Creates a new request users button. When the user presses the button, a
* list of suitable users will be opened. Tapping on any number of users
* will send their identifiers to the bot in a “users_shared” service
* message. Available in private chats only.
*
* @param text The text to display, and optional styling information
* @param requestId A signed 32-bit identifier of the request
* @param options Options object for further requirements
*/
static requestUsers(text: string | KeyboardButton.CommonButton, requestId: number, options?: Omit<KeyboardButtonRequestUsers, "request_id">): KeyboardButton.RequestUsersButton;
/**
* Adds a new request chat button. When the user presses the button, a list
* of suitable users will be opened. Tapping on a chat will send its
* identifier to the bot in a “chat_shared” service message. Available in
* private chats only.
*
* @param text The text to display, and optional styling information
* @param requestId A signed 32-bit identifier of the request
* @param options Options object for further requirements
*/
requestChat(text: string | KeyboardButton.CommonButton, requestId: number, options?: Omit<KeyboardButtonRequestChat, "request_id">): this;
/**
* Creates a new request chat button. When the user presses the button, a
* list of suitable users will be opened. Tapping on a chat will send its
* identifier to the bot in a “chat_shared” service message. Available in
* private chats only.
*
* @param text The text to display, and optional styling information
* @param requestId A signed 32-bit identifier of the request
* @param options Options object for further requirements
*/
static requestChat(text: string | KeyboardButton.CommonButton, requestId: number, options?: Omit<KeyboardButtonRequestChat, "request_id">): KeyboardButton.RequestChatButton;
/**
* Adds a new contact request button. The user's phone number will be sent
* as a contact when the button is pressed. Available in private chats only.
*
* @param text The text to display, and optional styling information
*/
requestContact(text: string | KeyboardButton.CommonButton): this;
/**
* Creates a new contact request button. The user's phone number will be
* sent as a contact when the button is pressed. Available in private chats
* only.
*
* @param text The text to display, and optional styling information
*/
static requestContact(text: string | KeyboardButton.CommonButton): KeyboardButton.RequestContactButton;
/**
* Adds a new location request button. The user's current location will be
* sent when the button is pressed. Available in private chats only.
*
* @param text The text to display, and optional styling information
*/
requestLocation(text: string | KeyboardButton.CommonButton): this;
/**
* Creates a new location request button. The user's current location will
* be sent when the button is pressed. Available in private chats only.
*
* @param text The text to display, and optional styling information
*/
static requestLocation(text: string | KeyboardButton.CommonButton): KeyboardButton.RequestLocationButton;
/**
* Adds a new poll request button. The user will be asked to create a poll
* and send it to the bot when the button is pressed. Available in private
* chats only.
*
* @param text The text to display, and optional styling information
* @param type The type of permitted polls to create, omit if the user may
* send a poll of any type
*/
requestPoll(text: string | KeyboardButton.CommonButton, type?: KeyboardButtonPollType["type"]): this;
/**
* Creates a new poll request button. The user will be asked to create a
* poll and send it to the bot when the button is pressed. Available in
* private chats only.
*
* @param text The text to display, and optional styling information
* @param type The type of permitted polls to create, omit if the user may
* send a poll of any type
*/
static requestPoll(text: string | KeyboardButton.CommonButton, type?: KeyboardButtonPollType["type"]): KeyboardButton.RequestPollButton;
/**
* Adds a new web app button. The Web App that will be launched when the
* user presses the button. The Web App will be able to send a
* “web_app_data” service message. Available in private chats only.
*
* @param text The text to display, and optional styling information
* @param url An HTTPS URL of a Web App to be opened with additional data
*/
webApp(text: string | KeyboardButton.CommonButton, url: string): this;
/**
* Creates a new web app button. The Web App that will be launched when the
* user presses the button. The Web App will be able to send a
* “web_app_data” service message. Available in private chats only.
*
* @param text The text to display, and optional styling information
* @param url An HTTPS URL of a Web App to be opened with additional data
*/
static webApp(text: string | KeyboardButton.CommonButton, url: string): KeyboardButton.WebAppButton;
/**
* Adds a style to the last added button of the keyboard.
*
* ```ts
* const keyboard = new Keyboard()
* .text('blue button')
* .style('primary')
* ```
*
* @param style Style of the button
*/
style(style: KeyboardButton.CommonButton["style"]): this;
/**
* Adds a danger style to the last added button of the keyboard. Alias for
* `.style('danger')`.
*
* ```ts
* const keyboard = new Keyboard()
* .text('red button')
* .danger()
* ```
*/
danger(): this;
/**
* Adds a success style to the last added button of the keyboard. Alias for
* `.style('success')`.
*
* ```ts
* const keyboard = new Keyboard()
* .text('green button')
* .success()
* ```
*/
success(): this;
/**
* Adds a primary style to the last added button of the keyboard. Alias for
* `.style('primary')`.
*
* ```ts
* const keyboard = new Keyboard()
* .text('blue button')
* .primary()
* ```
*/
primary(): this;
/**
* Adds a custom emoji icon to the last added button of the keyboard.
*
* ```ts
* const keyboard = new Keyboard()
* .text('button with icon')
* .icon(myCustomEmojiIconIdentifier)
* ```
*
* @param icon Unique identifier of the custom emoji shown before the text of the button
*/
icon(icon: KeyboardButton.CommonButton["icon_custom_emoji_id"]): this;
/**
* Make the current keyboard persistent. See
* https://grammy.dev/plugins/keyboard#persistent-keyboards for more
* details.
*
* Keyboards are not persistent by default, use this function to enable it
* (without any parameters or pass `true`). Pass `false` to force the
* keyboard to not persist.
*
* @param isEnabled `true` if the keyboard should persist, and `false` otherwise
*/
persistent(isEnabled?: boolean): this;
/**
* Make the current keyboard selective. See
* https://grammy.dev/plugins/keyboard#selectively-send-custom-keyboards
* for more details.
*
* Keyboards are non-selective by default, use this function to enable it
* (without any parameters or pass `true`). Pass `false` to force the
* keyboard to be non-selective.
*
* @param isEnabled `true` if the keyboard should be selective, and `false` otherwise
*/
selected(isEnabled?: boolean): this;
/**
* Make the current keyboard one-time. See
* https://grammy.dev/plugins/keyboard#one-time-custom-keyboards for
* more details.
*
* Keyboards are non-one-time by default, use this function to enable it
* (without any parameters or pass `true`). Pass `false` to force the
* keyboard to be non-one-time.
*
* @param isEnabled `true` if the keyboard should be one-time, and `false` otherwise
*/
oneTime(isEnabled?: boolean): this;
/**
* Make the current keyboard resized. See
* https://grammy.dev/plugins/keyboard#resize-custom-keyboard for more
* details.
*
* Keyboards are non-resized by default, use this function to enable it
* (without any parameters or pass `true`). Pass `false` to force the
* keyboard to be non-resized.
*
* @param isEnabled `true` if the keyboard should be resized, and `false` otherwise
*/
resized(isEnabled?: boolean): this;
/**
* Set the current keyboard's input field placeholder. See
* https://grammy.dev/plugins/keyboard#input-field-placeholder for more
* details.
*
* @param value The placeholder text
*/
placeholder(value: string): this;
/**
* Creates a new keyboard that contains the transposed grid of buttons of
* this keyboard. This means that the resulting keyboard has the rows and
* columns flipped.
*
* Note that buttons can only span multiple columns, but never multiple
* rows. This means that if the given arrays have different lengths, some
* buttons might flow up in the layout. In these cases, transposing a
* keyboard a second time will not undo the first transposition.
*
* Here are some examples.
*
* ```
* original transposed
* [ a ] ~> [ a ]
*
* [ a ]
* [a b c] ~> [ b ]
* [ c ]
*
* [ a b ] [a c e]
* [ c d ] ~> [ b d ]
* [ e ]
*
* [ a b ] [a c d]
* [ c ] ~> [ b e ]
* [d e f] [ f ]
* ```
*/
toTransposed(): Keyboard;
/**
* Creates a new keyboard with the same buttons but reflowed into a given
* number of columns as if the buttons were text elements. Optionally, you
* can specify if the flow should make sure to fill up the last row.
*
* This method is idempotent, so calling it a second time will effectively
* clone this keyboard without reordering the buttons.
*
* Here are some examples.
*
* ```
* original flowed
* [ a ] ~> [ a ] (4 columns)
*
* [ a ]
* [a b c] ~> [ b ] (1 column)
* [ c ]
*
* [ a b ] [a b c]
* [ c d ] ~> [ d e ] (3 columns)
* [ e ]
*
* [ a b ] [abcde]
* [ c ] ~> [ f ] (5 columns)
* [d e f]
*
* [a b c] [ a ]
* [d e f] ~> [b c d] (3 columns, { fillLastRow: true })
* [g h i] [e f g]
* [ j ] [h i j]
* ```
*
* @param columns Maximum number of buttons per row
* @param options Optional flowing behavior
*/
toFlowed(columns: number, options?: FlowOptions): Keyboard;
/**
* Creates and returns a deep copy of this keyboard.
*
* Optionally takes a new grid of buttons to replace the current buttons. If
* specified, only the options will be cloned, and the given buttons will be
* used instead.
*/
clone(keyboard?: KeyboardButton[][]): Keyboard;
/**
* Appends the buttons of the given keyboards to this keyboard. If other
* options are specified in these keyboards, they will be ignored.
*
* @param sources A number of keyboards to append
*/
append(...sources: KeyboardSource[]): this;
/**
* Returns the keyboard that was build. Note that it doesn't return
* `resize_keyboard` or other options that may be set. You don't usually
* need to call this method. It is no longer useful.
*/
build(): KeyboardButton[][];
/**
* Turns a two-dimensional keyboard button array into a keyboard instance.
* You can use the static button builder methods to create keyboard button
* objects.
*
* @param source A two-dimensional button array
*/
static from(source: KeyboardSource): Keyboard;
}
type InlineKeyboardSource = InlineKeyboardButton[][] | InlineKeyboard;
/**
* Use this class to simplify building an inline keyboard (something like this:
* https://core.telegram.org/bots/features#inline-keyboards).
*
* ```ts
* // Build an inline keyboard:
* const keyboard = new InlineKeyboard()
* .text('A').text('B', 'callback-data').row()
* .text('C').text('D').row()
* .url('Telegram', 'telegram.org')
*
* // Send the keyboard:
* await ctx.reply('Here is your inline keyboard!', {
* reply_markup: keyboard
* })
* ```
*
* If you already have some source data which you would like to turn into an
* inline button object, you can use the static equivalents which every inline
* button has. You can use them to create a two-dimensional inline button array.
* The resulting array can be turned into a keyboard instance.
*
* ```ts
* const button = InlineKeyboard.text('GO', 'go')
* const array = [[button]]
* const keyboard = InlineKeyboard.from(array)
* ```
*
* Be sure to to check the
* [documentation](https://grammy.dev/plugins/keyboard#inline-keyboards) on
* inline keyboards in grammY.
*/
export declare class InlineKeyboard {
readonly inline_keyboard: InlineKeyboardButton[][];
/**
* Initialize a new `InlineKeyboard` with an optional two-dimensional array
* of `InlineKeyboardButton` objects. This is the nested array that holds
* the inline keyboard. It will be extended every time you call one of the
* provided methods.
*
* @param inline_keyboard An optional initial two-dimensional button array
*/
constructor(inline_keyboard?: InlineKeyboardButton[][]);
/**
* Allows you to add your own `InlineKeyboardButton` objects if you already
* have them for some reason. You most likely want to call one of the other
* methods.
*
* @param buttons The buttons to add
*/
add(...buttons: InlineKeyboardButton[]): this;
/**
* Adds a 'line break'. Call this method to make sure that the next added
* buttons will be on a new row.
*
* You may pass a number of `InlineKeyboardButton` objects if you already
* have the instances for some reason. You most likely don't want to pass
* any arguments to `row`.
*
* @param buttons A number of buttons to add to the next row
*/
row(...buttons: InlineKeyboardButton[]): this;
/**
* Adds a new URL button. Telegram clients will open the provided URL when
* the button is pressed.
*
* @param text The text to display, and optional styling information
* @param url HTTP or tg:// url to be opened when the button is pressed. Links tg://user?id=<user_id> can be used to mention a user by their ID without using a username, if this is allowed by their privacy settings.
*/
url(text: string | InlineKeyboardButton.AbstractInlineKeyboardButton, url: string): this;
/**
* Creates a new URL button. Telegram clients will open the provided URL
* when the button is pressed.
*
* @param text The text to display, and optional styling information
* @param url HTTP or tg:// url to be opened when the button is pressed. Links tg://user?id=<user_id> can be used to mention a user by their ID without using a username, if this is allowed by their privacy settings.
*/
static url(text: string | InlineKeyboardButton.AbstractInlineKeyboardButton, url: string): InlineKeyboardButton.UrlButton;
/**
* Adds a new callback query button. The button contains a text and a custom
* payload. This payload will be sent back to your bot when the button is
* pressed. If you omit the payload, the display text will be sent back to
* your bot.
*
* Your bot will receive an update every time a user presses any of the text
* buttons. You can listen to these updates like this:
* ```ts
* // Specific buttons:
* bot.callbackQuery('button-data', ctx => { ... })
* // Any button of any inline keyboard:
* bot.on('callback_query:data', ctx => { ... })
* ```
*
* @param text The text to display, and optional styling information
* @param data The callback data to send back to your bot (default = text)
*/
text(text: string | InlineKeyboardButton.AbstractInlineKeyboardButton, data?: string): this;
/**
* Creates a new callback query button. The button contains a text and a
* custom payload. This payload will be sent back to your bot when the
* button is pressed. If you omit the payload, the display text will be sent
* back to your bot.
*
* Your bot will receive an update every time a user presses any of the text
* buttons. You can listen to these updates like this:
* ```ts
* // Specific buttons:
* bot.callbackQuery('button-data', ctx => { ... })
* // Any button of any inline keyboard:
* bot.on('callback_query:data', ctx => { ... })
* ```
*
* @param text The text to display, and optional styling information
* @param data The callback data to send back to your bot (default = text)
*/
static text(text: string | InlineKeyboardButton.AbstractInlineKeyboardButton, data?: string): InlineKeyboardButton.CallbackButton;
/**
* Adds a new web app button, confer https://core.telegram.org/bots/webapps
*
* @param text The text to display, and optional styling information
* @param url An HTTPS URL of a Web App to be opened with additional data
*/
webApp(text: string | InlineKeyboardButton.AbstractInlineKeyboardButton, url: string | WebAppInfo): this;
/**
* Creates a new web app button, confer https://core.telegram.org/bots/webapps
*
* @param text The text to display, and optional styling information
* @param url An HTTPS URL of a Web App to be opened with additional data
*/
static webApp(text: string | InlineKeyboardButton.AbstractInlineKeyboardButton, url: string | WebAppInfo): InlineKeyboardButton.WebAppButton;
/**
* Adds a new login button. This can be used as a replacement for the
* Telegram Login Widget. You must specify an HTTPS URL used to
* automatically authorize the user.
*
* @param text The text to display, and optional styling information
* @param loginUrl The login URL as string or `LoginUrl` object
*/
login(text: string | InlineKeyboardButton.AbstractInlineKeyboardButton, loginUrl: string | LoginUrl): this;
/**
* Creates a new login button. This can be used as a replacement for the
* Telegram Login Widget. You must specify an HTTPS URL used to
* automatically authorize the user.
*
* @param text The text to display, and optional styling information
* @param loginUrl The login URL as string or `LoginUrl` object
*/
static login(text: string | InlineKeyboardButton.AbstractInlineKeyboardButton, loginUrl: string | LoginUrl): InlineKeyboardButton.LoginButton;
/**
* Adds a new inline query button. Telegram clients will let the user pick a
* chat when this button is pressed. This will start an inline query. The
* selected chat will be prefilled with the name of your bot. You may
* provide a text that is specified along with it.
*
* Your bot will in turn receive updates for inline queries. You can listen
* to inline query updates like this:
* ```ts
* bot.on('inline_query', ctx => { ... })
* ```
*
* @param text The text to display, and optional styling information
* @param query The (optional) inline query string to prefill
*/
switchInline(text: string | InlineKeyboardButton.AbstractInlineKeyboardButton, query?: string): this;
/**
* Creates a new inline query button. Telegram clients will let the user pick a
* chat when this button is pressed. This will start an inline query. The
* selected chat will be prefilled with the name of your bot. You may
* provide a text that is specified along with it.
*
* Your bot will in turn receive updates for inline queries. You can listen
* to inline query updates like this:
* ```ts
* bot.on('inline_query', ctx => { ... })
* ```
*
* @param text The text to display, and optional styling information
* @param query The (optional) inline query string to prefill
*/
static switchInline(text: string | InlineKeyboardButton.AbstractInlineKeyboardButton, query?: string): InlineKeyboardButton.SwitchInlineButton;
/**
* Adds a new inline query button that acts on the current chat. The
* selected chat will be prefilled with the name of your bot. You may
* provide a text that is specified along with it. This will start an inline
* query.
*
* Your bot will in turn receive updates for inline queries. You can listen
* to inline query updates like this:
* ```ts
* bot.on('inline_query', ctx => { ... })
* ```
*
* @param text The text to display, and optional styling information
* @param query The (optional) inline query string to prefill
*/
switchInlineCurrent(text: string | InlineKeyboardButton.AbstractInlineKeyboardButton, query?: string): this;
/**
* Creates a new inline query button that acts on the current chat. The
* selected chat will be prefilled with the name of your bot. You may
* provide a text that is specified along with it. This will start an inline
* query.
*
* Your bot will in turn receive updates for inline queries. You can listen
* to inline query updates like this:
* ```ts
* bot.on('inline_query', ctx => { ... })
* ```
*
* @param text The text to display, and optional styling information
* @param query The (optional) inline query string to prefill
*/
static switchInlineCurrent(text: string | InlineKeyboardButton.AbstractInlineKeyboardButton, query?: string): InlineKeyboardButton.SwitchInlineCurrentChatButton;
/**
* Adds a new inline query button. Telegram clients will let the user pick a
* chat when this button is pressed. This will start an inline query. The
* selected chat will be prefilled with the name of your bot. You may
* provide a text that is specified along with it.
*
* Your bot will in turn receive updates for inline queries. You can listen
* to inline query updates like this:
* ```ts
* bot.on('inline_query', ctx => { ... })
* ```
*
* @param text The text to display, and optional styling information
* @param query The query object describing which chats can be picked
*/
switchInlineChosen(text: string | InlineKeyboardButton.AbstractInlineKeyboardButton, query?: SwitchInlineQueryChosenChat): this;
/**
* Creates a new inline query button. Telegram clients will let the user pick a
* chat when this button is pressed. This will start an inline query. The
* selected chat will be prefilled with the name of your bot. You may
* provide a text that is specified along with it.
*
* Your bot will in turn receive updates for inline queries. You can listen
* to inline query updates like this:
* ```ts
* bot.on('inline_query', ctx => { ... })
* ```
*
* @param text The text to display, and optional styling information
* @param query The query object describing which chats can be picked
*/
static switchInlineChosen(text: string | InlineKeyboardButton.AbstractInlineKeyboardButton, query?: SwitchInlineQueryChosenChat): InlineKeyboardButton.SwitchInlineChosenChatButton;
/**
* Adds a new copy text button. When clicked, the specified text will be
* copied to the clipboard.
*
* @param text The text to display, and optional styling information
* @param copyText The text to be copied to the clipboard
*/
copyText(text: string | InlineKeyboardButton.AbstractInlineKeyboardButton, copyText: string | CopyTextButton): this;
/**
* Creates a new copy text button. When clicked, the specified text will be
* copied to the clipboard.
*
* @param text The text to display, and optional styling information
* @param copyText The text to be copied to the clipboard
*/
static copyText(text: string | InlineKeyboardButton.AbstractInlineKeyboardButton, copyText: string | CopyTextButton): InlineKeyboardButton.CopyTextButtonButton;
/**
* Adds a new game query button, confer
* https://core.telegram.org/bots/api#games
*
* This type of button must always be the first button in the first row.
*
* @param text The text to display, and optional styling information
*/
game(text: string | InlineKeyboardButton.AbstractInlineKeyboardButton): this;
/**
* Creates a new game query button, confer
* https://core.telegram.org/bots/api#games
*
* This type of button must always be the first button in the first row.
*
* @param text The text to display, and optional styling information
*/
static game(text: string | InlineKeyboardButton.AbstractInlineKeyboardButton): InlineKeyboardButton.GameButton;
/**
* Adds a new payment button, confer
* https://core.telegram.org/bots/api#payments
*
* This type of button must always be the first button in the first row and
* can only be used in invoice messages.
*
* @param text The text to display, and optional styling information. Substrings “⭐” and “XTR” in the buttons's text will be replaced with a Telegram Star icon.
*/
pay(text: string | InlineKeyboardButton.AbstractInlineKeyboardButton): this;
/**
* Create a new payment button, confer
* https://core.telegram.org/bots/api#payments
*
* This type of button must always be the first button in the first row and
* can only be used in invoice messages.
*
* @param text The text to display, and optional styling information. Substrings “⭐” and “XTR” in the buttons's text will be replaced with a Telegram Star icon.
*/
static pay(text: string | InlineKeyboardButton.AbstractInlineKeyboardButton): InlineKeyboardButton.PayButton;
/**
* Adds a style to the last added button of the inline keyboard.
*
* ```ts
* const keyboard = new InlineKeyboard()
* .text('blue button')
* .style('primary')
* ```
*
* @param style Style of the button
*/
style(style: InlineKeyboardButton.AbstractInlineKeyboardButton["style"]): this;
/**
* Adds a danger style to the last added button of the inline keyboard.
* Alias for `.style('danger')`.
*
* ```ts
* const keyboard = new InlineKeyboard()
* .text('red button')
* .danger()
* ```
*/
danger(): this;
/**
* Adds a success style to the last added button of the inline keyboard.
* Alias for `.style('success')`.
*
* ```ts
* const keyboard = new InlineKeyboard()
* .text('green button')
* .success()
* ```
*/
success(): this;
/**
* Adds a primary style to the last added button of the inline keyboard.
* Alias for `.style('primary')`.
*
* ```ts
* const keyboard = new InlineKeyboard()
* .text('blue button')
* .primary()
* ```
*/
primary(): this;
/**
* Adds a custom emoji icon to the last added button of the inline keyboard.
*
* ```ts
* const keyboard = new InlineKeyboard()
* .text('button with icon')
* .icon(myCustomEmojiIconIdentifier)
* ```
*
* @param icon Unique identifier of the custom emoji shown before the text of the button
*/
icon(icon: InlineKeyboardButton.AbstractInlineKeyboardButton["icon_custom_emoji_id"]): this;
/**
* Creates a new inline keyboard that contains the transposed grid of
* buttons of this inline keyboard. This means that the resulting inline
* keyboard has the rows and columns flipped.
*
* Note that inline buttons can only span multiple columns, but never
* multiple rows. This means that if the given arrays have different
* lengths, some buttons might flow up in the layout. In these cases,
* transposing an inline keyboard a second time will not undo the first
* transposition.
*
* Here are some examples.
*
* ```
* original transposed
* [ a ] ~> [ a ]
*
* [ a ]
* [a b c] ~> [ b ]
* [ c ]
*
* [ a b ] [a c e]
* [ c d ] ~> [ b d ]
* [ e ]
*
* [ a b ] [a c d]
* [ c ] ~> [ b e ]
* [d e f] [ f ]
* ```
*/
toTransposed(): InlineKeyboard;
/**
* Creates a new inline keyboard with the same buttons but reflowed into a
* given number of columns as if the buttons were text elements. Optionally,
* you can specify if the flow should make sure to fill up the last row.
*
* This method is idempotent, so calling it a second time will effectively
* clone this inline keyboard without reordering the buttons.
*
* Here are some examples.
*
* ```
* original flowed
* [ a ] ~> [ a ] (4 columns)
*
* [ a ]
* [a b c] ~> [ b ] (1 column)
* [ c ]
*
* [ a b ] [a b c]
* [ c d ] ~> [ d e ] (3 columns)
* [ e ]
*
* [ a b ] [abcde]
* [ c ] ~> [ f ] (5 columns)
* [d e f]
*
* [a b c] [ a ]
* [d e f] ~> [b c d] (3 columns, { fillLastRow: true })
* [g h i] [e f g]
* [ j ] [h i j]
* ```
*
* @param columns Maximum number of buttons per row
* @param options Optional flowing behavior
*/
toFlowed(columns: number, options?: FlowOptions): InlineKeyboard;
/**
* Creates and returns a deep copy of this inline keyboard.
*/
clone(): InlineKeyboard;
/**
* Appends the buttons of the given inline keyboards to this keyboard.
*
* @param sources A number of inline keyboards to append
*/
append(...sources: InlineKeyboardSource[]): this;
/**
* Turns a two-dimensional inline button array into an inline keyboard
* instance. You can use the static button builder methods to create inline
* button objects.
*
* @param source A two-dimensional inline button array
*/
static from(source: InlineKeyboardSource): InlineKeyboard;
}
interface FlowOptions {
/** Set to `true` to completely fill up the last row */
fillLastRow?: boolean;
}
export {};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,345 @@
import { type MiddlewareFn } from "../composer.js";
import { type Context } from "../context.js";
type MaybePromise<T> = Promise<T> | T;
/**
* A session flavor is a context flavor that holds session data under
* `ctx.session`.
*
* Session middleware will load the session data of a specific chat from your
* storage solution, and make it available to you on the context object. Check
* out the [documentation](https://grammy.dev/ref/core/session) on session
* middleware to know more, and read the section about sessions on the
* [website](https://grammy.dev/plugins/session).
*/
export interface SessionFlavor<S> {
/**
* Session data on the context object.
*
* **WARNING:** You have to make sure that your session data is not
* undefined by _providing an initial value to the session middleware_, or
* by making sure that `ctx.session` is assigned if it is empty! The type
* system does not include `| undefined` because this is really annoying to
* work with.
*
* Accessing `ctx.session` by reading or writing will throw if
* `getSessionKey(ctx) === undefined` for the respective context object
* `ctx`.
*/
get session(): S;
set session(session: S | null | undefined);
}
/**
* A lazy session flavor is a context flavor that holds a promise of some
* session data under `ctx.session`.
*
* Lazy session middleware will provide this promise lazily on the context
* object. Once you access `ctx.session`, the storage will be queried and the
* session data becomes available. If you access `ctx.session` again for the
* same context object, the cached value will be used. Check out the
* [documentation](https://grammy.dev/ref/core/lazysession) on lazy session
* middleware to know more, and read the section about lazy sessions on the
* [website](https://grammy.dev/plugins/session#lazy-sessions).
*/
export interface LazySessionFlavor<S> {
/**
* Session data on the context object, potentially a promise.
*
* **WARNING:** You have to make sure that your session data is not
* undefined by _providing a default value to the session middleware_, or by
* making sure that `ctx.session` is assigned if it is empty! The type
* system does not include `| undefined` because this is really annoying to
* work with.
*
* Accessing `ctx.session` by reading or writing will throw iff
* `getSessionKey(ctx) === undefined` holds for the respective context
* object `ctx`.
*/
get session(): MaybePromise<S>;
set session(session: MaybePromise<S | null | undefined>);
}
/**
* A storage adapter is an abstraction that provides read, write, and delete
* access to a storage solution of any kind. Storage adapters are used to keep
* session middleware independent of your database provider, and they allow you
* to pass your own storage solution.
*/
export interface StorageAdapter<T> {
/**
* Reads a value for the given key from the storage. May return the value or
* undefined, or a promise of either.
*/
read: (key: string) => MaybePromise<T | undefined>;
/**
* Writes a value for the given key to the storage.
*/
write: (key: string, value: T) => MaybePromise<void>;
/**
* Deletes a value for the given key from the storage.
*/
delete: (key: string) => MaybePromise<void>;
/**
* Checks whether a key exists in the storage.
*/
has?: (key: string) => MaybePromise<boolean>;
/**
* Lists all keys.
*/
readAllKeys?: () => Iterable<string> | AsyncIterable<string>;
/**
* Lists all values.
*/
readAllValues?: () => Iterable<T> | AsyncIterable<T>;
/**
* Lists all keys with their values.
*/
readAllEntries?: () => Iterable<[key: string, value: T]> | AsyncIterable<[key: string, value: T]>;
}
/**
* Options for session middleware.
*/
export interface SessionOptions<S, C extends Context = Context> {
type?: "single";
/**
* **Recommended to use.**
*
* A function that produces an initial value for `ctx.session`. This
* function will be called every time the storage solution returns undefined
* for a given session key. Make sure to create a new value every time, such
* that different context objects do that accidentally share the same
* session data.
*/
initial?: () => S;
/**
* An optional prefix to prepend to the session key after it was generated.
*
* This makes it easier to store session data under a namespace. You can
* technically achieve the same functionality by returning an already
* prefixed key from `getSessionKey`. This option is merely more convenient,
* as it does not require you to think about session key generation.
*/
prefix?: string;
/**
* This option lets you generate your own session keys per context object.
* The session key determines how to map the different session objects to
* your chats and users. Check out the
* [documentation](https://grammy.dev/plugins/session#how-to-use-sessions)
* on the website about how to use session middleware to know how session
* keys are used.
*
* The default implementation will store sessions per chat, as determined by
* `ctx.chatId`.
*/
getSessionKey?: (ctx: Omit<C, "session">) => MaybePromise<string | undefined>;
/**
* A storage adapter to your storage solution. Provides read, write, and
* delete access to the session middleware.
*
* Consider using a [known storage
* adapter](https://grammy.dev/plugins/session#known-storage-adapters)
* instead of rolling your own implementation of this.
*
* The default implementation will store session in memory. The data will be
* lost whenever your bot restarts.
*/
storage?: StorageAdapter<S>;
}
/**
* Options for session middleware if multi sessions are used. Specify `"type":
* "multi"` in the options to use multi sessions.
*/
export type MultiSessionOptions<S, C extends Context> = S extends Record<string, any> ? {
type: "multi";
} & MultiSessionOptionsRecord<S, C> : never;
type MultiSessionOptionsRecord<S extends Record<string, unknown>, C extends Context> = {
[K in keyof S]: SessionOptions<S[K], C>;
};
/**
* Session middleware provides a persistent data storage for your bot. You can
* use it to let your bot remember any data you want, for example the messages
* it sent or received in the past. This is done by attaching _session data_ to
* every chat. The stored data is then provided on the context object under
* `ctx.session`.
*
* > **What is a session?** Simply put, the session of a chat is a little
* > persistent storage that is attached to it. As an example, your bot can send
* > a message to a chat and store the identifier of that message in the
* > corresponding session. The next time your bot receives an update from that
* > chat, the session will still contain that ID.
* >
* > Session data can be stored in a database, in a file, or simply in memory.
* > grammY only supports memory sessions out of the box, but you can use
* > third-party session middleware to connect to other storage solutions. Note
* > that memory sessions will be lost when you stop your bot and the process
* > exits, so they are usually not useful in production.
*
* Whenever your bot receives an update, the first thing the session middleware
* will do is to load the correct session from your storage solution. This
* object is then provided on `ctx.session` while your other middleware is
* running. As soon as your bot is done handling the update, the middleware
* takes over again and writes back the session object to your storage. This
* allows you to modify the session object arbitrarily in your middleware, and
* to stop worrying about the database.
*
* ```ts
* bot.use(session())
*
* bot.on('message', ctx => {
* // The session object is persisted across updates!
* const session = ctx.session
* })
* ```
*
* It is recommended to make use of the `initial` option in the configuration
* object, which correctly initializes session objects for new chats.
*
* You can delete the session data by setting `ctx.session` to `null` or
* `undefined`.
*
* Check out the [documentation](https://grammy.dev/plugins/session) on the
* website to know more about how sessions work in grammY.
*
* @param options Optional configuration to pass to the session middleware
*/
export declare function session<S, C extends Context>(options?: SessionOptions<S, C> | MultiSessionOptions<S, C>): MiddlewareFn<C & SessionFlavor<S>>;
/**
* > This is an advanced function of grammY.
*
* Generally speaking, lazy sessions work just like normal sessions—just they
* are loaded on demand. Except for a few `async`s and `await`s here and there,
* their usage looks 100 % identical.
*
* Instead of directly querying the storage every time an update arrives, lazy
* sessions quickly do this _once you access_ `ctx.session`. This can
* significantly reduce the database traffic (especially when your bot is added
* to group chats), because it skips a read and a wrote operation for all
* updates that the bot does not react to.
*
* ```ts
* // The options are identical
* bot.use(lazySession({ storage: ... }))
*
* bot.on('message', async ctx => {
* // The session object is persisted across updates!
* const session = await ctx.session
* // ^
* // |
* // This plain property access (no function call) will trigger the database query!
* })
* ```
*
* Check out the
* [documentation](https://grammy.dev/plugins/session#lazy-sessions) on the
* website to know more about how lazy sessions work in grammY.
*
* @param options Optional configuration to pass to the session middleware
*/
export declare function lazySession<S, C extends Context>(options?: SessionOptions<S, C>): MiddlewareFn<C & LazySessionFlavor<S>>;
/**
* When enhancing a storage adapter, it needs to be able to store additional
* information. It does this by wrapping the actual data inside an object, and
* adding more properties to this wrapper.
*
* This interface defines the additional properties that need to be stored by a
* storage adapter that supports enhanced sessions.
*/
export interface Enhance<T> {
/** Version */
v?: number;
/** Data */
__d: T;
/** Expiry date */
e?: number;
}
/** Options for enhanced sessions */
export interface MigrationOptions<T> {
/** The original storage adapter that will be enhanced */
storage: StorageAdapter<Enhance<T>>;
/**
* A set of session migrations, defined as an object mapping from version
* numbers to migration functions that transform data to the respective
* version.
*/
migrations?: Migrations;
/**
* Number of milliseconds after the last write operation until the session
* data expires.
*/
millisecondsToLive?: number;
}
/**
* A mapping from version numbers to session migration functions. Each entry in
* this object has a version number as a key, and a function as a value.
*
* For a key `n`, the respective value should be a function that takes the
* previous session data and migrates it to conform with the data that is used
* by version `n`. The previous session data is defined by the next key less
* than `n`, such as `n-1`. Versions don't have to be integers, nor do all
* versions have to be adjacent. For example, you can use `[1, 1.5, 4]` as
* versions. If `n` is the lowest value in the set of keys, the function stored
* for `n` can be used to migrate session data that was stored before migrations
* were used.
*/
export interface Migrations {
[version: number]: (old: any) => any;
}
/**
* You can use this function to transform an existing storage adapter, and add
* more features to it. Currently, you can add session migrations and expiry
* dates.
*
* You can use this function like so:
* ```ts
* const storage = ... // define your storage adapter
* const enhanced = enhanceStorage({ storage, millisecondsToLive: 500 })
* bot.use(session({ storage: enhanced }))
* ```
*
* @param options Session enhancing options
* @returns The enhanced storage adapter
*/
export declare function enhanceStorage<T>(options: MigrationOptions<T>): StorageAdapter<T>;
/**
* The memory session storage is a built-in storage adapter that saves your
* session data in RAM using a regular JavaScript `Map` object. If you use this
* storage adapter, all sessions will be lost when your process terminates or
* restarts. Hence, you should only use it for short-lived data that is not
* important to persist.
*
* This class is used as default if you do not provide a storage adapter, e.g.
* to your database.
*
* This storage adapter features expiring sessions. When instantiating this
* class yourself, you can pass a time to live in milliseconds that will be used
* for each session object. If a session for a user expired, the session data
* will be discarded on its first read, and a fresh session object as returned
* by the `initial` option (or undefined) will be put into place.
*/
export declare class MemorySessionStorage<S> implements StorageAdapter<S> {
private readonly timeToLive?;
/**
* Internally used `Map` instance that stores the session data
*/
protected readonly storage: Map<string, {
session: S;
expires?: number;
}>;
/**
* Constructs a new memory session storage with the given time to live. Note
* that this storage adapter will not store your data permanently.
*
* @param timeToLive TTL in milliseconds, default is `Infinity`
*/
constructor(timeToLive?: number | undefined);
read(key: string): S | undefined;
/**
* @deprecated Use {@link readAllValues} instead
*/
readAll(): S[];
readAllKeys(): string[];
readAllValues(): S[];
readAllEntries(): [string, S][];
has(key: string): boolean;
write(key: string, value: S): void;
delete(key: string): void;
}
export {};

View File

@@ -0,0 +1,455 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.MemorySessionStorage = void 0;
exports.session = session;
exports.lazySession = lazySession;
exports.enhanceStorage = enhanceStorage;
const platform_node_js_1 = require("../platform.node.js");
const debug = (0, platform_node_js_1.debug)("grammy:session");
/**
* Session middleware provides a persistent data storage for your bot. You can
* use it to let your bot remember any data you want, for example the messages
* it sent or received in the past. This is done by attaching _session data_ to
* every chat. The stored data is then provided on the context object under
* `ctx.session`.
*
* > **What is a session?** Simply put, the session of a chat is a little
* > persistent storage that is attached to it. As an example, your bot can send
* > a message to a chat and store the identifier of that message in the
* > corresponding session. The next time your bot receives an update from that
* > chat, the session will still contain that ID.
* >
* > Session data can be stored in a database, in a file, or simply in memory.
* > grammY only supports memory sessions out of the box, but you can use
* > third-party session middleware to connect to other storage solutions. Note
* > that memory sessions will be lost when you stop your bot and the process
* > exits, so they are usually not useful in production.
*
* Whenever your bot receives an update, the first thing the session middleware
* will do is to load the correct session from your storage solution. This
* object is then provided on `ctx.session` while your other middleware is
* running. As soon as your bot is done handling the update, the middleware
* takes over again and writes back the session object to your storage. This
* allows you to modify the session object arbitrarily in your middleware, and
* to stop worrying about the database.
*
* ```ts
* bot.use(session())
*
* bot.on('message', ctx => {
* // The session object is persisted across updates!
* const session = ctx.session
* })
* ```
*
* It is recommended to make use of the `initial` option in the configuration
* object, which correctly initializes session objects for new chats.
*
* You can delete the session data by setting `ctx.session` to `null` or
* `undefined`.
*
* Check out the [documentation](https://grammy.dev/plugins/session) on the
* website to know more about how sessions work in grammY.
*
* @param options Optional configuration to pass to the session middleware
*/
function session(options = {}) {
return options.type === "multi"
? strictMultiSession(options)
: strictSingleSession(options);
}
function strictSingleSession(options) {
const { initial, storage, getSessionKey, custom } = fillDefaults(options);
return async (ctx, next) => {
const propSession = new PropertySession(storage, ctx, "session", initial);
const key = await getSessionKey(ctx);
await propSession.init(key, { custom, lazy: false });
await next(); // no catch: do not write back if middleware throws
await propSession.finish();
};
}
function strictMultiSession(options) {
const props = Object.keys(options).filter((k) => k !== "type");
const defaults = Object.fromEntries(props.map((prop) => [prop, fillDefaults(options[prop])]));
return async (ctx, next) => {
ctx.session = {};
const propSessions = await Promise.all(props.map(async (prop) => {
const { initial, storage, getSessionKey, custom } = defaults[prop];
const s = new PropertySession(
// @ts-expect-error cannot express that the storage works for a concrete prop
storage, ctx.session, prop, initial);
const key = await getSessionKey(ctx);
await s.init(key, { custom, lazy: false });
return s;
}));
await next(); // no catch: do not write back if middleware throws
if (ctx.session == null)
propSessions.forEach((s) => s.delete());
await Promise.all(propSessions.map((s) => s.finish()));
};
}
/**
* > This is an advanced function of grammY.
*
* Generally speaking, lazy sessions work just like normal sessions—just they
* are loaded on demand. Except for a few `async`s and `await`s here and there,
* their usage looks 100 % identical.
*
* Instead of directly querying the storage every time an update arrives, lazy
* sessions quickly do this _once you access_ `ctx.session`. This can
* significantly reduce the database traffic (especially when your bot is added
* to group chats), because it skips a read and a wrote operation for all
* updates that the bot does not react to.
*
* ```ts
* // The options are identical
* bot.use(lazySession({ storage: ... }))
*
* bot.on('message', async ctx => {
* // The session object is persisted across updates!
* const session = await ctx.session
* // ^
* // |
* // This plain property access (no function call) will trigger the database query!
* })
* ```
*
* Check out the
* [documentation](https://grammy.dev/plugins/session#lazy-sessions) on the
* website to know more about how lazy sessions work in grammY.
*
* @param options Optional configuration to pass to the session middleware
*/
function lazySession(options = {}) {
if (options.type !== undefined && options.type !== "single") {
throw new Error("Cannot use lazy multi sessions!");
}
const { initial, storage, getSessionKey, custom } = fillDefaults(options);
return async (ctx, next) => {
const propSession = new PropertySession(
// @ts-expect-error suppress promise nature of values
storage, ctx, "session", initial);
const key = await getSessionKey(ctx);
await propSession.init(key, { custom, lazy: true });
await next(); // no catch: do not write back if middleware throws
await propSession.finish();
};
}
/**
* Internal class that manages a single property on the session. Can be used
* both in a strict and a lazy way. Works by using `Object.defineProperty` to
* install `O[P]`.
*/
// deno-lint-ignore ban-types
class PropertySession {
constructor(storage, obj, prop, initial) {
this.storage = storage;
this.obj = obj;
this.prop = prop;
this.initial = initial;
this.fetching = false;
this.read = false;
this.wrote = false;
}
/** Performs a read op and stores the result in `this.value` */
load() {
if (this.key === undefined) {
// No session key provided, cannot load
return;
}
if (this.wrote) {
// Value was set, no need to load
return;
}
// Perform read op if not cached
if (this.promise === undefined) {
this.fetching = true;
this.promise = Promise.resolve(this.storage.read(this.key))
.then((val) => {
var _a;
this.fetching = false;
// Check for write op in the meantime
if (this.wrote) {
// Discard read op
return this.value;
}
// Store received value in `this.value`
if (val !== undefined) {
this.value = val;
return val;
}
// No value, need to initialize
val = (_a = this.initial) === null || _a === void 0 ? void 0 : _a.call(this);
if (val !== undefined) {
// Wrote initial value
this.wrote = true;
this.value = val;
}
return val;
});
}
return this.promise;
}
async init(key, opts) {
this.key = key;
if (!opts.lazy)
await this.load();
Object.defineProperty(this.obj, this.prop, {
enumerable: true,
get: () => {
if (key === undefined) {
const msg = undef("access", opts);
throw new Error(msg);
}
this.read = true;
if (!opts.lazy || this.wrote)
return this.value;
this.load();
return this.fetching ? this.promise : this.value;
},
set: (v) => {
if (key === undefined) {
const msg = undef("assign", opts);
throw new Error(msg);
}
this.wrote = true;
this.fetching = false;
this.value = v;
},
});
}
delete() {
Object.assign(this.obj, { [this.prop]: undefined });
}
async finish() {
if (this.key !== undefined) {
if (this.read)
await this.load();
if (this.read || this.wrote) {
const value = await this.value;
if (value == null)
await this.storage.delete(this.key);
else
await this.storage.write(this.key, value);
}
}
}
}
function fillDefaults(opts = {}) {
let { prefix = "", getSessionKey = defaultGetSessionKey, initial, storage, } = opts;
if (storage == null) {
debug("Storing session data in memory, all data will be lost when the bot restarts.");
storage = new MemorySessionStorage();
}
const custom = getSessionKey !== defaultGetSessionKey;
return {
initial,
storage,
getSessionKey: async (ctx) => {
const key = await getSessionKey(ctx);
return key === undefined ? undefined : prefix + key;
},
custom,
};
}
/** Stores session data per chat by default */
function defaultGetSessionKey(ctx) {
var _a;
return (_a = ctx.chatId) === null || _a === void 0 ? void 0 : _a.toString();
}
/** Returns a useful error message for when the session key is undefined */
function undef(op, opts) {
const { lazy = false, custom } = opts;
const reason = custom
? "the custom `getSessionKey` function returned undefined for this update"
: "this update does not belong to a chat, so the session key is undefined";
return `Cannot ${op} ${lazy ? "lazy " : ""}session data because ${reason}!`;
}
function isEnhance(value) {
return value === undefined ||
typeof value === "object" && value !== null && "__d" in value;
}
/**
* You can use this function to transform an existing storage adapter, and add
* more features to it. Currently, you can add session migrations and expiry
* dates.
*
* You can use this function like so:
* ```ts
* const storage = ... // define your storage adapter
* const enhanced = enhanceStorage({ storage, millisecondsToLive: 500 })
* bot.use(session({ storage: enhanced }))
* ```
*
* @param options Session enhancing options
* @returns The enhanced storage adapter
*/
function enhanceStorage(options) {
let { storage, millisecondsToLive, migrations } = options;
storage = compatStorage(storage);
if (millisecondsToLive !== undefined) {
storage = timeoutStorage(storage, millisecondsToLive);
}
if (migrations !== undefined) {
storage = migrationStorage(storage, migrations);
}
return wrapStorage(storage);
}
function compatStorage(storage) {
return {
read: async (k) => {
const v = await storage.read(k);
return isEnhance(v) ? v : { __d: v };
},
write: (k, v) => storage.write(k, v),
delete: (k) => storage.delete(k),
};
}
function timeoutStorage(storage, millisecondsToLive) {
const ttlStorage = {
read: async (k) => {
const value = await storage.read(k);
if (value === undefined)
return undefined;
if (value.e === undefined) {
await ttlStorage.write(k, value);
return value;
}
if (value.e < Date.now()) {
await ttlStorage.delete(k);
return undefined;
}
return value;
},
write: async (k, v) => {
v.e = addExpiryDate(v, millisecondsToLive).expires;
await storage.write(k, v);
},
delete: (k) => storage.delete(k),
};
return ttlStorage;
}
function migrationStorage(storage, migrations) {
const versions = Object.keys(migrations)
.map((v) => parseInt(v))
.sort((a, b) => a - b);
const count = versions.length;
if (count === 0)
throw new Error("No migrations given!");
const earliest = versions[0];
const last = count - 1;
const latest = versions[last];
const index = new Map();
versions.forEach((v, i) => index.set(v, i)); // inverse array lookup
function nextAfter(current) {
// TODO: use `findLastIndex` with Node 18
let i = last;
while (current <= versions[i])
i--;
return i;
// return versions.findLastIndex((v) => v < current)
}
return {
read: async (k) => {
var _a;
const val = await storage.read(k);
if (val === undefined)
return val;
let { __d: value, v: current = earliest - 1 } = val;
let i = 1 + ((_a = index.get(current)) !== null && _a !== void 0 ? _a : nextAfter(current));
for (; i < count; i++)
value = migrations[versions[i]](value);
return { ...val, v: latest, __d: value };
},
write: (k, v) => storage.write(k, { v: latest, ...v }),
delete: (k) => storage.delete(k),
};
}
function wrapStorage(storage) {
return {
read: (k) => Promise.resolve(storage.read(k)).then((v) => v === null || v === void 0 ? void 0 : v.__d),
write: (k, v) => storage.write(k, { __d: v }),
delete: (k) => storage.delete(k),
};
}
// === Memory storage adapter
/**
* The memory session storage is a built-in storage adapter that saves your
* session data in RAM using a regular JavaScript `Map` object. If you use this
* storage adapter, all sessions will be lost when your process terminates or
* restarts. Hence, you should only use it for short-lived data that is not
* important to persist.
*
* This class is used as default if you do not provide a storage adapter, e.g.
* to your database.
*
* This storage adapter features expiring sessions. When instantiating this
* class yourself, you can pass a time to live in milliseconds that will be used
* for each session object. If a session for a user expired, the session data
* will be discarded on its first read, and a fresh session object as returned
* by the `initial` option (or undefined) will be put into place.
*/
class MemorySessionStorage {
/**
* Constructs a new memory session storage with the given time to live. Note
* that this storage adapter will not store your data permanently.
*
* @param timeToLive TTL in milliseconds, default is `Infinity`
*/
constructor(timeToLive) {
this.timeToLive = timeToLive;
/**
* Internally used `Map` instance that stores the session data
*/
this.storage = new Map();
}
read(key) {
const value = this.storage.get(key);
if (value === undefined)
return undefined;
if (value.expires !== undefined && value.expires < Date.now()) {
this.delete(key);
return undefined;
}
return value.session;
}
/**
* @deprecated Use {@link readAllValues} instead
*/
readAll() {
return this.readAllValues();
}
readAllKeys() {
return Array.from(this.storage.keys());
}
readAllValues() {
return Array
.from(this.storage.keys())
.map((key) => this.read(key))
.filter((value) => value !== undefined);
}
readAllEntries() {
return Array.from(this.storage.keys())
.map((key) => [key, this.read(key)])
.filter((pair) => pair[1] !== undefined);
}
has(key) {
return this.storage.has(key);
}
write(key, value) {
this.storage.set(key, addExpiryDate(value, this.timeToLive));
}
delete(key) {
this.storage.delete(key);
}
}
exports.MemorySessionStorage = MemorySessionStorage;
function addExpiryDate(value, ttl) {
if (ttl !== undefined && ttl < Infinity) {
const now = Date.now();
return { session: value, expires: now + ttl };
}
else {
return { session: value };
}
}

View File

@@ -0,0 +1,59 @@
import { type Bot } from "../bot.js";
import { type Context } from "../context.js";
import { type FrameworkAdapter } from "./frameworks.js";
declare const adapters: {
callback: FrameworkAdapter;
"aws-lambda": import("./frameworks.js").LambdaAdapter;
"aws-lambda-async": import("./frameworks.js").LambdaAsyncAdapter;
azure: import("./frameworks.js").AzureAdapter;
"azure-v4": import("./frameworks.js").AzureAdapterV4;
bun: import("./frameworks.js").BunAdapter;
cloudflare: import("./frameworks.js").CloudflareAdapter;
"cloudflare-mod": import("./frameworks.js").CloudflareModuleAdapter;
elysia: import("./frameworks.js").ElysiaAdapter;
express: import("./frameworks.js").ExpressAdapter;
fastify: import("./frameworks.js").FastifyAdapter;
hono: import("./frameworks.js").HonoAdapter;
http: import("./frameworks.js").HttpAdapter;
https: import("./frameworks.js").HttpAdapter;
koa: import("./frameworks.js").KoaAdapter;
"next-js": import("./frameworks.js").NextAdapter;
nhttp: import("./frameworks.js").NHttpAdapter;
oak: import("./frameworks.js").OakAdapter;
serveHttp: import("./frameworks.js").ServeHttpAdapter;
"std/http": import("./frameworks.js").StdHttpAdapter;
sveltekit: import("./frameworks.js").SveltekitAdapter;
worktop: import("./frameworks.js").WorktopAdapter;
};
export interface WebhookOptions {
/** An optional strategy to handle timeouts (default: 'throw') */
onTimeout?: "throw" | "return" | ((...args: any[]) => unknown);
/** An optional number of timeout milliseconds (default: 10_000) */
timeoutMilliseconds?: number;
/** An optional string to compare to X-Telegram-Bot-Api-Secret-Token */
secretToken?: string;
}
type Adapters = typeof adapters;
type AdapterNames = keyof Adapters;
type ResolveName<A extends FrameworkAdapter | AdapterNames> = A extends AdapterNames ? Adapters[A] : A;
/**
* Creates a callback function that you can pass to a web framework (such as
* express) if you want to run your bot via webhooks. Use it like this:
* ```ts
* const app = express() // or whatever you're using
* const bot = new Bot('<token>')
*
* app.use(webhookCallback(bot, 'express'))
* ```
*
* Confer the grammY
* [documentation](https://grammy.dev/guide/deployment-types) to read more
* about how to run your bot with webhooks.
*
* @param bot The bot for which to create a callback
* @param adapter An optional string identifying the framework (default: 'express')
* @param webhookOptions Further options for the webhook setup
*/
export declare function webhookCallback<C extends Context = Context, A extends FrameworkAdapter | AdapterNames = FrameworkAdapter | AdapterNames>(bot: Bot<C>, adapter: A, webhookOptions?: WebhookOptions): (...args: Parameters<ResolveName<A>>) => ReturnType<ResolveName<A>>["handlerReturn"] extends undefined ? Promise<void> : NonNullable<ReturnType<ResolveName<A>>["handlerReturn"]>;
export declare function webhookCallback<C extends Context = Context, A extends FrameworkAdapter | AdapterNames = FrameworkAdapter | AdapterNames>(bot: Bot<C>, adapter: A, onTimeout?: WebhookOptions["onTimeout"], timeoutMilliseconds?: WebhookOptions["timeoutMilliseconds"], secretToken?: WebhookOptions["secretToken"]): (...args: Parameters<ResolveName<A>>) => ReturnType<ResolveName<A>>["handlerReturn"] extends undefined ? Promise<void> : NonNullable<ReturnType<ResolveName<A>>["handlerReturn"]>;
export {};

View File

@@ -0,0 +1,120 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.webhookCallback = webhookCallback;
const platform_node_js_1 = require("../platform.node.js");
const frameworks_js_1 = require("./frameworks.js");
const debugErr = (0, platform_node_js_1.debug)("grammy:error");
const callbackAdapter = (update, callback, header, unauthorized = () => callback('"unauthorized"')) => ({
update: Promise.resolve(update),
respond: callback,
header,
unauthorized,
});
const adapters = { ...frameworks_js_1.adapters, callback: callbackAdapter };
/**
* Performs a constant-time comparison of two strings to prevent timing attacks.
* This function always compares all bytes regardless of early differences,
* ensuring the comparison time does not leak information about the secret.
*
* @param header The header value from the request (X-Telegram-Bot-Api-Secret-Token)
* @param token The expected secret token configured for the webhook
* @returns true if strings are equal, false otherwise
*/
function compareSecretToken(header, token) {
// If no token is configured, accept all requests
if (token === undefined) {
return true;
}
// If token is configured but no header provided, reject
if (header === undefined) {
return false;
}
// Convert strings to Uint8Array for byte-by-byte comparison
const encoder = new TextEncoder();
const headerBytes = encoder.encode(header);
const tokenBytes = encoder.encode(token);
// If lengths differ, reject
if (headerBytes.length !== tokenBytes.length) {
return false;
}
let hasDifference = 0;
// Always iterate exactly tokenBytes.length times to prevent timing attacks
// that could reveal the secret token's length. The loop time is constant
// relative to the secret token length, not the attacker's input length.
for (let i = 0; i < tokenBytes.length; i++) {
// If header is shorter than token, pad with 0 for comparison
const headerByte = i < headerBytes.length ? headerBytes[i] : 0;
const tokenByte = tokenBytes[i];
// If bytes differ, mark that we found a difference
// Using bitwise OR to maintain constant-time (no short-circuit evaluation)
hasDifference |= headerByte ^ tokenByte;
}
// Return true only if no differences were found
return hasDifference === 0;
}
function webhookCallback(bot, adapter = platform_node_js_1.defaultAdapter, onTimeout, timeoutMilliseconds, secretToken) {
if (bot.isRunning()) {
throw new Error("Bot is already running via long polling, the webhook setup won't receive any updates!");
}
else {
bot.start = () => {
throw new Error("You already started the bot via webhooks, calling `bot.start()` starts the bot with long polling and this will prevent your webhook setup from receiving any updates!");
};
}
const { onTimeout: timeout = "throw", timeoutMilliseconds: ms = 10000, secretToken: token, } = typeof onTimeout === "object"
? onTimeout
: { onTimeout, timeoutMilliseconds, secretToken };
let initialized = false;
const server = typeof adapter === "string"
? adapters[adapter]
: adapter;
return async (...args) => {
var _a;
const handler = server(...args);
if (!initialized) {
// Will dedupe concurrently incoming calls from several updates
await bot.init();
initialized = true;
}
if (!compareSecretToken(handler.header, token)) {
await handler.unauthorized();
return handler.handlerReturn;
}
let usedWebhookReply = false;
const webhookReplyEnvelope = {
async send(json) {
usedWebhookReply = true;
await handler.respond(json);
},
};
await timeoutIfNecessary(bot.handleUpdate(await handler.update, webhookReplyEnvelope), typeof timeout === "function" ? () => timeout(...args) : timeout, ms);
if (!usedWebhookReply)
(_a = handler.end) === null || _a === void 0 ? void 0 : _a.call(handler);
return handler.handlerReturn;
};
}
function timeoutIfNecessary(task, onTimeout, timeout) {
if (timeout === Infinity)
return task;
return new Promise((resolve, reject) => {
const handle = setTimeout(() => {
debugErr(`Request timed out after ${timeout} ms`);
if (onTimeout === "throw") {
reject(new Error(`Request timed out after ${timeout} ms`));
}
else {
if (typeof onTimeout === "function")
onTimeout();
resolve();
}
const now = Date.now();
task.finally(() => {
const diff = Date.now() - now;
debugErr(`Request completed ${diff} ms after timeout!`);
});
}, timeout);
task.then(resolve)
.catch(reject)
.finally(() => clearTimeout(handle));
});
}