From ea28e2a77a80658341bad52cd37d2750b551fd82 Mon Sep 17 00:00:00 2001 From: Oleg A Date: Wed, 28 Apr 2021 01:22:57 +0300 Subject: [PATCH] Telegram API 5.2 support (#572) * feat: version number update * feat: add InputInvoiceMessageContent type * refactor: every param on a new line * feat: add `max_tip_amount` and `suggested_tip_amounts` to `sendInvoice` * feat: `start_parameter` of `sendInvoice` became optional * refactor: reorder params * feat: add `chat_type` to `InlineQuery` * feat: add `VoiceChatScheduled` * feat: add `voice_chat_scheduled` to `Message` * fix: sendChatAction documentation update * feat: add `record_voice` and `upload_voice` to `ChatActions` * feat: allow sending invoices to group, supergroup and channel --- README.md | 2 +- README.rst | 2 +- aiogram/__init__.py | 4 +- aiogram/bot/api.py | 2 +- aiogram/bot/bot.py | 87 ++++++++++++++++++++------ aiogram/types/__init__.py | 5 +- aiogram/types/chat.py | 22 +++++++ aiogram/types/inline_query.py | 3 +- aiogram/types/input_message_content.py | 62 ++++++++++++++++++ aiogram/types/message.py | 5 ++ aiogram/types/voice_chat_scheduled.py | 15 +++++ docs/source/index.rst | 2 +- 12 files changed, 185 insertions(+), 26 deletions(-) create mode 100644 aiogram/types/voice_chat_scheduled.py diff --git a/README.md b/README.md index 5205646d..ae44c524 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ [![PyPi status](https://img.shields.io/pypi/status/aiogram.svg?style=flat-square)](https://pypi.python.org/pypi/aiogram) [![Downloads](https://img.shields.io/pypi/dm/aiogram.svg?style=flat-square)](https://pypi.python.org/pypi/aiogram) [![Supported python versions](https://img.shields.io/pypi/pyversions/aiogram.svg?style=flat-square)](https://pypi.python.org/pypi/aiogram) -[![Telegram Bot API](https://img.shields.io/badge/Telegram%20Bot%20API-5.1-blue.svg?style=flat-square&logo=telegram)](https://core.telegram.org/bots/api) +[![Telegram Bot API](https://img.shields.io/badge/Telegram%20Bot%20API-5.2-blue.svg?style=flat-square&logo=telegram)](https://core.telegram.org/bots/api) [![Documentation Status](https://img.shields.io/readthedocs/aiogram?style=flat-square)](http://docs.aiogram.dev/en/latest/?badge=latest) [![Github issues](https://img.shields.io/github/issues/aiogram/aiogram.svg?style=flat-square)](https://github.com/aiogram/aiogram/issues) [![MIT License](https://img.shields.io/pypi/l/aiogram.svg?style=flat-square)](https://opensource.org/licenses/MIT) diff --git a/README.rst b/README.rst index 3ec899c2..caf6149c 100644 --- a/README.rst +++ b/README.rst @@ -21,7 +21,7 @@ AIOGramBot :target: https://pypi.python.org/pypi/aiogram :alt: Supported python versions -.. image:: https://img.shields.io/badge/Telegram%20Bot%20API-5.1-blue.svg?style=flat-square&logo=telegram +.. image:: https://img.shields.io/badge/Telegram%20Bot%20API-5.2-blue.svg?style=flat-square&logo=telegram :target: https://core.telegram.org/bots/api :alt: Telegram Bot API diff --git a/aiogram/__init__.py b/aiogram/__init__.py index 455aebe8..a77ecdc0 100644 --- a/aiogram/__init__.py +++ b/aiogram/__init__.py @@ -43,5 +43,5 @@ __all__ = ( 'utils', ) -__version__ = '2.12.2' -__api_version__ = '5.1' +__version__ = '2.13.0' +__api_version__ = '5.2' diff --git a/aiogram/bot/api.py b/aiogram/bot/api.py index e3d3bf9a..38cbee89 100644 --- a/aiogram/bot/api.py +++ b/aiogram/bot/api.py @@ -189,7 +189,7 @@ class Methods(Helper): """ Helper for Telegram API Methods listed on https://core.telegram.org/bots/api - List is updated to Bot API 5.1 + List is updated to Bot API 5.2 """ mode = HelperMode.lowerCamelCase diff --git a/aiogram/bot/bot.py b/aiogram/bot/bot.py index 4b6c4c0b..0c72c050 100644 --- a/aiogram/bot/bot.py +++ b/aiogram/bot/bot.py @@ -1484,19 +1484,36 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin): async def send_chat_action(self, chat_id: typing.Union[base.Integer, base.String], action: base.String) -> base.Boolean: """ - Use this method when you need to tell the user that something is happening on the bot's side. - The status is set for 5 seconds or less - (when a message arrives from your bot, Telegram clients clear its typing status). + Use this method when you need to tell the user that something is + happening on the bot's side. The status is set for 5 seconds or + less (when a message arrives from your bot, Telegram clients + clear its typing status). Returns True on success. - We only recommend using this method when a response from the bot will take - a noticeable amount of time to arrive. + Example: The ImageBot needs some time to process a request and + upload the image. Instead of sending a text message along the + lines of “Retrieving image, please wait…”, the bot may use + sendChatAction with action = upload_photo. The user will see a + “sending photo” status for the bot. + + We only recommend using this method when a response from the bot + will take a noticeable amount of time to arrive. Source: https://core.telegram.org/bots/api#sendchataction - :param chat_id: Unique identifier for the target chat or username of the target channel + :param chat_id: Unique identifier for the target chat or + username of the target channel (in the format + @channelusername) :type chat_id: :obj:`typing.Union[base.Integer, base.String]` - :param action: Type of action to broadcast + + :param action: Type of action to broadcast. Choose one, + depending on what the user is about to receive: `typing` for + text messages, `upload_photo` for photos, `record_video` or + `upload_video` for videos, `record_voice` or `upload_voice` + for voice notes, `upload_document` for general files, + `find_location` for location data, `record_video_note` or + `upload_video_note` for video notes. :type action: :obj:`base.String` + :return: Returns True on success :rtype: :obj:`base.Boolean` """ @@ -2780,10 +2797,19 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin): # === Payments === # https://core.telegram.org/bots/api#payments - async def send_invoice(self, chat_id: base.Integer, title: base.String, - description: base.String, payload: base.String, - provider_token: base.String, start_parameter: base.String, - currency: base.String, prices: typing.List[types.LabeledPrice], + async def send_invoice(self, + chat_id: typing.Union[base.Integer, base.String], + title: base.String, + description: base.String, + payload: base.String, + provider_token: base.String, + currency: base.String, + prices: typing.List[types.LabeledPrice], + max_tip_amount: typing.Optional[base.Integer] = None, + suggested_tip_amounts: typing.Optional[ + typing.List[base.Integer] + ] = None, + start_parameter: typing.Optional[base.String] = None, provider_data: typing.Optional[typing.Dict] = None, photo_url: typing.Optional[base.String] = None, photo_size: typing.Optional[base.Integer] = None, @@ -2799,14 +2825,17 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin): disable_notification: typing.Optional[base.Boolean] = None, reply_to_message_id: typing.Optional[base.Integer] = None, allow_sending_without_reply: typing.Optional[base.Boolean] = None, - reply_markup: typing.Optional[types.InlineKeyboardMarkup] = None) -> types.Message: + reply_markup: typing.Optional[types.InlineKeyboardMarkup] = None, + ) -> types.Message: """ Use this method to send invoices. Source: https://core.telegram.org/bots/api#sendinvoice - :param chat_id: Unique identifier for the target private chat - :type chat_id: :obj:`base.Integer` + :param chat_id: Unique identifier for the target chat or + username of the target channel (in the format + @channelusername) + :type chat_id: :obj:`typing.Union[base.Integer, base.String]` :param title: Product name, 1-32 characters :type title: :obj:`base.String` @@ -2821,10 +2850,6 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin): :param provider_token: Payments provider token, obtained via Botfather :type provider_token: :obj:`base.String` - :param start_parameter: Unique deep-linking parameter that can be used to generate this - invoice when used as a start parameter - :type start_parameter: :obj:`base.String` - :param currency: Three-letter ISO 4217 currency code, see more on currencies :type currency: :obj:`base.String` @@ -2832,6 +2857,32 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin): (e.g. product price, tax, discount, delivery cost, delivery tax, bonus, etc.) :type prices: :obj:`typing.List[types.LabeledPrice]` + :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 US$ 1.45 + pass 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 + :type max_tip_amount: :obj:`typing.Optional[base.Integer]` + + :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. + :type suggested_tip_amounts: :obj:`typing.Optional[typing.List[base.Integer]]` + + :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 + :type start_parameter: :obj:`typing.Optional[base.String]` + :param provider_data: JSON-encoded data about the invoice, which will be shared with the payment provider :type provider_data: :obj:`typing.Optional[typing.Dict]` diff --git a/aiogram/types/__init__.py b/aiogram/types/__init__.py index 1dfa519f..90909e81 100644 --- a/aiogram/types/__init__.py +++ b/aiogram/types/__init__.py @@ -35,7 +35,7 @@ from .input_file import InputFile from .input_media import InputMedia, InputMediaAnimation, InputMediaAudio, InputMediaDocument, InputMediaPhoto, \ InputMediaVideo, MediaGroup from .input_message_content import InputContactMessageContent, InputLocationMessageContent, InputMessageContent, \ - InputTextMessageContent, InputVenueMessageContent + InputTextMessageContent, InputVenueMessageContent, InputInvoiceMessageContent from .invoice import Invoice from .labeled_price import LabeledPrice from .location import Location @@ -72,6 +72,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 @@ -131,6 +132,7 @@ __all__ = ( 'InlineQueryResultVideo', 'InlineQueryResultVoice', 'InputContactMessageContent', + 'InputInvoiceMessageContent', 'InputFile', 'InputLocationMessageContent', 'InputMedia', @@ -191,6 +193,7 @@ __all__ = ( 'Voice', 'VoiceChatEnded', 'VoiceChatParticipantsInvited', + 'VoiceChatScheduled', 'VoiceChatStarted', 'WebhookInfo', 'base', diff --git a/aiogram/types/chat.py b/aiogram/types/chat.py index 3b0a7b9d..5b3b315a 100644 --- a/aiogram/types/chat.py +++ b/aiogram/types/chat.py @@ -732,6 +732,8 @@ class ChatActions(helper.Helper): UPLOAD_VIDEO: str = helper.Item() # upload_video RECORD_AUDIO: str = helper.Item() # record_audio UPLOAD_AUDIO: str = helper.Item() # upload_audio + RECORD_VOICE: str = helper.Item() # record_voice + UPLOAD_VOICE: str = helper.Item() # upload_voice UPLOAD_DOCUMENT: str = helper.Item() # upload_document FIND_LOCATION: str = helper.Item() # find_location RECORD_VIDEO_NOTE: str = helper.Item() # record_video_note @@ -817,6 +819,26 @@ class ChatActions(helper.Helper): """ await cls._do(cls.UPLOAD_AUDIO, sleep) + @classmethod + async def record_voice(cls, sleep=None): + """ + Do record voice + + :param sleep: sleep timeout + :return: + """ + await cls._do(cls.RECORD_VOICE, sleep) + + @classmethod + async def upload_voice(cls, sleep=None): + """ + Do upload voice + + :param sleep: sleep timeout + :return: + """ + await cls._do(cls.UPLOAD_VOICE, sleep) + @classmethod async def upload_document(cls, sleep=None): """ diff --git a/aiogram/types/inline_query.py b/aiogram/types/inline_query.py index 436c11b0..63f4ab32 100644 --- a/aiogram/types/inline_query.py +++ b/aiogram/types/inline_query.py @@ -17,9 +17,10 @@ class InlineQuery(base.TelegramObject): """ id: base.String = fields.Field() from_user: User = fields.Field(alias='from', base=User) - location: Location = fields.Field(base=Location) query: base.String = fields.Field() offset: base.String = fields.Field() + chat_type: base.String = fields.Field() + location: Location = fields.Field(base=Location) async def answer(self, results: typing.List[InlineQueryResult], diff --git a/aiogram/types/input_message_content.py b/aiogram/types/input_message_content.py index 0008a2ee..f0c452cd 100644 --- a/aiogram/types/input_message_content.py +++ b/aiogram/types/input_message_content.py @@ -3,6 +3,8 @@ import typing from . import base from . import fields from .message_entity import MessageEntity +from .labeled_price import LabeledPrice +from ..utils.payload import generate_payload class InputMessageContent(base.TelegramObject): @@ -44,6 +46,66 @@ class InputContactMessageContent(InputMessageContent): ) +class InputInvoiceMessageContent(InputMessageContent): + """ + Represents the content of an invoice message to be sent as the + result of an inline query. + + https://core.telegram.org/bots/api#inputinvoicemessagecontent + """ + + title: base.String = fields.Field() + description: base.String = fields.Field() + payload: base.String = fields.Field() + provider_token: base.String = fields.Field() + currency: base.String = fields.Field() + prices: typing.List[LabeledPrice] = fields.ListField(base=LabeledPrice) + max_tip_amount: typing.Optional[base.Integer] = fields.Field() + suggested_tip_amounts: typing.Optional[ + typing.List[base.Integer] + ] = fields.ListField(base=base.Integer) + provider_data: typing.Optional[base.String] = fields.Field() + photo_url: typing.Optional[base.String] = fields.Field() + photo_size: typing.Optional[base.Integer] = fields.Field() + photo_width: typing.Optional[base.Integer] = fields.Field() + photo_height: typing.Optional[base.Integer] = fields.Field() + need_name: typing.Optional[base.Boolean] = fields.Field() + need_phone_number: typing.Optional[base.Boolean] = fields.Field() + need_email: typing.Optional[base.Boolean] = fields.Field() + need_shipping_address: typing.Optional[base.Boolean] = fields.Field() + send_phone_number_to_provider: typing.Optional[base.Boolean] = fields.Field() + send_email_to_provider: typing.Optional[base.Boolean] = fields.Field() + is_flexible: typing.Optional[base.Boolean] = fields.Field() + + def __init__( + self, + title: base.String, + description: base.String, + payload: base.String, + provider_token: base.String, + currency: base.String, + prices: typing.List[LabeledPrice] = None, + max_tip_amount: typing.Optional[base.Integer] = None, + suggested_tip_amounts: typing.Optional[typing.List[base.Integer]] = None, + provider_data: typing.Optional[base.String] = None, + photo_url: typing.Optional[base.String] = None, + photo_size: typing.Optional[base.Integer] = None, + photo_width: typing.Optional[base.Integer] = None, + photo_height: typing.Optional[base.Integer] = None, + need_name: typing.Optional[base.Boolean] = None, + need_phone_number: typing.Optional[base.Boolean] = None, + need_email: typing.Optional[base.Boolean] = None, + need_shipping_address: typing.Optional[base.Boolean] = None, + send_phone_number_to_provider: typing.Optional[base.Boolean] = None, + send_email_to_provider: typing.Optional[base.Boolean] = None, + is_flexible: typing.Optional[base.Boolean] = None, + ): + if prices is None: + prices = [] + payload = generate_payload(**locals()) + super().__init__(**payload) + + class InputLocationMessageContent(InputMessageContent): """ Represents the content of a location message to be sent as the result of an inline query. diff --git a/aiogram/types/message.py b/aiogram/types/message.py index c9cc3945..ce8395d2 100644 --- a/aiogram/types/message.py +++ b/aiogram/types/message.py @@ -34,6 +34,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 ..utils import helper from ..utils import markdown as md @@ -98,6 +99,7 @@ class Message(base.TelegramObject): connected_website: base.String = fields.Field() passport_data: PassportData = fields.Field(base=PassportData) proximity_alert_triggered: ProximityAlertTriggered = fields.Field(base=ProximityAlertTriggered) + voice_chat_scheduled: VoiceChatScheduled = fields.Field(base=VoiceChatScheduled) voice_chat_started: VoiceChatStarted = fields.Field(base=VoiceChatStarted) voice_chat_ended: VoiceChatEnded = fields.Field(base=VoiceChatEnded) voice_chat_participants_invited: VoiceChatParticipantsInvited = fields.Field(base=VoiceChatParticipantsInvited) @@ -166,6 +168,8 @@ class Message(base.TelegramObject): return ContentType.PASSPORT_DATA if self.proximity_alert_triggered: return ContentType.PROXIMITY_ALERT_TRIGGERED + if self.voice_chat_scheduled: + return ContentType.VOICE_CHAT_SCHEDULED if self.voice_chat_started: return ContentType.VOICE_CHAT_STARTED if self.voice_chat_ended: @@ -3033,6 +3037,7 @@ class ContentType(helper.Helper): GROUP_CHAT_CREATED = helper.Item() # group_chat_created PASSPORT_DATA = helper.Item() # passport_data PROXIMITY_ALERT_TRIGGERED = helper.Item() # proximity_alert_triggered + VOICE_CHAT_SCHEDULED = helper.Item() # voice_chat_scheduled VOICE_CHAT_STARTED = helper.Item() # voice_chat_started VOICE_CHAT_ENDED = helper.Item() # voice_chat_ended VOICE_CHAT_PARTICIPANTS_INVITED = helper.Item() # voice_chat_participants_invited diff --git a/aiogram/types/voice_chat_scheduled.py b/aiogram/types/voice_chat_scheduled.py new file mode 100644 index 00000000..c134eb0f --- /dev/null +++ b/aiogram/types/voice_chat_scheduled.py @@ -0,0 +1,15 @@ +from datetime import datetime + +from . import base +from . import fields +from .user import User + + +class VoiceChatScheduled(base.TelegramObject): + """ + This object represents a service message about a voice chat scheduled in the chat. + + https://core.telegram.org/bots/api#voicechatscheduled + """ + + start_date: datetime = fields.DateTimeField() diff --git a/docs/source/index.rst b/docs/source/index.rst index 809e195e..3631b150 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -22,7 +22,7 @@ Welcome to aiogram's documentation! :target: https://pypi.python.org/pypi/aiogram :alt: Supported python versions - .. image:: https://img.shields.io/badge/Telegram%20Bot%20API-5.1-blue.svg?style=flat-square&logo=telegram + .. image:: https://img.shields.io/badge/Telegram%20Bot%20API-5.2-blue.svg?style=flat-square&logo=telegram :target: https://core.telegram.org/bots/api :alt: Telegram Bot API