From 9c7935702f664c576e32ef50803c91f7b8d9ca7f Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 28 Apr 2018 23:48:38 +0200 Subject: [PATCH] Add callback query support --- compiler/api/compiler.py | 4 +- pyrogram/__init__.py | 5 +- pyrogram/client/__init__.py | 2 +- pyrogram/client/client.py | 23 ++++++ pyrogram/client/dispatcher/dispatcher.py | 75 ++++++++++-------- pyrogram/client/handlers/__init__.py | 2 +- pyrogram/client/handlers/handlers.py | 12 +++ pyrogram/client/types/__init__.py | 1 + pyrogram/client/types/callback_query.py | 76 +++++++++++++++++++ .../reply_markup/inline_keyboard_button.py | 18 ++--- pyrogram/client/utils.py | 20 +++++ 11 files changed, 194 insertions(+), 44 deletions(-) create mode 100644 pyrogram/client/types/callback_query.py diff --git a/compiler/api/compiler.py b/compiler/api/compiler.py index 25a2a3e6..10c0c1dc 100644 --- a/compiler/api/compiler.py +++ b/compiler/api/compiler.py @@ -500,7 +500,9 @@ def start(): f.write("\n 0xb0700020: \"pyrogram.client.types.reply_markup.InlineKeyboardMarkup\",") f.write("\n 0xb0700021: \"pyrogram.client.types.reply_markup.KeyboardButton\",") f.write("\n 0xb0700022: \"pyrogram.client.types.reply_markup.ReplyKeyboardMarkup\",") - f.write("\n 0xb0700023: \"pyrogram.client.types.reply_markup.ReplyKeyboardRemove\"") + f.write("\n 0xb0700023: \"pyrogram.client.types.reply_markup.ReplyKeyboardRemove\",") + + f.write("\n 0xb0700024: \"pyrogram.client.types.CallbackQuery\"") f.write("\n}\n") diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index 0b261922..e39ec4e1 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -30,7 +30,7 @@ from .client.types import ( Audio, Chat, ChatMember, ChatPhoto, Contact, Document, InputMediaPhoto, InputMediaVideo, InputPhoneContact, Location, Message, MessageEntity, PhotoSize, Sticker, Update, User, UserProfilePhotos, Venue, Video, - VideoNote, Voice + VideoNote, Voice, CallbackQuery ) from .client.types.reply_markup import ( ForceReply, InlineKeyboardButton, InlineKeyboardMarkup, @@ -38,5 +38,6 @@ from .client.types.reply_markup import ( ) from .client import ( Client, ChatAction, ParseMode, Emoji, - MessageHandler, RawUpdateHandler, Filters + MessageHandler, CallbackQueryHandler, RawUpdateHandler, + Filters ) diff --git a/pyrogram/client/__init__.py b/pyrogram/client/__init__.py index 435276d3..b7819bd8 100644 --- a/pyrogram/client/__init__.py +++ b/pyrogram/client/__init__.py @@ -20,5 +20,5 @@ from .chat_action import ChatAction from .client import Client from .emoji import Emoji from .filters import Filters -from .handlers import MessageHandler, RawUpdateHandler +from .handlers import MessageHandler, CallbackQueryHandler, RawUpdateHandler from .parse_mode import ParseMode diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index 4df0d299..f570b81c 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -227,6 +227,13 @@ class Client: return decorator + def on_callback_query(self, filters=None, group: int = 0): + def decorator(func): + self.add_handler(pyrogram.CallbackQueryHandler(func, filters), group) + return func + + return decorator + def on_raw_update(self, group: int = 0): """Use this decorator to automatically register a function for handling raw updates. This does the same thing as :meth:`add_handler` using the @@ -3525,3 +3532,19 @@ class Client: ) return messages if is_list else messages[0] + + def answer_callback_cuery(self, + callback_query_id: str, + text: str = None, + show_alert: bool = None, + url: str = None, + cache_time: int = 0): + return self.send( + functions.messages.SetBotCallbackAnswer( + query_id=int(callback_query_id), + cache_time=cache_time, + alert=show_alert, + message=text, + url=url + ) + ) diff --git a/pyrogram/client/dispatcher/dispatcher.py b/pyrogram/client/dispatcher/dispatcher.py index 5e89049f..b68ccc59 100644 --- a/pyrogram/client/dispatcher/dispatcher.py +++ b/pyrogram/client/dispatcher/dispatcher.py @@ -25,23 +25,23 @@ from threading import Thread import pyrogram from pyrogram.api import types from .. import utils -from ..handlers import RawUpdateHandler, MessageHandler +from ..handlers import RawUpdateHandler, CallbackQueryHandler, MessageHandler log = logging.getLogger(__name__) class Dispatcher: - MESSAGE_UPDATES = ( + NEW_MESSAGE_UPDATES = ( types.UpdateNewMessage, types.UpdateNewChannelMessage ) - EDIT_UPDATES = ( + EDIT_MESSAGE_UPDATES = ( types.UpdateEditMessage, types.UpdateEditChannelMessage ) - ALLOWED_UPDATES = MESSAGE_UPDATES + EDIT_UPDATES + MESSAGE_UPDATES = NEW_MESSAGE_UPDATES + EDIT_MESSAGE_UPDATES def __init__(self, client, workers): self.client = client @@ -84,18 +84,25 @@ class Dispatcher: args = (self.client, update, users, chats) else: - if not isinstance(handler, MessageHandler): - continue - message = (update.message or update.channel_post or update.edited_message or update.edited_channel_post) - if not handler.check(message): - continue + callback_query = update.callback_query - args = (self.client, message) + if message and isinstance(handler, MessageHandler): + if not handler.check(message): + continue + + args = (self.client, message) + elif callback_query and isinstance(handler, CallbackQueryHandler): + if not handler.check(callback_query): + continue + + args = (self.client, callback_query) + else: + continue handler.callback(*args) break @@ -117,7 +124,7 @@ class Dispatcher: self.dispatch(update, users=users, chats=chats, is_raw=True) - if isinstance(update, Dispatcher.ALLOWED_UPDATES): + if isinstance(update, Dispatcher.MESSAGE_UPDATES): if isinstance(update.message, types.Message): parser = utils.parse_message elif isinstance(update.message, types.MessageService): @@ -131,27 +138,35 @@ class Dispatcher: users, chats ) + + is_edited_message = isinstance(update, Dispatcher.EDIT_MESSAGE_UPDATES) + + self.dispatch( + pyrogram.Update( + message=((message if message.chat.type != "channel" + else None) if not is_edited_message + else None), + edited_message=((message if message.chat.type != "channel" + else None) if is_edited_message + else None), + channel_post=((message if message.chat.type == "channel" + else None) if not is_edited_message + else None), + edited_channel_post=((message if message.chat.type == "channel" + else None) if is_edited_message + else None) + ) + ) + elif isinstance(update, types.UpdateBotCallbackQuery): + self.dispatch( + pyrogram.Update( + callback_query=utils.parse_callback_query( + self.client, update, users, chats + ) + ) + ) else: continue - - is_edited_message = isinstance(update, Dispatcher.EDIT_UPDATES) - - self.dispatch( - pyrogram.Update( - message=((message if message.chat.type != "channel" - else None) if not is_edited_message - else None), - edited_message=((message if message.chat.type != "channel" - else None) if is_edited_message - else None), - channel_post=((message if message.chat.type == "channel" - else None) if not is_edited_message - else None), - edited_channel_post=((message if message.chat.type == "channel" - else None) if is_edited_message - else None) - ) - ) except Exception as e: log.error(e, exc_info=True) diff --git a/pyrogram/client/handlers/__init__.py b/pyrogram/client/handlers/__init__.py index d9c48359..ed330b21 100644 --- a/pyrogram/client/handlers/__init__.py +++ b/pyrogram/client/handlers/__init__.py @@ -16,4 +16,4 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from .handlers import MessageHandler, RawUpdateHandler +from .handlers import MessageHandler, CallbackQueryHandler, RawUpdateHandler diff --git a/pyrogram/client/handlers/handlers.py b/pyrogram/client/handlers/handlers.py index e909e218..cc91151b 100644 --- a/pyrogram/client/handlers/handlers.py +++ b/pyrogram/client/handlers/handlers.py @@ -52,6 +52,18 @@ class MessageHandler(Handler): ) +class CallbackQueryHandler(Handler): + def __init__(self, callback: callable, filters=None): + super().__init__(callback, filters) + + def check(self, callback_query): + return ( + self.filters(callback_query) + if self.filters + else True + ) + + class RawUpdateHandler(Handler): """The Raw Update handler class. Used to handle raw updates. It is intended to be used with :meth:`add_handler() ` diff --git a/pyrogram/client/types/__init__.py b/pyrogram/client/types/__init__.py index d16fdbbb..dbe7a85e 100644 --- a/pyrogram/client/types/__init__.py +++ b/pyrogram/client/types/__init__.py @@ -17,6 +17,7 @@ # along with Pyrogram. If not, see . from .audio import Audio +from .callback_query import CallbackQuery from .chat import Chat from .chat_member import ChatMember from .chat_photo import ChatPhoto diff --git a/pyrogram/client/types/callback_query.py b/pyrogram/client/types/callback_query.py new file mode 100644 index 00000000..e3b18b91 --- /dev/null +++ b/pyrogram/client/types/callback_query.py @@ -0,0 +1,76 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2018 Dan Tès +# +# This file is part of Pyrogram. +# +# Pyrogram is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrogram is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . + +from pyrogram.api.core import Object + + +class CallbackQuery(Object): + """This object represents an incoming callback query from a callback button in an inline keyboard. + If the button that originated the query was attached to a message sent by the bot, the field message + will be present. If the button was attached to a message sent via the bot (in inline mode), + the field inline_message_id will be present. Exactly one of the fields data or game_short_name will be present. + + Attributes: + ID: ``0xb0700024`` + + Args: + id (``str``): + Unique identifier for this query. + + from_user (:obj:`User `): + Sender. + + chat_instance (``str``, optional): + Message with the callback button that originated the query. Note that message content and message date will + not be available if the message is too old. + + message (:obj:`Message `, optional): + Identifier of the message sent via the bot in inline mode, that originated the query. + + inline_message_id (``str``): + Global identifier, uniquely corresponding to the chat to which the message with the callback button was + sent. Useful for high scores in games. + + data (``str``, optional): + Data associated with the callback button. Be aware that a bad client can send arbitrary data in this field. + + game_short_name (``str``, optional): + Short name of a Game to be returned, serves as the unique identifier for the game. + + """ + ID = 0xb0700024 + + def __init__( + self, + id: str, + from_user, + chat, + chat_instance: str, + message=None, + inline_message_id: str = None, + data: str = None, + game_short_name: str = None + ): + self.id = id # string + self.from_user = from_user # User + self.chat = chat + self.message = message # flags.0?Message + self.inline_message_id = inline_message_id # flags.1?string + self.chat_instance = chat_instance # string + self.data = data # flags.2?string + self.game_short_name = game_short_name # flags.3?string diff --git a/pyrogram/client/types/reply_markup/inline_keyboard_button.py b/pyrogram/client/types/reply_markup/inline_keyboard_button.py index 72f14eef..ba071c04 100644 --- a/pyrogram/client/types/reply_markup/inline_keyboard_button.py +++ b/pyrogram/client/types/reply_markup/inline_keyboard_button.py @@ -34,12 +34,12 @@ class InlineKeyboardButton(Object): text (``str``): Label text on the button. - url (``str``, optional): - HTTP url to be opened when button is pressed. - callback_data (``str``, optional): Data to be sent in a callback query to the bot when button is pressed, 1-64 bytes. + url (``str``, optional): + HTTP url to be opened when button is pressed. + switch_inline_query (``str``, optional): If set, pressing the button will prompt the user to select one of their chats, open that chat and insert the bot's username and the specified inline query in the input field. Can be empty, in which case just @@ -68,8 +68,8 @@ class InlineKeyboardButton(Object): def __init__( self, text: str, - url: str = None, callback_data: str = None, + url: str = None, switch_inline_query: str = None, switch_inline_query_current_chat: str = None, callback_game=None, @@ -90,13 +90,13 @@ class InlineKeyboardButton(Object): text=b.text, url=b.url ) - + if isinstance(b, KeyboardButtonCallback): return InlineKeyboardButton( text=b.text, callback_data=b.data.decode() ) - + if isinstance(b, KeyboardButtonSwitchInline): if b.same_peer: return InlineKeyboardButton( @@ -110,12 +110,12 @@ class InlineKeyboardButton(Object): ) def write(self): - if self.url: - return KeyboardButtonUrl(self.text, self.url) - if self.callback_data: return KeyboardButtonCallback(self.text, self.callback_data.encode()) + if self.url: + return KeyboardButtonUrl(self.text, self.url) + if self.switch_inline_query: return KeyboardButtonSwitchInline(self.text, self.switch_inline_query) diff --git a/pyrogram/client/utils.py b/pyrogram/client/utils.py index da45acda..8fc6efb8 100644 --- a/pyrogram/client/utils.py +++ b/pyrogram/client/utils.py @@ -721,3 +721,23 @@ def parse_photos(photos): total_count=total_count, photos=user_profile_photos ) + + +def parse_callback_query(client, callback_query, users, chats): + if isinstance(callback_query.peer, types.PeerUser): + chat = parse_user_chat(users[callback_query.peer.user_id]) + elif isinstance(callback_query.peer, types.PeerChat): + chat = parse_chat_chat(chats[callback_query.peer.chat_id]) + else: + chat = parse_channel_chat(chats[callback_query.peer.channel_id]) + + return pyrogram_types.CallbackQuery( + id=callback_query.query_id, + from_user=parse_user(users[callback_query.user_id]), + chat=chat, + message=client.get_messages(chat.id, callback_query.msg_id), + chat_instance=str(callback_query.chat_instance), + data=callback_query.data.decode(), + game_short_name=callback_query.game_short_name + # TODO: add inline_message_id + )