mirror of
https://github.com/aiogram/aiogram.git
synced 2026-04-08 16:37:47 +00:00
Merge branch 'dev-2.x'
This commit is contained in:
commit
11eeaa0325
16 changed files with 206 additions and 10 deletions
|
|
@ -21,7 +21,7 @@ You can [read the docs here](http://docs.aiogram.dev/en/latest/).
|
|||
- Community: [@aiogram](https://t.me/aiogram)
|
||||
- Russian community: [@aiogram_ru](https://t.me/aiogram_ru)
|
||||
- Pip: [aiogram](https://pypi.python.org/pypi/aiogram)
|
||||
- Docs: [ReadTheDocs](http://docs.aiogram.dev)
|
||||
- Docs: [aiogram site](https://docs.aiogram.dev/)
|
||||
- Source: [Github repo](https://github.com/aiogram/aiogram)
|
||||
- Issues/Bug tracker: [Github issues tracker](https://github.com/aiogram/aiogram/issues)
|
||||
- Test bot: [@aiogram_bot](https://t.me/aiogram_bot)
|
||||
|
|
|
|||
|
|
@ -863,6 +863,11 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
|
|||
async def send_poll(self, chat_id: typing.Union[base.Integer, base.String],
|
||||
question: base.String,
|
||||
options: typing.List[base.String],
|
||||
is_anonymous: typing.Optional[base.Boolean] = None,
|
||||
type: typing.Optional[base.String] = None,
|
||||
allows_multiple_answers: typing.Optional[base.Boolean] = None,
|
||||
correct_option_id: typing.Optional[base.Integer] = None,
|
||||
is_closed: typing.Optional[base.Boolean] = None,
|
||||
disable_notification: typing.Optional[base.Boolean] = None,
|
||||
reply_to_message_id: typing.Optional[base.Integer] = None,
|
||||
reply_markup: typing.Union[types.InlineKeyboardMarkup,
|
||||
|
|
@ -881,6 +886,16 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
|
|||
:type question: :obj:`base.String`
|
||||
:param options: List of answer options, 2-10 strings 1-100 characters each
|
||||
:param options: :obj:`typing.List[base.String]`
|
||||
:param is_anonymous: True, if the poll needs to be anonymous, defaults to True
|
||||
:param is_anonymous: :obj:`typing.Optional[base.Boolean]`
|
||||
:param type: Poll type, “quiz” or “regular”, defaults to “regular”
|
||||
:param type: :obj:`typing.Optional[base.String]`
|
||||
:param allows_multiple_answers: True, if the poll allows multiple answers, ignored for polls in quiz mode, defaults to False
|
||||
:param allows_multiple_answers: :obj:`typing.Optional[base.Boolean]`
|
||||
:param correct_option_id: 0-based identifier of the correct answer option, required for polls in quiz mode
|
||||
:param correct_option_id: :obj:`typing.Optional[base.Integer]`
|
||||
:param is_closed: Pass True, if the poll needs to be immediately closed
|
||||
:param is_closed: :obj:`typing.Optional[base.Boolean]`
|
||||
:param disable_notification: Sends the message silently. Users will receive a notification with no sound.
|
||||
:type disable_notification: :obj:`typing.Optional[Boolean]`
|
||||
:param reply_to_message_id: If the message is a reply, ID of the original message
|
||||
|
|
|
|||
|
|
@ -146,6 +146,20 @@ class LoggingMiddleware(BaseMiddleware):
|
|||
if timeout > 0:
|
||||
self.logger.info(f"Process update [ID:{update.update_id}]: [failed] (in {timeout} ms)")
|
||||
|
||||
async def on_pre_process_poll(self, poll, data):
|
||||
self.logger.info(f"Received poll [ID:{poll.id}]")
|
||||
|
||||
async def on_post_process_poll(self, poll, results, data):
|
||||
self.logger.debug(f"{HANDLED_STR[bool(len(results))]} poll [ID:{poll.id}]")
|
||||
|
||||
async def on_pre_process_poll_answer(self, poll_answer, data):
|
||||
self.logger.info(f"Received poll answer [ID:{poll_answer.poll_id}] "
|
||||
f"from user [ID:{poll_answer.user.id}]")
|
||||
|
||||
async def on_post_process_poll_answer(self, poll_answer, results, data):
|
||||
self.logger.debug(f"{HANDLED_STR[bool(len(results))]} poll answer [ID:{poll_answer.poll_id}] "
|
||||
f"from user [ID:{poll_answer.user.id}]")
|
||||
|
||||
|
||||
class LoggingFilter(logging.Filter):
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ from aiohttp.helpers import sentinel
|
|||
from aiogram.utils.deprecated import renamed_argument
|
||||
from .filters import Command, ContentTypeFilter, ExceptionsFilter, FiltersFactory, HashTag, Regexp, \
|
||||
RegexpCommandsFilter, StateFilter, Text, IDFilter, AdminFilter, IsReplyFilter
|
||||
from .filters.builtin import IsSenderContact
|
||||
from .handler import Handler
|
||||
from .middlewares import MiddlewareManager
|
||||
from .storage import BaseStorage, DELTA, DisabledStorage, EXCEEDED_COUNT, FSMContext, \
|
||||
|
|
@ -69,6 +70,7 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
|
|||
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.poll_answer_handlers = Handler(self, middleware_key='poll_answer')
|
||||
self.errors_handlers = Handler(self, once=False, middleware_key='error')
|
||||
|
||||
self.middleware = MiddlewareManager(self)
|
||||
|
|
@ -87,6 +89,7 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
|
|||
filters_factory.bind(StateFilter, exclude_event_handlers=[
|
||||
self.errors_handlers,
|
||||
self.poll_handlers,
|
||||
self.poll_answer_handlers,
|
||||
])
|
||||
filters_factory.bind(ContentTypeFilter, event_handlers=[
|
||||
self.message_handlers,
|
||||
|
|
@ -151,6 +154,12 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
|
|||
self.channel_post_handlers,
|
||||
self.edited_channel_post_handlers,
|
||||
])
|
||||
filters_factory.bind(IsSenderContact, event_handlers=[
|
||||
self.message_handlers,
|
||||
self.edited_message_handlers,
|
||||
self.channel_post_handlers,
|
||||
self.edited_channel_post_handlers,
|
||||
])
|
||||
|
||||
def __del__(self):
|
||||
self.stop_polling()
|
||||
|
|
@ -226,6 +235,8 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
|
|||
return await self.pre_checkout_query_handlers.notify(update.pre_checkout_query)
|
||||
if update.poll:
|
||||
return await self.poll_handlers.notify(update.poll)
|
||||
if update.poll_answer:
|
||||
return await self.poll_answer_handlers.notify(update.poll_answer)
|
||||
except Exception as e:
|
||||
err = await self.errors_handlers.notify(update, e)
|
||||
if err:
|
||||
|
|
@ -853,18 +864,90 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
|
|||
return decorator
|
||||
|
||||
def register_poll_handler(self, callback, *custom_filters, run_task=None, **kwargs):
|
||||
"""
|
||||
Register handler for poll
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: python3
|
||||
|
||||
dp.register_poll_handler(some_poll_handler)
|
||||
|
||||
:param callback:
|
||||
:param custom_filters:
|
||||
:param run_task: run callback in task (no wait results)
|
||||
:param 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):
|
||||
"""
|
||||
Decorator for poll handler
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: python3
|
||||
|
||||
@dp.poll_handler()
|
||||
async def some_poll_handler(poll: types.Poll)
|
||||
|
||||
:param custom_filters:
|
||||
:param run_task: run callback in task (no wait results)
|
||||
:param kwargs:
|
||||
"""
|
||||
|
||||
def decorator(callback):
|
||||
self.register_poll_handler(callback, *custom_filters, run_task=run_task,
|
||||
**kwargs)
|
||||
return callback
|
||||
|
||||
return decorator
|
||||
|
||||
def register_poll_answer_handler(self, callback, *custom_filters, run_task=None, **kwargs):
|
||||
"""
|
||||
Register handler for poll_answer
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: python3
|
||||
|
||||
dp.register_poll_answer_handler(some_poll_answer_handler)
|
||||
|
||||
:param callback:
|
||||
:param custom_filters:
|
||||
:param run_task: run callback in task (no wait results)
|
||||
:param kwargs:
|
||||
"""
|
||||
filters_set = self.filters_factory.resolve(self.poll_answer_handlers,
|
||||
*custom_filters,
|
||||
**kwargs)
|
||||
self.poll_answer_handlers.register(self._wrap_async_task(callback, run_task), filters_set)
|
||||
|
||||
def poll_answer_handler(self, *custom_filters, run_task=None, **kwargs):
|
||||
"""
|
||||
Decorator for poll_answer handler
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: python3
|
||||
|
||||
@dp.poll_answer_handler()
|
||||
async def some_poll_answer_handler(poll: types.Poll)
|
||||
|
||||
:param custom_filters:
|
||||
:param run_task: run callback in task (no wait results)
|
||||
:param kwargs:
|
||||
"""
|
||||
|
||||
def decorator(callback):
|
||||
self.register_poll_answer_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):
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
from .builtin import Command, CommandHelp, CommandPrivacy, CommandSettings, CommandStart, ContentTypeFilter, \
|
||||
ExceptionsFilter, HashTag, Regexp, RegexpCommandsFilter, StateFilter, \
|
||||
Text, IDFilter, AdminFilter, IsReplyFilter
|
||||
Text, IDFilter, AdminFilter, IsReplyFilter, IsSenderContact
|
||||
from .factory import FiltersFactory
|
||||
from .filters import AbstractFilter, BoundFilter, Filter, FilterNotPassed, FilterRecord, execute_filter, \
|
||||
check_filters, get_filter_spec, get_filters_spec
|
||||
|
|
@ -26,6 +26,7 @@ __all__ = [
|
|||
'Text',
|
||||
'IDFilter',
|
||||
'IsReplyFilter',
|
||||
'IsSenderContact',
|
||||
'AdminFilter',
|
||||
'get_filter_spec',
|
||||
'get_filters_spec',
|
||||
|
|
|
|||
|
|
@ -444,6 +444,8 @@ class ContentTypeFilter(BoundFilter):
|
|||
default = types.ContentTypes.TEXT
|
||||
|
||||
def __init__(self, content_types):
|
||||
if isinstance(content_types, str):
|
||||
content_types = (content_types,)
|
||||
self.content_types = content_types
|
||||
|
||||
async def check(self, message):
|
||||
|
|
@ -451,6 +453,28 @@ class ContentTypeFilter(BoundFilter):
|
|||
message.content_type in self.content_types
|
||||
|
||||
|
||||
class IsSenderContact(BoundFilter):
|
||||
"""
|
||||
Filter check that the contact matches the sender
|
||||
|
||||
`is_sender_contact=True` - contact matches the sender
|
||||
`is_sender_contact=False` - result will be inverted
|
||||
"""
|
||||
key = 'is_sender_contact'
|
||||
|
||||
def __init__(self, is_sender_contact: bool):
|
||||
self.is_sender_contact = is_sender_contact
|
||||
|
||||
async def check(self, message: types.Message) -> bool:
|
||||
if not message.contact:
|
||||
return False
|
||||
is_sender_contact = message.contact.user_id == message.from_user.id
|
||||
if self.is_sender_contact:
|
||||
return is_sender_contact
|
||||
else:
|
||||
return not is_sender_contact
|
||||
|
||||
|
||||
class StateFilter(BoundFilter):
|
||||
"""
|
||||
Check user state
|
||||
|
|
|
|||
|
|
@ -45,9 +45,9 @@ from .passport_element_error import PassportElementError, PassportElementErrorDa
|
|||
PassportElementErrorSelfie
|
||||
from .passport_file import PassportFile
|
||||
from .photo_size import PhotoSize
|
||||
from .poll import PollOption, Poll
|
||||
from .poll import PollOption, Poll, PollAnswer
|
||||
from .pre_checkout_query import PreCheckoutQuery
|
||||
from .reply_keyboard import KeyboardButton, ReplyKeyboardMarkup, ReplyKeyboardRemove
|
||||
from .reply_keyboard import KeyboardButton, ReplyKeyboardMarkup, ReplyKeyboardRemove, KeyboardButtonPollType
|
||||
from .response_parameters import ResponseParameters
|
||||
from .shipping_address import ShippingAddress
|
||||
from .shipping_option import ShippingOption
|
||||
|
|
@ -126,6 +126,7 @@ __all__ = (
|
|||
'InputVenueMessageContent',
|
||||
'Invoice',
|
||||
'KeyboardButton',
|
||||
'KeyboardButtonPollType',
|
||||
'LabeledPrice',
|
||||
'Location',
|
||||
'LoginUrl',
|
||||
|
|
@ -147,6 +148,7 @@ __all__ = (
|
|||
'PassportFile',
|
||||
'PhotoSize',
|
||||
'Poll',
|
||||
'PollAnswer',
|
||||
'PollOption',
|
||||
'PreCheckoutQuery',
|
||||
'ReplyKeyboardMarkup',
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ class MessageEntity(base.TelegramObject):
|
|||
length: base.Integer = fields.Field()
|
||||
url: base.String = fields.Field()
|
||||
user: User = fields.Field(base=User)
|
||||
language: base.String = fields.Field()
|
||||
|
||||
def get_text(self, text):
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -2,15 +2,42 @@ import typing
|
|||
|
||||
from . import base
|
||||
from . import fields
|
||||
from .user import User
|
||||
|
||||
|
||||
class PollOption(base.TelegramObject):
|
||||
"""
|
||||
This object contains information about one answer option in a poll.
|
||||
|
||||
https://core.telegram.org/bots/api#polloption
|
||||
"""
|
||||
text: base.String = fields.Field()
|
||||
voter_count: base.Integer = fields.Field()
|
||||
|
||||
|
||||
class PollAnswer(base.TelegramObject):
|
||||
"""
|
||||
This object represents an answer of a user in a non-anonymous poll.
|
||||
|
||||
https://core.telegram.org/bots/api#pollanswer
|
||||
"""
|
||||
poll_id: base.String = fields.Field()
|
||||
user: User = fields.Field(base=User)
|
||||
option_ids: typing.List[base.Integer] = fields.ListField()
|
||||
|
||||
|
||||
class Poll(base.TelegramObject):
|
||||
"""
|
||||
This object contains information about a poll.
|
||||
|
||||
https://core.telegram.org/bots/api#poll
|
||||
"""
|
||||
id: base.String = fields.Field()
|
||||
question: base.String = fields.Field()
|
||||
options: typing.List[PollOption] = fields.ListField(base=PollOption)
|
||||
total_voter_count: base.Integer = fields.Field()
|
||||
is_closed: base.Boolean = fields.Field()
|
||||
is_anonymous: base.Boolean = fields.Field()
|
||||
type: base.String = fields.Field()
|
||||
allows_multiple_answers: base.Boolean = fields.Field()
|
||||
correct_option_id: base.Integer = fields.Field()
|
||||
|
|
|
|||
|
|
@ -4,6 +4,18 @@ from . import base
|
|||
from . import fields
|
||||
|
||||
|
||||
class KeyboardButtonPollType(base.TelegramObject):
|
||||
"""
|
||||
This object represents type of a poll, which is allowed to be created and sent when the corresponding button is pressed.
|
||||
|
||||
https://core.telegram.org/bots/api#keyboardbuttonpolltype
|
||||
"""
|
||||
type: base.String = fields.Field()
|
||||
|
||||
def __init__(self, type: base.String):
|
||||
super(KeyboardButtonPollType, self).__init__(type=type)
|
||||
|
||||
|
||||
class ReplyKeyboardMarkup(base.TelegramObject):
|
||||
"""
|
||||
This object represents a custom keyboard with reply options (see Introduction to bots for details and examples).
|
||||
|
|
@ -81,14 +93,20 @@ class ReplyKeyboardMarkup(base.TelegramObject):
|
|||
|
||||
class KeyboardButton(base.TelegramObject):
|
||||
"""
|
||||
This object represents one button of the reply keyboard. For simple text buttons String can be used instead of this object to specify text of the button. Optional fields are mutually exclusive.
|
||||
Note: request_contact and request_location options will only work in Telegram versions released after 9 April, 2016. Older clients will ignore them.
|
||||
This object represents one button of the reply keyboard.
|
||||
For simple text buttons String can be used instead of this object to specify text of the button.
|
||||
Optional fields request_contact, request_location, and request_poll are mutually exclusive.
|
||||
Note: request_contact and request_location options will only work in Telegram versions released after 9 April, 2016.
|
||||
Older clients will ignore them.
|
||||
Note: request_poll option will only work in Telegram versions released after 23 January, 2020.
|
||||
Older clients will receive unsupported message.
|
||||
|
||||
https://core.telegram.org/bots/api#keyboardbutton
|
||||
"""
|
||||
text: base.String = fields.Field()
|
||||
request_contact: base.Boolean = fields.Field()
|
||||
request_location: base.Boolean = fields.Field()
|
||||
request_poll: KeyboardButtonPollType = fields.Field()
|
||||
|
||||
def __init__(self, text: base.String,
|
||||
request_contact: base.Boolean = None,
|
||||
|
|
|
|||
|
|
@ -6,7 +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 .poll import Poll, PollAnswer
|
||||
from .pre_checkout_query import PreCheckoutQuery
|
||||
from .shipping_query import ShippingQuery
|
||||
from ..utils import helper
|
||||
|
|
@ -30,6 +30,7 @@ class Update(base.TelegramObject):
|
|||
shipping_query: ShippingQuery = fields.Field(base=ShippingQuery)
|
||||
pre_checkout_query: PreCheckoutQuery = fields.Field(base=PreCheckoutQuery)
|
||||
poll: Poll = fields.Field(base=Poll)
|
||||
poll_answer: PollAnswer = fields.Field(base=PollAnswer)
|
||||
|
||||
def __hash__(self):
|
||||
return self.update_id
|
||||
|
|
@ -58,3 +59,5 @@ class AllowedUpdates(helper.Helper):
|
|||
CALLBACK_QUERY = helper.ListItem() # callback_query
|
||||
SHIPPING_QUERY = helper.ListItem() # shipping_query
|
||||
PRE_CHECKOUT_QUERY = helper.ListItem() # pre_checkout_query
|
||||
POLL = helper.ListItem() # poll
|
||||
POLL_ANSWER = helper.ListItem() # poll_answer
|
||||
|
|
|
|||
|
|
@ -22,6 +22,9 @@ class User(base.TelegramObject):
|
|||
last_name: base.String = fields.Field()
|
||||
username: base.String = fields.Field()
|
||||
language_code: base.String = fields.Field()
|
||||
can_join_groups: base.Boolean = fields.Field()
|
||||
can_read_all_group_messages: base.Boolean = fields.Field()
|
||||
supports_inline_queries: base.Boolean = fields.Field()
|
||||
|
||||
@property
|
||||
def full_name(self):
|
||||
|
|
|
|||
|
|
@ -13,6 +13,6 @@ wheel>=0.31.1
|
|||
sphinx>=2.0.1
|
||||
sphinx-rtd-theme>=0.4.3
|
||||
sphinxcontrib-programoutput>=0.14
|
||||
aiohttp-socks>=0.3.3
|
||||
aiohttp-socks>=0.3.4
|
||||
rethinkdb>=2.4.1
|
||||
coverage==4.5.3
|
||||
|
|
|
|||
|
|
@ -94,6 +94,12 @@ ContentTypeFilter
|
|||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
IsSenderContact
|
||||
---------------
|
||||
|
||||
.. autoclass:: aiogram.dispatcher.filters.builtin.IsSenderContact
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
StateFilter
|
||||
-----------
|
||||
|
|
|
|||
|
|
@ -83,7 +83,6 @@ def get_likes() -> int:
|
|||
def increase_likes() -> int:
|
||||
LIKES_STORAGE['count'] += 1
|
||||
return get_likes()
|
||||
#
|
||||
|
||||
|
||||
@dp.message_handler(commands='like')
|
||||
|
|
|
|||
2
setup.py
2
setup.py
|
|
@ -66,7 +66,7 @@ setup(
|
|||
],
|
||||
extras_require={
|
||||
'proxy': [
|
||||
'aiohttp-socks>=3.3,<4.0.0',
|
||||
'aiohttp-socks>=0.3.4,<0.4.0',
|
||||
],
|
||||
'fast': [
|
||||
'uvloop>=0.14.0,<0.15.0',
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue