feat: support callback queries

This commit is contained in:
teidesu 2021-05-05 01:50:04 +03:00
parent 95f6515340
commit 1fb7057866
8 changed files with 375 additions and 2 deletions

View file

@ -15,6 +15,7 @@ import { signIn } from './methods/auth/sign-in'
import { signUp } from './methods/auth/sign-up' import { signUp } from './methods/auth/sign-up'
import { startTest } from './methods/auth/start-test' import { startTest } from './methods/auth/start-test'
import { start } from './methods/auth/start' import { start } from './methods/auth/start'
import { answerCallbackQuery } from './methods/bots/answer-callback-query'
import { answerInlineQuery } from './methods/bots/answer-inline-query' import { answerInlineQuery } from './methods/bots/answer-inline-query'
import { addChatMembers } from './methods/chats/add-chat-members' import { addChatMembers } from './methods/chats/add-chat-members'
import { archiveChats } from './methods/chats/archive-chats' import { archiveChats } from './methods/chats/archive-chats'
@ -396,6 +397,50 @@ export interface TelegramClient extends BaseTelegramClient {
*/ */
catchUp?: boolean catchUp?: boolean
}): Promise<User> }): Promise<User>
/**
* 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<void>
/** /**
* Answer an inline query. * Answer an inline query.
* *
@ -2060,6 +2105,7 @@ export class TelegramClient extends BaseTelegramClient {
signUp = signUp signUp = signUp
startTest = startTest startTest = startTest
start = start start = start
answerCallbackQuery = answerCallbackQuery
answerInlineQuery = answerInlineQuery answerInlineQuery = answerInlineQuery
addChatMembers = addChatMembers addChatMembers = addChatMembers
archiveChats = archiveChats archiveChats = archiveChats

View file

@ -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<void> {
if (!params) params = {}
await this.call({
_: 'messages.setBotCallbackAnswer',
queryId,
cacheTime: params.cacheTime ?? 0,
alert: params.alert,
message: params.text,
url: params.url
})
}

View file

@ -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<number, tl.TypeUser>
constructor(
client: TelegramClient,
raw: tl.RawUpdateBotCallbackQuery | tl.RawUpdateInlineBotCallbackQuery,
users: Record<number, tl.TypeUser>
) {
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<Message> {
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<TelegramClient['answerCallbackQuery']>[1]
): Promise<void> {
return this.client.answerCallbackQuery(this.raw.queryId, params)
}
/**
* Edit the message that originated this callback query
*/
async editMessage(
params: Parameters<TelegramClient['editInlineMessage']>[1]
): Promise<void> {
// 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)

View file

@ -1,3 +1,5 @@
export * from './input'
export * from './keyboards' export * from './keyboards'
export * from './inline-query' export * from './inline-query'
export * from './input' export * from './callback-query'

View file

@ -6,3 +6,4 @@ edit_message = Message
chat_member: ChatMemberUpdate = ChatMemberUpdate chat_member: ChatMemberUpdate = ChatMemberUpdate
inline_query = InlineQuery inline_query = InlineQuery
chosen_inline_result = ChosenInlineResult chosen_inline_result = ChosenInlineResult
callback_query = CallbackQuery

View file

@ -7,10 +7,11 @@ import {
ChatMemberUpdateHandler, ChatMemberUpdateHandler,
InlineQueryHandler, InlineQueryHandler,
ChosenInlineResultHandler, ChosenInlineResultHandler,
CallbackQueryHandler,
} from './handler' } from './handler'
// end-codegen-imports // end-codegen-imports
import { filters, UpdateFilter } from './filters' import { filters, UpdateFilter } from './filters'
import { InlineQuery, Message } from '@mtcute/client' import { CallbackQuery, InlineQuery, Message } from '@mtcute/client'
import { ChatMemberUpdate } from './updates' import { ChatMemberUpdate } from './updates'
import { ChosenInlineResult } from './updates/chosen-inline-result' import { ChosenInlineResult } from './updates/chosen-inline-result'
@ -204,5 +205,35 @@ export namespace handlers {
return _create('chosen_inline_result', filter, handler) 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<Mod>(
filter: UpdateFilter<CallbackQuery, Mod>,
handler: CallbackQueryHandler<
filters.Modify<CallbackQuery, Mod>
>['callback']
): CallbackQueryHandler
/** @internal */
export function callbackQuery(
filter: any,
handler?: any
): CallbackQueryHandler {
return _create('callback_query', filter, handler)
}
// end-codegen // end-codegen
} }

View file

@ -1,4 +1,5 @@
import { import {
CallbackQuery,
InlineQuery, InlineQuery,
Message, Message,
MtCuteArgumentError, MtCuteArgumentError,
@ -20,6 +21,7 @@ import {
ChatMemberUpdateHandler, ChatMemberUpdateHandler,
InlineQueryHandler, InlineQueryHandler,
ChosenInlineResultHandler, ChosenInlineResultHandler,
CallbackQueryHandler,
} from './handler' } from './handler'
// end-codegen-imports // end-codegen-imports
import { filters, UpdateFilter } from './filters' import { filters, UpdateFilter } from './filters'
@ -57,6 +59,10 @@ const chatMemberParser: UpdateParser = [
(client, upd, users, chats) => (client, upd, users, chats) =>
new ChatMemberUpdate(client, upd as any, 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< const PARSERS: Partial<
Record<(tl.TypeUpdate | tl.TypeMessage)['_'], UpdateParser> Record<(tl.TypeUpdate | tl.TypeMessage)['_'], UpdateParser>
@ -80,6 +86,8 @@ const PARSERS: Partial<
(client, upd, users) => (client, upd, users) =>
new ChosenInlineResult(client, upd as any, 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) 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<Mod>(
filter: UpdateFilter<CallbackQuery, Mod>,
handler: CallbackQueryHandler<
filters.Modify<CallbackQuery, Mod>
>['callback'],
group?: number
): void
/** @internal */
onCallbackQuery(filter: any, handler?: any, group?: number): void {
this._addKnownHandler('callbackQuery', filter, handler, group)
}
// end-codegen // end-codegen
} }

View file

@ -3,6 +3,7 @@ import {
Message, Message,
TelegramClient, TelegramClient,
InlineQuery, InlineQuery,
CallbackQuery,
} from '@mtcute/client' } from '@mtcute/client'
import { tl } from '@mtcute/tl' import { tl } from '@mtcute/tl'
import { PropagationSymbol } from './propagation' import { PropagationSymbol } from './propagation'
@ -61,6 +62,10 @@ export type InlineQueryHandler<T = InlineQuery> = ParsedUpdateHandler<
export type ChosenInlineResultHandler< export type ChosenInlineResultHandler<
T = ChosenInlineResult T = ChosenInlineResult
> = ParsedUpdateHandler<'chosen_inline_result', T> > = ParsedUpdateHandler<'chosen_inline_result', T>
export type CallbackQueryHandler<T = CallbackQuery> = ParsedUpdateHandler<
'callback_query',
T
>
export type UpdateHandler = export type UpdateHandler =
| RawUpdateHandler | RawUpdateHandler
@ -69,5 +74,6 @@ export type UpdateHandler =
| ChatMemberUpdateHandler | ChatMemberUpdateHandler
| InlineQueryHandler | InlineQueryHandler
| ChosenInlineResultHandler | ChosenInlineResultHandler
| CallbackQueryHandler
// end-codegen // end-codegen