Add callback query support

This commit is contained in:
Dan 2018-04-28 23:48:38 +02:00
parent c1459aa22c
commit 9c7935702f
11 changed files with 194 additions and 44 deletions

View File

@ -500,7 +500,9 @@ def start():
f.write("\n 0xb0700020: \"pyrogram.client.types.reply_markup.InlineKeyboardMarkup\",") f.write("\n 0xb0700020: \"pyrogram.client.types.reply_markup.InlineKeyboardMarkup\",")
f.write("\n 0xb0700021: \"pyrogram.client.types.reply_markup.KeyboardButton\",") f.write("\n 0xb0700021: \"pyrogram.client.types.reply_markup.KeyboardButton\",")
f.write("\n 0xb0700022: \"pyrogram.client.types.reply_markup.ReplyKeyboardMarkup\",") 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") f.write("\n}\n")

View File

@ -30,7 +30,7 @@ from .client.types import (
Audio, Chat, ChatMember, ChatPhoto, Contact, Document, InputMediaPhoto, Audio, Chat, ChatMember, ChatPhoto, Contact, Document, InputMediaPhoto,
InputMediaVideo, InputPhoneContact, Location, Message, MessageEntity, InputMediaVideo, InputPhoneContact, Location, Message, MessageEntity,
PhotoSize, Sticker, Update, User, UserProfilePhotos, Venue, Video, PhotoSize, Sticker, Update, User, UserProfilePhotos, Venue, Video,
VideoNote, Voice VideoNote, Voice, CallbackQuery
) )
from .client.types.reply_markup import ( from .client.types.reply_markup import (
ForceReply, InlineKeyboardButton, InlineKeyboardMarkup, ForceReply, InlineKeyboardButton, InlineKeyboardMarkup,
@ -38,5 +38,6 @@ from .client.types.reply_markup import (
) )
from .client import ( from .client import (
Client, ChatAction, ParseMode, Emoji, Client, ChatAction, ParseMode, Emoji,
MessageHandler, RawUpdateHandler, Filters MessageHandler, CallbackQueryHandler, RawUpdateHandler,
Filters
) )

View File

@ -20,5 +20,5 @@ from .chat_action import ChatAction
from .client import Client from .client import Client
from .emoji import Emoji from .emoji import Emoji
from .filters import Filters from .filters import Filters
from .handlers import MessageHandler, RawUpdateHandler from .handlers import MessageHandler, CallbackQueryHandler, RawUpdateHandler
from .parse_mode import ParseMode from .parse_mode import ParseMode

View File

@ -227,6 +227,13 @@ class Client:
return decorator 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): def on_raw_update(self, group: int = 0):
"""Use this decorator to automatically register a function for handling """Use this decorator to automatically register a function for handling
raw updates. This does the same thing as :meth:`add_handler` using the 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] 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
)
)

View File

@ -25,23 +25,23 @@ from threading import Thread
import pyrogram import pyrogram
from pyrogram.api import types from pyrogram.api import types
from .. import utils from .. import utils
from ..handlers import RawUpdateHandler, MessageHandler from ..handlers import RawUpdateHandler, CallbackQueryHandler, MessageHandler
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
class Dispatcher: class Dispatcher:
MESSAGE_UPDATES = ( NEW_MESSAGE_UPDATES = (
types.UpdateNewMessage, types.UpdateNewMessage,
types.UpdateNewChannelMessage types.UpdateNewChannelMessage
) )
EDIT_UPDATES = ( EDIT_MESSAGE_UPDATES = (
types.UpdateEditMessage, types.UpdateEditMessage,
types.UpdateEditChannelMessage types.UpdateEditChannelMessage
) )
ALLOWED_UPDATES = MESSAGE_UPDATES + EDIT_UPDATES MESSAGE_UPDATES = NEW_MESSAGE_UPDATES + EDIT_MESSAGE_UPDATES
def __init__(self, client, workers): def __init__(self, client, workers):
self.client = client self.client = client
@ -84,18 +84,25 @@ class Dispatcher:
args = (self.client, update, users, chats) args = (self.client, update, users, chats)
else: else:
if not isinstance(handler, MessageHandler):
continue
message = (update.message message = (update.message
or update.channel_post or update.channel_post
or update.edited_message or update.edited_message
or update.edited_channel_post) or update.edited_channel_post)
if not handler.check(message): callback_query = update.callback_query
continue
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) handler.callback(*args)
break break
@ -117,7 +124,7 @@ class Dispatcher:
self.dispatch(update, users=users, chats=chats, is_raw=True) 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): if isinstance(update.message, types.Message):
parser = utils.parse_message parser = utils.parse_message
elif isinstance(update.message, types.MessageService): elif isinstance(update.message, types.MessageService):
@ -131,27 +138,35 @@ class Dispatcher:
users, users,
chats 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: else:
continue 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: except Exception as e:
log.error(e, exc_info=True) log.error(e, exc_info=True)

