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): """