@emulon/telegram 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,354 @@
1
+ // Bot API objects are snake_case on the wire.
2
+ // deno-lint-ignore-file camelcase
3
+ import { z } from 'zod';
4
+ import { DomainError } from 'emulon';
5
+ import { botSchema } from './bots.js';
6
+ /**
7
+ * Every update type typed by `@grammyjs/types@3.28.0`; a test keeps this list
8
+ * equal to those types. Only `message_reaction_count` is ever generated.
9
+ */
10
+ export const updateTypes = [
11
+ 'message',
12
+ 'edited_message',
13
+ 'channel_post',
14
+ 'edited_channel_post',
15
+ 'business_connection',
16
+ 'business_message',
17
+ 'edited_business_message',
18
+ 'deleted_business_messages',
19
+ 'guest_message',
20
+ 'message_reaction',
21
+ 'message_reaction_count',
22
+ 'inline_query',
23
+ 'chosen_inline_result',
24
+ 'callback_query',
25
+ 'shipping_query',
26
+ 'pre_checkout_query',
27
+ 'poll',
28
+ 'poll_answer',
29
+ 'my_chat_member',
30
+ 'chat_member',
31
+ 'managed_bot',
32
+ 'chat_join_request',
33
+ 'chat_boost',
34
+ 'removed_chat_boost',
35
+ 'purchased_paid_media',
36
+ ];
37
+ /** The reaction emoji typed by `@grammyjs/types@3.28.0`. */
38
+ export const reactionEmoji = [
39
+ '๐Ÿ‘',
40
+ '๐Ÿ‘Ž',
41
+ 'โค',
42
+ '๐Ÿ”ฅ',
43
+ '๐Ÿฅฐ',
44
+ '๐Ÿ‘',
45
+ '๐Ÿ˜',
46
+ '๐Ÿค”',
47
+ '๐Ÿคฏ',
48
+ '๐Ÿ˜ฑ',
49
+ '๐Ÿคฌ',
50
+ '๐Ÿ˜ข',
51
+ '๐ŸŽ‰',
52
+ '๐Ÿคฉ',
53
+ '๐Ÿคฎ',
54
+ '๐Ÿ’ฉ',
55
+ '๐Ÿ™',
56
+ '๐Ÿ‘Œ',
57
+ '๐Ÿ•Š',
58
+ '๐Ÿคก',
59
+ '๐Ÿฅฑ',
60
+ '๐Ÿฅด',
61
+ '๐Ÿ˜',
62
+ '๐Ÿณ',
63
+ 'โคโ€๐Ÿ”ฅ',
64
+ '๐ŸŒš',
65
+ '๐ŸŒญ',
66
+ '๐Ÿ’ฏ',
67
+ '๐Ÿคฃ',
68
+ 'โšก',
69
+ '๐ŸŒ',
70
+ '๐Ÿ†',
71
+ '๐Ÿ’”',
72
+ '๐Ÿคจ',
73
+ '๐Ÿ˜',
74
+ '๐Ÿ“',
75
+ '๐Ÿพ',
76
+ '๐Ÿ’‹',
77
+ '๐Ÿ–•',
78
+ '๐Ÿ˜ˆ',
79
+ '๐Ÿ˜ด',
80
+ '๐Ÿ˜ญ',
81
+ '๐Ÿค“',
82
+ '๐Ÿ‘ป',
83
+ '๐Ÿ‘จโ€๐Ÿ’ป',
84
+ '๐Ÿ‘€',
85
+ '๐ŸŽƒ',
86
+ '๐Ÿ™ˆ',
87
+ '๐Ÿ˜‡',
88
+ '๐Ÿ˜จ',
89
+ '๐Ÿค',
90
+ 'โœ',
91
+ '๐Ÿค—',
92
+ '๐Ÿซก',
93
+ '๐ŸŽ…',
94
+ '๐ŸŽ„',
95
+ 'โ˜ƒ',
96
+ '๐Ÿ’…',
97
+ '๐Ÿคช',
98
+ '๐Ÿ—ฟ',
99
+ '๐Ÿ†’',
100
+ '๐Ÿ’˜',
101
+ '๐Ÿ™‰',
102
+ '๐Ÿฆ„',
103
+ '๐Ÿ˜˜',
104
+ '๐Ÿ’Š',
105
+ '๐Ÿ™Š',
106
+ '๐Ÿ˜Ž',
107
+ '๐Ÿ‘พ',
108
+ '๐Ÿคทโ€โ™‚',
109
+ '๐Ÿคท',
110
+ '๐Ÿคทโ€โ™€',
111
+ '๐Ÿ˜ก',
112
+ ];
113
+ export const reactionTypeSchema = z
114
+ .discriminatedUnion('type', [
115
+ z.strictObject({ type: z.literal('emoji'), emoji: z.enum(reactionEmoji) }),
116
+ z.strictObject({
117
+ type: z.literal('custom_emoji'),
118
+ custom_emoji_id: z.string().regex(/^[0-9]{1,20}$/),
119
+ }),
120
+ z.strictObject({ type: z.literal('paid') }),
121
+ ]);
122
+ export const reactionCountSchema = z
123
+ .strictObject({
124
+ type: reactionTypeSchema,
125
+ total_count: z.number().int().nonnegative().safe(),
126
+ });
127
+ export const updateSchema = z.strictObject({
128
+ update_id: z.number().int().positive().safe(),
129
+ message_reaction_count: z.strictObject({
130
+ chat: z.strictObject({
131
+ id: z.number().int(),
132
+ title: z.string(),
133
+ username: z.string(),
134
+ type: z.literal('channel'),
135
+ }),
136
+ message_id: z.number().int().positive(),
137
+ date: z.number().int().nonnegative(),
138
+ reactions: z.array(reactionCountSchema),
139
+ }),
140
+ });
141
+ /** A rejected `getUpdates`; descriptions are fixed and never echo input. */
142
+ export class UpdatesError extends Error {
143
+ status;
144
+ description;
145
+ constructor(status, description) {
146
+ super(description);
147
+ this.status = status;
148
+ this.description = description;
149
+ }
150
+ }
151
+ export const updatesErrors = {
152
+ invalid: () => new UpdatesError(400, 'Bad Request: invalid parameter value'),
153
+ limit: () => new UpdatesError(400, 'Bad Request: limit must be between 1 and 100'),
154
+ allowedUpdates: () => new UpdatesError(400, 'Bad Request: invalid allowed_updates'),
155
+ unsupported: () => new UpdatesError(501, 'Not Implemented: parameter is not emulated'),
156
+ conflict: () => new UpdatesError(409, 'Conflict: terminated by other getUpdates request; make sure that only one bot instance is running'),
157
+ cancelled: () => new UpdatesError(503, 'Service Unavailable: the request was cancelled'),
158
+ };
159
+ const accepted = new Set(['offset', 'limit', 'timeout', 'allowed_updates']);
160
+ const known = new Set(updateTypes);
161
+ const isInteger = (value) => typeof value === 'number' && Number.isSafeInteger(value);
162
+ /** Only the documented polling fields; anything else is refused, not ignored. */
163
+ export function updatesRequest(params) {
164
+ if (Object.keys(params).some((key) => !accepted.has(key))) {
165
+ throw updatesErrors.unsupported();
166
+ }
167
+ const { offset, limit, timeout, allowed_updates } = params;
168
+ const request = { limit: 100, timeout: 0 };
169
+ if (offset !== undefined) {
170
+ if (!isInteger(offset)) {
171
+ throw updatesErrors.invalid();
172
+ }
173
+ request.offset = offset;
174
+ }
175
+ if (limit !== undefined) {
176
+ if (!isInteger(limit) || limit < 1 || limit > 100) {
177
+ throw updatesErrors.limit();
178
+ }
179
+ request.limit = limit;
180
+ }
181
+ if (timeout !== undefined) {
182
+ if (!isInteger(timeout) || timeout < 0) {
183
+ throw updatesErrors.invalid();
184
+ }
185
+ request.timeout = timeout;
186
+ }
187
+ if (allowed_updates !== undefined) {
188
+ if (!Array.isArray(allowed_updates) ||
189
+ allowed_updates.some((type) => typeof type !== 'string' || !known.has(type))) {
190
+ throw updatesErrors.allowedUpdates();
191
+ }
192
+ request.allowedUpdates = [...new Set(allowed_updates)];
193
+ }
194
+ return request;
195
+ }
196
+ const queueSchema = z.strictObject({
197
+ nextUpdateId: z.number().int().positive().safe(),
198
+ allowedUpdates: z.array(z.enum(updateTypes)),
199
+ });
200
+ const storedUpdateSchema = z.strictObject({
201
+ botId: z.number().int().positive().safe(),
202
+ update: updateSchema,
203
+ });
204
+ /** Update IDs sort as text in store order and as numbers in the key. */
205
+ const updateKey = (botId, updateId) => `${botId}:${String(updateId).padStart(16, '0')}`;
206
+ export async function queueOf(tx, botId) {
207
+ const row = await tx.get('updateQueues', String(botId));
208
+ return row === undefined
209
+ ? { nextUpdateId: 1, allowedUpdates: [] }
210
+ : queueSchema.parse(row);
211
+ }
212
+ export function receives(queue, type) {
213
+ return queue.allowedUpdates.includes(type);
214
+ }
215
+ /** The bot's queued updates in ID order. */
216
+ export async function queued(tx, botId) {
217
+ return (await tx.list('updates'))
218
+ .map((row) => storedUpdateSchema.parse(row.value))
219
+ .filter((row) => row.botId === botId)
220
+ .map((row) => row.update)
221
+ .sort((a, b) => a.update_id - b.update_id);
222
+ }
223
+ /** Allocates the next update ID of one bot and queues the update it builds. */
224
+ export async function enqueue(tx, botId, queue, build) {
225
+ const update = build(queue.nextUpdateId);
226
+ await tx.put({
227
+ collection: 'updateQueues',
228
+ id: String(botId),
229
+ value: { ...queue, nextUpdateId: queue.nextUpdateId + 1 },
230
+ });
231
+ await tx.put({
232
+ collection: 'updates',
233
+ id: updateKey(botId, update.update_id),
234
+ value: { botId, update },
235
+ });
236
+ }
237
+ /**
238
+ * The first read of a `getUpdates` call: it confirms by offset, replaces the
239
+ * subscription when one is given, then returns the head of the queue without
240
+ * confirming it.
241
+ */
242
+ export async function takeUpdates(tx, botId, request) {
243
+ let updates = await queued(tx, botId);
244
+ if (request.offset !== undefined) {
245
+ const forgotten = request.offset < 0
246
+ ? updates.slice(0, Math.max(0, updates.length + request.offset))
247
+ : updates.filter((update) => update.update_id < request.offset);
248
+ for (const update of forgotten) {
249
+ await tx.delete('updates', updateKey(botId, update.update_id));
250
+ }
251
+ updates = updates.slice(forgotten.length);
252
+ }
253
+ if (request.allowedUpdates !== undefined) {
254
+ await tx.put({
255
+ collection: 'updateQueues',
256
+ id: String(botId),
257
+ value: {
258
+ ...await queueOf(tx, botId),
259
+ allowedUpdates: request.allowedUpdates,
260
+ },
261
+ });
262
+ }
263
+ return updates.slice(0, request.limit);
264
+ }
265
+ /** `setTimeout` accepts at most a signed 32-bit millisecond delay. */
266
+ const maxDelay = 2 ** 31 - 1;
267
+ /**
268
+ * Waits on real time for committed updates. The store listener is attached
269
+ * before every read, so a commit between the read and the wait is not lost.
270
+ * Cancellation is an error, never an empty batch: after a reset the caller
271
+ * must not mistake a cancelled poll for a drained queue.
272
+ */
273
+ export async function pollUpdates(store, botId, request, signal) {
274
+ const deadline = performance.now() + request.timeout * 1000;
275
+ let changed = false;
276
+ let wake = () => { };
277
+ const unsubscribe = store.subscribe(() => {
278
+ changed = true;
279
+ wake();
280
+ });
281
+ const cancel = () => wake();
282
+ signal.addEventListener('abort', cancel);
283
+ try {
284
+ let first = true;
285
+ while (true) {
286
+ if (signal.aborted) {
287
+ throw updatesErrors.cancelled();
288
+ }
289
+ changed = false;
290
+ let batch;
291
+ try {
292
+ batch = await store.transaction((tx) => first
293
+ ? takeUpdates(tx, botId, request)
294
+ : takeUpdates(tx, botId, { limit: request.limit }));
295
+ }
296
+ catch (error) {
297
+ throw signal.aborted ? updatesErrors.cancelled() : error;
298
+ }
299
+ first = false;
300
+ if (signal.aborted) {
301
+ throw updatesErrors.cancelled();
302
+ }
303
+ const remaining = deadline - performance.now();
304
+ if (batch.length > 0 || remaining <= 0) {
305
+ return batch;
306
+ }
307
+ if (!changed) {
308
+ let timer;
309
+ await new Promise((resolve) => {
310
+ wake = resolve;
311
+ timer = setTimeout(resolve, Math.min(remaining, maxDelay));
312
+ });
313
+ clearTimeout(timer);
314
+ wake = () => { };
315
+ }
316
+ }
317
+ }
318
+ finally {
319
+ signal.removeEventListener('abort', cancel);
320
+ unsubscribe();
321
+ }
322
+ }
323
+ export const inspectInput = z
324
+ .strictObject({
325
+ botId: z.number().int().positive().safe(),
326
+ limit: z.number().int().min(1).max(100).optional(),
327
+ });
328
+ export const queueViewSchema = z.strictObject({
329
+ botId: z.number().int().positive().safe(),
330
+ allowedUpdates: z.array(z.enum(updateTypes)),
331
+ pending: z.number().int().nonnegative(),
332
+ updates: z.array(updateSchema),
333
+ });
334
+ /**
335
+ * The oldest `limit` pending updates (default 100) and the subscription of one
336
+ * bot. Reading confirms nothing and shows no credential.
337
+ */
338
+ export async function inspectUpdates(store, raw) {
339
+ const input = inspectInput.parse(raw);
340
+ return await store.transaction(async (tx) => {
341
+ const row = await tx.get('bots', String(input.botId));
342
+ if (row === undefined) {
343
+ throw new DomainError('BOT_NOT_FOUND', 'No such bot.');
344
+ }
345
+ const bot = botSchema.parse(row);
346
+ const updates = await queued(tx, bot.id);
347
+ return {
348
+ botId: bot.id,
349
+ allowedUpdates: (await queueOf(tx, bot.id)).allowedUpdates,
350
+ pending: updates.length,
351
+ updates: updates.slice(0, input.limit ?? 100),
352
+ };
353
+ });
354
+ }
@@ -0,0 +1,3 @@
1
+ {
2
+ "type": "module"
3
+ }
@@ -0,0 +1,31 @@
1
+ import type { Hono } from 'hono';
2
+ import type { PluginContext } from 'emulon';
3
+ /**
4
+ * A Bot API failure. Descriptions are fixed strings: the request path carries
5
+ * the bot token, so no failure may echo the path, the token or caller input.
6
+ */
7
+ export declare class BotApiError extends Error {
8
+ readonly status: number;
9
+ readonly description: string;
10
+ constructor(status: number, description: string);
11
+ }
12
+ export declare const notFound: () => BotApiError;
13
+ export declare const unauthorized: () => BotApiError;
14
+ export declare const unsupportedMethod: () => BotApiError;
15
+ export declare const unsupportedTransport: () => BotApiError;
16
+ export declare const unsupportedParameter: () => BotApiError;
17
+ export declare const invalidBody: () => BotApiError;
18
+ export declare function success(result: unknown): Response;
19
+ export declare function failure(error: unknown): Response;
20
+ /** Exactly one `bot<token>/<method>` pair; anything else is not a Bot API path. */
21
+ export declare function parseRoute(pathname: string): {
22
+ token: string;
23
+ method: string;
24
+ } | null;
25
+ /** grammY sends `application/json`, optionally with a charset parameter. */
26
+ export declare function isJson(contentType: string | null): boolean;
27
+ /**
28
+ * The token is checked before the method is resolved, so an unknown or
29
+ * unsupported method reveals nothing to a caller without a valid token.
30
+ */
31
+ export declare function routes(ctx: PluginContext, api: Hono): void;
@@ -0,0 +1,128 @@
1
+ import { parseToken } from '../auth/tokens.js';
2
+ import { authenticate, botUser } from '../model/bots.js';
3
+ import { SendError, sendMessage, sendRequest } from '../model/messages.js';
4
+ import { pollUpdates, UpdatesError, updatesErrors, updatesRequest, } from '../model/updates.js';
5
+ import { knownMethod } from './methods.js';
6
+ /**
7
+ * A Bot API failure. Descriptions are fixed strings: the request path carries
8
+ * the bot token, so no failure may echo the path, the token or caller input.
9
+ */
10
+ export class BotApiError extends Error {
11
+ status;
12
+ description;
13
+ constructor(status, description) {
14
+ super(description);
15
+ this.status = status;
16
+ this.description = description;
17
+ }
18
+ }
19
+ export const notFound = () => new BotApiError(404, 'Not Found');
20
+ export const unauthorized = () => new BotApiError(401, 'Unauthorized');
21
+ export const unsupportedMethod = () => new BotApiError(501, 'Not Implemented: method is not emulated');
22
+ export const unsupportedTransport = () => new BotApiError(501, 'Not Implemented: only POST requests with a JSON body are emulated');
23
+ export const unsupportedParameter = () => new BotApiError(501, 'Not Implemented: parameter is not emulated');
24
+ export const invalidBody = () => new BotApiError(400, 'Bad Request: request body must be a JSON object');
25
+ export function success(result) {
26
+ return Response.json({ ok: true, result });
27
+ }
28
+ export function failure(error) {
29
+ const known = error instanceof BotApiError || error instanceof SendError ||
30
+ error instanceof UpdatesError
31
+ ? error
32
+ : new BotApiError(500, 'Internal Server Error');
33
+ return Response.json({
34
+ ok: false,
35
+ error_code: known.status,
36
+ description: known.description,
37
+ }, { status: known.status });
38
+ }
39
+ /** Exactly one `bot<token>/<method>` pair; anything else is not a Bot API path. */
40
+ export function parseRoute(pathname) {
41
+ const match = /^\/bot([^/]+)\/([^/]+)$/.exec(pathname);
42
+ return match ? { token: match[1], method: match[2] } : null;
43
+ }
44
+ /** grammY sends `application/json`, optionally with a charset parameter. */
45
+ export function isJson(contentType) {
46
+ return contentType?.split(';')[0]?.trim().toLowerCase() ===
47
+ 'application/json';
48
+ }
49
+ const handlers = {
50
+ getMe(params, bot) {
51
+ if (Object.keys(params).length > 0) {
52
+ throw unsupportedParameter();
53
+ }
54
+ return botUser(bot);
55
+ },
56
+ sendMessage(params, bot, { store, now }) {
57
+ return sendMessage(store, bot, sendRequest(params), now);
58
+ },
59
+ async getUpdates(params, bot, { store, signal, polling }) {
60
+ const request = updatesRequest(params);
61
+ // A second reader would race the first for confirmations.
62
+ if (polling.has(bot.id)) {
63
+ throw updatesErrors.conflict();
64
+ }
65
+ polling.add(bot.id);
66
+ try {
67
+ return await pollUpdates(store, bot.id, request, signal());
68
+ }
69
+ finally {
70
+ polling.delete(bot.id);
71
+ }
72
+ },
73
+ };
74
+ async function params(request) {
75
+ if (request.method !== 'POST' || !isJson(request.headers.get('content-type'))) {
76
+ throw unsupportedTransport();
77
+ }
78
+ let body;
79
+ try {
80
+ body = JSON.parse(await request.text());
81
+ }
82
+ catch {
83
+ throw invalidBody();
84
+ }
85
+ if (typeof body !== 'object' || body === null || Array.isArray(body)) {
86
+ throw invalidBody();
87
+ }
88
+ return body;
89
+ }
90
+ /**
91
+ * The token is checked before the method is resolved, so an unknown or
92
+ * unsupported method reveals nothing to a caller without a valid token.
93
+ */
94
+ export function routes(ctx, api) {
95
+ const polling = new Set();
96
+ api.all('*', async (c) => {
97
+ const store = ctx.store.scope();
98
+ try {
99
+ const route = parseRoute(new URL(c.req.url).pathname);
100
+ const token = route && parseToken(route.token);
101
+ if (!route || !token) {
102
+ throw notFound();
103
+ }
104
+ const bot = await authenticate(store, token);
105
+ if (!bot) {
106
+ throw unauthorized();
107
+ }
108
+ const method = knownMethod(route.method);
109
+ if (!method) {
110
+ throw notFound();
111
+ }
112
+ const handler = handlers[method];
113
+ if (!handler) {
114
+ throw unsupportedMethod();
115
+ }
116
+ return success(await handler(await params(c.req.raw), bot, {
117
+ store,
118
+ now: ctx.clock.now(),
119
+ signal: () => c.req.raw.signal,
120
+ polling,
121
+ }));
122
+ }
123
+ catch (error) {
124
+ return failure(error);
125
+ }
126
+ });
127
+ api.onError(() => failure(undefined));
128
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Every Bot API method typed by `@grammyjs/types@3.28.0`, the types package of
3
+ * the pinned `grammy@1.44.0`. A test keeps this list equal to those types.
4
+ * Known methods the slice does not implement fail with an explicit 501 instead
5
+ * of the 404 that an unknown method receives.
6
+ */
7
+ export declare const knownMethods: readonly ["getUpdates", "setWebhook", "deleteWebhook", "getWebhookInfo", "getMe", "logOut", "close", "sendMessage", "sendRichMessage", "forwardMessage", "forwardMessages", "copyMessage", "copyMessages", "sendPhoto", "sendLivePhoto", "sendAudio", "sendDocument", "sendVideo", "sendAnimation", "sendVoice", "sendVideoNote", "sendPaidMedia", "sendMediaGroup", "sendLocation", "editMessageLiveLocation", "stopMessageLiveLocation", "sendVenue", "sendContact", "sendPoll", "sendChecklist", "editMessageChecklist", "sendDice", "sendMessageDraft", "sendRichMessageDraft", "sendChatAction", "setMessageReaction", "getUserProfilePhotos", "getUserProfileAudios", "setUserEmojiStatus", "getFile", "banChatMember", "kickChatMember", "unbanChatMember", "restrictChatMember", "promoteChatMember", "setChatAdministratorCustomTitle", "setChatMemberTag", "banChatSenderChat", "unbanChatSenderChat", "setChatPermissions", "exportChatInviteLink", "createChatInviteLink", "editChatInviteLink", "createChatSubscriptionInviteLink", "editChatSubscriptionInviteLink", "revokeChatInviteLink", "approveChatJoinRequest", "declineChatJoinRequest", "answerChatJoinRequestQuery", "sendChatJoinRequestWebApp", "approveSuggestedPost", "declineSuggestedPost", "setChatPhoto", "deleteChatPhoto", "setChatTitle", "setChatDescription", "pinChatMessage", "unpinChatMessage", "unpinAllChatMessages", "leaveChat", "getChat", "getChatAdministrators", "getChatMemberCount", "getChatMembersCount", "getChatMember", "getUserPersonalChatMessages", "setChatStickerSet", "deleteChatStickerSet", "getForumTopicIconStickers", "createForumTopic", "editForumTopic", "closeForumTopic", "reopenForumTopic", "deleteForumTopic", "unpinAllForumTopicMessages", "editGeneralForumTopic", "closeGeneralForumTopic", "reopenGeneralForumTopic", "hideGeneralForumTopic", "unhideGeneralForumTopic", "unpinAllGeneralForumTopicMessages", "answerCallbackQuery", "answerGuestQuery", "getUserChatBoosts", "getUserGifts", "getChatGifts", "getBusinessConnection", "getManagedBotToken", "replaceManagedBotToken", "getManagedBotAccessSettings", "setManagedBotAccessSettings", "setMyCommands", "deleteMyCommands", "getMyCommands", "setMyName", "getMyName", "setMyDescription", "getMyDescription", "setMyShortDescription", "getMyShortDescription", "setMyProfilePhoto", "removeMyProfilePhoto", "setChatMenuButton", "getChatMenuButton", "setMyDefaultAdministratorRights", "getMyDefaultAdministratorRights", "getMyStarBalance", "editMessageText", "editMessageCaption", "editMessageMedia", "editMessageReplyMarkup", "stopPoll", "deleteMessage", "deleteMessages", "deleteMessageReaction", "deleteAllMessageReactions", "deleteBusinessMessages", "setBusinessAccountName", "setBusinessAccountUsername", "setBusinessAccountBio", "setBusinessAccountProfilePhoto", "removeBusinessAccountProfilePhoto", "setBusinessAccountGiftSettings", "getBusinessAccountStarBalance", "transferBusinessAccountStars", "getBusinessAccountGifts", "convertGiftToStars", "upgradeGift", "transferGift", "postStory", "repostStory", "editStory", "deleteStory", "sendSticker", "getStickerSet", "getCustomEmojiStickers", "uploadStickerFile", "createNewStickerSet", "addStickerToSet", "setStickerPositionInSet", "deleteStickerFromSet", "replaceStickerInSet", "setStickerEmojiList", "setStickerKeywords", "setStickerMaskPosition", "setStickerSetTitle", "deleteStickerSet", "setStickerSetThumbnail", "setCustomEmojiStickerSetThumbnail", "getAvailableGifts", "sendGift", "giftPremiumSubscription", "answerInlineQuery", "answerWebAppQuery", "savePreparedInlineMessage", "savePreparedKeyboardButton", "sendInvoice", "createInvoiceLink", "answerShippingQuery", "answerPreCheckoutQuery", "getStarTransactions", "refundStarPayment", "editUserStarSubscription", "verifyUser", "verifyChat", "removeUserVerification", "removeChatVerification", "readBusinessMessage", "setPassportDataErrors", "sendGame", "setGameScore", "getGameHighScores"];
8
+ export type KnownMethod = typeof knownMethods[number];
9
+ /** Bot API method names are case-insensitive. */
10
+ export declare function knownMethod(name: string): KnownMethod | undefined;