import * as tg from './core/types/typegram' import * as tt from './telegram-types' import { Deunionize, PropOr, UnionKeys } from './deunionize' import ApiClient from './core/network/client' import Telegram from './telegram' type Tail = T extends [unknown, ...infer U] ? U : never type Shorthand> = Tail< Parameters > export class Context = tg.Update> { readonly state: Record = {} constructor( readonly update: U, readonly tg: Telegram, readonly botInfo: tg.UserFromGetMe ) {} get updateType() { const types = Object.keys(this.update).filter( (k) => typeof this.update[k as keyof U] === 'object' ) if (types.length !== 1) { throw new Error( `Cannot determine \`updateType\` of ${JSON.stringify(this.update)}` ) } return types[0] as UpdateTypes } get me() { return this.botInfo?.username } get telegram() { return this.tg } get message() { return this.update.message as PropOr } get editedMessage() { return this.update.edited_message as PropOr } get inlineQuery() { return this.update.inline_query as PropOr } get shippingQuery() { return this.update.shipping_query as PropOr } get preCheckoutQuery() { return this.update.pre_checkout_query as PropOr } get chosenInlineResult() { return this.update.chosen_inline_result as PropOr } get channelPost() { return this.update.channel_post as PropOr } get editedChannelPost() { return this.update.edited_channel_post as PropOr } get callbackQuery() { return this.update.callback_query as PropOr } get poll() { return this.update.poll as PropOr } get pollAnswer() { return this.update.poll_answer as PropOr } get myChatMember() { return this.update.my_chat_member as PropOr } get chatMember() { return this.update.chat_member as PropOr } get chat(): Getter { return ( this.chatMember ?? this.myChatMember ?? getMessageFromAnySource(this) )?.chat as Getter } get senderChat() { return getMessageFromAnySource(this)?.sender_chat as Getter< U, 'sender_chat' > } get from() { return ( this.callbackQuery ?? this.inlineQuery ?? this.shippingQuery ?? this.preCheckoutQuery ?? this.chosenInlineResult ?? this.chatMember ?? this.myChatMember ?? getMessageFromAnySource(this) )?.from as Getter } get inlineMessageId() { return (this.callbackQuery ?? this.chosenInlineResult)?.inline_message_id } get passportData() { if (this.message == null) return undefined if (!('passport_data' in this.message)) return undefined return this.message?.passport_data } /** * @deprecated use {@link Telegram.webhookReply} */ get webhookReply(): boolean { return this.tg.webhookReply } set webhookReply(enable: boolean) { this.tg.webhookReply = enable } private assert( value: T | undefined, method: string ): asserts value is T { if (value === undefined) { throw new TypeError( `Telegraf: "${method}" isn't available for "${this.updateType}"` ) } } /** * @see https://core.telegram.org/bots/api#answerinlinequery */ answerInlineQuery(this: Context, ...args: Shorthand<'answerInlineQuery'>) { this.assert(this.inlineQuery, 'answerInlineQuery') return this.telegram.answerInlineQuery(this.inlineQuery.id, ...args) } /** * @see https://core.telegram.org/bots/api#answercallbackquery */ answerCbQuery(this: Context, ...args: Shorthand<'answerCbQuery'>) { this.assert(this.callbackQuery, 'answerCbQuery') return this.telegram.answerCbQuery(this.callbackQuery.id, ...args) } /** * @see https://core.telegram.org/bots/api#answercallbackquery */ answerGameQuery(this: Context, ...args: Shorthand<'answerGameQuery'>) { this.assert(this.callbackQuery, 'answerGameQuery') return this.telegram.answerGameQuery(this.callbackQuery.id, ...args) } /** * @see https://core.telegram.org/bots/api#answershippingquery */ answerShippingQuery( this: Context, ...args: Shorthand<'answerShippingQuery'> ) { this.assert(this.shippingQuery, 'answerShippingQuery') return this.telegram.answerShippingQuery(this.shippingQuery.id, ...args) } /** * @see https://core.telegram.org/bots/api#answerprecheckoutquery */ answerPreCheckoutQuery( this: Context, ...args: Shorthand<'answerPreCheckoutQuery'> ) { this.assert(this.preCheckoutQuery, 'answerPreCheckoutQuery') return this.telegram.answerPreCheckoutQuery( this.preCheckoutQuery.id, ...args ) } /** * @see https://core.telegram.org/bots/api#editmessagetext */ editMessageText( this: Context, text: string, extra?: tt.ExtraEditMessageText ) { this.assert(this.callbackQuery ?? this.inlineMessageId, 'editMessageText') return this.telegram.editMessageText( this.chat?.id, this.callbackQuery?.message?.message_id, this.inlineMessageId, text, extra ) } /** * @see https://core.telegram.org/bots/api#editmessagecaption */ editMessageCaption( this: Context, caption: string | undefined, extra?: tt.ExtraEditMessageCaption ) { this.assert( this.callbackQuery ?? this.inlineMessageId, 'editMessageCaption' ) return this.telegram.editMessageCaption( this.chat?.id, this.callbackQuery?.message?.message_id, this.inlineMessageId, caption, extra ) } /** * @see https://core.telegram.org/bots/api#editmessagemedia */ editMessageMedia( this: Context, media: tg.InputMedia, extra?: tt.ExtraEditMessageMedia ) { this.assert(this.callbackQuery ?? this.inlineMessageId, 'editMessageMedia') return this.telegram.editMessageMedia( this.chat?.id, this.callbackQuery?.message?.message_id, this.inlineMessageId, media, extra ) } /** * @see https://core.telegram.org/bots/api#editmessagereplymarkup */ editMessageReplyMarkup( this: Context, markup: tg.InlineKeyboardMarkup | undefined ) { this.assert( this.callbackQuery ?? this.inlineMessageId, 'editMessageReplyMarkup' ) return this.telegram.editMessageReplyMarkup( this.chat?.id, this.callbackQuery?.message?.message_id, this.inlineMessageId, markup ) } /** * @see https://core.telegram.org/bots/api#editmessagelivelocation */ editMessageLiveLocation( this: Context, latitude: number, longitude: number, extra?: tt.ExtraEditMessageLiveLocation ) { this.assert( this.callbackQuery ?? this.inlineMessageId, 'editMessageLiveLocation' ) return this.telegram.editMessageLiveLocation( this.chat?.id, this.callbackQuery?.message?.message_id, this.inlineMessageId, latitude, longitude, extra ) } /** * @see https://core.telegram.org/bots/api#stopmessagelivelocation */ stopMessageLiveLocation(this: Context, markup?: tg.InlineKeyboardMarkup) { this.assert( this.callbackQuery ?? this.inlineMessageId, 'stopMessageLiveLocation' ) return this.telegram.stopMessageLiveLocation( this.chat?.id, this.callbackQuery?.message?.message_id, this.inlineMessageId, markup ) } /** * @see https://core.telegram.org/bots/api#sendmessage */ reply(this: Context, ...args: Shorthand<'sendMessage'>) { this.assert(this.chat, 'reply') return this.telegram.sendMessage(this.chat.id, ...args) } /** * @see https://core.telegram.org/bots/api#getchat */ getChat(this: Context, ...args: Shorthand<'getChat'>) { this.assert(this.chat, 'getChat') return this.telegram.getChat(this.chat.id, ...args) } /** * @see https://core.telegram.org/bots/api#exportchatinvitelink */ exportChatInviteLink( this: Context, ...args: Shorthand<'exportChatInviteLink'> ) { this.assert(this.chat, 'exportChatInviteLink') return this.telegram.exportChatInviteLink(this.chat.id, ...args) } /** * @see https://core.telegram.org/bots/api#createchatinvitelink */ createChatInviteLink( this: Context, ...args: Shorthand<'createChatInviteLink'> ) { this.assert(this.chat, 'createChatInviteLink') return this.telegram.createChatInviteLink(this.chat.id, ...args) } /** * @see https://core.telegram.org/bots/api#editchatinvitelink */ editChatInviteLink(this: Context, ...args: Shorthand<'editChatInviteLink'>) { this.assert(this.chat, 'editChatInviteLink') return this.telegram.editChatInviteLink(this.chat.id, ...args) } /** * @see https://core.telegram.org/bots/api#revokechatinvitelink */ revokeChatInviteLink( this: Context, ...args: Shorthand<'revokeChatInviteLink'> ) { this.assert(this.chat, 'revokeChatInviteLink') return this.telegram.revokeChatInviteLink(this.chat.id, ...args) } /** * @see https://core.telegram.org/bots/api#banchatmember */ kickChatMember(this: Context, ...args: Shorthand<'kickChatMember'>) { this.assert(this.chat, 'kickChatMember') return this.telegram.kickChatMember(this.chat.id, ...args) } /** * @see https://core.telegram.org/bots/api#unbanchatmember */ unbanChatMember(this: Context, ...args: Shorthand<'unbanChatMember'>) { this.assert(this.chat, 'unbanChatMember') return this.telegram.unbanChatMember(this.chat.id, ...args) } /** * @see https://core.telegram.org/bots/api#restrictchatmember */ restrictChatMember(this: Context, ...args: Shorthand<'restrictChatMember'>) { this.assert(this.chat, 'restrictChatMember') return this.telegram.restrictChatMember(this.chat.id, ...args) } /** * @see https://core.telegram.org/bots/api#promotechatmember */ promoteChatMember(this: Context, ...args: Shorthand<'promoteChatMember'>) { this.assert(this.chat, 'promoteChatMember') return this.telegram.promoteChatMember(this.chat.id, ...args) } /** * @see https://core.telegram.org/bots/api#setchatadministratorcustomtitle */ setChatAdministratorCustomTitle( this: Context, ...args: Shorthand<'setChatAdministratorCustomTitle'> ) { this.assert(this.chat, 'setChatAdministratorCustomTitle') return this.telegram.setChatAdministratorCustomTitle(this.chat.id, ...args) } /** * @see https://core.telegram.org/bots/api#setchatphoto */ setChatPhoto(this: Context, ...args: Shorthand<'setChatPhoto'>) { this.assert(this.chat, 'setChatPhoto') return this.telegram.setChatPhoto(this.chat.id, ...args) } /** * @see https://core.telegram.org/bots/api#deletechatphoto */ deleteChatPhoto(this: Context, ...args: Shorthand<'deleteChatPhoto'>) { this.assert(this.chat, 'deleteChatPhoto') return this.telegram.deleteChatPhoto(this.chat.id, ...args) } /** * @see https://core.telegram.org/bots/api#setchattitle */ setChatTitle(this: Context, ...args: Shorthand<'setChatTitle'>) { this.assert(this.chat, 'setChatTitle') return this.telegram.setChatTitle(this.chat.id, ...args) } /** * @see https://core.telegram.org/bots/api#setchatdescription */ setChatDescription(this: Context, ...args: Shorthand<'setChatDescription'>) { this.assert(this.chat, 'setChatDescription') return this.telegram.setChatDescription(this.chat.id, ...args) } /** * @see https://core.telegram.org/bots/api#pinchatmessage */ pinChatMessage(this: Context, ...args: Shorthand<'pinChatMessage'>) { this.assert(this.chat, 'pinChatMessage') return this.telegram.pinChatMessage(this.chat.id, ...args) } /** * @see https://core.telegram.org/bots/api#unpinchatmessage */ unpinChatMessage(this: Context, ...args: Shorthand<'unpinChatMessage'>) { this.assert(this.chat, 'unpinChatMessage') return this.telegram.unpinChatMessage(this.chat.id, ...args) } /** * @see https://core.telegram.org/bots/api#unpinallchatmessages */ unpinAllChatMessages( this: Context, ...args: Shorthand<'unpinAllChatMessages'> ) { this.assert(this.chat, 'unpinAllChatMessages') return this.telegram.unpinAllChatMessages(this.chat.id, ...args) } /** * @see https://core.telegram.org/bots/api#leavechat */ leaveChat(this: Context, ...args: Shorthand<'leaveChat'>) { this.assert(this.chat, 'leaveChat') return this.telegram.leaveChat(this.chat.id, ...args) } /** * @see https://core.telegram.org/bots/api#setchatpermissions */ setChatPermissions(this: Context, ...args: Shorthand<'setChatPermissions'>) { this.assert(this.chat, 'setChatPermissions') return this.telegram.setChatPermissions(this.chat.id, ...args) } /** * @see https://core.telegram.org/bots/api#getchatadministrators */ getChatAdministrators( this: Context, ...args: Shorthand<'getChatAdministrators'> ) { this.assert(this.chat, 'getChatAdministrators') return this.telegram.getChatAdministrators(this.chat.id, ...args) } /** * @see https://core.telegram.org/bots/api#getchatmember */ getChatMember(this: Context, ...args: Shorthand<'getChatMember'>) { this.assert(this.chat, 'getChatMember') return this.telegram.getChatMember(this.chat.id, ...args) } /** * @see https://core.telegram.org/bots/api#getchatmembercount */ getChatMembersCount( this: Context, ...args: Shorthand<'getChatMembersCount'> ) { this.assert(this.chat, 'getChatMembersCount') return this.telegram.getChatMembersCount(this.chat.id, ...args) } /** * @see https://core.telegram.org/bots/api#setpassportdataerrors */ setPassportDataErrors( this: Context, errors: readonly tg.PassportElementError[] ) { this.assert(this.from, 'setPassportDataErrors') return this.telegram.setPassportDataErrors(this.from.id, errors) } /** * @see https://core.telegram.org/bots/api#replywithphoto */ replyWithPhoto(this: Context, ...args: Shorthand<'sendPhoto'>) { this.assert(this.chat, 'replyWithPhoto') return this.telegram.sendPhoto(this.chat.id, ...args) } /** * @see https://core.telegram.org/bots/api#replywithmediagroup */ replyWithMediaGroup(this: Context, ...args: Shorthand<'sendMediaGroup'>) { this.assert(this.chat, 'replyWithMediaGroup') return this.telegram.sendMediaGroup(this.chat.id, ...args) } /** * @see https://core.telegram.org/bots/api#replywithaudio */ replyWithAudio(this: Context, ...args: Shorthand<'sendAudio'>) { this.assert(this.chat, 'replyWithAudio') return this.telegram.sendAudio(this.chat.id, ...args) } /** * @see https://core.telegram.org/bots/api#replywithdice */ replyWithDice(this: Context, ...args: Shorthand<'sendDice'>) { this.assert(this.chat, 'replyWithDice') return this.telegram.sendDice(this.chat.id, ...args) } /** * @see https://core.telegram.org/bots/api#replywithdocument */ replyWithDocument(this: Context, ...args: Shorthand<'sendDocument'>) { this.assert(this.chat, 'replyWithDocument') return this.telegram.sendDocument(this.chat.id, ...args) } /** * @see https://core.telegram.org/bots/api#replywithsticker */ replyWithSticker(this: Context, ...args: Shorthand<'sendSticker'>) { this.assert(this.chat, 'replyWithSticker') return this.telegram.sendSticker(this.chat.id, ...args) } /** * @see https://core.telegram.org/bots/api#replywithvideo */ replyWithVideo(this: Context, ...args: Shorthand<'sendVideo'>) { this.assert(this.chat, 'replyWithVideo') return this.telegram.sendVideo(this.chat.id, ...args) } /** * @see https://core.telegram.org/bots/api#replywithanimation */ replyWithAnimation(this: Context, ...args: Shorthand<'sendAnimation'>) { this.assert(this.chat, 'replyWithAnimation') return this.telegram.sendAnimation(this.chat.id, ...args) } /** * @see https://core.telegram.org/bots/api#replywithvideonote */ replyWithVideoNote(this: Context, ...args: Shorthand<'sendVideoNote'>) { this.assert(this.chat, 'replyWithVideoNote') return this.telegram.sendVideoNote(this.chat.id, ...args) } /** * @see https://core.telegram.org/bots/api#replywithinvoice */ replyWithInvoice(this: Context, ...args: Shorthand<'sendInvoice'>) { this.assert(this.chat, 'replyWithInvoice') return this.telegram.sendInvoice(this.chat.id, ...args) } /** * @see https://core.telegram.org/bots/api#replywithgame */ replyWithGame(this: Context, ...args: Shorthand<'sendGame'>) { this.assert(this.chat, 'replyWithGame') return this.telegram.sendGame(this.chat.id, ...args) } /** * @see https://core.telegram.org/bots/api#replywithvoice */ replyWithVoice(this: Context, ...args: Shorthand<'sendVoice'>) { this.assert(this.chat, 'replyWithVoice') return this.telegram.sendVoice(this.chat.id, ...args) } /** * @see https://core.telegram.org/bots/api#replywithpoll */ replyWithPoll(this: Context, ...args: Shorthand<'sendPoll'>) { this.assert(this.chat, 'replyWithPoll') return this.telegram.sendPoll(this.chat.id, ...args) } /** * @see https://core.telegram.org/bots/api#replywithquiz */ replyWithQuiz(this: Context, ...args: Shorthand<'sendQuiz'>) { this.assert(this.chat, 'replyWithQuiz') return this.telegram.sendQuiz(this.chat.id, ...args) } /** * @see https://core.telegram.org/bots/api#stoppoll */ stopPoll(this: Context, ...args: Shorthand<'stopPoll'>) { this.assert(this.chat, 'stopPoll') return this.telegram.stopPoll(this.chat.id, ...args) } /** * @see https://core.telegram.org/bots/api#replywithchataction */ replyWithChatAction(this: Context, ...args: Shorthand<'sendChatAction'>) { this.assert(this.chat, 'replyWithChatAction') return this.telegram.sendChatAction(this.chat.id, ...args) } /** * @see https://core.telegram.org/bots/api#replywithlocation */ replyWithLocation(this: Context, ...args: Shorthand<'sendLocation'>) { this.assert(this.chat, 'replyWithLocation') return this.telegram.sendLocation(this.chat.id, ...args) } /** * @see https://core.telegram.org/bots/api#replywithvenue */ replyWithVenue(this: Context, ...args: Shorthand<'sendVenue'>) { this.assert(this.chat, 'replyWithVenue') return this.telegram.sendVenue(this.chat.id, ...args) } /** * @see https://core.telegram.org/bots/api#replywithcontact */ replyWithContact(this: Context, ...args: Shorthand<'sendContact'>) { this.assert(this.chat, 'replyWithContact') return this.telegram.sendContact(this.chat.id, ...args) } /** * @deprecated use {@link Telegram.getStickerSet} * @see https://core.telegram.org/bots/api#getstickerset */ getStickerSet(this: Context, setName: string) { return this.telegram.getStickerSet(setName) } /** * @see https://core.telegram.org/bots/api#setchatstickerset */ setChatStickerSet(this: Context, setName: string) { this.assert(this.chat, 'setChatStickerSet') return this.telegram.setChatStickerSet(this.chat.id, setName) } /** * @see https://core.telegram.org/bots/api#deletechatstickerset */ deleteChatStickerSet(this: Context) { this.assert(this.chat, 'deleteChatStickerSet') return this.telegram.deleteChatStickerSet(this.chat.id) } /** * @deprecated use {@link Telegram.setStickerPositionInSet} * @see https://core.telegram.org/bots/api#setstickerpositioninset */ setStickerPositionInSet(this: Context, sticker: string, position: number) { return this.telegram.setStickerPositionInSet(sticker, position) } /** * @deprecated use {@link Telegram.setStickerSetThumb} * @see https://core.telegram.org/bots/api#setstickersetthumb */ setStickerSetThumb( this: Context, ...args: Parameters ) { return this.telegram.setStickerSetThumb(...args) } /** * @deprecated use {@link Telegram.deleteStickerFromSet} * @see https://core.telegram.org/bots/api#deletestickerfromset */ deleteStickerFromSet(this: Context, sticker: string) { return this.telegram.deleteStickerFromSet(sticker) } /** * @see https://core.telegram.org/bots/api#uploadstickerfile */ uploadStickerFile(this: Context, ...args: Shorthand<'uploadStickerFile'>) { this.assert(this.from, 'uploadStickerFile') return this.telegram.uploadStickerFile(this.from.id, ...args) } /** * @see https://core.telegram.org/bots/api#createnewstickerset */ createNewStickerSet( this: Context, ...args: Shorthand<'createNewStickerSet'> ) { this.assert(this.from, 'createNewStickerSet') return this.telegram.createNewStickerSet(this.from.id, ...args) } /** * @see https://core.telegram.org/bots/api#addstickertoset */ addStickerToSet(this: Context, ...args: Shorthand<'addStickerToSet'>) { this.assert(this.from, 'addStickerToSet') return this.telegram.addStickerToSet(this.from.id, ...args) } /** * @deprecated use {@link Telegram.getMyCommands} * @see https://core.telegram.org/bots/api#getmycommands */ getMyCommands(this: Context) { return this.telegram.getMyCommands() } /** * @deprecated use {@link Telegram.setMyCommands} * @see https://core.telegram.org/bots/api#setmycommands */ setMyCommands(this: Context, commands: readonly tg.BotCommand[]) { return this.telegram.setMyCommands(commands) } /** * @see https://core.telegram.org/bots/api#sendmessage */ replyWithMarkdown( this: Context, markdown: string, extra?: tt.ExtraReplyMessage ) { return this.reply(markdown, { parse_mode: 'Markdown', ...extra }) } /** * @see https://core.telegram.org/bots/api#sendmessage */ replyWithMarkdownV2( this: Context, markdown: string, extra?: tt.ExtraReplyMessage ) { return this.reply(markdown, { parse_mode: 'MarkdownV2', ...extra }) } /** * @see https://core.telegram.org/bots/api#sendmessage */ replyWithHTML(this: Context, html: string, extra?: tt.ExtraReplyMessage) { return this.reply(html, { parse_mode: 'HTML', ...extra }) } /** * @see https://core.telegram.org/bots/api#deletemessage */ deleteMessage(this: Context, messageId?: number) { this.assert(this.chat, 'deleteMessage') if (typeof messageId !== 'undefined') { return this.telegram.deleteMessage(this.chat.id, messageId) } const message = getMessageFromAnySource(this) this.assert(message, 'deleteMessage') return this.telegram.deleteMessage(this.chat.id, message.message_id) } /** * @see https://core.telegram.org/bots/api#forwardmessage */ forwardMessage( this: Context, chatId: string | number, extra?: { disable_notification?: boolean } ) { const message = getMessageFromAnySource(this) this.assert(message, 'forwardMessage') return this.telegram.forwardMessage( chatId, message.chat.id, message.message_id, extra ) } /** * @see https://core.telegram.org/bots/api#copymessage */ copyMessage( this: Context, chatId: string | number, extra?: tt.ExtraCopyMessage ) { const message = getMessageFromAnySource(this) this.assert(message, 'copyMessage') return this.telegram.copyMessage( chatId, message.chat.id, message.message_id, extra ) } } export default Context type UpdateTypes> = Extract< UnionKeys, tt.UpdateType > export type GetUpdateContent< U extends tg.Update > = U extends tg.Update.CallbackQueryUpdate ? U['callback_query']['message'] : U[UpdateTypes] type Getter, P extends string> = PropOr< GetUpdateContent, P > function getMessageFromAnySource(ctx: Context) { return ( ctx.message ?? ctx.editedMessage ?? ctx.callbackQuery?.message ?? ctx.channelPost ?? ctx.editedChannelPost ) }