From a06cdd188dedbd12b1353e52cb2b1fca9be4df75 Mon Sep 17 00:00:00 2001 From: Oleg A Date: Sat, 21 Jul 2018 11:31:18 +0300 Subject: [PATCH 01/17] Update json.py fix pyCharm warning 'ujson' in try block with 'except ImportError' should also be defined in except block This inspection detects names that should resolve but don't. Due to dynamic dispatch and duck typing, this is possible in a limited but useful number of cases. Top-level and class-level items are supported better than instance items. --- aiogram/utils/json.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/aiogram/utils/json.py b/aiogram/utils/json.py index a5d214d7..4cd02bc6 100644 --- a/aiogram/utils/json.py +++ b/aiogram/utils/json.py @@ -3,11 +3,10 @@ import json try: import ujson - _UJSON_IS_AVAILABLE = True except ImportError: - _UJSON_IS_AVAILABLE = False + ujson = None -_use_ujson = _UJSON_IS_AVAILABLE +_use_ujson = True if ujson else False def disable_ujson(): From 52a4eb72264f99045f77a3fac533a7d3e011ee9a Mon Sep 17 00:00:00 2001 From: Alex Root Junior Date: Thu, 26 Jul 2018 21:01:39 +0300 Subject: [PATCH 02/17] Bump version --- aiogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aiogram/__init__.py b/aiogram/__init__.py index ad97a44a..da2bc4b3 100644 --- a/aiogram/__init__.py +++ b/aiogram/__init__.py @@ -10,5 +10,5 @@ except ImportError: else: asyncio.set_event_loop_policy(uvloop.EventLoopPolicy()) -__version__ = '1.3.3.dev1' +__version__ = '1.4.dev1' __api_version__ = '3.6' From ad96efbd71c238268c17671d5a0e088244221e69 Mon Sep 17 00:00:00 2001 From: Alex Root Junior Date: Thu, 26 Jul 2018 21:29:25 +0300 Subject: [PATCH 03/17] Added two new MessageEntity types: `cashtag` and `phone_number`. --- aiogram/types/message_entity.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/aiogram/types/message_entity.py b/aiogram/types/message_entity.py index 6ca81518..abb4f060 100644 --- a/aiogram/types/message_entity.py +++ b/aiogram/types/message_entity.py @@ -83,9 +83,11 @@ class MessageEntityType(helper.Helper): :key: MENTION :key: HASHTAG + :key: CASHTAG :key: BOT_COMMAND :key: URL :key: EMAIL + :key: PHONE_NUMBER :key: BOLD :key: ITALIC :key: CODE @@ -97,9 +99,11 @@ class MessageEntityType(helper.Helper): MENTION = helper.Item() # mention - @username HASHTAG = helper.Item() # hashtag + CASHTAG = helper.Item() # cashtag BOT_COMMAND = helper.Item() # bot_command URL = helper.Item() # url EMAIL = helper.Item() # email + PHONE_NUMBER = helper.Item() # phone_number BOLD = helper.Item() # bold - bold text ITALIC = helper.Item() # italic - italic text CODE = helper.Item() # code - monowidth string From f6573ded9647538a3313ca7f2556f4c39cbdcb4a Mon Sep 17 00:00:00 2001 From: Alex Root Junior Date: Thu, 26 Jul 2018 21:29:41 +0300 Subject: [PATCH 04/17] Fix docs Makefile. (revert) --- docs/Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/Makefile b/docs/Makefile index d4bd90d4..4e50ed99 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -10,11 +10,11 @@ BUILDDIR = build # Put it first so that "make" without argument is like "make help". help: - @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) .PHONY: help Makefile # Catch-all target: route all unknown targets to Sphinx using the new # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). %: Makefile - @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) \ No newline at end of file + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) \ No newline at end of file From f7a071cb0bf05b86ccfdd4ed3d2b2febb5b0a9a2 Mon Sep 17 00:00:00 2001 From: Alex Root Junior Date: Thu, 26 Jul 2018 22:53:56 +0300 Subject: [PATCH 05/17] Added the field animation to the Message object. --- aiogram/types/message.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/aiogram/types/message.py b/aiogram/types/message.py index 75b86291..c107220a 100644 --- a/aiogram/types/message.py +++ b/aiogram/types/message.py @@ -5,6 +5,7 @@ import typing from . import base from . import fields +from .animation import Animation from .audio import Audio from .chat import Chat from .contact import Contact @@ -49,6 +50,7 @@ class Message(base.TelegramObject): caption_entities: typing.List[MessageEntity] = fields.ListField(base=MessageEntity) audio: Audio = fields.Field(base=Audio) document: Document = fields.Field(base=Document) + animation: Animation = fields.Field(base=Animation) game: Game = fields.Field(base=Game) photo: typing.List[PhotoSize] = fields.ListField(base=PhotoSize) sticker: Sticker = fields.Field(base=Sticker) @@ -81,6 +83,8 @@ class Message(base.TelegramObject): return ContentType.TEXT[0] elif self.audio: return ContentType.AUDIO[0] + elif self.animation: + return ContentType.ANIMATION[0] elif self.document: return ContentType.DOCUMENT[0] elif self.game: @@ -748,6 +752,7 @@ class ContentType(helper.Helper): TEXT = helper.ListItem() # text AUDIO = helper.ListItem() # audio DOCUMENT = helper.ListItem() # document + ANIMATION = helper.ListItem() # animation GAME = helper.ListItem() # game PHOTO = helper.ListItem() # photo STICKER = helper.ListItem() # sticker From 6f3eda16e573346a65697408ee57156145799379 Mon Sep 17 00:00:00 2001 From: Alex Root Junior Date: Sat, 28 Jul 2018 19:16:54 +0300 Subject: [PATCH 06/17] Telegram Passport --- aiogram/bot/api.py | 3 + aiogram/bot/bot.py | 32 ++++++ aiogram/types/__init__.py | 18 ++++ aiogram/types/encrypted_credentials.py | 16 +++ aiogram/types/encrypted_passport_element.py | 21 ++++ aiogram/types/message.py | 5 + aiogram/types/passport_data.py | 16 +++ aiogram/types/passport_element_error.py | 110 ++++++++++++++++++++ aiogram/types/passport_file.py | 15 +++ 9 files changed, 236 insertions(+) create mode 100644 aiogram/types/encrypted_credentials.py create mode 100644 aiogram/types/encrypted_passport_element.py create mode 100644 aiogram/types/passport_data.py create mode 100644 aiogram/types/passport_element_error.py create mode 100644 aiogram/types/passport_file.py diff --git a/aiogram/bot/api.py b/aiogram/bot/api.py index 48904446..a9c6978a 100644 --- a/aiogram/bot/api.py +++ b/aiogram/bot/api.py @@ -238,6 +238,9 @@ class Methods(Helper): ANSWER_SHIPPING_QUERY = Item() # answerShippingQuery ANSWER_PRE_CHECKOUT_QUERY = Item() # answerPreCheckoutQuery + # Telegram Passport + SET_PASSPORT_DATA_ERRORS = Item() # setPassportDataErrors + # Games SEND_GAME = Item() # sendGame SET_GAME_SCORE = Item() # setGameScore diff --git a/aiogram/bot/bot.py b/aiogram/bot/bot.py index fefdef9f..868b7aa0 100644 --- a/aiogram/bot/bot.py +++ b/aiogram/bot/bot.py @@ -1759,6 +1759,38 @@ class Bot(BaseBot): # === Games === # https://core.telegram.org/bots/api#games + async def set_passport_data_errors(self, + user_id: base.Integer, + errors: typing.List[types.PassportElementError]) -> base.Boolean: + """ + Informs a user that some of the Telegram Passport elements they provided contains errors. + The user will not be able to re-submit their Passport to you until the errors are fixed + (the contents of the field for which you returned the error must change). + Returns True on success. + + Use this if the data submitted by the user doesn't satisfy the standards your service + requires for any reason. For example, if a birthday date seems invalid, a submitted document + is blurry, a scan shows evidence of tampering, etc. Supply some details in the error message + to make sure the user knows how to correct the issues. + + Source https://core.telegram.org/bots/api#setpassportdataerrors + + :param user_id: User identifier + :type user_id: :obj:`base.Integer` + :param errors: A JSON-serialized array describing the errors + :type errors: :obj:`typing.List[types.PassportElementError]` + :return: Returns True on success. + :rtype: :obj:`base.Boolean` + """ + errors = prepare_arg(errors) + payload = generate_payload(**locals()) + result = await self.request(api.Methods.SET_PASSPORT_DATA_ERRORS, payload) + + return result + + # === Games === + # https://core.telegram.org/bots/api#games + async def send_game(self, chat_id: base.Integer, game_short_name: base.String, disable_notification: typing.Union[base.Boolean, None] = None, reply_to_message_id: typing.Union[base.Integer, None] = None, diff --git a/aiogram/types/__init__.py b/aiogram/types/__init__.py index cc7abb11..08e3118c 100644 --- a/aiogram/types/__init__.py +++ b/aiogram/types/__init__.py @@ -11,6 +11,8 @@ from .chat_photo import ChatPhoto from .chosen_inline_result import ChosenInlineResult from .contact import Contact from .document import Document +from .encrypted_credentials import EncryptedCredentials +from .encrypted_passport_element import EncryptedPassportElement from .file import File from .force_reply import ForceReply from .game import Game @@ -34,6 +36,11 @@ from .mask_position import MaskPosition from .message import ContentType, Message, ParseMode from .message_entity import MessageEntity, MessageEntityType from .order_info import OrderInfo +from .passport_data import PassportData +from .passport_element_error import PassportElementError, PassportElementErrorDataField, PassportElementErrorFile, \ + PassportElementErrorFiles, PassportElementErrorFrontSide, PassportElementErrorReverseSide, \ + PassportElementErrorSelfie +from .passport_file import PassportFile from .photo_size import PhotoSize from .pre_checkout_query import PreCheckoutQuery from .reply_keyboard import KeyboardButton, ReplyKeyboardMarkup, ReplyKeyboardRemove @@ -70,6 +77,8 @@ __all__ = ( 'Contact', 'ContentType', 'Document', + 'EncryptedCredentials', + 'EncryptedPassportElement', 'File', 'ForceReply', 'Game', @@ -116,6 +125,15 @@ __all__ = ( 'MessageEntity', 'MessageEntityType', 'OrderInfo', + 'PassportData', + 'PassportElementError', + 'PassportElementErrorDataField', + 'PassportElementErrorFile', + 'PassportElementErrorFiles', + 'PassportElementErrorFrontSide', + 'PassportElementErrorReverseSide', + 'PassportElementErrorSelfie', + 'PassportFile', 'ParseMode', 'PhotoSize', 'PreCheckoutQuery', diff --git a/aiogram/types/encrypted_credentials.py b/aiogram/types/encrypted_credentials.py new file mode 100644 index 00000000..d649c8d9 --- /dev/null +++ b/aiogram/types/encrypted_credentials.py @@ -0,0 +1,16 @@ +from . import base +from . import fields + + +class EncryptedCredentials(base.TelegramObject): + """ + Contains data required for decrypting and authenticating EncryptedPassportElement. + See the Telegram Passport Documentation for a complete description of the data decryption + and authentication processes. + + https://core.telegram.org/bots/api#encryptedcredentials + """ + + data: base.String = fields.Field() + hash: base.String = fields.Field() + secret: base.String = fields.Field() diff --git a/aiogram/types/encrypted_passport_element.py b/aiogram/types/encrypted_passport_element.py new file mode 100644 index 00000000..68630649 --- /dev/null +++ b/aiogram/types/encrypted_passport_element.py @@ -0,0 +1,21 @@ +from . import base +from . import fields +import typing +from .passport_file import PassportFile + + +class EncryptedPassportElement(base.TelegramObject): + """ + Contains information about documents or other Telegram Passport elements shared with the bot by the user. + + https://core.telegram.org/bots/api#encryptedpassportelement + """ + + encrypted_passport_element_type: base.String = fields.Field(alias="type") + data: base.String = fields.Field() + phone_number: base.String = fields.Field() + email: base.String = fields.Field() + files: typing.List[PassportFile] = fields.ListField(base=PassportFile) + front_side: PassportFile = fields.Field(base=PassportFile) + reverse_side: PassportFile = fields.Field(base=PassportFile) + selfie: PassportFile = fields.Field(base=PassportFile) diff --git a/aiogram/types/message.py b/aiogram/types/message.py index c107220a..ead27faa 100644 --- a/aiogram/types/message.py +++ b/aiogram/types/message.py @@ -14,6 +14,7 @@ from .game import Game from .invoice import Invoice from .location import Location from .message_entity import MessageEntity +from .passport_data import PassportData from .photo_size import PhotoSize from .sticker import Sticker from .successful_payment import SuccessfulPayment @@ -75,6 +76,7 @@ class Message(base.TelegramObject): invoice: Invoice = fields.Field(base=Invoice) successful_payment: SuccessfulPayment = fields.Field(base=SuccessfulPayment) connected_website: base.String = fields.Field() + passport_data: PassportData = fields.Field(base=PassportData) @property @functools.lru_cache() @@ -129,6 +131,8 @@ class Message(base.TelegramObject): return ContentType.DELETE_CHAT_PHOTO[0] elif self.group_chat_created: return ContentType.GROUP_CHAT_CREATED[0] + elif self.passport_data: + return ContentType.PASSPORT_DATA[0] else: return ContentType.UNKNOWN[0] @@ -774,6 +778,7 @@ class ContentType(helper.Helper): NEW_CHAT_PHOTO = helper.ListItem() # new_chat_photo DELETE_CHAT_PHOTO = helper.ListItem() # delete_chat_photo GROUP_CHAT_CREATED = helper.ListItem() # group_chat_created + PASSPORT_DATA = helper.ListItem() # passport_data UNKNOWN = helper.ListItem() # unknown ANY = helper.ListItem() # any diff --git a/aiogram/types/passport_data.py b/aiogram/types/passport_data.py new file mode 100644 index 00000000..06cbad1c --- /dev/null +++ b/aiogram/types/passport_data.py @@ -0,0 +1,16 @@ +from . import base +from . import fields +import typing +from .encrypted_passport_element import EncryptedPassportElement +from .encrypted_credentials import EncryptedCredentials + + +class PassportData(base.TelegramObject): + """ + Contains information about Telegram Passport data shared with the bot by the user. + + https://core.telegram.org/bots/api#passportdata + """ + + data: typing.List[EncryptedPassportElement] = fields.ListField(base=EncryptedPassportElement) + credentials: EncryptedCredentials = fields.Field(base=EncryptedCredentials) diff --git a/aiogram/types/passport_element_error.py b/aiogram/types/passport_element_error.py new file mode 100644 index 00000000..f673ba16 --- /dev/null +++ b/aiogram/types/passport_element_error.py @@ -0,0 +1,110 @@ +import typing + +from . import base +from . import fields + + +class PassportElementError(base.TelegramObject): + """ + This object represents an error in the Telegram Passport element which was submitted that + should be resolved by the user. + + https://core.telegram.org/bots/api#passportelementerror + """ + + source: base.String = fields.Field() + type: base.String = fields.Field() + message: base.String = fields.Field() + + +class PassportElementErrorDataField(PassportElementError): + """ + Represents an issue in one of the data fields that was provided by the user. + The error is considered resolved when the field's value changes. + + https://core.telegram.org/bots/api#passportelementerrordatafield + """ + + field_name: base.String = fields.Field() + data_hash: base.String = fields.Field() + + def __init__(self, source: base.String, type: base.String, field_name: base.String, + data_hash: base.String, message: base.String): + super(PassportElementErrorDataField, self).__init__(source=source, type=type, field_name=field_name, + data_hash=data_hash, message=message) + + +class PassportElementErrorFile(PassportElementError): + """ + Represents an issue with a document scan. + The error is considered resolved when the file with the document scan changes. + + https://core.telegram.org/bots/api#passportelementerrorfile + """ + + file_hash: base.String = fields.Field() + + def __init__(self, source: base.String, type: base.String, file_hash: base.String, message: base.String): + super(PassportElementErrorFile, self).__init__(source=source, type=type, file_hash=file_hash, + message=message) + + +class PassportElementErrorFiles(PassportElementError): + """ + Represents an issue with a list of scans. + The error is considered resolved when the list of files containing the scans changes. + + https://core.telegram.org/bots/api#passportelementerrorfiles + """ + + file_hashes: typing.List[base.String] = fields.ListField() + + def __init__(self, source: base.String, type: base.String, file_hashes: typing.List[base.String], + message: base.String): + super(PassportElementErrorFiles, self).__init__(source=source, type=type, file_hashes=file_hashes, + message=message) + + +class PassportElementErrorFrontSide(PassportElementError): + """ + Represents an issue with the front side of a document. + The error is considered resolved when the file with the front side of the document changes. + + https://core.telegram.org/bots/api#passportelementerrorfrontside + """ + + file_hash: base.String = fields.Field() + + def __init__(self, source: base.String, type: base.String, file_hash: base.String, message: base.String): + super(PassportElementErrorFrontSide, self).__init__(source=source, type=type, file_hash=file_hash, + message=message) + + +class PassportElementErrorReverseSide(PassportElementError): + """ + Represents an issue with the reverse side of a document. + The error is considered resolved when the file with reverse side of the document changes. + + https://core.telegram.org/bots/api#passportelementerrorreverseside + """ + + file_hash: base.String = fields.Field() + + def __init__(self, source: base.String, type: base.String, file_hash: base.String, message: base.String): + super(PassportElementErrorReverseSide, self).__init__(source=source, type=type, file_hash=file_hash, + message=message) + + +class PassportElementErrorSelfie(PassportElementError): + """ + Represents an issue with the selfie with a document. + The error is considered resolved when the file with the selfie changes. + + https://core.telegram.org/bots/api#passportelementerrorselfie + """ + + file_hash: base.String = fields.Field() + + def __init__(self, source: base.String, type: base.String, file_hash: base.String, message: base.String): + super(PassportElementErrorSelfie, self).__init__(source=source, type=type, file_hash=file_hash, + message=message) diff --git a/aiogram/types/passport_file.py b/aiogram/types/passport_file.py new file mode 100644 index 00000000..f00e80c7 --- /dev/null +++ b/aiogram/types/passport_file.py @@ -0,0 +1,15 @@ +from . import base +from . import fields + + +class PassportFile(base.TelegramObject): + """ + This object represents a file uploaded to Telegram Passport. + Currently all Telegram Passport files are in JPEG format when decrypted and don't exceed 10MB. + + https://core.telegram.org/bots/api#passportfile + """ + + file_id: base.String = fields.Field() + file_size: base.Integer = fields.Field() + file_date: base.Integer = fields.Field() From 0a847c4de70731316cb13de34bedca68627c1dad Mon Sep 17 00:00:00 2001 From: Alex Root Junior Date: Sun, 29 Jul 2018 02:05:07 +0300 Subject: [PATCH 07/17] Added the field thumb to the Audio object to contain the thumbnail of the album cover to which the music file belongs. --- aiogram/types/audio.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/aiogram/types/audio.py b/aiogram/types/audio.py index ed323f81..9423d02c 100644 --- a/aiogram/types/audio.py +++ b/aiogram/types/audio.py @@ -1,6 +1,7 @@ from . import base from . import fields from . import mixins +from .photo_size import PhotoSize class Audio(base.TelegramObject, mixins.Downloadable): @@ -15,3 +16,4 @@ class Audio(base.TelegramObject, mixins.Downloadable): title: base.String = fields.Field() mime_type: base.String = fields.Field() file_size: base.Integer = fields.Field() + thumb: PhotoSize = fields.Field(base=PhotoSize) From f635b5c96555b2222b55412fe15f69d4b5182ebe Mon Sep 17 00:00:00 2001 From: Alex Root Junior Date: Sun, 29 Jul 2018 02:17:17 +0300 Subject: [PATCH 08/17] Added vCard support when sharing contacts --- aiogram/bot/bot.py | 3 +++ aiogram/types/contact.py | 1 + aiogram/types/inline_query_result.py | 1 + aiogram/types/input_message_content.py | 1 + 4 files changed, 6 insertions(+) diff --git a/aiogram/bot/bot.py b/aiogram/bot/bot.py index 868b7aa0..f249c78e 100644 --- a/aiogram/bot/bot.py +++ b/aiogram/bot/bot.py @@ -706,6 +706,7 @@ class Bot(BaseBot): async def send_contact(self, chat_id: typing.Union[base.Integer, base.String], phone_number: base.String, first_name: base.String, last_name: typing.Union[base.String, None] = None, + vcard: typing.Union[base.String, None] = None, disable_notification: typing.Union[base.Boolean, None] = None, reply_to_message_id: typing.Union[base.Integer, None] = None, reply_markup: typing.Union[types.InlineKeyboardMarkup, @@ -725,6 +726,8 @@ class Bot(BaseBot): :type first_name: :obj:`base.String` :param last_name: Contact's last name :type last_name: :obj:`typing.Union[base.String, None]` + :param vcard: vcard + :type vcard: :obj:`typing.Union[base.String, None]` :param disable_notification: Sends the message silently. Users will receive a notification with no sound. :type disable_notification: :obj:`typing.Union[base.Boolean, None]` :param reply_to_message_id: If the message is a reply, ID of the original message diff --git a/aiogram/types/contact.py b/aiogram/types/contact.py index 842d6044..b70045b9 100644 --- a/aiogram/types/contact.py +++ b/aiogram/types/contact.py @@ -12,6 +12,7 @@ class Contact(base.TelegramObject): first_name: base.String = fields.Field() last_name: base.String = fields.Field() user_id: base.Integer = fields.Field() + vcard: base.String = fields.Field() @property def full_name(self): diff --git a/aiogram/types/inline_query_result.py b/aiogram/types/inline_query_result.py index 62936961..1c6c0007 100644 --- a/aiogram/types/inline_query_result.py +++ b/aiogram/types/inline_query_result.py @@ -441,6 +441,7 @@ class InlineQueryResultContact(InlineQueryResult): phone_number: base.String = fields.Field() first_name: base.String = fields.Field() last_name: base.String = fields.Field() + vcard: base.String = fields.Field() input_message_content: InputMessageContent = fields.Field(base=InputMessageContent) thumb_url: base.String = fields.Field() thumb_width: base.Integer = fields.Field() diff --git a/aiogram/types/input_message_content.py b/aiogram/types/input_message_content.py index 88f8a74f..736a4454 100644 --- a/aiogram/types/input_message_content.py +++ b/aiogram/types/input_message_content.py @@ -27,6 +27,7 @@ class InputContactMessageContent(InputMessageContent): phone_number: base.String = fields.Field() first_name: base.String = fields.Field() last_name: base.String = fields.Field() + vcard: base.String = fields.Field() def __init__(self, phone_number: base.String, first_name: typing.Optional[base.String] = None, From a8f6f3adf5fdafec23e0d67038d01e55d8af61a0 Mon Sep 17 00:00:00 2001 From: Alex Root Junior Date: Sun, 29 Jul 2018 02:36:27 +0300 Subject: [PATCH 09/17] Added the method editMessageMedia and new types InputMediaAnimation, InputMediaAudio, and InputMediaDocument. --- aiogram/bot/api.py | 1 + aiogram/bot/bot.py | 48 +++++++++++ aiogram/types/__init__.py | 7 +- aiogram/types/input_media.py | 153 ++++++++++++++++++++++++++++++++++- 4 files changed, 206 insertions(+), 3 deletions(-) diff --git a/aiogram/bot/api.py b/aiogram/bot/api.py index a9c6978a..7adb3160 100644 --- a/aiogram/bot/api.py +++ b/aiogram/bot/api.py @@ -218,6 +218,7 @@ class Methods(Helper): # Updating messages EDIT_MESSAGE_TEXT = Item() # editMessageText EDIT_MESSAGE_CAPTION = Item() # editMessageCaption + EDIT_MESSAGE_MEDIA = Item() # editMessageMedia EDIT_MESSAGE_REPLY_MARKUP = Item() # editMessageReplyMarkup DELETE_MESSAGE = Item() # deleteMessage diff --git a/aiogram/bot/bot.py b/aiogram/bot/bot.py index f249c78e..513f6bfe 100644 --- a/aiogram/bot/bot.py +++ b/aiogram/bot/bot.py @@ -1344,6 +1344,54 @@ class Bot(BaseBot): return types.Message(**result) + async def edit_message_media(self, + media: types.InputMedia, + chat_id: typing.Union[typing.Union[base.Integer, base.String], None] = None, + message_id: typing.Union[base.Integer, None] = None, + inline_message_id: typing.Union[base.String, None] = None, + reply_markup: typing.Union[types.InlineKeyboardMarkup, None] = None, + ) -> typing.Union[types.Message, base.Boolean]: + """ + Use this method to edit audio, document, photo, or video messages. + If a message is a part of a message album, then it can be edited only to a photo or a video. + Otherwise, message type can be changed arbitrarily. + When inline message is edited, new file can't be uploaded. + Use previously uploaded file via its file_id or specify a URL. + + On success, if the edited message was sent by the bot, + the edited Message is returned, otherwise True is returned. + + Source https://core.telegram.org/bots/api#editmessagemedia + + :param chat_id: Required if inline_message_id is not specified. + :type chat_id: :obj:`typing.Union[typing.Union[base.Integer, base.String], None]` + :param message_id: Required if inline_message_id is not specified. Identifier of the sent message + :type message_id: :obj:`typing.Union[base.Integer, None]` + :param inline_message_id: Required if chat_id and message_id are not specified. Identifier of the inline message + :type inline_message_id: :obj:`typing.Union[base.String, None]` + :param media: A JSON-serialized object for a new media content of the message + :type media: :obj:`types.InputMedia` + :param reply_markup: A JSON-serialized object for a new inline keyboard. + :type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup, None]` + :return: On success, if the edited message was sent by the bot, the edited Message is returned, otherwise True is returned. + :rtype: :obj:`typing.Union[types.Message, base.Boolean]` + """ + + if isinstance(media, types.InputMedia) and media.file: + files = {media.attachment_key: media.file} + else: + files = None + + reply_markup = prepare_arg(reply_markup) + payload = generate_payload(**locals()) + + result = await self.request(api.Methods.EDIT_MESSAGE_MEDIA, payload, files) + + if isinstance(result, bool): + return result + + return types.Message(**result) + async def edit_message_reply_markup(self, chat_id: typing.Union[base.Integer, base.String, None] = None, message_id: typing.Union[base.Integer, None] = None, diff --git a/aiogram/types/__init__.py b/aiogram/types/__init__.py index 08e3118c..943efb4b 100644 --- a/aiogram/types/__init__.py +++ b/aiogram/types/__init__.py @@ -26,7 +26,8 @@ from .inline_query_result import InlineQueryResult, InlineQueryResultArticle, In InlineQueryResultGame, InlineQueryResultGif, InlineQueryResultLocation, InlineQueryResultMpeg4Gif, \ InlineQueryResultPhoto, InlineQueryResultVenue, InlineQueryResultVideo, InlineQueryResultVoice from .input_file import InputFile -from .input_media import InputMediaPhoto, InputMediaVideo, MediaGroup +from .input_media import InputMedia, InputMediaAnimation, InputMediaAudio, InputMediaDocument, InputMediaPhoto, \ + InputMediaVideo, MediaGroup from .input_message_content import InputContactMessageContent, InputLocationMessageContent, InputMessageContent, \ InputTextMessageContent, InputVenueMessageContent from .invoice import Invoice @@ -109,6 +110,10 @@ __all__ = ( 'InlineQueryResultVoice', 'InputContactMessageContent', 'InputFile', + 'InputMedia', + 'InputMediaAnimation', + 'InputMediaAudio', + 'InputMediaDocument', 'InputMediaPhoto', 'InputMediaVideo', 'InputLocationMessageContent', diff --git a/aiogram/types/input_media.py b/aiogram/types/input_media.py index 2ae4da4f..1f68e632 100644 --- a/aiogram/types/input_media.py +++ b/aiogram/types/input_media.py @@ -21,6 +21,7 @@ class InputMedia(base.TelegramObject): """ type: base.String = fields.Field(default='photo') media: base.String = fields.Field() + thumb: typing.Union[base.InputFile, base.String] = fields.Field() caption: base.String = fields.Field() parse_mode: base.Boolean = fields.Field() @@ -51,6 +52,77 @@ class InputMedia(base.TelegramObject): self.conf['attachment_key'] = value +class InputMediaAnimation(InputMedia): + """ + Represents an animation file (GIF or H.264/MPEG-4 AVC video without sound) to be sent. + + https://core.telegram.org/bots/api#inputmediaanimation + """ + + width: base.Integer = fields.Field() + height: base.Integer = fields.Field() + duration: base.Integer = fields.Field() + + def __init__(self, media: base.InputFile, + thumb: typing.Union[base.InputFile, base.String] = None, + caption: base.String = None, + width: base.Integer = None, height: base.Integer = None, duration: base.Integer = None, + parse_mode: base.Boolean = None, **kwargs): + super(InputMediaAnimation, self).__init__(type='animation', media=media, thumb=thumb, caption=caption, + width=width, height=height, duration=duration, + parse_mode=parse_mode, conf=kwargs) + + if isinstance(media, (io.IOBase, InputFile)): + self.file = media + + +class InputMediaDocument(InputMedia): + """ + Represents a photo to be sent. + + https://core.telegram.org/bots/api#inputmediadocument + """ + + def __init__(self, media: base.InputFile, thumb: typing.Union[base.InputFile, base.String] = None, + caption: base.String = None, parse_mode: base.Boolean = None, **kwargs): + super(InputMediaDocument, self).__init__(type='document', media=media, thumb=thumb, + caption=caption, parse_mode=parse_mode, + conf=kwargs) + + if isinstance(media, (io.IOBase, InputFile)): + self.file = media + + +class InputMediaAudio(InputMedia): + """ + Represents an animation file (GIF or H.264/MPEG-4 AVC video without sound) to be sent. + + https://core.telegram.org/bots/api#inputmediaanimation + """ + + width: base.Integer = fields.Field() + height: base.Integer = fields.Field() + duration: base.Integer = fields.Field() + performer: base.String = fields.Field() + title: base.String = fields.Field() + + def __init__(self, media: base.InputFile, + thumb: typing.Union[base.InputFile, base.String] = None, + caption: base.String = None, + width: base.Integer = None, height: base.Integer = None, + duration: base.Integer = None, + performer: base.String = None, + title: base.String = None, + parse_mode: base.Boolean = None, **kwargs): + super(InputMediaAudio, self).__init__(type='audio', media=media, thumb=thumb, caption=caption, + width=width, height=height, duration=duration, + performer=performer, title=title, + parse_mode=parse_mode, conf=kwargs) + + if isinstance(media, (io.IOBase, InputFile)): + self.file = media + + class InputMediaPhoto(InputMedia): """ Represents a photo to be sent. @@ -58,8 +130,10 @@ class InputMediaPhoto(InputMedia): https://core.telegram.org/bots/api#inputmediaphoto """ - def __init__(self, media: base.InputFile, caption: base.String = None, parse_mode: base.Boolean = None, **kwargs): - super(InputMediaPhoto, self).__init__(type='photo', media=media, caption=caption, parse_mode=parse_mode, + def __init__(self, media: base.InputFile, thumb: typing.Union[base.InputFile, base.String] = None, + caption: base.String = None, parse_mode: base.Boolean = None, **kwargs): + super(InputMediaPhoto, self).__init__(type='photo', media=media, thumb=thumb, + caption=caption, parse_mode=parse_mode, conf=kwargs) if isinstance(media, (io.IOBase, InputFile)): @@ -126,14 +200,89 @@ class MediaGroup(base.TelegramObject): media = InputMediaPhoto(**media) elif media_type == 'video': media = InputMediaVideo(**media) + # elif media_type == 'document': + # media = InputMediaDocument(**media) + # elif media_type == 'audio': + # media = InputMediaAudio(**media) + # elif media_type == 'animation': + # media = InputMediaAnimation(**media) else: raise TypeError(f"Invalid media type '{media_type}'!") elif not isinstance(media, InputMedia): raise TypeError(f"Media must be an instance of InputMedia or dict, not {type(media).__name__}") + elif media.type in ['document', 'audio', 'animation']: + raise ValueError(f"This type of media is not supported by media groups!") + self.media.append(media) + ''' + def attach_animation(self, animation: base.InputFile, + thumb: typing.Union[base.InputFile, base.String] = None, + caption: base.String = None, + width: base.Integer = None, height: base.Integer = None, duration: base.Integer = None, + parse_mode: base.Boolean = None): + """ + Attach animation + + :param animation: + :param thumb: + :param caption: + :param width: + :param height: + :param duration: + :param parse_mode: + """ + if not isinstance(animation, InputMedia): + animation = InputMediaAnimation(media=animation, thumb=thumb, caption=caption, + width=width, height=height, duration=duration, + parse_mode=parse_mode) + self.attach(animation) + + def attach_audio(self, audio: base.InputFile, + thumb: typing.Union[base.InputFile, base.String] = None, + caption: base.String = None, + width: base.Integer = None, height: base.Integer = None, + duration: base.Integer = None, + performer: base.String = None, + title: base.String = None, + parse_mode: base.Boolean = None): + """ + Attach animation + + :param audio: + :param thumb: + :param caption: + :param width: + :param height: + :param duration: + :param performer: + :param title: + :param parse_mode: + """ + if not isinstance(audio, InputMedia): + audio = InputMediaAudio(media=audio, thumb=thumb, caption=caption, + width=width, height=height, duration=duration, + performer=performer, title=title, + parse_mode=parse_mode) + self.attach(audio) + + def attach_document(self, document: base.InputFile, thumb: typing.Union[base.InputFile, base.String] = None, + caption: base.String = None, parse_mode: base.Boolean = None): + """ + Attach document + + :param parse_mode: + :param caption: + :param thumb: + :param document: + """ + if not isinstance(document, InputMedia): + document = InputMediaDocument(media=document, thumb=thumb, caption=caption, parse_mode=parse_mode) + self.attach(document) + ''' + def attach_photo(self, photo: typing.Union[InputMediaPhoto, base.InputFile], caption: base.String = None): """ From f006e53ddca283468258a35929018a7f8a8ec74b Mon Sep 17 00:00:00 2001 From: Alex Root Junior Date: Sun, 29 Jul 2018 02:55:24 +0300 Subject: [PATCH 10/17] Added support for attaching custom thumbnails to uploaded files. Note: is not work properly and will be fixed in 2.0 --- aiogram/bot/bot.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/aiogram/bot/bot.py b/aiogram/bot/bot.py index 513f6bfe..6c0fd812 100644 --- a/aiogram/bot/bot.py +++ b/aiogram/bot/bot.py @@ -278,6 +278,7 @@ class Bot(BaseBot): duration: typing.Union[base.Integer, None] = None, performer: typing.Union[base.String, None] = None, title: typing.Union[base.String, None] = None, + thumb: typing.Union[base.InputFile, base.String, None] = None, disable_notification: typing.Union[base.Boolean, None] = None, reply_to_message_id: typing.Union[base.Integer, None] = None, reply_markup: typing.Union[types.InlineKeyboardMarkup, @@ -306,6 +307,8 @@ class Bot(BaseBot): :param performer: Performer :type performer: :obj:`typing.Union[base.String, None]` :param title: Track name + :param thumb: Thumbnail of the file sent. + :param :obj:`typing.Union[base.InputFile, base.String, None]` :type title: :obj:`typing.Union[base.String, None]` :param disable_notification: Sends the message silently. Users will receive a notification with no sound. :type disable_notification: :obj:`typing.Union[base.Boolean, None]` @@ -328,6 +331,7 @@ class Bot(BaseBot): async def send_document(self, chat_id: typing.Union[base.Integer, base.String], document: typing.Union[base.InputFile, base.String], + thumb: typing.Union[base.InputFile, base.String, None] = None, caption: typing.Union[base.String, None] = None, parse_mode: typing.Union[base.String, None] = None, disable_notification: typing.Union[base.Boolean, None] = None, @@ -347,6 +351,8 @@ class Bot(BaseBot): :type chat_id: :obj:`typing.Union[base.Integer, base.String]` :param document: File to send. :type document: :obj:`typing.Union[base.InputFile, base.String]` + :param thumb: Thumbnail of the file sent. + :param :obj:`typing.Union[base.InputFile, base.String, None]` :param caption: Document caption (may also be used when resending documents by file_id), 0-200 characters :type caption: :obj:`typing.Union[base.String, None]` :param parse_mode: Send Markdown or HTML, if you want Telegram apps to show bold, italic, @@ -376,6 +382,7 @@ class Bot(BaseBot): duration: typing.Union[base.Integer, None] = None, width: typing.Union[base.Integer, None] = None, height: typing.Union[base.Integer, None] = None, + thumb: typing.Union[base.InputFile, base.String, None] = None, caption: typing.Union[base.String, None] = None, parse_mode: typing.Union[base.String, None] = None, supports_streaming: typing.Union[base.Boolean, None] = None, @@ -401,6 +408,8 @@ class Bot(BaseBot): :type width: :obj:`typing.Union[base.Integer, None]` :param height: Video height :type height: :obj:`typing.Union[base.Integer, None]` + :param thumb: Thumbnail of the file sent. + :param :obj:`typing.Union[base.InputFile, base.String, None]` :param caption: Video caption (may also be used when resending videos by file_id), 0-200 characters :type caption: :obj:`typing.Union[base.String, None]` :param parse_mode: Send Markdown or HTML, if you want Telegram apps to show bold, italic, @@ -481,6 +490,7 @@ class Bot(BaseBot): video_note: typing.Union[base.InputFile, base.String], duration: typing.Union[base.Integer, None] = None, length: typing.Union[base.Integer, None] = None, + thumb: typing.Union[base.InputFile, base.String, None] = None, disable_notification: typing.Union[base.Boolean, None] = None, reply_to_message_id: typing.Union[base.Integer, None] = None, reply_markup: typing.Union[types.InlineKeyboardMarkup, @@ -501,6 +511,8 @@ class Bot(BaseBot): :type duration: :obj:`typing.Union[base.Integer, None]` :param length: Video width and height :type length: :obj:`typing.Union[base.Integer, None]` + :param thumb: Thumbnail of the file sent. + :param :obj:`typing.Union[base.InputFile, base.String, None]` :param disable_notification: Sends the message silently. Users will receive a notification with no sound. :type disable_notification: :obj:`typing.Union[base.Boolean, None]` :param reply_to_message_id: If the message is a reply, ID of the original message From 8df8792f1c3909461ee0c6f351b18452b95376ad Mon Sep 17 00:00:00 2001 From: Alex Root Junior Date: Sun, 29 Jul 2018 02:58:17 +0300 Subject: [PATCH 11/17] Added the method sendAnimation --- aiogram/bot/api.py | 1 + aiogram/bot/bot.py | 54 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+) diff --git a/aiogram/bot/api.py b/aiogram/bot/api.py index 7adb3160..49c7ef63 100644 --- a/aiogram/bot/api.py +++ b/aiogram/bot/api.py @@ -184,6 +184,7 @@ class Methods(Helper): SEND_AUDIO = Item() # sendAudio SEND_DOCUMENT = Item() # sendDocument SEND_VIDEO = Item() # sendVideo + SEND_ANIMATION = Item() # sendAnimation SEND_VOICE = Item() # sendVoice SEND_VIDEO_NOTE = Item() # sendVideoNote SEND_MEDIA_GROUP = Item() # sendMediaGroup diff --git a/aiogram/bot/bot.py b/aiogram/bot/bot.py index 6c0fd812..de390ef7 100644 --- a/aiogram/bot/bot.py +++ b/aiogram/bot/bot.py @@ -436,6 +436,60 @@ class Bot(BaseBot): return types.Message(**result) + async def send_animation(self, + chat_id: typing.Union[base.Integer, base.String], + animation: typing.Union[base.InputFile, base.String], + duration: typing.Union[base.Integer, None] = None, + width: typing.Union[base.Integer, None] = None, + height: typing.Union[base.Integer, None] = None, + thumb: typing.Union[typing.Union[base.InputFile, base.String], None] = None, + caption: typing.Union[base.String, None] = None, + parse_mode: typing.Union[base.String, None] = None, + disable_notification: typing.Union[base.Boolean, None] = None, + reply_to_message_id: typing.Union[base.Integer, None] = None, + reply_markup: typing.Union[typing.Union[types.InlineKeyboardMarkup, + types.ReplyKeyboardMarkup, + types.ReplyKeyboardRemove, + types.ForceReply], None] = None,) -> types.Message: + """ + Use this method to send animation files (GIF or H.264/MPEG-4 AVC video without sound). + + On success, the sent Message is returned. + Bots can currently send animation files of up to 50 MB in size, this limit may be changed in the future. + + Source https://core.telegram.org/bots/api#sendanimation + + :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 animation: Animation to send. Pass a file_id as String to send an animation that exists on the Telegram servers (recommended), pass an HTTP URL as a String for Telegram to get an animation from the Internet, or upload a new animation using multipart/form-data. + :type animation: :obj:`typing.Union[base.InputFile, base.String]` + :param duration: Duration of sent animation in seconds + :type duration: :obj:`typing.Union[base.Integer, None]` + :param width: Animation width + :type width: :obj:`typing.Union[base.Integer, None]` + :param height: Animation height + :type height: :obj:`typing.Union[base.Integer, None]` + :param thumb: Thumbnail of the file sent. The thumbnail should be in JPEG format and less than 200 kB in size. A thumbnail‘s width and height should not exceed 90. 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://” if the thumbnail was uploaded using multipart/form-data under . + :type thumb: :obj:`typing.Union[typing.Union[base.InputFile, base.String], None]` + :param caption: Animation caption (may also be used when resending animation by file_id), 0-200 characters + :type caption: :obj:`typing.Union[base.String, None]` + :param parse_mode: Send Markdown or HTML, if you want Telegram apps to show bold, italic, fixed-width text or inline URLs in the media caption. + :type parse_mode: :obj:`typing.Union[base.String, None]` + :param disable_notification: Sends the message silently. Users will receive a notification with no sound. + :type disable_notification: :obj:`typing.Union[base.Boolean, None]` + :param reply_to_message_id: If the message is a reply, ID of the original message + :type reply_to_message_id: :obj:`typing.Union[base.Integer, None]` + :param reply_markup: Additional interface options. A JSON-serialized object for an inline keyboard, custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user. + :type reply_markup: :obj:`typing.Union[typing.Union[types.InlineKeyboardMarkup, types.ReplyKeyboardMarkup, types.ReplyKeyboardRemove, types.ForceReply], None]` + :return: On success, the sent Message is returned. + :rtype: :obj:`types.Message` + """ + reply_markup = prepare_arg(reply_markup) + payload = generate_payload(**locals(), exclude=["animation"]) + result = await self.send_file("animation", api.Methods.SEND_ANIMATION, thumb, payload) + + return types.Message(**result) + async def send_voice(self, chat_id: typing.Union[base.Integer, base.String], voice: typing.Union[base.InputFile, base.String], caption: typing.Union[base.String, None] = None, From a43e196d6d47490d0b11420d84aa261542fc5f3d Mon Sep 17 00:00:00 2001 From: Alex Root Junior Date: Sun, 29 Jul 2018 03:08:33 +0300 Subject: [PATCH 12/17] Added the new field foursquare_type to the objects Venue, InlineQueryResultVenue and InputVenueMessageContent, and the parameter foursquare_type to the sendVenue method. --- aiogram/bot/bot.py | 3 +++ aiogram/types/inline_query_result.py | 14 ++++++++++---- aiogram/types/venue.py | 2 ++ 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/aiogram/bot/bot.py b/aiogram/bot/bot.py index de390ef7..7cc0606a 100644 --- a/aiogram/bot/bot.py +++ b/aiogram/bot/bot.py @@ -730,6 +730,7 @@ class Bot(BaseBot): latitude: base.Float, longitude: base.Float, title: base.String, address: base.String, foursquare_id: typing.Union[base.String, None] = None, + foursquare_type: typing.Union[base.String, None] = None, disable_notification: typing.Union[base.Boolean, None] = None, reply_to_message_id: typing.Union[base.Integer, None] = None, reply_markup: typing.Union[types.InlineKeyboardMarkup, @@ -753,6 +754,8 @@ class Bot(BaseBot): :type address: :obj:`base.String` :param foursquare_id: Foursquare identifier of the venue :type foursquare_id: :obj:`typing.Union[base.String, None]` + :param foursquare_type: Foursquare type of the venue, if known. + :type foursquare_type: :obj:`typing.Union[base.String, None]` :param disable_notification: Sends the message silently. Users will receive a notification with no sound. :type disable_notification: :obj:`typing.Union[base.Boolean, None]` :param reply_to_message_id: If the message is a reply, ID of the original message diff --git a/aiogram/types/inline_query_result.py b/aiogram/types/inline_query_result.py index 1c6c0007..a80352d7 100644 --- a/aiogram/types/inline_query_result.py +++ b/aiogram/types/inline_query_result.py @@ -405,6 +405,7 @@ class InlineQueryResultVenue(InlineQueryResult): thumb_url: base.String = fields.Field() thumb_width: base.Integer = fields.Field() thumb_height: base.Integer = fields.Field() + foursquare_type: base.String = fields.Field() def __init__(self, *, id: base.String, @@ -417,12 +418,14 @@ class InlineQueryResultVenue(InlineQueryResult): input_message_content: typing.Optional[InputMessageContent] = None, thumb_url: typing.Optional[base.String] = None, thumb_width: typing.Optional[base.Integer] = None, - thumb_height: typing.Optional[base.Integer] = None): + thumb_height: typing.Optional[base.Integer] = None, + foursquare_type: typing.Optional[base.String] = None): super(InlineQueryResultVenue, self).__init__(id=id, latitude=latitude, longitude=longitude, title=title, address=address, foursquare_id=foursquare_id, reply_markup=reply_markup, input_message_content=input_message_content, thumb_url=thumb_url, - thumb_width=thumb_width, thumb_height=thumb_height) + thumb_width=thumb_width, thumb_height=thumb_height, + foursquare_type=foursquare_type) class InlineQueryResultContact(InlineQueryResult): @@ -446,6 +449,7 @@ class InlineQueryResultContact(InlineQueryResult): thumb_url: base.String = fields.Field() thumb_width: base.Integer = fields.Field() thumb_height: base.Integer = fields.Field() + foursquare_type: base.String = fields.Field() def __init__(self, *, id: base.String, @@ -456,12 +460,14 @@ class InlineQueryResultContact(InlineQueryResult): input_message_content: typing.Optional[InputMessageContent] = None, thumb_url: typing.Optional[base.String] = None, thumb_width: typing.Optional[base.Integer] = None, - thumb_height: typing.Optional[base.Integer] = None): + thumb_height: typing.Optional[base.Integer] = None, + foursquare_type: typing.Optional[base.String] = None): super(InlineQueryResultContact, self).__init__(id=id, phone_number=phone_number, first_name=first_name, last_name=last_name, reply_markup=reply_markup, input_message_content=input_message_content, thumb_url=thumb_url, - thumb_width=thumb_width, thumb_height=thumb_height) + thumb_width=thumb_width, thumb_height=thumb_height, + foursquare_type=foursquare_type) class InlineQueryResultGame(InlineQueryResult): diff --git a/aiogram/types/venue.py b/aiogram/types/venue.py index 38f28cf9..1b420d57 100644 --- a/aiogram/types/venue.py +++ b/aiogram/types/venue.py @@ -13,3 +13,5 @@ class Venue(base.TelegramObject): title: base.String = fields.Field() address: base.String = fields.Field() foursquare_id: base.String = fields.Field() + foursquare_type: base.String = fields.Field() + From 9477e9af862bbb81a247ade7c396c491c0a01072 Mon Sep 17 00:00:00 2001 From: Alex Root Junior Date: Sun, 29 Jul 2018 03:12:21 +0300 Subject: [PATCH 13/17] Implement Gone request handler --- aiogram/dispatcher/webhook.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/aiogram/dispatcher/webhook.py b/aiogram/dispatcher/webhook.py index ca717202..970fd655 100644 --- a/aiogram/dispatcher/webhook.py +++ b/aiogram/dispatcher/webhook.py @@ -7,6 +7,7 @@ import typing from typing import Dict, List, Optional, Union from aiohttp import web +from aiohttp.web_exceptions import HTTPGone from .. import types from ..bot import api @@ -245,6 +246,19 @@ class WebhookRequestHandler(web.View): context.set_value('TELEGRAM_IP', ip_address) +class GoneRequestHandler(web.View): + """ + If a webhook returns the HTTP error 410 Gone for all requests for more than 23 hours successively, + it can be automatically removed. + """ + + async def get(self): + raise HTTPGone() + + async def post(self): + raise HTTPGone() + + def configure_app(dispatcher, app: web.Application, path=DEFAULT_WEB_PATH): """ You can prepare web.Application for working with webhook handler. From 94d3dd4b13595781475210a19b7b5ca89a787d8c Mon Sep 17 00:00:00 2001 From: Alex Root Junior Date: Sun, 29 Jul 2018 03:17:44 +0300 Subject: [PATCH 14/17] You can now use the Retry-After response header to configure the delay after which the Bot API will retry the request after an unsuccessful response from a webhook. --- aiogram/dispatcher/webhook.py | 10 ++++++++-- aiogram/utils/executor.py | 11 ++++++++--- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/aiogram/dispatcher/webhook.py b/aiogram/dispatcher/webhook.py index 970fd655..83c5fb74 100644 --- a/aiogram/dispatcher/webhook.py +++ b/aiogram/dispatcher/webhook.py @@ -128,8 +128,14 @@ class WebhookRequestHandler(web.View): response = self.get_response(results) if response: - return response.get_web_response() - return web.Response(text='ok') + web_response = response.get_web_response() + else: + web_response = web.Response(text='ok') + + if self.request.app['RETRY_AFTER']: + web_response.headers['Retry-After'] = self.request.app['RETRY_AFTER'] + + return web_response async def get(self): self.validate_ip() diff --git a/aiogram/utils/executor.py b/aiogram/utils/executor.py index e114e534..e3d8aa1f 100644 --- a/aiogram/utils/executor.py +++ b/aiogram/utils/executor.py @@ -40,7 +40,7 @@ def start_polling(dispatcher, *, loop=None, skip_updates=False, reset_webhook=Tr def start_webhook(dispatcher, webhook_path, *, loop=None, skip_updates=None, - on_startup=None, on_shutdown=None, check_ip=False, **kwargs): + on_startup=None, on_shutdown=None, check_ip=False, retry_after=None, **kwargs): """ Start bot in webhook mode @@ -54,7 +54,8 @@ def start_webhook(dispatcher, webhook_path, *, loop=None, skip_updates=None, :param kwargs: :return: """ - executor = Executor(dispatcher, skip_updates=skip_updates, check_ip=check_ip, loop=loop) + executor = Executor(dispatcher, skip_updates=skip_updates, check_ip=check_ip, retry_after=retry_after, + loop=loop) _setup_callbacks(executor, on_startup, on_shutdown) executor.start_webhook(webhook_path, **kwargs) @@ -84,12 +85,13 @@ class Executor: Main executor class """ - def __init__(self, dispatcher, skip_updates=None, check_ip=False, loop=None): + def __init__(self, dispatcher, skip_updates=None, check_ip=False, retry_after=None, loop=None): if loop is None: loop = dispatcher.loop self.dispatcher = dispatcher self.skip_updates = skip_updates self.check_ip = check_ip + self.retry_after = retry_after self.loop = loop self._identity = secrets.token_urlsafe(16) @@ -186,6 +188,9 @@ class Executor: if app is None: self._web_app = app = web.Application() + if self.retry_after: + app['RETRY_AFTER'] = self.retry_after + if self._identity == app.get(self._identity): # App is already configured return From db94260df02864ffa6985174b0064369b797741e Mon Sep 17 00:00:00 2001 From: Oleg A Date: Tue, 31 Jul 2018 11:51:03 +0300 Subject: [PATCH 15/17] MessageCantBeDeleted exception If bot without admin rights try to delete message it receive BadRequest: message can't be deleted --- aiogram/utils/exceptions.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/aiogram/utils/exceptions.py b/aiogram/utils/exceptions.py index 43692247..3e4a7dba 100644 --- a/aiogram/utils/exceptions.py +++ b/aiogram/utils/exceptions.py @@ -10,6 +10,7 @@ TelegramAPIError MessageIdentifierNotSpecified MessageTextIsEmpty MessageCantBeEdited + MessageCantBeDeleted MessageToEditNotFound ToMuchMessages ObjectExpectedAsReplyMarkup @@ -171,6 +172,10 @@ class MessageTextIsEmpty(MessageError): class MessageCantBeEdited(MessageError): match = 'message can\'t be edited' + + +class MessageCantBeDeleted(MessageError): + match = 'message can\'t be deleted' class MessageToEditNotFound(MessageError): From 13408426db7e7ea33096f563c062c2292db55d3a Mon Sep 17 00:00:00 2001 From: Alex Root Junior Date: Fri, 3 Aug 2018 00:11:43 +0300 Subject: [PATCH 16/17] Fix webhook (retry-after) --- aiogram/dispatcher/webhook.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aiogram/dispatcher/webhook.py b/aiogram/dispatcher/webhook.py index 83c5fb74..c9e39a4f 100644 --- a/aiogram/dispatcher/webhook.py +++ b/aiogram/dispatcher/webhook.py @@ -132,7 +132,7 @@ class WebhookRequestHandler(web.View): else: web_response = web.Response(text='ok') - if self.request.app['RETRY_AFTER']: + if self.request.app.get('RETRY_AFTER', None): web_response.headers['Retry-After'] = self.request.app['RETRY_AFTER'] return web_response From c73888bd9849837cf1741c19949f2872e938efd2 Mon Sep 17 00:00:00 2001 From: Alex Root Junior Date: Fri, 3 Aug 2018 00:12:38 +0300 Subject: [PATCH 17/17] Rename `encrypted_passport_element_type` -> `type` --- aiogram/types/encrypted_passport_element.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aiogram/types/encrypted_passport_element.py b/aiogram/types/encrypted_passport_element.py index 68630649..bc7b212b 100644 --- a/aiogram/types/encrypted_passport_element.py +++ b/aiogram/types/encrypted_passport_element.py @@ -11,7 +11,7 @@ class EncryptedPassportElement(base.TelegramObject): https://core.telegram.org/bots/api#encryptedpassportelement """ - encrypted_passport_element_type: base.String = fields.Field(alias="type") + type: base.String = fields.Field() data: base.String = fields.Field() phone_number: base.String = fields.Field() email: base.String = fields.Field()