From e2e2d9c9fe805286d75a1eb05ae514ac95b94a23 Mon Sep 17 00:00:00 2001 From: Alex RootJunior Date: Thu, 11 Apr 2019 00:32:46 +0300 Subject: [PATCH] Implemented polls [From test API] --- aiogram/__init__.py | 2 +- aiogram/bot/api.py | 4 ++++ aiogram/bot/bot.py | 17 +++++++++++++++++ aiogram/dispatcher/dispatcher.py | 24 +++++++++++++++++++++--- aiogram/dispatcher/filters/builtin.py | 14 +++++++++++--- aiogram/types/__init__.py | 3 +++ aiogram/types/message.py | 6 ++++++ aiogram/types/poll.py | 16 ++++++++++++++++ aiogram/types/update.py | 2 ++ aiogram/utils/exceptions.py | 26 ++++++++++++++++++++++++++ 10 files changed, 107 insertions(+), 7 deletions(-) create mode 100644 aiogram/types/poll.py diff --git a/aiogram/__init__.py b/aiogram/__init__.py index 321a4e34..f7e61e76 100644 --- a/aiogram/__init__.py +++ b/aiogram/__init__.py @@ -38,5 +38,5 @@ __all__ = [ 'utils' ] -__version__ = '2.0.2.dev1' +__version__ = '2.1.dev1' __api_version__ = '4.1' diff --git a/aiogram/bot/api.py b/aiogram/bot/api.py index 25935dac..16dbb9fa 100644 --- a/aiogram/bot/api.py +++ b/aiogram/bot/api.py @@ -229,6 +229,10 @@ class Methods(Helper): SET_GAME_SCORE = Item() # setGameScore GET_GAME_HIGH_SCORES = Item() # getGameHighScores + # Polls + SEND_POLL = Item() + STOP_POLL = Item() + @staticmethod def api_url(token, method): """ diff --git a/aiogram/bot/bot.py b/aiogram/bot/bot.py index 1f76823d..42caf989 100644 --- a/aiogram/bot/bot.py +++ b/aiogram/bot/bot.py @@ -2056,3 +2056,20 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin): result = await self.request(api.Methods.GET_GAME_HIGH_SCORES, payload) return [types.GameHighScore(**gamehighscore) for gamehighscore in result] + + async def send_poll(self, chat_id: typing.Union[base.Integer, base.String], + question: base.String, + options: typing.List[base.String], + reply_to_message_id: typing.Union[base.Integer, None]): + options = prepare_arg(options) + payload = generate_payload(**locals()) + + result = await self.request(api.Methods.SEND_POLL, payload) + return types.Message(**result) + + async def stop_poll(self, chat_id: typing.Union[base.String, base.Integer], + message_id: base.Integer) -> types.Poll: + payload = generate_payload(**locals()) + + result = await self.request(api.Methods.STOP_POLL, payload) + return types.Poll(**result) diff --git a/aiogram/dispatcher/dispatcher.py b/aiogram/dispatcher/dispatcher.py index d1fc72ba..d1096718 100644 --- a/aiogram/dispatcher/dispatcher.py +++ b/aiogram/dispatcher/dispatcher.py @@ -64,6 +64,7 @@ class Dispatcher(DataMixin, ContextInstanceMixin): self.callback_query_handlers = Handler(self, middleware_key='callback_query') self.shipping_query_handlers = Handler(self, middleware_key='shipping_query') self.pre_checkout_query_handlers = Handler(self, middleware_key='pre_checkout_query') + self.poll_handlers = Handler(self, middleware_key='poll') self.errors_handlers = Handler(self, once=False, middleware_key='error') self.middleware = MiddlewareManager(self) @@ -80,7 +81,8 @@ class Dispatcher(DataMixin, ContextInstanceMixin): filters_factory = self.filters_factory filters_factory.bind(StateFilter, exclude_event_handlers=[ - self.errors_handlers + self.errors_handlers, + self.poll_handlers ]) filters_factory.bind(ContentTypeFilter, event_handlers=[ self.message_handlers, self.edited_message_handlers, @@ -92,7 +94,7 @@ class Dispatcher(DataMixin, ContextInstanceMixin): filters_factory.bind(Text, event_handlers=[ self.message_handlers, self.edited_message_handlers, self.channel_post_handlers, self.edited_channel_post_handlers, - self.callback_query_handlers + self.callback_query_handlers, self.poll_handlers ]) filters_factory.bind(HashTag, event_handlers=[ self.message_handlers, self.edited_message_handlers, @@ -101,7 +103,7 @@ class Dispatcher(DataMixin, ContextInstanceMixin): filters_factory.bind(Regexp, event_handlers=[ self.message_handlers, self.edited_message_handlers, self.channel_post_handlers, self.edited_channel_post_handlers, - self.callback_query_handlers + self.callback_query_handlers, self.poll_handlers ]) filters_factory.bind(RegexpCommandsFilter, event_handlers=[ self.message_handlers, self.edited_message_handlers @@ -185,6 +187,8 @@ class Dispatcher(DataMixin, ContextInstanceMixin): if update.pre_checkout_query: types.User.set_current(update.pre_checkout_query.from_user) return await self.pre_checkout_query_handlers.notify(update.pre_checkout_query) + if update.poll: + return await self.poll_handlers.notify(update.poll) except Exception as e: err = await self.errors_handlers.notify(update, e) if err: @@ -796,6 +800,20 @@ class Dispatcher(DataMixin, ContextInstanceMixin): return decorator + def register_poll_handler(self, callback, *custom_filters, run_task=None, **kwargs): + filters_set = self.filters_factory.resolve(self.poll_handlers, + *custom_filters, + **kwargs) + self.poll_handlers.register(self._wrap_async_task(callback, run_task), filters_set) + + def poll_handler(self, *custom_filters, run_task=None, **kwargs): + def decorator(callback): + self.register_poll_handler(callback, *custom_filters, run_task=run_task, + **kwargs) + return callback + + return decorator + def register_errors_handler(self, callback, *custom_filters, exception=None, run_task=None, **kwargs): """ Register handler for errors diff --git a/aiogram/dispatcher/filters/builtin.py b/aiogram/dispatcher/filters/builtin.py index 07e702de..de61c560 100644 --- a/aiogram/dispatcher/filters/builtin.py +++ b/aiogram/dispatcher/filters/builtin.py @@ -8,7 +8,7 @@ from babel.support import LazyProxy from aiogram import types from aiogram.dispatcher.filters.filters import BoundFilter, Filter -from aiogram.types import CallbackQuery, Message, InlineQuery +from aiogram.types import CallbackQuery, Message, InlineQuery, Poll from aiogram.utils.deprecated import warn_deprecated @@ -164,10 +164,14 @@ class Text(Filter): async def check(self, obj: Union[Message, CallbackQuery, InlineQuery]): if isinstance(obj, Message): text = obj.text or obj.caption or '' + if not text and obj.poll: + text = obj.poll.question elif isinstance(obj, CallbackQuery): text = obj.data elif isinstance(obj, InlineQuery): text = obj.query + elif isinstance(obj, Poll): + text = obj.question else: return False @@ -269,12 +273,16 @@ class Regexp(Filter): async def check(self, obj: Union[Message, CallbackQuery]): if isinstance(obj, Message): - match = self.regexp.search(obj.text or obj.caption or '') + content = obj.text or obj.caption or '' + if not content and obj.poll: + content = obj.poll.question elif isinstance(obj, CallbackQuery) and obj.data: - match = self.regexp.search(obj.data) + content = obj.data else: return False + match = self.regexp.search(content) + if match: return {'regexp': match} return False diff --git a/aiogram/types/__init__.py b/aiogram/types/__init__.py index 5416afd9..1dcd2c1f 100644 --- a/aiogram/types/__init__.py +++ b/aiogram/types/__init__.py @@ -43,6 +43,7 @@ from .passport_element_error import PassportElementError, PassportElementErrorDa PassportElementErrorSelfie from .passport_file import PassportFile from .photo_size import PhotoSize +from .poll import PollOptions, Poll from .pre_checkout_query import PreCheckoutQuery from .reply_keyboard import KeyboardButton, ReplyKeyboardMarkup, ReplyKeyboardRemove from .response_parameters import ResponseParameters @@ -142,6 +143,8 @@ __all__ = ( 'PassportElementErrorSelfie', 'PassportFile', 'PhotoSize', + 'Poll', + 'PollOptions', 'PreCheckoutQuery', 'ReplyKeyboardMarkup', 'ReplyKeyboardRemove', diff --git a/aiogram/types/message.py b/aiogram/types/message.py index f2741a9c..5dde2c73 100644 --- a/aiogram/types/message.py +++ b/aiogram/types/message.py @@ -19,6 +19,7 @@ from .location import Location from .message_entity import MessageEntity from .passport_data import PassportData from .photo_size import PhotoSize +from .poll import Poll from .sticker import Sticker from .successful_payment import SuccessfulPayment from .user import User @@ -81,6 +82,7 @@ class Message(base.TelegramObject): successful_payment: SuccessfulPayment = fields.Field(base=SuccessfulPayment) connected_website: base.String = fields.Field() passport_data: PassportData = fields.Field(base=PassportData) + poll: Poll = fields.Field(base=Poll) @property @functools.lru_cache() @@ -137,6 +139,8 @@ class Message(base.TelegramObject): return ContentType.GROUP_CHAT_CREATED elif self.passport_data: return ContentType.PASSPORT_DATA + elif self.poll: + return ContentType.POLL else: return ContentType.UNKNOWN @@ -1539,6 +1543,7 @@ class ContentType(helper.Helper): DELETE_CHAT_PHOTO = helper.Item() # delete_chat_photo GROUP_CHAT_CREATED = helper.Item() # group_chat_created PASSPORT_DATA = helper.Item() # passport_data + POLL = helper.Item() UNKNOWN = helper.Item() # unknown ANY = helper.Item() # any @@ -1600,6 +1605,7 @@ class ContentTypes(helper.Helper): DELETE_CHAT_PHOTO = helper.ListItem() # delete_chat_photo GROUP_CHAT_CREATED = helper.ListItem() # group_chat_created PASSPORT_DATA = helper.ListItem() # passport_data + POLL = helper.ListItem() UNKNOWN = helper.ListItem() # unknown ANY = helper.ListItem() # any diff --git a/aiogram/types/poll.py b/aiogram/types/poll.py new file mode 100644 index 00000000..e3f8caca --- /dev/null +++ b/aiogram/types/poll.py @@ -0,0 +1,16 @@ +import typing + +from . import base +from . import fields + + +class PollOptions(base.TelegramObject): + text: base.String = fields.Field() + voter_count: base.Integer = fields.Field() + + +class Poll(base.TelegramObject): + id: base.String = fields.Field() + question: base.String = fields.Field() + options: typing.List[PollOptions] = fields.ListField(base=PollOptions) + is_closed: base.Boolean = fields.Field() diff --git a/aiogram/types/update.py b/aiogram/types/update.py index 2753ae5f..9f8ce0fb 100644 --- a/aiogram/types/update.py +++ b/aiogram/types/update.py @@ -6,6 +6,7 @@ from .callback_query import CallbackQuery from .chosen_inline_result import ChosenInlineResult from .inline_query import InlineQuery from .message import Message +from .poll import Poll from .pre_checkout_query import PreCheckoutQuery from .shipping_query import ShippingQuery from ..utils import helper @@ -28,6 +29,7 @@ class Update(base.TelegramObject): callback_query: CallbackQuery = fields.Field(base=CallbackQuery) shipping_query: ShippingQuery = fields.Field(base=ShippingQuery) pre_checkout_query: PreCheckoutQuery = fields.Field(base=PreCheckoutQuery) + poll: Poll = fields.Field(base=Poll) def __hash__(self): return self.update_id diff --git a/aiogram/utils/exceptions.py b/aiogram/utils/exceptions.py index 24afe356..b08c681d 100644 --- a/aiogram/utils/exceptions.py +++ b/aiogram/utils/exceptions.py @@ -7,12 +7,16 @@ TelegramAPIError MessageNotModified MessageToForwardNotFound MessageToDeleteNotFound + MessageWithPollNotFound + MessageIsNotAPoll MessageIdentifierNotSpecified MessageTextIsEmpty MessageCantBeEdited MessageCantBeDeleted MessageToEditNotFound ToMuchMessages + PollCantBeStopped + PollHasAlreadyClosed ObjectExpectedAsReplyMarkup InlineKeyboardExpected ChatNotFound @@ -164,6 +168,20 @@ class MessageToDeleteNotFound(MessageError): match = 'message to delete not found' +class MessageWithPollNotFound(MessageError): + """ + Will be raised when you try to stop poll with message without poll + """ + match = 'message with poll to stop not found' + + +class MessageIsNotAPoll(MessageError): + """ + Will be raised when you try to stop poll with message without poll + """ + match = 'message is not a poll' + + class MessageIdentifierNotSpecified(MessageError): match = 'message identifier is not specified' @@ -203,6 +221,14 @@ class InlineKeyboardExpected(BadRequest): match = 'inline keyboard expected' +class PollCantBeStopped(BadRequest): + match = "poll can't be stopped" + + +class PollHasAlreadyBeenClosed(BadRequest): + match = 'poll has already been closed' + + class ChatNotFound(BadRequest): match = 'chat not found'