Update API, added some new features

This commit is contained in:
Alex Root Junior 2022-02-11 15:21:38 +02:00
parent c705858421
commit 7be5808136
34 changed files with 547 additions and 17 deletions

View file

@ -2,6 +2,7 @@ from .client import session
from .client.bot import Bot
from .dispatcher import filters, handler
from .dispatcher.dispatcher import Dispatcher
from .dispatcher.flags.flags import FlagGenerator
from .dispatcher.middlewares.base import BaseMiddleware
from .dispatcher.router import Router
from .utils.magic_filter import MagicFilter
@ -18,6 +19,7 @@ except ImportError: # pragma: no cover
F = MagicFilter()
html = _html_decoration
md = _markdown_decoration
flags = FlagGenerator()
__all__ = (
"__api_version__",
@ -34,6 +36,7 @@ __all__ = (
"F",
"html",
"md",
"flags",
)
__version__ = "3.0.0b1"

View file

@ -533,6 +533,7 @@ class Bot(ContextInstanceMixin["Bot"]):
entities: Optional[List[MessageEntity]] = None,
disable_web_page_preview: Optional[bool] = None,
disable_notification: Optional[bool] = None,
protect_content: Optional[bool] = None,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: Optional[bool] = None,
reply_markup: Optional[
@ -551,6 +552,7 @@ class Bot(ContextInstanceMixin["Bot"]):
:param entities: A JSON-serialized list of special entities that appear in message text, which can be specified instead of *parse_mode*
:param disable_web_page_preview: Disables link previews for links in this message
:param disable_notification: Sends the message `silently <https://telegram.org/blog/channels-2-0#silent-messages>`_. Users will receive a notification with no sound.
:param protect_content: Protects the contents of the sent message from forwarding and saving
:param reply_to_message_id: If the message is a reply, ID of the original message
:param allow_sending_without_reply: Pass :code:`True`, if the message should be sent even if the specified replied-to message is not found
:param reply_markup: Additional interface options. A JSON-serialized object for an `inline keyboard <https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating>`_, `custom reply keyboard <https://core.telegram.org/bots#keyboards>`_, instructions to remove reply keyboard or to force a reply from the user.
@ -564,6 +566,7 @@ class Bot(ContextInstanceMixin["Bot"]):
entities=entities,
disable_web_page_preview=disable_web_page_preview,
disable_notification=disable_notification,
protect_content=protect_content,
reply_to_message_id=reply_to_message_id,
allow_sending_without_reply=allow_sending_without_reply,
reply_markup=reply_markup,
@ -576,6 +579,7 @@ class Bot(ContextInstanceMixin["Bot"]):
from_chat_id: Union[int, str],
message_id: int,
disable_notification: Optional[bool] = None,
protect_content: Optional[bool] = None,
request_timeout: Optional[int] = None,
) -> Message:
"""
@ -587,6 +591,7 @@ class Bot(ContextInstanceMixin["Bot"]):
:param from_chat_id: Unique identifier for the chat where the original message was sent (or channel username in the format :code:`@channelusername`)
:param message_id: Message identifier in the chat specified in *from_chat_id*
:param disable_notification: Sends the message `silently <https://telegram.org/blog/channels-2-0#silent-messages>`_. Users will receive a notification with no sound.
:param protect_content: Protects the contents of the forwarded message from forwarding and saving
:param request_timeout: Request timeout
:return: On success, the sent Message is returned.
"""
@ -595,6 +600,7 @@ class Bot(ContextInstanceMixin["Bot"]):
from_chat_id=from_chat_id,
message_id=message_id,
disable_notification=disable_notification,
protect_content=protect_content,
)
return await self(call, request_timeout=request_timeout)
@ -607,6 +613,7 @@ class Bot(ContextInstanceMixin["Bot"]):
parse_mode: Optional[str] = UNSET,
caption_entities: Optional[List[MessageEntity]] = None,
disable_notification: Optional[bool] = None,
protect_content: Optional[bool] = None,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: Optional[bool] = None,
reply_markup: Optional[
@ -626,6 +633,7 @@ class Bot(ContextInstanceMixin["Bot"]):
:param parse_mode: Mode for parsing entities in the new caption. See `formatting options <https://core.telegram.org/bots/api#formatting-options>`_ for more details.
:param caption_entities: A JSON-serialized list of special entities that appear in the new caption, which can be specified instead of *parse_mode*
:param disable_notification: Sends the message `silently <https://telegram.org/blog/channels-2-0#silent-messages>`_. Users will receive a notification with no sound.
:param protect_content: Protects the contents of the sent message from forwarding and saving
:param reply_to_message_id: If the message is a reply, ID of the original message
:param allow_sending_without_reply: Pass :code:`True`, if the message should be sent even if the specified replied-to message is not found
:param reply_markup: Additional interface options. A JSON-serialized object for an `inline keyboard <https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating>`_, `custom reply keyboard <https://core.telegram.org/bots#keyboards>`_, instructions to remove reply keyboard or to force a reply from the user.
@ -640,6 +648,7 @@ class Bot(ContextInstanceMixin["Bot"]):
parse_mode=parse_mode,
caption_entities=caption_entities,
disable_notification=disable_notification,
protect_content=protect_content,
reply_to_message_id=reply_to_message_id,
allow_sending_without_reply=allow_sending_without_reply,
reply_markup=reply_markup,
@ -654,6 +663,7 @@ class Bot(ContextInstanceMixin["Bot"]):
parse_mode: Optional[str] = UNSET,
caption_entities: Optional[List[MessageEntity]] = None,
disable_notification: Optional[bool] = None,
protect_content: Optional[bool] = None,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: Optional[bool] = None,
reply_markup: Optional[
@ -672,6 +682,7 @@ class Bot(ContextInstanceMixin["Bot"]):
:param parse_mode: Mode for parsing entities in the photo caption. See `formatting options <https://core.telegram.org/bots/api#formatting-options>`_ for more details.
:param caption_entities: A JSON-serialized list of special entities that appear in the caption, which can be specified instead of *parse_mode*
:param disable_notification: Sends the message `silently <https://telegram.org/blog/channels-2-0#silent-messages>`_. Users will receive a notification with no sound.
:param protect_content: Protects the contents of the sent message from forwarding and saving
:param reply_to_message_id: If the message is a reply, ID of the original message
:param allow_sending_without_reply: Pass :code:`True`, if the message should be sent even if the specified replied-to message is not found
:param reply_markup: Additional interface options. A JSON-serialized object for an `inline keyboard <https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating>`_, `custom reply keyboard <https://core.telegram.org/bots#keyboards>`_, instructions to remove reply keyboard or to force a reply from the user.
@ -685,6 +696,7 @@ class Bot(ContextInstanceMixin["Bot"]):
parse_mode=parse_mode,
caption_entities=caption_entities,
disable_notification=disable_notification,
protect_content=protect_content,
reply_to_message_id=reply_to_message_id,
allow_sending_without_reply=allow_sending_without_reply,
reply_markup=reply_markup,
@ -703,6 +715,7 @@ class Bot(ContextInstanceMixin["Bot"]):
title: Optional[str] = None,
thumb: Optional[Union[InputFile, str]] = None,
disable_notification: Optional[bool] = None,
protect_content: Optional[bool] = None,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: Optional[bool] = None,
reply_markup: Optional[
@ -726,6 +739,7 @@ class Bot(ContextInstanceMixin["Bot"]):
:param title: Track name
:param thumb: Thumbnail of the file sent; can be ignored if thumbnail generation for the file is supported server-side. The thumbnail should be in JPEG format and less than 200 kB in size. A thumbnail's width and height should not exceed 320. Ignored if the file is not uploaded using multipart/form-data. Thumbnails can't be reused and can be only uploaded as a new file, so you can pass 'attach://<file_attach_name>' if the thumbnail was uploaded using multipart/form-data under <file_attach_name>. :ref:`More info on Sending Files » <sending-files>`
:param disable_notification: Sends the message `silently <https://telegram.org/blog/channels-2-0#silent-messages>`_. Users will receive a notification with no sound.
:param protect_content: Protects the contents of the sent message from forwarding and saving
:param reply_to_message_id: If the message is a reply, ID of the original message
:param allow_sending_without_reply: Pass :code:`True`, if the message should be sent even if the specified replied-to message is not found
:param reply_markup: Additional interface options. A JSON-serialized object for an `inline keyboard <https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating>`_, `custom reply keyboard <https://core.telegram.org/bots#keyboards>`_, instructions to remove reply keyboard or to force a reply from the user.
@ -743,6 +757,7 @@ class Bot(ContextInstanceMixin["Bot"]):
title=title,
thumb=thumb,
disable_notification=disable_notification,
protect_content=protect_content,
reply_to_message_id=reply_to_message_id,
allow_sending_without_reply=allow_sending_without_reply,
reply_markup=reply_markup,
@ -759,6 +774,7 @@ class Bot(ContextInstanceMixin["Bot"]):
caption_entities: Optional[List[MessageEntity]] = None,
disable_content_type_detection: Optional[bool] = None,
disable_notification: Optional[bool] = None,
protect_content: Optional[bool] = None,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: Optional[bool] = None,
reply_markup: Optional[
@ -779,6 +795,7 @@ class Bot(ContextInstanceMixin["Bot"]):
:param caption_entities: A JSON-serialized list of special entities that appear in the caption, which can be specified instead of *parse_mode*
:param disable_content_type_detection: Disables automatic server-side content type detection for files uploaded using multipart/form-data
:param disable_notification: Sends the message `silently <https://telegram.org/blog/channels-2-0#silent-messages>`_. Users will receive a notification with no sound.
:param protect_content: Protects the contents of the sent message from forwarding and saving
:param reply_to_message_id: If the message is a reply, ID of the original message
:param allow_sending_without_reply: Pass :code:`True`, if the message should be sent even if the specified replied-to message is not found
:param reply_markup: Additional interface options. A JSON-serialized object for an `inline keyboard <https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating>`_, `custom reply keyboard <https://core.telegram.org/bots#keyboards>`_, instructions to remove reply keyboard or to force a reply from the user.
@ -794,6 +811,7 @@ class Bot(ContextInstanceMixin["Bot"]):
caption_entities=caption_entities,
disable_content_type_detection=disable_content_type_detection,
disable_notification=disable_notification,
protect_content=protect_content,
reply_to_message_id=reply_to_message_id,
allow_sending_without_reply=allow_sending_without_reply,
reply_markup=reply_markup,
@ -813,6 +831,7 @@ class Bot(ContextInstanceMixin["Bot"]):
caption_entities: Optional[List[MessageEntity]] = None,
supports_streaming: Optional[bool] = None,
disable_notification: Optional[bool] = None,
protect_content: Optional[bool] = None,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: Optional[bool] = None,
reply_markup: Optional[
@ -836,6 +855,7 @@ class Bot(ContextInstanceMixin["Bot"]):
:param caption_entities: A JSON-serialized list of special entities that appear in the caption, which can be specified instead of *parse_mode*
:param supports_streaming: Pass :code:`True`, if the uploaded video is suitable for streaming
:param disable_notification: Sends the message `silently <https://telegram.org/blog/channels-2-0#silent-messages>`_. Users will receive a notification with no sound.
:param protect_content: Protects the contents of the sent message from forwarding and saving
:param reply_to_message_id: If the message is a reply, ID of the original message
:param allow_sending_without_reply: Pass :code:`True`, if the message should be sent even if the specified replied-to message is not found
:param reply_markup: Additional interface options. A JSON-serialized object for an `inline keyboard <https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating>`_, `custom reply keyboard <https://core.telegram.org/bots#keyboards>`_, instructions to remove reply keyboard or to force a reply from the user.
@ -854,6 +874,7 @@ class Bot(ContextInstanceMixin["Bot"]):
caption_entities=caption_entities,
supports_streaming=supports_streaming,
disable_notification=disable_notification,
protect_content=protect_content,
reply_to_message_id=reply_to_message_id,
allow_sending_without_reply=allow_sending_without_reply,
reply_markup=reply_markup,
@ -872,6 +893,7 @@ class Bot(ContextInstanceMixin["Bot"]):
parse_mode: Optional[str] = UNSET,
caption_entities: Optional[List[MessageEntity]] = None,
disable_notification: Optional[bool] = None,
protect_content: Optional[bool] = None,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: Optional[bool] = None,
reply_markup: Optional[
@ -894,6 +916,7 @@ class Bot(ContextInstanceMixin["Bot"]):
:param parse_mode: Mode for parsing entities in the animation caption. See `formatting options <https://core.telegram.org/bots/api#formatting-options>`_ for more details.
:param caption_entities: A JSON-serialized list of special entities that appear in the caption, which can be specified instead of *parse_mode*
:param disable_notification: Sends the message `silently <https://telegram.org/blog/channels-2-0#silent-messages>`_. Users will receive a notification with no sound.
:param protect_content: Protects the contents of the sent message from forwarding and saving
:param reply_to_message_id: If the message is a reply, ID of the original message
:param allow_sending_without_reply: Pass :code:`True`, if the message should be sent even if the specified replied-to message is not found
:param reply_markup: Additional interface options. A JSON-serialized object for an `inline keyboard <https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating>`_, `custom reply keyboard <https://core.telegram.org/bots#keyboards>`_, instructions to remove reply keyboard or to force a reply from the user.
@ -911,6 +934,7 @@ class Bot(ContextInstanceMixin["Bot"]):
parse_mode=parse_mode,
caption_entities=caption_entities,
disable_notification=disable_notification,
protect_content=protect_content,
reply_to_message_id=reply_to_message_id,
allow_sending_without_reply=allow_sending_without_reply,
reply_markup=reply_markup,
@ -926,6 +950,7 @@ class Bot(ContextInstanceMixin["Bot"]):
caption_entities: Optional[List[MessageEntity]] = None,
duration: Optional[int] = None,
disable_notification: Optional[bool] = None,
protect_content: Optional[bool] = None,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: Optional[bool] = None,
reply_markup: Optional[
@ -945,6 +970,7 @@ class Bot(ContextInstanceMixin["Bot"]):
:param caption_entities: A JSON-serialized list of special entities that appear in the caption, which can be specified instead of *parse_mode*
:param duration: Duration of the voice message in seconds
:param disable_notification: Sends the message `silently <https://telegram.org/blog/channels-2-0#silent-messages>`_. Users will receive a notification with no sound.
:param protect_content: Protects the contents of the sent message from forwarding and saving
:param reply_to_message_id: If the message is a reply, ID of the original message
:param allow_sending_without_reply: Pass :code:`True`, if the message should be sent even if the specified replied-to message is not found
:param reply_markup: Additional interface options. A JSON-serialized object for an `inline keyboard <https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating>`_, `custom reply keyboard <https://core.telegram.org/bots#keyboards>`_, instructions to remove reply keyboard or to force a reply from the user.
@ -959,6 +985,7 @@ class Bot(ContextInstanceMixin["Bot"]):
caption_entities=caption_entities,
duration=duration,
disable_notification=disable_notification,
protect_content=protect_content,
reply_to_message_id=reply_to_message_id,
allow_sending_without_reply=allow_sending_without_reply,
reply_markup=reply_markup,
@ -973,6 +1000,7 @@ class Bot(ContextInstanceMixin["Bot"]):
length: Optional[int] = None,
thumb: Optional[Union[InputFile, str]] = None,
disable_notification: Optional[bool] = None,
protect_content: Optional[bool] = None,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: Optional[bool] = None,
reply_markup: Optional[
@ -991,6 +1019,7 @@ class Bot(ContextInstanceMixin["Bot"]):
:param length: Video width and height, i.e. diameter of the video message
:param thumb: Thumbnail of the file sent; can be ignored if thumbnail generation for the file is supported server-side. The thumbnail should be in JPEG format and less than 200 kB in size. A thumbnail's width and height should not exceed 320. Ignored if the file is not uploaded using multipart/form-data. Thumbnails can't be reused and can be only uploaded as a new file, so you can pass 'attach://<file_attach_name>' if the thumbnail was uploaded using multipart/form-data under <file_attach_name>. :ref:`More info on Sending Files » <sending-files>`
:param disable_notification: Sends the message `silently <https://telegram.org/blog/channels-2-0#silent-messages>`_. Users will receive a notification with no sound.
:param protect_content: Protects the contents of the sent message from forwarding and saving
:param reply_to_message_id: If the message is a reply, ID of the original message
:param allow_sending_without_reply: Pass :code:`True`, if the message should be sent even if the specified replied-to message is not found
:param reply_markup: Additional interface options. A JSON-serialized object for an `inline keyboard <https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating>`_, `custom reply keyboard <https://core.telegram.org/bots#keyboards>`_, instructions to remove reply keyboard or to force a reply from the user.
@ -1004,6 +1033,7 @@ class Bot(ContextInstanceMixin["Bot"]):
length=length,
thumb=thumb,
disable_notification=disable_notification,
protect_content=protect_content,
reply_to_message_id=reply_to_message_id,
allow_sending_without_reply=allow_sending_without_reply,
reply_markup=reply_markup,
@ -1015,6 +1045,7 @@ class Bot(ContextInstanceMixin["Bot"]):
chat_id: Union[int, str],
media: List[Union[InputMediaAudio, InputMediaDocument, InputMediaPhoto, InputMediaVideo]],
disable_notification: Optional[bool] = None,
protect_content: Optional[bool] = None,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: Optional[bool] = None,
request_timeout: Optional[int] = None,
@ -1027,6 +1058,7 @@ class Bot(ContextInstanceMixin["Bot"]):
:param chat_id: Unique identifier for the target chat or username of the target channel (in the format :code:`@channelusername`)
:param media: A JSON-serialized array describing messages to be sent, must include 2-10 items
:param disable_notification: Sends messages `silently <https://telegram.org/blog/channels-2-0#silent-messages>`_. Users will receive a notification with no sound.
:param protect_content: Protects the contents of the sent messages from forwarding and saving
:param reply_to_message_id: If the messages are a reply, ID of the original message
:param allow_sending_without_reply: Pass :code:`True`, if the message should be sent even if the specified replied-to message is not found
:param request_timeout: Request timeout
@ -1036,6 +1068,7 @@ class Bot(ContextInstanceMixin["Bot"]):
chat_id=chat_id,
media=media,
disable_notification=disable_notification,
protect_content=protect_content,
reply_to_message_id=reply_to_message_id,
allow_sending_without_reply=allow_sending_without_reply,
)
@ -1051,6 +1084,7 @@ class Bot(ContextInstanceMixin["Bot"]):
heading: Optional[int] = None,
proximity_alert_radius: Optional[int] = None,
disable_notification: Optional[bool] = None,
protect_content: Optional[bool] = None,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: Optional[bool] = None,
reply_markup: Optional[
@ -1071,6 +1105,7 @@ class Bot(ContextInstanceMixin["Bot"]):
:param heading: For live locations, a direction in which the user is moving, in degrees. Must be between 1 and 360 if specified.
:param proximity_alert_radius: For live locations, a maximum distance for proximity alerts about approaching another chat member, in meters. Must be between 1 and 100000 if specified.
:param disable_notification: Sends the message `silently <https://telegram.org/blog/channels-2-0#silent-messages>`_. Users will receive a notification with no sound.
:param protect_content: Protects the contents of the sent message from forwarding and saving
:param reply_to_message_id: If the message is a reply, ID of the original message
:param allow_sending_without_reply: Pass :code:`True`, if the message should be sent even if the specified replied-to message is not found
:param reply_markup: Additional interface options. A JSON-serialized object for an `inline keyboard <https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating>`_, `custom reply keyboard <https://core.telegram.org/bots#keyboards>`_, instructions to remove reply keyboard or to force a reply from the user.
@ -1086,6 +1121,7 @@ class Bot(ContextInstanceMixin["Bot"]):
heading=heading,
proximity_alert_radius=proximity_alert_radius,
disable_notification=disable_notification,
protect_content=protect_content,
reply_to_message_id=reply_to_message_id,
allow_sending_without_reply=allow_sending_without_reply,
reply_markup=reply_markup,
@ -1177,6 +1213,7 @@ class Bot(ContextInstanceMixin["Bot"]):
google_place_id: Optional[str] = None,
google_place_type: Optional[str] = None,
disable_notification: Optional[bool] = None,
protect_content: Optional[bool] = None,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: Optional[bool] = None,
reply_markup: Optional[
@ -1199,6 +1236,7 @@ class Bot(ContextInstanceMixin["Bot"]):
:param google_place_id: Google Places identifier of the venue
:param google_place_type: Google Places type of the venue. (See `supported types <https://developers.google.com/places/web-service/supported_types>`_.)
:param disable_notification: Sends the message `silently <https://telegram.org/blog/channels-2-0#silent-messages>`_. Users will receive a notification with no sound.
:param protect_content: Protects the contents of the sent message from forwarding and saving
:param reply_to_message_id: If the message is a reply, ID of the original message
:param allow_sending_without_reply: Pass :code:`True`, if the message should be sent even if the specified replied-to message is not found
:param reply_markup: Additional interface options. A JSON-serialized object for an `inline keyboard <https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating>`_, `custom reply keyboard <https://core.telegram.org/bots#keyboards>`_, instructions to remove reply keyboard or to force a reply from the user.
@ -1216,6 +1254,7 @@ class Bot(ContextInstanceMixin["Bot"]):
google_place_id=google_place_id,
google_place_type=google_place_type,
disable_notification=disable_notification,
protect_content=protect_content,
reply_to_message_id=reply_to_message_id,
allow_sending_without_reply=allow_sending_without_reply,
reply_markup=reply_markup,
@ -1230,6 +1269,7 @@ class Bot(ContextInstanceMixin["Bot"]):
last_name: Optional[str] = None,
vcard: Optional[str] = None,
disable_notification: Optional[bool] = None,
protect_content: Optional[bool] = None,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: Optional[bool] = None,
reply_markup: Optional[
@ -1248,6 +1288,7 @@ class Bot(ContextInstanceMixin["Bot"]):
:param last_name: Contact's last name
:param vcard: Additional data about the contact in the form of a `vCard <https://en.wikipedia.org/wiki/VCard>`_, 0-2048 bytes
:param disable_notification: Sends the message `silently <https://telegram.org/blog/channels-2-0#silent-messages>`_. Users will receive a notification with no sound.
:param protect_content: Protects the contents of the sent message from forwarding and saving
:param reply_to_message_id: If the message is a reply, ID of the original message
:param allow_sending_without_reply: Pass :code:`True`, if the message should be sent even if the specified replied-to message is not found
:param reply_markup: Additional interface options. A JSON-serialized object for an `inline keyboard <https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating>`_, `custom reply keyboard <https://core.telegram.org/bots#keyboards>`_, instructions to remove keyboard or to force a reply from the user.
@ -1261,6 +1302,7 @@ class Bot(ContextInstanceMixin["Bot"]):
last_name=last_name,
vcard=vcard,
disable_notification=disable_notification,
protect_content=protect_content,
reply_to_message_id=reply_to_message_id,
allow_sending_without_reply=allow_sending_without_reply,
reply_markup=reply_markup,
@ -1283,6 +1325,7 @@ class Bot(ContextInstanceMixin["Bot"]):
close_date: Optional[Union[datetime.datetime, datetime.timedelta, int]] = None,
is_closed: Optional[bool] = None,
disable_notification: Optional[bool] = None,
protect_content: Optional[bool] = None,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: Optional[bool] = None,
reply_markup: Optional[
@ -1309,6 +1352,7 @@ class Bot(ContextInstanceMixin["Bot"]):
:param close_date: Point in time (Unix timestamp) when the poll will be automatically closed. Must be at least 5 and no more than 600 seconds in the future. Can't be used together with *open_period*.
:param is_closed: Pass :code:`True`, if the poll needs to be immediately closed. This can be useful for poll preview.
:param disable_notification: Sends the message `silently <https://telegram.org/blog/channels-2-0#silent-messages>`_. Users will receive a notification with no sound.
:param protect_content: Protects the contents of the sent message from forwarding and saving
:param reply_to_message_id: If the message is a reply, ID of the original message
:param allow_sending_without_reply: Pass :code:`True`, if the message should be sent even if the specified replied-to message is not found
:param reply_markup: Additional interface options. A JSON-serialized object for an `inline keyboard <https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating>`_, `custom reply keyboard <https://core.telegram.org/bots#keyboards>`_, instructions to remove reply keyboard or to force a reply from the user.
@ -1330,6 +1374,7 @@ class Bot(ContextInstanceMixin["Bot"]):
close_date=close_date,
is_closed=is_closed,
disable_notification=disable_notification,
protect_content=protect_content,
reply_to_message_id=reply_to_message_id,
allow_sending_without_reply=allow_sending_without_reply,
reply_markup=reply_markup,
@ -1341,6 +1386,7 @@ class Bot(ContextInstanceMixin["Bot"]):
chat_id: Union[int, str],
emoji: Optional[str] = None,
disable_notification: Optional[bool] = None,
protect_content: Optional[bool] = None,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: Optional[bool] = None,
reply_markup: Optional[
@ -1356,6 +1402,7 @@ class Bot(ContextInstanceMixin["Bot"]):
:param chat_id: Unique identifier for the target chat or username of the target channel (in the format :code:`@channelusername`)
:param emoji: Emoji on which the dice throw animation is based. Currently, must be one of '🎲', '🎯', '🏀', '', '🎳', or '🎰'. Dice can have values 1-6 for '🎲', '🎯' and '🎳', values 1-5 for '🏀' and '', and values 1-64 for '🎰'. Defaults to '🎲'
:param disable_notification: Sends the message `silently <https://telegram.org/blog/channels-2-0#silent-messages>`_. Users will receive a notification with no sound.
:param protect_content: Protects the contents of the sent message from forwarding
:param reply_to_message_id: If the message is a reply, ID of the original message
:param allow_sending_without_reply: Pass :code:`True`, if the message should be sent even if the specified replied-to message is not found
:param reply_markup: Additional interface options. A JSON-serialized object for an `inline keyboard <https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating>`_, `custom reply keyboard <https://core.telegram.org/bots#keyboards>`_, instructions to remove reply keyboard or to force a reply from the user.
@ -1366,6 +1413,7 @@ class Bot(ContextInstanceMixin["Bot"]):
chat_id=chat_id,
emoji=emoji,
disable_notification=disable_notification,
protect_content=protect_content,
reply_to_message_id=reply_to_message_id,
allow_sending_without_reply=allow_sending_without_reply,
reply_markup=reply_markup,
@ -2512,6 +2560,7 @@ class Bot(ContextInstanceMixin["Bot"]):
chat_id: Union[int, str],
sticker: Union[InputFile, str],
disable_notification: Optional[bool] = None,
protect_content: Optional[bool] = None,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: Optional[bool] = None,
reply_markup: Optional[
@ -2527,6 +2576,7 @@ class Bot(ContextInstanceMixin["Bot"]):
:param chat_id: Unique identifier for the target chat or username of the target channel (in the format :code:`@channelusername`)
:param sticker: Sticker to send. Pass a file_id as String to send a file that exists on the Telegram servers (recommended), pass an HTTP URL as a String for Telegram to get a .WEBP file from the Internet, or upload a new one using multipart/form-data. :ref:`More info on Sending Files » <sending-files>`
:param disable_notification: Sends the message `silently <https://telegram.org/blog/channels-2-0#silent-messages>`_. Users will receive a notification with no sound.
:param protect_content: Protects the contents of the sent message from forwarding and saving
:param reply_to_message_id: If the message is a reply, ID of the original message
:param allow_sending_without_reply: Pass :code:`True`, if the message should be sent even if the specified replied-to message is not found
:param reply_markup: Additional interface options. A JSON-serialized object for an `inline keyboard <https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating>`_, `custom reply keyboard <https://core.telegram.org/bots#keyboards>`_, instructions to remove reply keyboard or to force a reply from the user.
@ -2537,6 +2587,7 @@ class Bot(ContextInstanceMixin["Bot"]):
chat_id=chat_id,
sticker=sticker,
disable_notification=disable_notification,
protect_content=protect_content,
reply_to_message_id=reply_to_message_id,
allow_sending_without_reply=allow_sending_without_reply,
reply_markup=reply_markup,
@ -2798,6 +2849,7 @@ class Bot(ContextInstanceMixin["Bot"]):
send_email_to_provider: Optional[bool] = None,
is_flexible: Optional[bool] = None,
disable_notification: Optional[bool] = None,
protect_content: Optional[bool] = None,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: Optional[bool] = None,
reply_markup: Optional[InlineKeyboardMarkup] = None,
@ -2831,6 +2883,7 @@ class Bot(ContextInstanceMixin["Bot"]):
:param send_email_to_provider: Pass :code:`True`, if user's email address should be sent to provider
:param is_flexible: Pass :code:`True`, if the final price depends on the shipping method
:param disable_notification: Sends the message `silently <https://telegram.org/blog/channels-2-0#silent-messages>`_. Users will receive a notification with no sound.
:param protect_content: Protects the contents of the sent message from forwarding and saving
:param reply_to_message_id: If the message is a reply, ID of the original message
:param allow_sending_without_reply: Pass :code:`True`, if the message should be sent even if the specified replied-to message is not found
:param reply_markup: A JSON-serialized object for an `inline keyboard <https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating>`_. If empty, one 'Pay :code:`total price`' button will be shown. If not empty, the first button must be a Pay button.
@ -2861,6 +2914,7 @@ class Bot(ContextInstanceMixin["Bot"]):
send_email_to_provider=send_email_to_provider,
is_flexible=is_flexible,
disable_notification=disable_notification,
protect_content=protect_content,
reply_to_message_id=reply_to_message_id,
allow_sending_without_reply=allow_sending_without_reply,
reply_markup=reply_markup,
@ -2960,6 +3014,7 @@ class Bot(ContextInstanceMixin["Bot"]):
chat_id: int,
game_short_name: str,
disable_notification: Optional[bool] = None,
protect_content: Optional[bool] = None,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: Optional[bool] = None,
reply_markup: Optional[InlineKeyboardMarkup] = None,
@ -2973,6 +3028,7 @@ class Bot(ContextInstanceMixin["Bot"]):
:param chat_id: Unique identifier for the target chat
:param game_short_name: Short name of the game, serves as the unique identifier for the game. Set up your games via `Botfather <https://t.me/botfather>`_.
:param disable_notification: Sends the message `silently <https://telegram.org/blog/channels-2-0#silent-messages>`_. Users will receive a notification with no sound.
:param protect_content: Protects the contents of the sent message from forwarding and saving
:param reply_to_message_id: If the message is a reply, ID of the original message
:param allow_sending_without_reply: Pass :code:`True`, if the message should be sent even if the specified replied-to message is not found
:param reply_markup: A JSON-serialized object for an `inline keyboard <https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating>`_. If empty, one 'Play game_title' button will be shown. If not empty, the first button must launch the game.
@ -2983,6 +3039,7 @@ class Bot(ContextInstanceMixin["Bot"]):
chat_id=chat_id,
game_short_name=game_short_name,
disable_notification=disable_notification,
protect_content=protect_content,
reply_to_message_id=reply_to_message_id,
allow_sending_without_reply=allow_sending_without_reply,
reply_markup=reply_markup,

View file

@ -8,6 +8,7 @@ from typing import Any, Awaitable, Callable, Dict, List, Optional, Tuple, Type,
from magic_filter import MagicFilter
from aiogram.dispatcher.filters.base import BaseFilter
from aiogram.dispatcher.flags.getter import extract_flags_from_object
from aiogram.dispatcher.handler.base import BaseHandler
CallbackType = Callable[..., Awaitable[Any]]
@ -71,6 +72,7 @@ class HandlerObject(CallableMixin):
callback = inspect.unwrap(self.callback)
if inspect.isclass(callback) and issubclass(callback, BaseHandler):
self.awaitable = True
self.flags.update(extract_flags_from_object(callback))
async def check(self, *args: Any, **kwargs: Any) -> Tuple[bool, Dict[str, Any]]:
if not self.filters:

View file

View file

@ -0,0 +1,56 @@
from dataclasses import dataclass
from typing import Any, Callable, Optional, Union, cast, overload
from magic_filter import AttrDict
from aiogram.dispatcher.flags.getter import extract_flags_from_object
@dataclass
class Flag:
name: str
value: Any
@dataclass
class FlagDecorator:
flag: Flag
def with_value(self, value: Any) -> "FlagDecorator":
new_flag = Flag(self.flag.name, value)
return type(self)(new_flag)
@overload
def __call__(self, value: Callable[..., Any]) -> Callable[..., Any]: # type: ignore
pass
@overload
def __call__(self, value: Any) -> "FlagDecorator":
pass
@overload
def __call__(self, **kwargs: Any) -> "FlagDecorator":
pass
def __call__(
self,
value: Optional[Any] = None,
**kwargs: Any,
) -> Union[Callable[..., Any], "FlagDecorator"]:
if value and kwargs:
raise ValueError("The arguments `value` and **kwargs can not be used together")
if value is not None and callable(value):
value.aiogram_flag = {
**extract_flags_from_object(value),
self.flag.name: self.flag.value,
}
return cast(Callable[..., Any], value)
return self.with_value(AttrDict(kwargs) if value is None else value)
class FlagGenerator:
def __getattr__(self, name: str) -> FlagDecorator:
if name[0] == "_":
raise AttributeError("Flag name must NOT start with underscore")
return FlagDecorator(Flag(name, True))

View file

@ -0,0 +1,35 @@
from typing import TYPE_CHECKING, Any, Dict, Optional, Union, cast
from magic_filter import AttrDict, MagicFilter
if TYPE_CHECKING:
from aiogram.dispatcher.event.handler import HandlerObject
def extract_flags_from_object(obj: Any) -> Dict[str, Any]:
if not hasattr(obj, "aiogram_flag"):
return {}
return cast(Dict[str, Any], obj.aiogram_flag)
def extract_flags(handler: Union["HandlerObject", Dict[str, Any]]) -> Dict[str, Any]:
if isinstance(handler, dict) and "handler" in handler:
handler = handler["handler"]
if not hasattr(handler, "flags"):
return {}
return handler.flags # type: ignore
def get_flag(
handler: Union["HandlerObject", Dict[str, Any]],
name: str,
*,
default: Optional[Any] = None,
) -> Any:
flags = extract_flags(handler)
return flags.get(name, default)
def check_flags(handler: Union["HandlerObject", Dict[str, Any]], magic: MagicFilter) -> Any:
flags = extract_flags(handler)
return magic.resolve(AttrDict(flags))

View file

@ -134,7 +134,7 @@ class BaseRequestHandler(ABC):
bot=bot, update=await request.json(loads=bot.session.json_loads)
)
)
return web.json_response({})
return web.json_response({}, dumps=bot.session.json_dumps)
async def _handle_request(self, bot: Bot, request: web.Request) -> web.Response:
result = await self.dispatcher.feed_webhook_update(
@ -143,8 +143,8 @@ class BaseRequestHandler(ABC):
**self.data,
)
if result:
return web.json_response(result)
return web.json_response({})
return web.json_response(result, dumps=bot.session.json_dumps)
return web.json_response({}, dumps=bot.session.json_dumps)
async def handle(self, request: web.Request) -> web.Response:
bot = await self.resolve_bot(request)

View file

@ -40,6 +40,8 @@ class CopyMessage(TelegramMethod[MessageId]):
"""A JSON-serialized list of special entities that appear in the new caption, which can be specified instead of *parse_mode*"""
disable_notification: Optional[bool] = None
"""Sends the message `silently <https://telegram.org/blog/channels-2-0#silent-messages>`_. Users will receive a notification with no sound."""
protect_content: Optional[bool] = None
"""Protects the contents of the sent message from forwarding and saving"""
reply_to_message_id: Optional[int] = None
"""If the message is a reply, ID of the original message"""
allow_sending_without_reply: Optional[bool] = None

View file

@ -26,6 +26,8 @@ class ForwardMessage(TelegramMethod[Message]):
"""Message identifier in the chat specified in *from_chat_id*"""
disable_notification: Optional[bool] = None
"""Sends the message `silently <https://telegram.org/blog/channels-2-0#silent-messages>`_. Users will receive a notification with no sound."""
protect_content: Optional[bool] = None
"""Protects the contents of the forwarded message from forwarding and saving"""
def build_request(self, bot: Bot) -> Request:
data: Dict[str, Any] = self.dict()

View file

@ -47,6 +47,8 @@ class SendAnimation(TelegramMethod[Message]):
"""A JSON-serialized list of special entities that appear in the caption, which can be specified instead of *parse_mode*"""
disable_notification: Optional[bool] = None
"""Sends the message `silently <https://telegram.org/blog/channels-2-0#silent-messages>`_. Users will receive a notification with no sound."""
protect_content: Optional[bool] = None
"""Protects the contents of the sent message from forwarding and saving"""
reply_to_message_id: Optional[int] = None
"""If the message is a reply, ID of the original message"""
allow_sending_without_reply: Optional[bool] = None

View file

@ -48,6 +48,8 @@ class SendAudio(TelegramMethod[Message]):
"""Thumbnail of the file sent; can be ignored if thumbnail generation for the file is supported server-side. The thumbnail should be in JPEG format and less than 200 kB in size. A thumbnail's width and height should not exceed 320. Ignored if the file is not uploaded using multipart/form-data. Thumbnails can't be reused and can be only uploaded as a new file, so you can pass 'attach://<file_attach_name>' if the thumbnail was uploaded using multipart/form-data under <file_attach_name>. :ref:`More info on Sending Files » <sending-files>`"""
disable_notification: Optional[bool] = None
"""Sends the message `silently <https://telegram.org/blog/channels-2-0#silent-messages>`_. Users will receive a notification with no sound."""
protect_content: Optional[bool] = None
"""Protects the contents of the sent message from forwarding and saving"""
reply_to_message_id: Optional[int] = None
"""If the message is a reply, ID of the original message"""
allow_sending_without_reply: Optional[bool] = None

View file

@ -36,6 +36,8 @@ class SendContact(TelegramMethod[Message]):
"""Additional data about the contact in the form of a `vCard <https://en.wikipedia.org/wiki/VCard>`_, 0-2048 bytes"""
disable_notification: Optional[bool] = None
"""Sends the message `silently <https://telegram.org/blog/channels-2-0#silent-messages>`_. Users will receive a notification with no sound."""
protect_content: Optional[bool] = None
"""Protects the contents of the sent message from forwarding and saving"""
reply_to_message_id: Optional[int] = None
"""If the message is a reply, ID of the original message"""
allow_sending_without_reply: Optional[bool] = None

View file

@ -30,6 +30,8 @@ class SendDice(TelegramMethod[Message]):
"""Emoji on which the dice throw animation is based. Currently, must be one of '🎲', '🎯', '🏀', '', '🎳', or '🎰'. Dice can have values 1-6 for '🎲', '🎯' and '🎳', values 1-5 for '🏀' and '', and values 1-64 for '🎰'. Defaults to '🎲'"""
disable_notification: Optional[bool] = None
"""Sends the message `silently <https://telegram.org/blog/channels-2-0#silent-messages>`_. Users will receive a notification with no sound."""
protect_content: Optional[bool] = None
"""Protects the contents of the sent message from forwarding"""
reply_to_message_id: Optional[int] = None
"""If the message is a reply, ID of the original message"""
allow_sending_without_reply: Optional[bool] = None

View file

@ -43,6 +43,8 @@ class SendDocument(TelegramMethod[Message]):
"""Disables automatic server-side content type detection for files uploaded using multipart/form-data"""
disable_notification: Optional[bool] = None
"""Sends the message `silently <https://telegram.org/blog/channels-2-0#silent-messages>`_. Users will receive a notification with no sound."""
protect_content: Optional[bool] = None
"""Protects the contents of the sent message from forwarding and saving"""
reply_to_message_id: Optional[int] = None
"""If the message is a reply, ID of the original message"""
allow_sending_without_reply: Optional[bool] = None

View file

@ -24,6 +24,8 @@ class SendGame(TelegramMethod[Message]):
"""Short name of the game, serves as the unique identifier for the game. Set up your games via `Botfather <https://t.me/botfather>`_."""
disable_notification: Optional[bool] = None
"""Sends the message `silently <https://telegram.org/blog/channels-2-0#silent-messages>`_. Users will receive a notification with no sound."""
protect_content: Optional[bool] = None
"""Protects the contents of the sent message from forwarding and saving"""
reply_to_message_id: Optional[int] = None
"""If the message is a reply, ID of the original message"""
allow_sending_without_reply: Optional[bool] = None

View file

@ -64,6 +64,8 @@ class SendInvoice(TelegramMethod[Message]):
"""Pass :code:`True`, if the final price depends on the shipping method"""
disable_notification: Optional[bool] = None
"""Sends the message `silently <https://telegram.org/blog/channels-2-0#silent-messages>`_. Users will receive a notification with no sound."""
protect_content: Optional[bool] = None
"""Protects the contents of the sent message from forwarding and saving"""
reply_to_message_id: Optional[int] = None
"""If the message is a reply, ID of the original message"""
allow_sending_without_reply: Optional[bool] = None

View file

@ -40,6 +40,8 @@ class SendLocation(TelegramMethod[Message]):
"""For live locations, a maximum distance for proximity alerts about approaching another chat member, in meters. Must be between 1 and 100000 if specified."""
disable_notification: Optional[bool] = None
"""Sends the message `silently <https://telegram.org/blog/channels-2-0#silent-messages>`_. Users will receive a notification with no sound."""
protect_content: Optional[bool] = None
"""Protects the contents of the sent message from forwarding and saving"""
reply_to_message_id: Optional[int] = None
"""If the message is a reply, ID of the original message"""
allow_sending_without_reply: Optional[bool] = None

View file

@ -31,6 +31,8 @@ class SendMediaGroup(TelegramMethod[List[Message]]):
"""A JSON-serialized array describing messages to be sent, must include 2-10 items"""
disable_notification: Optional[bool] = None
"""Sends messages `silently <https://telegram.org/blog/channels-2-0#silent-messages>`_. Users will receive a notification with no sound."""
protect_content: Optional[bool] = None
"""Protects the contents of the sent messages from forwarding and saving"""
reply_to_message_id: Optional[int] = None
"""If the messages are a reply, ID of the original message"""
allow_sending_without_reply: Optional[bool] = None

View file

@ -38,6 +38,8 @@ class SendMessage(TelegramMethod[Message]):
"""Disables link previews for links in this message"""
disable_notification: Optional[bool] = None
"""Sends the message `silently <https://telegram.org/blog/channels-2-0#silent-messages>`_. Users will receive a notification with no sound."""
protect_content: Optional[bool] = None
"""Protects the contents of the sent message from forwarding and saving"""
reply_to_message_id: Optional[int] = None
"""If the message is a reply, ID of the original message"""
allow_sending_without_reply: Optional[bool] = None

View file

@ -39,6 +39,8 @@ class SendPhoto(TelegramMethod[Message]):
"""A JSON-serialized list of special entities that appear in the caption, which can be specified instead of *parse_mode*"""
disable_notification: Optional[bool] = None
"""Sends the message `silently <https://telegram.org/blog/channels-2-0#silent-messages>`_. Users will receive a notification with no sound."""
protect_content: Optional[bool] = None
"""Protects the contents of the sent message from forwarding and saving"""
reply_to_message_id: Optional[int] = None
"""If the message is a reply, ID of the original message"""
allow_sending_without_reply: Optional[bool] = None

View file

@ -55,6 +55,8 @@ class SendPoll(TelegramMethod[Message]):
"""Pass :code:`True`, if the poll needs to be immediately closed. This can be useful for poll preview."""
disable_notification: Optional[bool] = None
"""Sends the message `silently <https://telegram.org/blog/channels-2-0#silent-messages>`_. Users will receive a notification with no sound."""
protect_content: Optional[bool] = None
"""Protects the contents of the sent message from forwarding and saving"""
reply_to_message_id: Optional[int] = None
"""If the message is a reply, ID of the original message"""
allow_sending_without_reply: Optional[bool] = None

View file

@ -31,6 +31,8 @@ class SendSticker(TelegramMethod[Message]):
"""Sticker to send. Pass a file_id as String to send a file that exists on the Telegram servers (recommended), pass an HTTP URL as a String for Telegram to get a .WEBP file from the Internet, or upload a new one using multipart/form-data. :ref:`More info on Sending Files » <sending-files>`"""
disable_notification: Optional[bool] = None
"""Sends the message `silently <https://telegram.org/blog/channels-2-0#silent-messages>`_. Users will receive a notification with no sound."""
protect_content: Optional[bool] = None
"""Protects the contents of the sent message from forwarding and saving"""
reply_to_message_id: Optional[int] = None
"""If the message is a reply, ID of the original message"""
allow_sending_without_reply: Optional[bool] = None

View file

@ -44,6 +44,8 @@ class SendVenue(TelegramMethod[Message]):
"""Google Places type of the venue. (See `supported types <https://developers.google.com/places/web-service/supported_types>`_.)"""
disable_notification: Optional[bool] = None
"""Sends the message `silently <https://telegram.org/blog/channels-2-0#silent-messages>`_. Users will receive a notification with no sound."""
protect_content: Optional[bool] = None
"""Protects the contents of the sent message from forwarding and saving"""
reply_to_message_id: Optional[int] = None
"""If the message is a reply, ID of the original message"""
allow_sending_without_reply: Optional[bool] = None

View file

@ -49,6 +49,8 @@ class SendVideo(TelegramMethod[Message]):
"""Pass :code:`True`, if the uploaded video is suitable for streaming"""
disable_notification: Optional[bool] = None
"""Sends the message `silently <https://telegram.org/blog/channels-2-0#silent-messages>`_. Users will receive a notification with no sound."""
protect_content: Optional[bool] = None
"""Protects the contents of the sent message from forwarding and saving"""
reply_to_message_id: Optional[int] = None
"""If the message is a reply, ID of the original message"""
allow_sending_without_reply: Optional[bool] = None

View file

@ -37,6 +37,8 @@ class SendVideoNote(TelegramMethod[Message]):
"""Thumbnail of the file sent; can be ignored if thumbnail generation for the file is supported server-side. The thumbnail should be in JPEG format and less than 200 kB in size. A thumbnail's width and height should not exceed 320. Ignored if the file is not uploaded using multipart/form-data. Thumbnails can't be reused and can be only uploaded as a new file, so you can pass 'attach://<file_attach_name>' if the thumbnail was uploaded using multipart/form-data under <file_attach_name>. :ref:`More info on Sending Files » <sending-files>`"""
disable_notification: Optional[bool] = None
"""Sends the message `silently <https://telegram.org/blog/channels-2-0#silent-messages>`_. Users will receive a notification with no sound."""
protect_content: Optional[bool] = None
"""Protects the contents of the sent message from forwarding and saving"""
reply_to_message_id: Optional[int] = None
"""If the message is a reply, ID of the original message"""
allow_sending_without_reply: Optional[bool] = None

View file

@ -41,6 +41,8 @@ class SendVoice(TelegramMethod[Message]):
"""Duration of the voice message in seconds"""
disable_notification: Optional[bool] = None
"""Sends the message `silently <https://telegram.org/blog/channels-2-0#silent-messages>`_. Users will receive a notification with no sound."""
protect_content: Optional[bool] = None
"""Protects the contents of the sent message from forwarding and saving"""
reply_to_message_id: Optional[int] = None
"""If the message is a reply, ID of the original message"""
allow_sending_without_reply: Optional[bool] = None

View file

@ -11,6 +11,6 @@ class BotCommand(MutableTelegramObject):
"""
command: str
"""Text of the command, 1-32 characters. Can contain only lowercase English letters, digits and underscores."""
"""Text of the command; 1-32 characters. Can contain only lowercase English letters, digits and underscores."""
description: str
"""Description of the command, 3-256 characters."""
"""Description of the command; 1-256 characters."""

View file

@ -95,7 +95,7 @@ class Message(TelegramObject):
forward_from_message_id: Optional[int] = None
"""*Optional*. For messages forwarded from channels, identifier of the original message in the channel"""
forward_signature: Optional[str] = None
"""*Optional*. For messages forwarded from channels, signature of the post author if present"""
"""*Optional*. For forwarded messages that were originally sent in channels or by an anonymous chat administrator, signature of the message sender if present"""
forward_sender_name: Optional[str] = None
"""*Optional*. Sender's name for messages forwarded from users who disallow adding a link to their account in forwarded messages"""
forward_date: Optional[int] = None

View file

@ -18,7 +18,7 @@ class MessageEntity(MutableTelegramObject):
"""
type: str
"""Type of the entity. Can be 'mention' (:code:`@username`), 'hashtag' (:code:`#hashtag`), 'cashtag' (:code:`$USD`), 'bot_command' (:code:`/start@jobs_bot`), 'url' (:code:`https://telegram.org`), 'email' (:code:`do-not-reply@telegram.org`), 'phone_number' (:code:`+1-212-555-0123`), 'bold' (**bold text**), 'italic' (*italic text*), 'underline' (underlined text), 'strikethrough' (strikethrough text), 'code' (monowidth string), 'pre' (monowidth block), 'text_link' (for clickable text URLs), 'text_mention' (for users `without usernames <https://telegram.org/blog/edit#new-mentions>`_)"""
"""Type of the entity. Currently, can be 'mention' (:code:`@username`), 'hashtag' (:code:`#hashtag`), 'cashtag' (:code:`$USD`), 'bot_command' (:code:`/start@jobs_bot`), 'url' (:code:`https://telegram.org`), 'email' (:code:`do-not-reply@telegram.org`), 'phone_number' (:code:`+1-212-555-0123`), 'bold' (**bold text**), 'italic' (*italic text*), 'underline' (underlined text), 'strikethrough' (strikethrough text), 'spoiler' (spoiler message), 'code' (monowidth string), 'pre' (monowidth block), 'text_link' (for clickable text URLs), 'text_mention' (for users `without usernames <https://telegram.org/blog/edit#new-mentions>`_)"""
offset: int
"""Offset in UTF-16 code units to the start of the entity"""
length: int

View file

@ -0,0 +1,298 @@
import asyncio
import logging
import time
from asyncio import Event, Lock
from contextlib import suppress
from types import TracebackType
from typing import Any, Awaitable, Callable, Dict, Optional, Type, Union
from aiogram import BaseMiddleware, Bot
from aiogram.dispatcher.flags.getter import get_flag
from aiogram.types import Message, TelegramObject
logger = logging.getLogger(__name__)
DEFAULT_INTERVAL = 5.0
DEFAULT_INITIAL_SLEEP = 0.1
class ChatActionSender:
def __init__(
self,
*,
chat_id: Union[str, int],
action: str = "typing",
interval: float = DEFAULT_INTERVAL,
initial_sleep: float = DEFAULT_INTERVAL,
bot: Optional[Bot] = None,
) -> None:
if bot is None:
bot = Bot.get_current(False)
self.chat_id = chat_id
self.action = action
self.interval = interval
self.initial_sleep = initial_sleep
self.bot = bot
self._close_event = Event()
self._running = False
self._lock = Lock()
async def _worker(self) -> None:
logger.debug(
"Started chat action %r sender in chat_id=%s via bot id=%d",
self.action,
self.chat_id,
self.bot.id,
)
try:
counter = 0
await asyncio.sleep(self.initial_sleep)
while not self._close_event.is_set():
start = time.monotonic()
logger.debug(
"Sent chat action %r to chat_id=%s via bot %d (already sent actions %d)",
self.action,
self.chat_id,
self.bot.id,
counter,
)
await self.bot.send_chat_action(chat_id=self.chat_id, action=self.action)
counter += 1
interval = self.interval - (time.monotonic() - start)
with suppress(asyncio.TimeoutError):
await asyncio.wait_for(self._close_event.wait(), interval)
finally:
logger.debug(
"Finished chat action %r sender in chat_id=%s via bot id=%d",
self.action,
self.chat_id,
self.bot.id,
)
self._running = False
async def _run(self) -> None:
async with self._lock:
if self._running:
raise RuntimeError("Already running")
asyncio.create_task(self._worker())
def _stop(self) -> None:
if not self._close_event.is_set():
self._close_event.set()
async def __aenter__(self) -> "ChatActionSender":
await self._run()
return self
async def __aexit__(
self,
exc_type: Optional[Type[BaseException]],
exc_value: Optional[BaseException],
traceback: Optional[TracebackType],
) -> Any:
self._stop()
@classmethod
def typing(
cls,
bot: Bot,
chat_id: Union[int, str],
interval: float = DEFAULT_INTERVAL,
initial_sleep: float = DEFAULT_INITIAL_SLEEP,
) -> "ChatActionSender":
return cls(
bot=bot,
chat_id=chat_id,
action="typing",
interval=interval,
initial_sleep=initial_sleep,
)
@classmethod
def upload_photo(
cls,
bot: Bot,
chat_id: Union[int, str],
interval: float = DEFAULT_INTERVAL,
initial_sleep: float = DEFAULT_INITIAL_SLEEP,
) -> "ChatActionSender":
return cls(
bot=bot,
chat_id=chat_id,
action="upload_photo",
interval=interval,
initial_sleep=initial_sleep,
)
@classmethod
def record_video(
cls,
bot: Bot,
chat_id: Union[int, str],
interval: float = DEFAULT_INTERVAL,
initial_sleep: float = DEFAULT_INITIAL_SLEEP,
) -> "ChatActionSender":
return cls(
bot=bot,
chat_id=chat_id,
action="record_video",
interval=interval,
initial_sleep=initial_sleep,
)
@classmethod
def upload_video(
cls,
bot: Bot,
chat_id: Union[int, str],
interval: float = DEFAULT_INTERVAL,
initial_sleep: float = DEFAULT_INITIAL_SLEEP,
) -> "ChatActionSender":
return cls(
bot=bot,
chat_id=chat_id,
action="upload_video",
interval=interval,
initial_sleep=initial_sleep,
)
@classmethod
def record_voice(
cls,
bot: Bot,
chat_id: Union[int, str],
interval: float = DEFAULT_INTERVAL,
initial_sleep: float = DEFAULT_INITIAL_SLEEP,
) -> "ChatActionSender":
return cls(
bot=bot,
chat_id=chat_id,
action="record_voice",
interval=interval,
initial_sleep=initial_sleep,
)
@classmethod
def upload_voice(
cls,
bot: Bot,
chat_id: Union[int, str],
interval: float = DEFAULT_INTERVAL,
initial_sleep: float = DEFAULT_INITIAL_SLEEP,
) -> "ChatActionSender":
return cls(
bot=bot,
chat_id=chat_id,
action="upload_voice",
interval=interval,
initial_sleep=initial_sleep,
)
@classmethod
def upload_document(
cls,
bot: Bot,
chat_id: Union[int, str],
interval: float = DEFAULT_INTERVAL,
initial_sleep: float = DEFAULT_INITIAL_SLEEP,
) -> "ChatActionSender":
return cls(
bot=bot,
chat_id=chat_id,
action="upload_document",
interval=interval,
initial_sleep=initial_sleep,
)
@classmethod
def choose_sticker(
cls,
bot: Bot,
chat_id: Union[int, str],
interval: float = DEFAULT_INTERVAL,
initial_sleep: float = DEFAULT_INITIAL_SLEEP,
) -> "ChatActionSender":
return cls(
bot=bot,
chat_id=chat_id,
action="choose_sticker",
interval=interval,
initial_sleep=initial_sleep,
)
@classmethod
def find_location(
cls,
bot: Bot,
chat_id: Union[int, str],
interval: float = DEFAULT_INTERVAL,
initial_sleep: float = DEFAULT_INITIAL_SLEEP,
) -> "ChatActionSender":
return cls(
bot=bot,
chat_id=chat_id,
action="find_location",
interval=interval,
initial_sleep=initial_sleep,
)
@classmethod
def record_video_note(
cls,
bot: Bot,
chat_id: Union[int, str],
interval: float = DEFAULT_INTERVAL,
initial_sleep: float = DEFAULT_INITIAL_SLEEP,
) -> "ChatActionSender":
return cls(
bot=bot,
chat_id=chat_id,
action="record_video_note",
interval=interval,
initial_sleep=initial_sleep,
)
@classmethod
def upload_video_note(
cls,
bot: Bot,
chat_id: Union[int, str],
interval: float = DEFAULT_INTERVAL,
initial_sleep: float = DEFAULT_INITIAL_SLEEP,
) -> "ChatActionSender":
return cls(
bot=bot,
chat_id=chat_id,
action="upload_video_note",
interval=interval,
initial_sleep=initial_sleep,
)
class ChatActionMiddleware(BaseMiddleware):
async def __call__(
self,
handler: Callable[[TelegramObject, Dict[str, Any]], Awaitable[Any]],
event: TelegramObject,
data: Dict[str, Any],
) -> Any:
if not isinstance(event, Message):
return await handler(event, data)
bot = data["bot"]
chat_action = get_flag(data, "chat_action")
kwargs = {}
if isinstance(chat_action, dict):
if initial_sleep := chat_action.get("initial_sleep"):
kwargs["initial_sleep"] = initial_sleep
if interval := chat_action.get("interval"):
kwargs["interval"] = interval
if action := chat_action.get("action"):
kwargs["action"] = action
elif isinstance(chat_action, bool):
kwargs["action"] = "typing"
else:
kwargs["action"] = chat_action
async with ChatActionSender(bot=bot, chat_id=event.chat.id, **kwargs):
return await handler(event, data)

View file

@ -2,10 +2,14 @@ from abc import ABC, abstractmethod
from typing import Any, Awaitable, Callable, Dict, Optional, Set, cast
try:
from babel import Locale
from babel import Locale, UnknownLocaleError
except ImportError: # pragma: no cover
Locale = None
class UnknownLocaleError(Exception): # type: ignore
pass
from aiogram import BaseMiddleware, Router
from aiogram.dispatcher.fsm.context import FSMContext
from aiogram.types import TelegramObject, User
@ -116,7 +120,11 @@ class SimpleI18nMiddleware(I18nMiddleware):
event_from_user: Optional[User] = data.get("event_from_user", None)
if event_from_user is None:
return self.i18n.default_locale
locale = Locale.parse(event_from_user.language_code, sep="-")
try:
locale = Locale.parse(event_from_user.language_code, sep="-")
except UnknownLocaleError:
return self.i18n.default_locale
if locale.language not in self.i18n.available_locales:
return self.i18n.default_locale
return cast(str, locale.language)

46
poetry.lock generated
View file

@ -212,7 +212,7 @@ name = "certifi"
version = "2021.10.8"
description = "Python package for providing Mozilla's CA Bundle."
category = "main"
optional = true
optional = false
python-versions = "*"
[[package]]
@ -917,7 +917,7 @@ pytest = ">=3.5"
[[package]]
name = "python-socks"
version = "2.0.0"
version = "2.0.2"
description = "Core proxy (SOCKS4, SOCKS5, HTTP tunneling) functionality for Python"
category = "main"
optional = true
@ -974,6 +974,36 @@ urllib3 = ">=1.21.1,<1.27"
socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"]
use_chardet_on_py3 = ["chardet (>=3.0.2,<5)"]
[[package]]
name = "sentry-sdk"
version = "1.5.3"
description = "Python client for Sentry (https://sentry.io)"
category = "dev"
optional = false
python-versions = "*"
[package.dependencies]
certifi = "*"
urllib3 = ">=1.10.0"
[package.extras]
aiohttp = ["aiohttp (>=3.5)"]
beam = ["apache-beam (>=2.12)"]
bottle = ["bottle (>=0.12.13)"]
celery = ["celery (>=3)"]
chalice = ["chalice (>=1.16.0)"]
django = ["django (>=1.8)"]
falcon = ["falcon (>=1.4)"]
flask = ["flask (>=0.11)", "blinker (>=1.1)"]
httpx = ["httpx (>=0.16.0)"]
pure_eval = ["pure-eval", "executing", "asttokens"]
pyspark = ["pyspark (>=2.4.4)"]
quart = ["quart (>=0.16.1)", "blinker (>=1.1)"]
rq = ["rq (>=0.6)"]
sanic = ["sanic (>=0.8)"]
sqlalchemy = ["sqlalchemy (>=1.2)"]
tornado = ["tornado (>=5)"]
[[package]]
name = "six"
version = "1.16.0"
@ -1242,7 +1272,7 @@ name = "urllib3"
version = "1.26.7"
description = "HTTP library with thread-safe connection pooling, file post, and more."
category = "main"
optional = true
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4"
[package.extras]
@ -1324,7 +1354,7 @@ redis = ["aioredis"]
[metadata]
lock-version = "1.1"
python-versions = "^3.8"
content-hash = "27d602728b6dab256d184fbd44953030676edf320652ad828c6e1b4beaa80f8b"
content-hash = "678dbb9bbf362c01757842a977f7b0f0f65638a657e3599b6125f0f4e42f4b6c"
[metadata.files]
aiofiles = [
@ -2003,8 +2033,8 @@ pytest-mypy = [
{file = "pytest_mypy-0.8.1-py3-none-any.whl", hash = "sha256:6e68e8eb7ceeb7d1c83a1590912f784879f037b51adfb9c17b95c6b2fc57466b"},
]
python-socks = [
{file = "python-socks-2.0.0.tar.gz", hash = "sha256:7944dad882846ac73e5f79e180c841e3895ee058e16855b7e8fff24f4cd0b90b"},
{file = "python_socks-2.0.0-py3-none-any.whl", hash = "sha256:aac65671cbd3b0eb55b20f8558c8de3894a315536aaab3ec0a7b9d46ff89c1bf"},
{file = "python-socks-2.0.2.tar.gz", hash = "sha256:aa9b7a53e81ae6b6e3ada602761012e470ea1c4cbcd5548f99b3fc102dce4fca"},
{file = "python_socks-2.0.2-py3-none-any.whl", hash = "sha256:faa46857c79a8bf7def2e904ac839fb56755d7ab76c4cad12a131a85fec07241"},
]
pytz = [
{file = "pytz-2021.3-py2.py3-none-any.whl", hash = "sha256:3672058bc3453457b622aab7a1c3bfd5ab0bdae451512f6cf25f64ed37f5b87c"},
@ -2100,6 +2130,10 @@ requests = [
{file = "requests-2.26.0-py2.py3-none-any.whl", hash = "sha256:6c1246513ecd5ecd4528a0906f910e8f0f9c6b8ec72030dc9fd154dc1a6efd24"},
{file = "requests-2.26.0.tar.gz", hash = "sha256:b8aa58f8cf793ffd8782d3d8cb19e66ef36f7aba4353eec859e74678b01b07a7"},
]
sentry-sdk = [
{file = "sentry-sdk-1.5.3.tar.gz", hash = "sha256:141da032f0fa4c56f9af6b361fda57360af1789576285bd1944561f9c274f9c0"},
{file = "sentry_sdk-1.5.3-py2.py3-none-any.whl", hash = "sha256:9aeff2a47f4038460296b920bf4d269284e8454e1c67547ee002ccafd9c2442b"},
]
six = [
{file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
{file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},

View file

@ -84,6 +84,7 @@ toml = "^0.10.2"
pre-commit = "^2.15.0"
packaging = "^20.3"
typing-extensions = "^3.7.4"
sentry-sdk = "^1.5.3"
[tool.poetry.extras]

View file

@ -1,5 +1,3 @@
from typing import Literal
import pytest
from aiogram.dispatcher.fsm.storage.base import DEFAULT_DESTINY, StorageKey