From eff414b9e54374dd8d55ad6f81aed8668f7a4134 Mon Sep 17 00:00:00 2001 From: Alex Root Junior Date: Wed, 5 May 2021 05:43:55 +0300 Subject: [PATCH] Bump Bot API 5.2. Added integration with MagicFilter --- aiogram/client/bot.py | 25 +++++--- aiogram/dispatcher/event/handler.py | 12 +++- aiogram/methods/copy_message.py | 2 +- aiogram/methods/forward_message.py | 2 +- aiogram/methods/promote_chat_member.py | 2 +- aiogram/methods/send_invoice.py | 14 +++-- aiogram/types/__init__.py | 4 ++ aiogram/types/inline_query.py | 2 + aiogram/types/inline_query_result.py | 2 +- .../types/input_invoice_message_content.py | 57 +++++++++++++++++++ aiogram/types/input_message_content.py | 3 +- aiogram/types/message.py | 3 + aiogram/types/message_entity.py | 15 +++++ aiogram/types/voice_chat_scheduled.py | 19 +++++++ aiogram/utils/text_decorations.py | 22 +++---- docs2/api/types/index.rst | 2 + .../types/input_invoice_message_content.rst | 9 +++ docs2/api/types/voice_chat_scheduled.rst | 9 +++ poetry.lock | 48 ++++++++-------- pyproject.toml | 18 +++--- 20 files changed, 208 insertions(+), 62 deletions(-) create mode 100644 aiogram/types/input_invoice_message_content.py create mode 100644 aiogram/types/voice_chat_scheduled.py create mode 100644 docs2/api/types/input_invoice_message_content.rst create mode 100644 docs2/api/types/voice_chat_scheduled.rst diff --git a/aiogram/client/bot.py b/aiogram/client/bot.py index 5db8feaa..19fe7838 100644 --- a/aiogram/client/bot.py +++ b/aiogram/client/bot.py @@ -283,6 +283,9 @@ class Bot(ContextInstanceMixin["Bot"]): raise TypeError("file can only be of the string or Downloadable type") file_ = await self.get_file(file_id) + + # `file_path` can be None for large files but this files can't be downloaded + # So we need to do type-cast # https://github.com/aiogram/aiogram/pull/282/files#r394110017 file_path = cast(str, file_.file_path) @@ -543,7 +546,7 @@ class Bot(ContextInstanceMixin["Bot"]): request_timeout: Optional[int] = None, ) -> Message: """ - Use this method to forward messages of any kind. On success, the sent :class:`aiogram.types.message.Message` is returned. + Use this method to forward messages of any kind. Service messages can't be forwarded. On success, the sent :class:`aiogram.types.message.Message` is returned. Source: https://core.telegram.org/bots/api#forwardmessage @@ -579,7 +582,7 @@ class Bot(ContextInstanceMixin["Bot"]): request_timeout: Optional[int] = None, ) -> MessageId: """ - Use this method to copy messages of any kind. The method is analogous to the method :class:`aiogram.methods.forward_message.ForwardMessage`, but the copied message doesn't have a link to the original message. Returns the :class:`aiogram.types.message_id.MessageId` of the sent message on success. + Use this method to copy messages of any kind. Service messages and invoice messages can't be copied. The method is analogous to the method :class:`aiogram.methods.forward_message.ForwardMessage`, but the copied message doesn't have a link to the original message. Returns the :class:`aiogram.types.message_id.MessageId` of the sent message on success. Source: https://core.telegram.org/bots/api#copymessage @@ -1519,7 +1522,7 @@ class Bot(ContextInstanceMixin["Bot"]): :param can_post_messages: Pass True, if the administrator can create channel posts, channels only :param can_edit_messages: Pass True, if the administrator can edit messages of other users and can pin messages, channels only :param can_delete_messages: Pass True, if the administrator can delete messages of other users - :param can_manage_voice_chats: Pass True, if the administrator can manage voice chats, supergroups only + :param can_manage_voice_chats: Pass True, if the administrator can manage voice chats :param can_restrict_members: Pass True, if the administrator can restrict, ban or unban chat members :param can_promote_members: Pass True, if the administrator can add new administrators with a subset of their own privileges or demote administrators that he has promoted, directly or indirectly (promoted by administrators that were appointed by him) :param can_change_info: Pass True, if the administrator can change chat title, photo and other settings @@ -2531,14 +2534,16 @@ class Bot(ContextInstanceMixin["Bot"]): async def send_invoice( self, - chat_id: int, + chat_id: Union[int, str], title: str, description: str, payload: str, provider_token: str, - start_parameter: str, currency: str, prices: List[LabeledPrice], + max_tip_amount: Optional[int] = None, + suggested_tip_amounts: Optional[List[int]] = None, + start_parameter: Optional[str] = None, provider_data: Optional[str] = None, photo_url: Optional[str] = None, photo_size: Optional[int] = None, @@ -2562,14 +2567,16 @@ class Bot(ContextInstanceMixin["Bot"]): Source: https://core.telegram.org/bots/api#sendinvoice - :param chat_id: Unique identifier for the target private chat + :param chat_id: Unique identifier for the target chat or username of the target channel (in the format :code:`@channelusername`) :param title: Product name, 1-32 characters :param description: Product description, 1-255 characters :param payload: Bot-defined invoice payload, 1-128 bytes. This will not be displayed to the user, use for your internal processes. :param provider_token: Payments provider token, obtained via `Botfather `_ - :param start_parameter: Unique deep-linking parameter that can be used to generate this invoice when used as a start parameter :param currency: Three-letter ISO 4217 currency code, see `more on currencies `_ :param prices: Price breakdown, a JSON-serialized list of components (e.g. product price, tax, discount, delivery cost, delivery tax, bonus, etc.) + :param max_tip_amount: The maximum accepted amount for tips in the *smallest units* of the currency (integer, **not** float/double). For example, for a maximum tip of :code:`US$ 1.45` pass :code:`max_tip_amount = 145`. See the *exp* parameter in `currencies.json `_, it shows the number of digits past the decimal point for each currency (2 for the majority of currencies). Defaults to 0 + :param suggested_tip_amounts: A JSON-serialized array of suggested amounts of tips in the *smallest units* of the currency (integer, **not** float/double). At most 4 suggested tip amounts can be specified. The suggested tip amounts must be positive, passed in a strictly increased order and must not exceed *max_tip_amount*. + :param start_parameter: Unique deep-linking parameter. If left empty, **forwarded copies** of the sent message will have a *Pay* button, allowing multiple users to pay directly from the forwarded message, using the same invoice. If non-empty, forwarded copies of the sent message will have a *URL* button with a deep link to the bot (instead of a *Pay* button), with the value used as the start parameter :param provider_data: A JSON-serialized data about the invoice, which will be shared with the payment provider. A detailed description of required fields should be provided by the payment provider. :param photo_url: URL of the product photo for the invoice. Can be a photo of the goods or a marketing image for a service. People like it better when they see what they are paying for. :param photo_size: Photo size @@ -2595,9 +2602,11 @@ class Bot(ContextInstanceMixin["Bot"]): description=description, payload=payload, provider_token=provider_token, - start_parameter=start_parameter, currency=currency, prices=prices, + max_tip_amount=max_tip_amount, + suggested_tip_amounts=suggested_tip_amounts, + start_parameter=start_parameter, provider_data=provider_data, photo_url=photo_url, photo_size=photo_size, diff --git a/aiogram/dispatcher/event/handler.py b/aiogram/dispatcher/event/handler.py index bc7ee10d..63f5130b 100644 --- a/aiogram/dispatcher/event/handler.py +++ b/aiogram/dispatcher/event/handler.py @@ -5,13 +5,15 @@ from dataclasses import dataclass, field from functools import partial from typing import Any, Awaitable, Callable, Dict, List, Optional, Tuple, Type, Union +from magic_filter import MagicFilter + from aiogram.dispatcher.filters.base import BaseFilter from aiogram.dispatcher.handler.base import BaseHandler CallbackType = Callable[..., Awaitable[Any]] SyncFilter = Callable[..., Any] AsyncFilter = Callable[..., Awaitable[Any]] -FilterType = Union[SyncFilter, AsyncFilter, BaseFilter] +FilterType = Union[SyncFilter, AsyncFilter, BaseFilter, MagicFilter] HandlerType = Union[FilterType, Type[BaseHandler]] @@ -47,6 +49,14 @@ class CallableMixin: class FilterObject(CallableMixin): callback: FilterType + def __post_init__(self) -> None: + # TODO: Make possibility to extract and explain magic from filter object. + # Current solution is hard for debugging because the MagicFilter instance can't be extracted + if isinstance(self.callback, MagicFilter): + # MagicFilter instance is callable but generates only "CallOperation" instead of applying the filter + self.callback = self.callback.resolve + super().__post_init__() + @dataclass class HandlerObject(CallableMixin): diff --git a/aiogram/methods/copy_message.py b/aiogram/methods/copy_message.py index 443ac298..be94e4cc 100644 --- a/aiogram/methods/copy_message.py +++ b/aiogram/methods/copy_message.py @@ -19,7 +19,7 @@ if TYPE_CHECKING: # pragma: no cover class CopyMessage(TelegramMethod[MessageId]): """ - Use this method to copy messages of any kind. The method is analogous to the method :class:`aiogram.methods.forward_message.ForwardMessage`, but the copied message doesn't have a link to the original message. Returns the :class:`aiogram.types.message_id.MessageId` of the sent message on success. + Use this method to copy messages of any kind. Service messages and invoice messages can't be copied. The method is analogous to the method :class:`aiogram.methods.forward_message.ForwardMessage`, but the copied message doesn't have a link to the original message. Returns the :class:`aiogram.types.message_id.MessageId` of the sent message on success. Source: https://core.telegram.org/bots/api#copymessage """ diff --git a/aiogram/methods/forward_message.py b/aiogram/methods/forward_message.py index 24b68919..e18e4cf6 100644 --- a/aiogram/methods/forward_message.py +++ b/aiogram/methods/forward_message.py @@ -11,7 +11,7 @@ if TYPE_CHECKING: # pragma: no cover class ForwardMessage(TelegramMethod[Message]): """ - Use this method to forward messages of any kind. On success, the sent :class:`aiogram.types.message.Message` is returned. + Use this method to forward messages of any kind. Service messages can't be forwarded. On success, the sent :class:`aiogram.types.message.Message` is returned. Source: https://core.telegram.org/bots/api#forwardmessage """ diff --git a/aiogram/methods/promote_chat_member.py b/aiogram/methods/promote_chat_member.py index 236d3283..22347dcd 100644 --- a/aiogram/methods/promote_chat_member.py +++ b/aiogram/methods/promote_chat_member.py @@ -32,7 +32,7 @@ class PromoteChatMember(TelegramMethod[bool]): can_delete_messages: Optional[bool] = None """Pass True, if the administrator can delete messages of other users""" can_manage_voice_chats: Optional[bool] = None - """Pass True, if the administrator can manage voice chats, supergroups only""" + """Pass True, if the administrator can manage voice chats""" can_restrict_members: Optional[bool] = None """Pass True, if the administrator can restrict, ban or unban chat members""" can_promote_members: Optional[bool] = None diff --git a/aiogram/methods/send_invoice.py b/aiogram/methods/send_invoice.py index 99bacb6c..97d569ea 100644 --- a/aiogram/methods/send_invoice.py +++ b/aiogram/methods/send_invoice.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Any, Dict, List, Optional +from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union from ..types import InlineKeyboardMarkup, LabeledPrice, Message from .base import Request, TelegramMethod @@ -18,8 +18,8 @@ class SendInvoice(TelegramMethod[Message]): __returning__ = Message - chat_id: int - """Unique identifier for the target private chat""" + chat_id: Union[int, str] + """Unique identifier for the target chat or username of the target channel (in the format :code:`@channelusername`)""" title: str """Product name, 1-32 characters""" description: str @@ -28,12 +28,16 @@ class SendInvoice(TelegramMethod[Message]): """Bot-defined invoice payload, 1-128 bytes. This will not be displayed to the user, use for your internal processes.""" provider_token: str """Payments provider token, obtained via `Botfather `_""" - start_parameter: str - """Unique deep-linking parameter that can be used to generate this invoice when used as a start parameter""" currency: str """Three-letter ISO 4217 currency code, see `more on currencies `_""" prices: List[LabeledPrice] """Price breakdown, a JSON-serialized list of components (e.g. product price, tax, discount, delivery cost, delivery tax, bonus, etc.)""" + max_tip_amount: Optional[int] = None + """The maximum accepted amount for tips in the *smallest units* of the currency (integer, **not** float/double). For example, for a maximum tip of :code:`US$ 1.45` pass :code:`max_tip_amount = 145`. See the *exp* parameter in `currencies.json `_, it shows the number of digits past the decimal point for each currency (2 for the majority of currencies). Defaults to 0""" + suggested_tip_amounts: Optional[List[int]] = None + """A JSON-serialized array of suggested amounts of tips in the *smallest units* of the currency (integer, **not** float/double). At most 4 suggested tip amounts can be specified. The suggested tip amounts must be positive, passed in a strictly increased order and must not exceed *max_tip_amount*.""" + start_parameter: Optional[str] = None + """Unique deep-linking parameter. If left empty, **forwarded copies** of the sent message will have a *Pay* button, allowing multiple users to pay directly from the forwarded message, using the same invoice. If non-empty, forwarded copies of the sent message will have a *URL* button with a deep link to the bot (instead of a *Pay* button), with the value used as the start parameter""" provider_data: Optional[str] = None """A JSON-serialized data about the invoice, which will be shared with the payment provider. A detailed description of required fields should be provided by the payment provider.""" photo_url: Optional[str] = None diff --git a/aiogram/types/__init__.py b/aiogram/types/__init__.py index 99944532..eb4c2c55 100644 --- a/aiogram/types/__init__.py +++ b/aiogram/types/__init__.py @@ -48,6 +48,7 @@ from .inline_query_result_video import InlineQueryResultVideo from .inline_query_result_voice import InlineQueryResultVoice from .input_contact_message_content import InputContactMessageContent from .input_file import BufferedInputFile, FSInputFile, InputFile, URLInputFile +from .input_invoice_message_content import InputInvoiceMessageContent from .input_location_message_content import InputLocationMessageContent from .input_media import InputMedia from .input_media_animation import InputMediaAnimation @@ -106,6 +107,7 @@ from .video_note import VideoNote from .voice import Voice from .voice_chat_ended import VoiceChatEnded from .voice_chat_participants_invited import VoiceChatParticipantsInvited +from .voice_chat_scheduled import VoiceChatScheduled from .voice_chat_started import VoiceChatStarted from .webhook_info import WebhookInfo @@ -140,6 +142,7 @@ __all__ = ( "Venue", "ProximityAlertTriggered", "MessageAutoDeleteTimerChanged", + "VoiceChatScheduled", "VoiceChatStarted", "VoiceChatEnded", "VoiceChatParticipantsInvited", @@ -199,6 +202,7 @@ __all__ = ( "InputLocationMessageContent", "InputVenueMessageContent", "InputContactMessageContent", + "InputInvoiceMessageContent", "ChosenInlineResult", "LabeledPrice", "Invoice", diff --git a/aiogram/types/inline_query.py b/aiogram/types/inline_query.py index 1389181b..60bb67d7 100644 --- a/aiogram/types/inline_query.py +++ b/aiogram/types/inline_query.py @@ -28,6 +28,8 @@ class InlineQuery(TelegramObject): """Text of the query (up to 256 characters)""" offset: str """Offset of the results to be returned, can be controlled by the bot""" + chat_type: Optional[str] = None + """*Optional*. Type of the chat, from which the inline query was sent. Can be either 'sender' for a private chat with the inline query sender, 'private', 'group', 'supergroup', or 'channel'. The chat type should be always known for requests sent from official clients and most third-party clients, unless the request was sent from a secret chat""" location: Optional[Location] = None """*Optional*. Sender location, only for bots that request user location""" diff --git a/aiogram/types/inline_query_result.py b/aiogram/types/inline_query_result.py index 246552b3..b615aa2b 100644 --- a/aiogram/types/inline_query_result.py +++ b/aiogram/types/inline_query_result.py @@ -28,7 +28,7 @@ class InlineQueryResult(MutableTelegramObject): - :class:`aiogram.types.inline_query_result_video.InlineQueryResultVideo` - :class:`aiogram.types.inline_query_result_voice.InlineQueryResultVoice` - **Note:** All URLs passed in inline query results will be available to end users and therefore must be assumed to be public. + **Note:** All URLs passed in inline query results will be available to end users and therefore must be assumed to be **public**. Source: https://core.telegram.org/bots/api#inlinequeryresult """ diff --git a/aiogram/types/input_invoice_message_content.py b/aiogram/types/input_invoice_message_content.py new file mode 100644 index 00000000..b001bfdf --- /dev/null +++ b/aiogram/types/input_invoice_message_content.py @@ -0,0 +1,57 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, List, Optional + +from .input_message_content import InputMessageContent + +if TYPE_CHECKING: # pragma: no cover + from .labeled_price import LabeledPrice + + +class InputInvoiceMessageContent(InputMessageContent): + """ + Represents the `content `_ of an invoice message to be sent as the result of an inline query. + + Source: https://core.telegram.org/bots/api#inputinvoicemessagecontent + """ + + title: str + """Product name, 1-32 characters""" + description: str + """Product description, 1-255 characters""" + payload: str + """Bot-defined invoice payload, 1-128 bytes. This will not be displayed to the user, use for your internal processes.""" + provider_token: str + """Payment provider token, obtained via `Botfather `_""" + currency: str + """Three-letter ISO 4217 currency code, see `more on currencies `_""" + prices: List[LabeledPrice] + """Price breakdown, a JSON-serialized list of components (e.g. product price, tax, discount, delivery cost, delivery tax, bonus, etc.)""" + max_tip_amount: Optional[int] = None + """*Optional*. The maximum accepted amount for tips in the *smallest units* of the currency (integer, **not** float/double). For example, for a maximum tip of :code:`US$ 1.45` pass :code:`max_tip_amount = 145`. See the *exp* parameter in `currencies.json `_, it shows the number of digits past the decimal point for each currency (2 for the majority of currencies). Defaults to 0""" + suggested_tip_amounts: Optional[List[int]] = None + """*Optional*. A JSON-serialized array of suggested amounts of tip in the *smallest units* of the currency (integer, **not** float/double). At most 4 suggested tip amounts can be specified. The suggested tip amounts must be positive, passed in a strictly increased order and must not exceed *max_tip_amount*.""" + provider_data: Optional[str] = None + """*Optional*. A JSON-serialized object for data about the invoice, which will be shared with the payment provider. A detailed description of the required fields should be provided by the payment provider.""" + photo_url: Optional[str] = None + """*Optional*. URL of the product photo for the invoice. Can be a photo of the goods or a marketing image for a service. People like it better when they see what they are paying for.""" + photo_size: Optional[int] = None + """*Optional*. Photo size""" + photo_width: Optional[int] = None + """*Optional*. Photo width""" + photo_height: Optional[int] = None + """*Optional*. Photo height""" + need_name: Optional[bool] = None + """*Optional*. Pass :code:`True`, if you require the user's full name to complete the order""" + need_phone_number: Optional[bool] = None + """*Optional*. Pass :code:`True`, if you require the user's phone number to complete the order""" + need_email: Optional[bool] = None + """*Optional*. Pass :code:`True`, if you require the user's email address to complete the order""" + need_shipping_address: Optional[bool] = None + """*Optional*. Pass :code:`True`, if you require the user's shipping address to complete the order""" + send_phone_number_to_provider: Optional[bool] = None + """*Optional*. Pass :code:`True`, if user's phone number should be sent to provider""" + send_email_to_provider: Optional[bool] = None + """*Optional*. Pass :code:`True`, if user's email address should be sent to provider""" + is_flexible: Optional[bool] = None + """*Optional*. Pass :code:`True`, if the final price depends on the shipping method""" diff --git a/aiogram/types/input_message_content.py b/aiogram/types/input_message_content.py index b763dd92..4d26d4f0 100644 --- a/aiogram/types/input_message_content.py +++ b/aiogram/types/input_message_content.py @@ -5,12 +5,13 @@ from .base import TelegramObject class InputMessageContent(TelegramObject): """ - This object represents the content of a message to be sent as a result of an inline query. Telegram clients currently support the following 4 types: + This object represents the content of a message to be sent as a result of an inline query. Telegram clients currently support the following 5 types: - :class:`aiogram.types.input_text_message_content.InputTextMessageContent` - :class:`aiogram.types.input_location_message_content.InputLocationMessageContent` - :class:`aiogram.types.input_venue_message_content.InputVenueMessageContent` - :class:`aiogram.types.input_contact_message_content.InputContactMessageContent` + - :class:`aiogram.types.input_invoice_message_content.InputInvoiceMessageContent` Source: https://core.telegram.org/bots/api#inputmessagecontent """ diff --git a/aiogram/types/message.py b/aiogram/types/message.py index acc9809b..2707da56 100644 --- a/aiogram/types/message.py +++ b/aiogram/types/message.py @@ -62,6 +62,7 @@ if TYPE_CHECKING: # pragma: no cover from .voice import Voice from .voice_chat_ended import VoiceChatEnded from .voice_chat_participants_invited import VoiceChatParticipantsInvited + from .voice_chat_scheduled import VoiceChatScheduled from .voice_chat_started import VoiceChatStarted @@ -174,6 +175,8 @@ class Message(TelegramObject): """*Optional*. Telegram Passport data""" proximity_alert_triggered: Optional[ProximityAlertTriggered] = None """*Optional*. Service message. A user in the chat triggered another user's proximity alert while sharing Live Location.""" + voice_chat_scheduled: Optional[VoiceChatScheduled] = None + """*Optional*. Service message: voice chat scheduled""" voice_chat_started: Optional[VoiceChatStarted] = None """*Optional*. Service message: voice chat started""" voice_chat_ended: Optional[VoiceChatEnded] = None diff --git a/aiogram/types/message_entity.py b/aiogram/types/message_entity.py index c0324656..2fb6623a 100644 --- a/aiogram/types/message_entity.py +++ b/aiogram/types/message_entity.py @@ -1,7 +1,9 @@ from __future__ import annotations +import warnings from typing import TYPE_CHECKING, Optional +from ..utils.text_decorations import add_surrogates, remove_surrogates from .base import MutableTelegramObject if TYPE_CHECKING: # pragma: no cover @@ -27,3 +29,16 @@ class MessageEntity(MutableTelegramObject): """*Optional*. For 'text_mention' only, the mentioned user""" language: Optional[str] = None """*Optional*. For 'pre' only, the programming language of the entity text""" + + def extract(self, text: str) -> str: + return remove_surrogates( + add_surrogates(text)[self.offset * 2 : (self.offset + self.length) * 2] + ) + + def get_text(self, text: str) -> str: + warnings.warn( + "Method `MessageEntity.get_text(...)` deprecated and will be removed in 3.2.\n" + " Use `MessageEntity.extract(...)` instead.", + DeprecationWarning, + ) + return self.extract(text=text) diff --git a/aiogram/types/voice_chat_scheduled.py b/aiogram/types/voice_chat_scheduled.py new file mode 100644 index 00000000..8aa8eb97 --- /dev/null +++ b/aiogram/types/voice_chat_scheduled.py @@ -0,0 +1,19 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +from .base import TelegramObject + +if TYPE_CHECKING: # pragma: no cover + pass + + +class VoiceChatScheduled(TelegramObject): + """ + This object represents a service message about a voice chat scheduled in the chat. + + Source: https://core.telegram.org/bots/api#voicechatscheduled + """ + + start_date: int + """Point in time (Unix timestamp) when the voice chat is supposed to be started by a chat administrator""" diff --git a/aiogram/utils/text_decorations.py b/aiogram/utils/text_decorations.py index 6377223b..a41e481f 100644 --- a/aiogram/utils/text_decorations.py +++ b/aiogram/utils/text_decorations.py @@ -17,6 +17,14 @@ __all__ = ( ) +def add_surrogates(text: str) -> bytes: + return text.encode("utf-16-le") + + +def remove_surrogates(text: bytes) -> str: + return text.decode("utf-16-le") + + class TextDecoration(ABC): def apply_entity(self, entity: MessageEntity, text: str) -> str: """ @@ -57,7 +65,7 @@ class TextDecoration(ABC): """ result = "".join( self._unparse_entities( - self._add_surrogates(text), + add_surrogates(text), sorted(entities, key=lambda item: item.offset) if entities else [], ) ) @@ -78,7 +86,7 @@ class TextDecoration(ABC): if entity.offset * 2 < offset: continue if entity.offset * 2 > offset: - yield self.quote(self._remove_surrogates(text[offset : entity.offset * 2])) + yield self.quote(remove_surrogates(text[offset : entity.offset * 2])) start = entity.offset * 2 offset = entity.offset * 2 + entity.length * 2 @@ -91,15 +99,7 @@ class TextDecoration(ABC): ) if offset < length: - yield self.quote(self._remove_surrogates(text[offset:length])) - - @staticmethod - def _add_surrogates(text: str) -> bytes: - return text.encode("utf-16-le") - - @staticmethod - def _remove_surrogates(text: bytes) -> str: - return text.decode("utf-16-le") + yield self.quote(remove_surrogates(text[offset:length])) @abstractmethod def link(self, value: str, link: str) -> str: # pragma: no cover diff --git a/docs2/api/types/index.rst b/docs2/api/types/index.rst index 7a5d0ac3..2e3b3812 100644 --- a/docs2/api/types/index.rst +++ b/docs2/api/types/index.rst @@ -42,6 +42,7 @@ Available types venue proximity_alert_triggered message_auto_delete_timer_changed + voice_chat_scheduled voice_chat_started voice_chat_ended voice_chat_participants_invited @@ -117,6 +118,7 @@ Inline mode input_location_message_content input_venue_message_content input_contact_message_content + input_invoice_message_content chosen_inline_result Payments diff --git a/docs2/api/types/input_invoice_message_content.rst b/docs2/api/types/input_invoice_message_content.rst new file mode 100644 index 00000000..f5d7a0ab --- /dev/null +++ b/docs2/api/types/input_invoice_message_content.rst @@ -0,0 +1,9 @@ +########################## +InputInvoiceMessageContent +########################## + + +.. automodule:: aiogram.types.input_invoice_message_content + :members: + :member-order: bysource + :undoc-members: True diff --git a/docs2/api/types/voice_chat_scheduled.rst b/docs2/api/types/voice_chat_scheduled.rst new file mode 100644 index 00000000..e63936e2 --- /dev/null +++ b/docs2/api/types/voice_chat_scheduled.rst @@ -0,0 +1,9 @@ +################## +VoiceChatScheduled +################## + + +.. automodule:: aiogram.types.voice_chat_scheduled + :members: + :member-order: bysource + :undoc-members: True diff --git a/poetry.lock b/poetry.lock index 335cffe3..86a831d1 100644 --- a/poetry.lock +++ b/poetry.lock @@ -122,7 +122,7 @@ tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (> [[package]] name = "babel" -version = "2.9.0" +version = "2.9.1" description = "Internationalization utilities" category = "main" optional = false @@ -156,25 +156,26 @@ lxml = ["lxml"] [[package]] name = "black" -version = "20.8b1" +version = "21.4b2" description = "The uncompromising code formatter." category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=3.6.2" [package.dependencies] appdirs = "*" click = ">=7.1.2" mypy-extensions = ">=0.4.3" -pathspec = ">=0.6,<1" +pathspec = ">=0.8.1,<1" regex = ">=2020.1.8" toml = ">=0.10.1" -typed-ast = ">=1.4.0" -typing-extensions = ">=3.7.4" +typed-ast = {version = ">=1.4.2", markers = "python_version < \"3.8\""} +typing-extensions = {version = ">=3.7.4", markers = "python_version < \"3.8\""} [package.extras] colorama = ["colorama (>=0.4.3)"] d = ["aiohttp (>=3.3.2)", "aiohttp-cors"] +python2 = ["typed-ast (>=1.4.2)"] [[package]] name = "cfgv" @@ -441,11 +442,11 @@ tornado = {version = "*", markers = "python_version > \"2.7\""} [[package]] name = "magic-filter" -version = "0.1.2" +version = "1.0.0a1" description = "This package provides magic filter based on dynamic attribute getter" category = "main" optional = false -python-versions = ">=3.6.1,<4.0.0" +python-versions = ">=3.6.2,<4.0.0" [[package]] name = "markdown" @@ -718,17 +719,17 @@ testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xm [[package]] name = "pytest-asyncio" -version = "0.14.0" +version = "0.15.1" description = "Pytest support for asyncio." category = "dev" optional = false -python-versions = ">= 3.5" +python-versions = ">= 3.6" [package.dependencies] pytest = ">=5.4.0" [package.extras] -testing = ["async-generator (>=1.3)", "coverage", "hypothesis (>=5.7.1)"] +testing = ["coverage", "hypothesis (>=5.7.1)"] [[package]] name = "pytest-cov" @@ -770,11 +771,11 @@ pytest = ">=2.9.0" [[package]] name = "pytest-mock" -version = "3.5.1" +version = "3.6.0" description = "Thin-wrapper around the mock package for easier use with pytest" category = "dev" optional = false -python-versions = ">=3.5" +python-versions = ">=3.6" [package.dependencies] pytest = ">=5.0" @@ -1171,7 +1172,7 @@ proxy = ["aiohttp-socks"] [metadata] lock-version = "1.1" python-versions = "^3.7" -content-hash = "0ac1a8626f409f57f8b7f5e1ef3325d7896cc60b42f323f566353a95debe6ebc" +content-hash = "9a787135a6d8ed2a395c07246db424e9961281cf7e0dc00fc08507da3081143e" [metadata.files] aiofiles = [ @@ -1257,8 +1258,8 @@ attrs = [ {file = "attrs-20.3.0.tar.gz", hash = "sha256:832aa3cde19744e49938b91fea06d69ecb9e649c93ba974535d08ad92164f700"}, ] babel = [ - {file = "Babel-2.9.0-py2.py3-none-any.whl", hash = "sha256:9d35c22fcc79893c3ecc85ac4a56cde1ecf3f19c540bba0922308a6c06ca6fa5"}, - {file = "Babel-2.9.0.tar.gz", hash = "sha256:da031ab54472314f210b0adcff1588ee5d1d1d0ba4dbd07b94dba82bde791e05"}, + {file = "Babel-2.9.1-py2.py3-none-any.whl", hash = "sha256:ab49e12b91d937cd11f0b67cb259a57ab4ad2b59ac7a3b41d6c06c0ac5b0def9"}, + {file = "Babel-2.9.1.tar.gz", hash = "sha256:bc0c176f9f6a994582230df350aa6e05ba2ebe4b3ac317eab29d9be5d2768da0"}, ] backcall = [ {file = "backcall-0.2.0-py2.py3-none-any.whl", hash = "sha256:fbbce6a29f263178a1f7915c1940bde0ec2b2a967566fe1c65c1dfb7422bd255"}, @@ -1270,7 +1271,8 @@ beautifulsoup4 = [ {file = "beautifulsoup4-4.9.3.tar.gz", hash = "sha256:84729e322ad1d5b4d25f805bfa05b902dd96450f43842c4e99067d5e1369eb25"}, ] black = [ - {file = "black-20.8b1.tar.gz", hash = "sha256:1c02557aa099101b9d21496f8a914e9ed2222ef70336404eeeac8edba836fbea"}, + {file = "black-21.4b2-py3-none-any.whl", hash = "sha256:bff7067d8bc25eb21dcfdbc8c72f2baafd9ec6de4663241a52fb904b304d391f"}, + {file = "black-21.4b2.tar.gz", hash = "sha256:fc9bcf3b482b05c1f35f6a882c079dc01b9c7795827532f4cc43c0ec88067bbc"}, ] cfgv = [ {file = "cfgv-3.2.0-py2.py3-none-any.whl", hash = "sha256:32e43d604bbe7896fe7c248a9c2276447dbef840feb28fe20494f62af110211d"}, @@ -1414,8 +1416,8 @@ livereload = [ {file = "livereload-2.6.3.tar.gz", hash = "sha256:776f2f865e59fde56490a56bcc6773b6917366bce0c267c60ee8aaf1a0959869"}, ] magic-filter = [ - {file = "magic-filter-0.1.2.tar.gz", hash = "sha256:dfd1a778493083ac1355791d1716c3beb6629598739f2c2ec078815952282c1d"}, - {file = "magic_filter-0.1.2-py3-none-any.whl", hash = "sha256:16d0c96584f0660fd7fa94b6cd16f92383616208a32568bf8f95a57fc1a69e9d"}, + {file = "magic-filter-1.0.0a1.tar.gz", hash = "sha256:af77522f1ab2a7aac6a960fb731097ada793da18f7ad96b1e29c11bd9c2d09cd"}, + {file = "magic_filter-1.0.0a1-py3-none-any.whl", hash = "sha256:ae4268493a6955887b63d1deb6f9409c063c7518d5e4bc6feb1dc1ce7ac61a0d"}, ] markdown = [ {file = "Markdown-3.3.4-py3-none-any.whl", hash = "sha256:96c3ba1261de2f7547b46a00ea8463832c921d3f9d6aba3f255a6f71386db20c"}, @@ -1642,8 +1644,8 @@ pytest = [ {file = "pytest-6.2.3.tar.gz", hash = "sha256:671238a46e4df0f3498d1c3270e5deb9b32d25134c99b7d75370a68cfbe9b634"}, ] pytest-asyncio = [ - {file = "pytest-asyncio-0.14.0.tar.gz", hash = "sha256:9882c0c6b24429449f5f969a5158b528f39bde47dc32e85b9f0403965017e700"}, - {file = "pytest_asyncio-0.14.0-py3-none-any.whl", hash = "sha256:2eae1e34f6c68fc0a9dc12d4bea190483843ff4708d24277c41568d6b6044f1d"}, + {file = "pytest-asyncio-0.15.1.tar.gz", hash = "sha256:2564ceb9612bbd560d19ca4b41347b54e7835c2f792c504f698e05395ed63f6f"}, + {file = "pytest_asyncio-0.15.1-py3-none-any.whl", hash = "sha256:3042bcdf1c5d978f6b74d96a151c4cfb9dcece65006198389ccd7e6c60eb1eea"}, ] pytest-cov = [ {file = "pytest-cov-2.11.1.tar.gz", hash = "sha256:359952d9d39b9f822d9d29324483e7ba04a3a17dd7d05aa6beb7ea01e359e5f7"}, @@ -1658,8 +1660,8 @@ pytest-metadata = [ {file = "pytest_metadata-1.11.0-py2.py3-none-any.whl", hash = "sha256:576055b8336dd4a9006dd2a47615f76f2f8c30ab12b1b1c039d99e834583523f"}, ] pytest-mock = [ - {file = "pytest-mock-3.5.1.tar.gz", hash = "sha256:a1e2aba6af9560d313c642dae7e00a2a12b022b80301d9d7fc8ec6858e1dd9fc"}, - {file = "pytest_mock-3.5.1-py3-none-any.whl", hash = "sha256:379b391cfad22422ea2e252bdfc008edd08509029bcde3c25b2c0bd741e0424e"}, + {file = "pytest-mock-3.6.0.tar.gz", hash = "sha256:f7c3d42d6287f4e45846c8231c31902b6fa2bea98735af413a43da4cf5b727f1"}, + {file = "pytest_mock-3.6.0-py3-none-any.whl", hash = "sha256:952139a535b5b48ac0bb2f90b5dd36b67c7e1ba92601f3a8012678c4bd7f0bcc"}, ] pytest-mypy = [ {file = "pytest-mypy-0.8.1.tar.gz", hash = "sha256:1fa55723a4bf1d054fcba1c3bd694215a2a65cc95ab10164f5808afd893f3b11"}, diff --git a/pyproject.toml b/pyproject.toml index a12d69ce..b465f2a2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -33,14 +33,14 @@ classifiers = [ [tool.poetry.dependencies] python = "^3.7" -aiohttp = "^3.6" -pydantic = "^1.5" -Babel = "^2.7" +aiohttp = "^3.7.4" +pydantic = "^1.8.1" +Babel = "^2.9.1" aiofiles = "^0.6.0" -async_lru = "^1.0" +async_lru = "^1.0.2" aiohttp-socks = { version = "^0.5.5", optional = true } typing-extensions = { version = "^3.7.4", python = "<3.8" } -magic-filter = "^0.1.2" +magic-filter = {version = "1.0.0a1", allow-prereleases = true} sphinx = { version = "^3.1.0", optional = true } sphinx-intl = { version = "^2.0.1", optional = true } sphinx-autobuild = { version = "^2020.9.1", optional = true } @@ -51,18 +51,18 @@ Sphinx-Substitution-Extensions = { version = "^2020.9.30", optional = true } [tool.poetry.dev-dependencies] aiohttp-socks = "^0.5" -ipython = "^7.10" +ipython = "^7.22.0" uvloop = { version = "^0.15.2", markers = "sys_platform == 'darwin' or sys_platform == 'linux'" } -black = "^20.8b1" +black = "^21.4b2" isort = "^5.8.0" flake8 = "^3.9.1" flake8-html = "^0.4.1" mypy = "^0.812" pytest = "^6.2.3" pytest-html = "^3.1.1" -pytest-asyncio = "^0.14.0" +pytest-asyncio = "^0.15.1" pytest-mypy = "^0.8.1" -pytest-mock = "^3.5.1" +pytest-mock = "^3.6.0" pytest-cov = "^2.11.1" aresponses = "^2.1.4" asynctest = "^0.13.0"