From 1fb7057866bf51a7e8af18c78e41d1b1892f955f Mon Sep 17 00:00:00 2001 From: teidesu Date: Wed, 5 May 2021 01:50:04 +0300 Subject: [PATCH] feat: support callback queries --- packages/client/src/client.ts | 46 +++++ .../src/methods/bots/answer-callback-query.ts | 60 ++++++ .../client/src/types/bots/callback-query.ts | 187 ++++++++++++++++++ packages/client/src/types/bots/index.ts | 4 +- packages/dispatcher/scripts/update-types.txt | 1 + packages/dispatcher/src/builders.ts | 33 +++- packages/dispatcher/src/dispatcher.ts | 40 ++++ packages/dispatcher/src/handler.ts | 6 + 8 files changed, 375 insertions(+), 2 deletions(-) create mode 100644 packages/client/src/methods/bots/answer-callback-query.ts create mode 100644 packages/client/src/types/bots/callback-query.ts diff --git a/packages/client/src/client.ts b/packages/client/src/client.ts index 5fafd58e..903d42e3 100644 --- a/packages/client/src/client.ts +++ b/packages/client/src/client.ts @@ -15,6 +15,7 @@ import { signIn } from './methods/auth/sign-in' import { signUp } from './methods/auth/sign-up' import { startTest } from './methods/auth/start-test' import { start } from './methods/auth/start' +import { answerCallbackQuery } from './methods/bots/answer-callback-query' import { answerInlineQuery } from './methods/bots/answer-inline-query' import { addChatMembers } from './methods/chats/add-chat-members' import { archiveChats } from './methods/chats/archive-chats' @@ -396,6 +397,50 @@ export interface TelegramClient extends BaseTelegramClient { */ catchUp?: boolean }): Promise + /** + * Send an answer to a callback query. + * + * @param queryId ID of the callback query + * @param params Parameters of the answer + */ + answerCallbackQuery( + queryId: tl.Long, + params?: { + /** + * Maximum amount of time in seconds for which + * this result can be cached by the client (not server!). + * + * Defaults to `0` + */ + cacheTime?: number + + /** + * Text of the notification (0-200 chars). + * + * If not set, nothing will be displayed + */ + text?: string + + /** + * Whether to show an alert in the middle of the screen + * instead of a notification at the top of the screen. + * + * Defaults to `false`. + */ + alert?: boolean + + /** + * URL that the client should open. + * + * If this was a button containing a game, + * you can provide arbitrary link to your game. + * Otherwise, you can only use links in the format + * `t.me/your_bot?start=...` that open your bot + * with a deep-link parameter. + */ + url?: string + } + ): Promise /** * Answer an inline query. * @@ -2060,6 +2105,7 @@ export class TelegramClient extends BaseTelegramClient { signUp = signUp startTest = startTest start = start + answerCallbackQuery = answerCallbackQuery answerInlineQuery = answerInlineQuery addChatMembers = addChatMembers archiveChats = archiveChats diff --git a/packages/client/src/methods/bots/answer-callback-query.ts b/packages/client/src/methods/bots/answer-callback-query.ts new file mode 100644 index 00000000..57f4d5cd --- /dev/null +++ b/packages/client/src/methods/bots/answer-callback-query.ts @@ -0,0 +1,60 @@ +import { TelegramClient } from '../../client' +import { tl } from '@mtcute/tl' + +/** + * Send an answer to a callback query. + * + * @param queryId ID of the callback query + * @param params Parameters of the answer + * @internal + */ +export async function answerCallbackQuery( + this: TelegramClient, + queryId: tl.Long, + params?: { + /** + * Maximum amount of time in seconds for which + * this result can be cached by the client (not server!). + * + * Defaults to `0` + */ + cacheTime?: number + + /** + * Text of the notification (0-200 chars). + * + * If not set, nothing will be displayed + */ + text?: string + + /** + * Whether to show an alert in the middle of the screen + * instead of a notification at the top of the screen. + * + * Defaults to `false`. + */ + alert?: boolean + + /** + * URL that the client should open. + * + * If this was a button containing a game, + * you can provide arbitrary link to your game. + * Otherwise, you can only use links in the format + * `t.me/your_bot?start=...` that open your bot + * with a deep-link parameter. + */ + url?: string + } +): Promise { + if (!params) params = {} + + await this.call({ + _: 'messages.setBotCallbackAnswer', + queryId, + cacheTime: params.cacheTime ?? 0, + alert: params.alert, + message: params.text, + url: params.url + }) +} diff --git a/packages/client/src/types/bots/callback-query.ts b/packages/client/src/types/bots/callback-query.ts new file mode 100644 index 00000000..bc4f6560 --- /dev/null +++ b/packages/client/src/types/bots/callback-query.ts @@ -0,0 +1,187 @@ +import { makeInspectable } from '../utils' +import { TelegramClient } from '../../client' +import { tl } from '@mtcute/tl' +import { Message } from '../messages' +import { MtCuteArgumentError } from '../errors' +import { getMarkedPeerId } from '@mtcute/core' +import { encodeInlineMessageId } from '../../utils/inline-utils' +import { User } from '../peers' + +/** + * An incoming callback query, originated from a callback button + * of an inline keyboard. + */ +export class CallbackQuery { + readonly client: TelegramClient + readonly raw: + | tl.RawUpdateBotCallbackQuery + | tl.RawUpdateInlineBotCallbackQuery + + readonly _users: Record + + constructor( + client: TelegramClient, + raw: tl.RawUpdateBotCallbackQuery | tl.RawUpdateInlineBotCallbackQuery, + users: Record + ) { + this.client = client + this.raw = raw + this._users = users + } + + /** + * ID of this callback query + */ + get id(): tl.Long { + return this.raw.queryId + } + + private _user?: User + /** + * User who has pressed the button + */ + get user(): User { + if (!this._user) { + this._user = new User(this.client, this._users[this.raw.userId]) + } + + return this._user + } + + /** + * Unique ID, that represents the chat to which the inline + * message was sent. Does *not* contain actual chat ID. + * + * Useful for high scores in games + */ + get uniqueChatId(): tl.Long { + return this.raw.chatInstance + } + + /** + * Whether this callback query originates from + * a button that was attached to a message sent + * *via* the bot (i.e. using inline mode). + * + * If `true`, `messageId` is available and `getMessage` can be used, + * otherwise `inlineMessageId` and `inlineMessageIdStr` are available + */ + get isInline(): boolean { + return this.raw._ === 'updateInlineBotCallbackQuery' + } + + /** + * Identifier of the previously sent inline message, + * that contained the button which was clicked. + * This ID can be used in `TelegramClient.editInlineMessage` + * + * Is only available in case `isInline = true` + */ + get inlineMessageId(): tl.TypeInputBotInlineMessageID { + if (this.raw._ !== 'updateInlineBotCallbackQuery') + throw new MtCuteArgumentError( + 'Cannot get inline message id for non-inline callback' + ) + + return this.raw.msgId + } + + /** + * Identifier of the previously sent inline message, + * that contained the button which was clicked, + * as a TDLib and Bot API compatible string. + * Can be used instead of {@link inlineMessageId} in + * case you want to store it in some storage. + * + * Is only available in case `isInline = true` + */ + get inlineMessageIdStr(): string { + if (this.raw._ !== 'updateInlineBotCallbackQuery') + throw new MtCuteArgumentError( + 'Cannot get inline message id for non-inline callback' + ) + + return encodeInlineMessageId(this.raw.msgId) + } + + /** + * Identifier of the message sent by the bot + * that contained the button which was clicked. + * + * Is only available in case `isInline = false` + */ + get messageId(): number { + if (this.raw._ !== 'updateBotCallbackQuery') + throw new MtCuteArgumentError( + 'Cannot get message id for inline callback' + ) + + return this.raw.msgId + } + + /** + * Data that was contained in the callback button, if any + * + * Note that this field is defined by the client, and a bad + * client can send arbitrary data in this field. + */ + get data(): Buffer | null { + return this.raw.data ?? null + } + + /** + * In case this message was from {@link InputInlineResultGame}, + * or the button was {@link BotKeyboard.game}, + * short name of the game that should be returned. + */ + get game(): string | null { + return this.raw.gameShortName ?? null + } + + /** + * Message that contained the callback button that was clicked. + * + * Can only be used if `isInline = false` + */ + async getMessage(): Promise { + if (this.raw._ !== 'updateBotCallbackQuery') + throw new MtCuteArgumentError( + 'Cannot get a message for inline callback' + ) + + return this.client.getMessages( + getMarkedPeerId(this.raw.peer), + this.raw.msgId + ) + } + + /** + * Answer this query + */ + async answer( + params: Parameters[1] + ): Promise { + return this.client.answerCallbackQuery(this.raw.queryId, params) + } + + /** + * Edit the message that originated this callback query + */ + async editMessage( + params: Parameters[1] + ): Promise { + // we can use editInlineMessage as a parameter since they share most of the parameters, + // except the ones that won't apply to already sent message anyways. + if (this.raw._ === 'updateInlineBotCallbackQuery') { + return this.client.editInlineMessage(this.raw.msgId, params) + } else { + await this.client.editMessage( + getMarkedPeerId(this.raw.peer), + this.raw.msgId, + params + ) + } + } +} + +makeInspectable(CallbackQuery) diff --git a/packages/client/src/types/bots/index.ts b/packages/client/src/types/bots/index.ts index c27980db..5d91c796 100644 --- a/packages/client/src/types/bots/index.ts +++ b/packages/client/src/types/bots/index.ts @@ -1,3 +1,5 @@ +export * from './input' + export * from './keyboards' export * from './inline-query' -export * from './input' +export * from './callback-query' diff --git a/packages/dispatcher/scripts/update-types.txt b/packages/dispatcher/scripts/update-types.txt index bd0fd529..56bb32ae 100644 --- a/packages/dispatcher/scripts/update-types.txt +++ b/packages/dispatcher/scripts/update-types.txt @@ -6,3 +6,4 @@ edit_message = Message chat_member: ChatMemberUpdate = ChatMemberUpdate inline_query = InlineQuery chosen_inline_result = ChosenInlineResult +callback_query = CallbackQuery diff --git a/packages/dispatcher/src/builders.ts b/packages/dispatcher/src/builders.ts index 525b5e91..fd34fbb5 100644 --- a/packages/dispatcher/src/builders.ts +++ b/packages/dispatcher/src/builders.ts @@ -7,10 +7,11 @@ import { ChatMemberUpdateHandler, InlineQueryHandler, ChosenInlineResultHandler, + CallbackQueryHandler, } from './handler' // end-codegen-imports import { filters, UpdateFilter } from './filters' -import { InlineQuery, Message } from '@mtcute/client' +import { CallbackQuery, InlineQuery, Message } from '@mtcute/client' import { ChatMemberUpdate } from './updates' import { ChosenInlineResult } from './updates/chosen-inline-result' @@ -204,5 +205,35 @@ export namespace handlers { return _create('chosen_inline_result', filter, handler) } + /** + * Create a callback query handler + * + * @param handler Callback query handler + */ + export function callbackQuery( + handler: CallbackQueryHandler['callback'] + ): CallbackQueryHandler + + /** + * Create a callback query handler with a filter + * + * @param filter Update filter + * @param handler Callback query handler + */ + export function callbackQuery( + filter: UpdateFilter, + handler: CallbackQueryHandler< + filters.Modify + >['callback'] + ): CallbackQueryHandler + + /** @internal */ + export function callbackQuery( + filter: any, + handler?: any + ): CallbackQueryHandler { + return _create('callback_query', filter, handler) + } + // end-codegen } diff --git a/packages/dispatcher/src/dispatcher.ts b/packages/dispatcher/src/dispatcher.ts index 28df2e3e..d6a681e7 100644 --- a/packages/dispatcher/src/dispatcher.ts +++ b/packages/dispatcher/src/dispatcher.ts @@ -1,4 +1,5 @@ import { + CallbackQuery, InlineQuery, Message, MtCuteArgumentError, @@ -20,6 +21,7 @@ import { ChatMemberUpdateHandler, InlineQueryHandler, ChosenInlineResultHandler, + CallbackQueryHandler, } from './handler' // end-codegen-imports import { filters, UpdateFilter } from './filters' @@ -57,6 +59,10 @@ const chatMemberParser: UpdateParser = [ (client, upd, users, chats) => new ChatMemberUpdate(client, upd as any, users, chats), ] +const callbackQueryParser: UpdateParser = [ + 'callback_query', + (client, upd, users) => new CallbackQuery(client, upd as any, users), +] const PARSERS: Partial< Record<(tl.TypeUpdate | tl.TypeMessage)['_'], UpdateParser> @@ -80,6 +86,8 @@ const PARSERS: Partial< (client, upd, users) => new ChosenInlineResult(client, upd as any, users), ], + updateBotCallbackQuery: callbackQueryParser, + updateInlineBotCallbackQuery: callbackQueryParser, } /** @@ -559,5 +567,37 @@ export class Dispatcher { this._addKnownHandler('chosenInlineResult', filter, handler, group) } + /** + * Register a callback query handler without any filters + * + * @param handler Callback query handler + * @param group Handler group index + * @internal + */ + onCallbackQuery( + handler: CallbackQueryHandler['callback'], + group?: number + ): void + + /** + * Register a callback query handler with a filter + * + * @param filter Update filter + * @param handler Callback query handler + * @param group Handler group index + */ + onCallbackQuery( + filter: UpdateFilter, + handler: CallbackQueryHandler< + filters.Modify + >['callback'], + group?: number + ): void + + /** @internal */ + onCallbackQuery(filter: any, handler?: any, group?: number): void { + this._addKnownHandler('callbackQuery', filter, handler, group) + } + // end-codegen } diff --git a/packages/dispatcher/src/handler.ts b/packages/dispatcher/src/handler.ts index b7e23b1e..d669dbdc 100644 --- a/packages/dispatcher/src/handler.ts +++ b/packages/dispatcher/src/handler.ts @@ -3,6 +3,7 @@ import { Message, TelegramClient, InlineQuery, + CallbackQuery, } from '@mtcute/client' import { tl } from '@mtcute/tl' import { PropagationSymbol } from './propagation' @@ -61,6 +62,10 @@ export type InlineQueryHandler = ParsedUpdateHandler< export type ChosenInlineResultHandler< T = ChosenInlineResult > = ParsedUpdateHandler<'chosen_inline_result', T> +export type CallbackQueryHandler = ParsedUpdateHandler< + 'callback_query', + T +> export type UpdateHandler = | RawUpdateHandler @@ -69,5 +74,6 @@ export type UpdateHandler = | ChatMemberUpdateHandler | InlineQueryHandler | ChosenInlineResultHandler + | CallbackQueryHandler // end-codegen