View File

@ -16,4 +16,4 @@
# You should have received a copy of the GNU Lesser General Public License # You should have received a copy of the GNU Lesser General Public License
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>. # along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
from .handlers import MessageHandler, RawUpdateHandler from .handlers import MessageHandler, CallbackQueryHandler, RawUpdateHandler

View File

@ -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): class RawUpdateHandler(Handler):
"""The Raw Update handler class. Used to handle raw updates. It is intended to be used with """The Raw Update handler class. Used to handle raw updates. It is intended to be used with
:meth:`add_handler() <pyrogram.Client.add_handler>` :meth:`add_handler() <pyrogram.Client.add_handler>`

View File

@ -17,6 +17,7 @@
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>. # along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
from .audio import Audio from .audio import Audio
from .callback_query import CallbackQuery
from .chat import Chat from .chat import Chat
from .chat_member import ChatMember from .chat_member import ChatMember
from .chat_photo import ChatPhoto from .chat_photo import ChatPhoto

View File

@ -0,0 +1,76 @@
# Pyrogram - Telegram MTProto API Client Library for Python
# Copyright (C) 2017-2018 Dan Tès <https://github.com/delivrance>
#
# 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 <http://www.gnu.org/licenses/>.
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 <pyrogram.types.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 <pyrogram.types.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

View File

@ -34,12 +34,12 @@ class InlineKeyboardButton(Object):
text (``str``): text (``str``):
Label text on the button. Label text on the button.
url (``str``, optional):
HTTP url to be opened when button is pressed.
callback_data (``str``, optional): callback_data (``str``, optional):
Data to be sent in a callback query to the bot when button is pressed, 1-64 bytes. 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): 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 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 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__( def __init__(
self, self,
text: str, text: str,
url: str = None,
callback_data: str = None, callback_data: str = None,
url: str = None,
switch_inline_query: str = None, switch_inline_query: str = None,
switch_inline_query_current_chat: str = None, switch_inline_query_current_chat: str = None,
callback_game=None, callback_game=None,
@ -90,13 +90,13 @@ class InlineKeyboardButton(Object):
text=b.text, text=b.text,
url=b.url url=b.url
) )
if isinstance(b, KeyboardButtonCallback): if isinstance(b, KeyboardButtonCallback):
return InlineKeyboardButton( return InlineKeyboardButton(
text=b.text, text=b.text,
callback_data=b.data.decode() callback_data=b.data.decode()
) )
if isinstance(b, KeyboardButtonSwitchInline): if isinstance(b, KeyboardButtonSwitchInline):
if b.same_peer: if b.same_peer:
return InlineKeyboardButton( return InlineKeyboardButton(
@ -110,12 +110,12 @@ class InlineKeyboardButton(Object):
) )
def write(self): def write(self):
if self.url:
return KeyboardButtonUrl(self.text, self.url)
if self.callback_data: if self.callback_data:
return KeyboardButtonCallback(self.text, self.callback_data.encode()) return KeyboardButtonCallback(self.text, self.callback_data.encode())
if self.url:
return KeyboardButtonUrl(self.text, self.url)
if self.switch_inline_query: if self.switch_inline_query:
return KeyboardButtonSwitchInline(self.text, self.switch_inline_query) return KeyboardButtonSwitchInline(self.text, self.switch_inline_query)

View File

@ -721,3 +721,23 @@ def parse_photos(photos):
total_count=total_count, total_count=total_count,
photos=user_profile_photos 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
)