From 04c25b760fabeadf0f76a11674d6fe5c697dca25 Mon Sep 17 00:00:00 2001 From: Ling-ex Date: Thu, 24 Apr 2025 20:19:32 +0000 Subject: [PATCH] Add progress and progress_args parameters to Client.send_media_group and Message.reply_media_group Signed-off-by: Ling-ex --- pyrogram/methods/messages/send_media_group.py | 217 +++++++++++------- pyrogram/types/messages_and_media/message.py | 30 ++- 2 files changed, 158 insertions(+), 89 deletions(-) diff --git a/pyrogram/methods/messages/send_media_group.py b/pyrogram/methods/messages/send_media_group.py index bbdeeac5..ca66cab6 100644 --- a/pyrogram/methods/messages/send_media_group.py +++ b/pyrogram/methods/messages/send_media_group.py @@ -22,7 +22,12 @@ import os import re from datetime import datetime from pymediainfo import MediaInfo -from typing import Union, List, Optional +from typing import ( + Union, + List, + Optional, + Callable, +) import pyrogram from pyrogram import enums @@ -35,7 +40,6 @@ log = logging.getLogger(__name__) class SendMediaGroup: - # TODO: Add progress parameter async def send_media_group( self: "pyrogram.Client", chat_id: Union[int, str], @@ -59,7 +63,9 @@ class SendMediaGroup: protect_content: bool = None, allow_paid_broadcast: bool = None, message_effect_id: int = None, - invert_media: bool = None + invert_media: bool = None, + progress: Callable = None, + progress_args: tuple = (), ) -> List["types.Message"]: """Send a group of photos or videos as an album. @@ -89,7 +95,7 @@ class SendMediaGroup: reply_to_message_id (``int``, *optional*): If the message is a reply, ID of the original message. - + reply_to_story_id (``int``, *optional*): Unique identifier for the target story. @@ -126,6 +132,28 @@ class SendMediaGroup: invert_media (``bool``, *optional*): Inverts the position of the media and caption. + progress (``Callable``, *optional*): + Pass a callback function to view the file transmission progress. + The function must take *(current, total)* as positional arguments (look at Other Parameters below for a + detailed description) and will be called back each time a new file chunk has been successfully + transmitted. + + progress_args (``tuple``, *optional*): + Extra custom arguments for the progress callback function. + You can pass anything you need to be available in the progress callback scope; for example, a Message + object or a Client instance in order to edit the message with the updated progress status. + + Other Parameters: + current (``int``): + The amount of bytes transmitted so far. + + total (``int``): + The total size of the file. + + *args (``tuple``, *optional*): + Extra custom arguments as defined in the ``progress_args`` parameter. + You can either keep ``*args`` or add every single extra argument in your function signature. + Returns: List of :obj:`~pyrogram.types.Message`: On success, a list of the sent messages is returned. @@ -154,21 +182,22 @@ class SendMediaGroup: reply_to_chat_id=reply_to_chat_id, quote_text=quote_text, quote_entities=quote_entities, - parse_mode=parse_mode + parse_mode=parse_mode, ) for i in media: if isinstance(i, types.InputMediaPhoto): if isinstance(i.media, str): if os.path.isfile(i.media): + file = await self.save_file(i.media, progress=progress, progress_args=progress_args) media = await self.invoke( raw.functions.messages.UploadMedia( peer=await self.resolve_peer(chat_id), media=raw.types.InputMediaUploadedPhoto( - file=await self.save_file(i.media), - spoiler=i.has_spoiler - ) - ) + file=file, + spoiler=i.has_spoiler, + ), + ), ) media = raw.types.InputMediaPhoto( @@ -177,7 +206,7 @@ class SendMediaGroup: access_hash=media.photo.access_hash, file_reference=media.photo.file_reference ), - spoiler=i.has_spoiler + spoiler=i.has_spoiler, ) elif re.match("^https?://", i.media): media = await self.invoke( @@ -185,9 +214,9 @@ class SendMediaGroup: peer=await self.resolve_peer(chat_id), media=raw.types.InputMediaPhotoExternal( url=i.media, - spoiler=i.has_spoiler - ) - ) + spoiler=i.has_spoiler, + ), + ), ) media = raw.types.InputMediaPhoto( @@ -196,19 +225,20 @@ class SendMediaGroup: access_hash=media.photo.access_hash, file_reference=media.photo.file_reference ), - spoiler=i.has_spoiler + spoiler=i.has_spoiler, ) else: media = utils.get_input_media_from_file_id(i.media, FileType.PHOTO) else: + file = await self.save_file(i.media, progress=progress, progress_args=progress_args) media = await self.invoke( raw.functions.messages.UploadMedia( peer=await self.resolve_peer(chat_id), media=raw.types.InputMediaUploadedPhoto( - file=await self.save_file(i.media), - spoiler=i.has_spoiler - ) - ) + file=file, + spoiler=i.has_spoiler, + ), + ), ) media = raw.types.InputMediaPhoto( @@ -217,7 +247,7 @@ class SendMediaGroup: access_hash=media.photo.access_hash, file_reference=media.photo.file_reference ), - spoiler=i.has_spoiler + spoiler=i.has_spoiler, ) elif ( isinstance(i, types.InputMediaVideo) @@ -241,22 +271,25 @@ class SendMediaGroup: w=i.width, h=i.height ), - raw.types.DocumentAttributeFilename(file_name=os.path.basename(i.media)) + raw.types.DocumentAttributeFilename(file_name=os.path.basename(i.media)), ] if is_animation: attributes.append(raw.types.DocumentAttributeAnimated()) + + thumb = await self.save_file(i.thumb) + file = await self.save_file(i.media, progress=progress, progress_args=progress_args) media = await self.invoke( raw.functions.messages.UploadMedia( peer=await self.resolve_peer(chat_id), media=raw.types.InputMediaUploadedDocument( - file=await self.save_file(i.media), - thumb=await self.save_file(i.thumb), + file=file, + thumb=thumb, spoiler=i.has_spoiler, mime_type=self.guess_mime_type(i.media) or "video/mp4", nosound_video=is_animation, - attributes=attributes - ) - ) + attributes=attributes, + ), + ), ) media = raw.types.InputMediaDocument( @@ -265,7 +298,7 @@ class SendMediaGroup: access_hash=media.document.access_hash, file_reference=media.document.file_reference ), - spoiler=i.has_spoiler + spoiler=i.has_spoiler, ) elif re.match("^https?://", i.media): media = await self.invoke( @@ -273,9 +306,9 @@ class SendMediaGroup: peer=await self.resolve_peer(chat_id), media=raw.types.InputMediaDocumentExternal( url=i.media, - spoiler=i.has_spoiler - ) - ) + spoiler=i.has_spoiler, + ), + ), ) media = raw.types.InputMediaDocument( @@ -284,17 +317,19 @@ class SendMediaGroup: access_hash=media.document.access_hash, file_reference=media.document.file_reference ), - spoiler=i.has_spoiler + spoiler=i.has_spoiler, ) else: media = utils.get_input_media_from_file_id(i.media, FileType.VIDEO) else: + thumb = await self.save_file(i.thumb) + file = await self.save_file(i.media, progress=progress, progress_args=progress_args) media = await self.invoke( raw.functions.messages.UploadMedia( peer=await self.resolve_peer(chat_id), media=raw.types.InputMediaUploadedDocument( - file=await self.save_file(i.media), - thumb=await self.save_file(i.thumb), + file=file, + thumb=thumb, spoiler=i.has_spoiler, mime_type=self.guess_mime_type(getattr(i.media, "name", "video.mp4")) or "video/mp4", attributes=[ @@ -304,10 +339,10 @@ class SendMediaGroup: w=i.width, h=i.height ), - raw.types.DocumentAttributeFilename(file_name=getattr(i.media, "name", "video.mp4")) - ] - ) - ) + raw.types.DocumentAttributeFilename(file_name=getattr(i.media, "name", "video.mp4")), + ], + ), + ), ) media = raw.types.InputMediaDocument( @@ -316,127 +351,135 @@ class SendMediaGroup: access_hash=media.document.access_hash, file_reference=media.document.file_reference ), - spoiler=i.has_spoiler + spoiler=i.has_spoiler, ) elif isinstance(i, types.InputMediaAudio): if isinstance(i.media, str): if os.path.isfile(i.media): + thumb = await self.save_file(i.thumb) + file = await self.save_file(i.media, progress=progress, progress_args=progress_args) media = await self.invoke( raw.functions.messages.UploadMedia( peer=await self.resolve_peer(chat_id), media=raw.types.InputMediaUploadedDocument( mime_type=self.guess_mime_type(i.media) or "audio/mpeg", - file=await self.save_file(i.media), - thumb=await self.save_file(i.thumb), + file=file, + thumb=thumb, attributes=[ raw.types.DocumentAttributeAudio( duration=i.duration, performer=i.performer, title=i.title ), - raw.types.DocumentAttributeFilename(file_name=os.path.basename(i.media)) - ] - ) - ) + raw.types.DocumentAttributeFilename(file_name=os.path.basename(i.media)), + ], + ), + ), ) media = raw.types.InputMediaDocument( id=raw.types.InputDocument( id=media.document.id, access_hash=media.document.access_hash, - file_reference=media.document.file_reference - ) + file_reference=media.document.file_reference, + ), ) elif re.match("^https?://", i.media): media = await self.invoke( raw.functions.messages.UploadMedia( peer=await self.resolve_peer(chat_id), media=raw.types.InputMediaDocumentExternal( - url=i.media - ) - ) + url=i.media, + ), + ), ) media = raw.types.InputMediaDocument( id=raw.types.InputDocument( id=media.document.id, access_hash=media.document.access_hash, - file_reference=media.document.file_reference - ) + file_reference=media.document.file_reference, + ), ) else: media = utils.get_input_media_from_file_id(i.media, FileType.AUDIO) else: + thumb = await self.save_file(i.thumb) + file = await self.save_file(i.media, progress=progress, progress_args=progress_args) media = await self.invoke( raw.functions.messages.UploadMedia( peer=await self.resolve_peer(chat_id), media=raw.types.InputMediaUploadedDocument( mime_type=self.guess_mime_type(getattr(i.media, "name", "audio.mp3")) or "audio/mpeg", - file=await self.save_file(i.media), - thumb=await self.save_file(i.thumb), + file=file, + thumb=thumb, attributes=[ raw.types.DocumentAttributeAudio( duration=i.duration, performer=i.performer, title=i.title ), - raw.types.DocumentAttributeFilename(file_name=getattr(i.media, "name", "audio.mp3")) - ] - ) - ) + raw.types.DocumentAttributeFilename(file_name=getattr(i.media, "name", "audio.mp3")), + ], + ), + ), ) media = raw.types.InputMediaDocument( id=raw.types.InputDocument( id=media.document.id, access_hash=media.document.access_hash, - file_reference=media.document.file_reference - ) + file_reference=media.document.file_reference, + ), ) elif isinstance(i, types.InputMediaDocument): if isinstance(i.media, str): if os.path.isfile(i.media): + thumb = await self.save_file(i.thumb) + file = await self.save_file(i.media, progress=progress, progress_args=progress_args) media = await self.invoke( raw.functions.messages.UploadMedia( peer=await self.resolve_peer(chat_id), media=raw.types.InputMediaUploadedDocument( mime_type=self.guess_mime_type(i.media) or "application/zip", - file=await self.save_file(i.media), - thumb=await self.save_file(i.thumb), + file=file, + thumb=thumb, attributes=[ - raw.types.DocumentAttributeFilename(file_name=os.path.basename(i.media)) - ] - ) - ) + raw.types.DocumentAttributeFilename(file_name=os.path.basename(i.media)), + ], + ), + ), ) media = raw.types.InputMediaDocument( id=raw.types.InputDocument( id=media.document.id, access_hash=media.document.access_hash, - file_reference=media.document.file_reference - ) + file_reference=media.document.file_reference, + ), ) elif re.match("^https?://", i.media): media = await self.invoke( raw.functions.messages.UploadMedia( peer=await self.resolve_peer(chat_id), media=raw.types.InputMediaDocumentExternal( - url=i.media - ) - ) + url=i.media, + ), + ), ) media = raw.types.InputMediaDocument( id=raw.types.InputDocument( id=media.document.id, access_hash=media.document.access_hash, - file_reference=media.document.file_reference - ) + file_reference=media.document.file_reference, + ), ) else: media = utils.get_input_media_from_file_id(i.media, FileType.DOCUMENT) else: + thumb = await self.save_file(i.thumb) + file = await self.save_file(i.media, progress=progress, progress_args=progress_args) media = await self.invoke( raw.functions.messages.UploadMedia( peer=await self.resolve_peer(chat_id), @@ -444,21 +487,21 @@ class SendMediaGroup: mime_type=self.guess_mime_type( getattr(i.media, "name", "file.zip") ) or "application/zip", - file=await self.save_file(i.media), - thumb=await self.save_file(i.thumb), + file=file, + thumb=thumb, attributes=[ - raw.types.DocumentAttributeFilename(file_name=getattr(i.media, "name", "file.zip")) - ] - ) - ) + raw.types.DocumentAttributeFilename(file_name=getattr(i.media, "name", "file.zip")), + ], + ), + ), ) media = raw.types.InputMediaDocument( id=raw.types.InputDocument( id=media.document.id, access_hash=media.document.access_hash, - file_reference=media.document.file_reference - ) + file_reference=media.document.file_reference, + ), ) else: raise ValueError(f"{i.__class__.__name__} is not a supported type for send_media_group") @@ -467,8 +510,8 @@ class SendMediaGroup: raw.types.InputSingleMedia( media=media, random_id=self.rnd_id(), - **(await utils.parse_text_entities(self, i.caption, i.parse_mode, i.caption_entities)) - ) + **(await utils.parse_text_entities(self, i.caption, i.parse_mode, i.caption_entities)), + ), ) rpc = raw.functions.messages.SendMultiMedia( @@ -480,20 +523,20 @@ class SendMediaGroup: noforwards=protect_content, allow_paid_floodskip=allow_paid_broadcast, effect=message_effect_id, - invert_media=invert_media + invert_media=invert_media, ) - + if business_connection_id is not None: r = await self.invoke( raw.functions.InvokeWithBusinessConnection( connection_id=business_connection_id, query=rpc ), - sleep_threshold=60 + sleep_threshold=60, ) else: r = await self.invoke(rpc, sleep_threshold=60) - + return await utils.parse_messages( self, raw.types.messages.Messages( @@ -505,7 +548,7 @@ class SendMediaGroup: r.updates )], users=r.users, - chats=r.chats + chats=r.chats, ), - business_connection_id=business_connection_id + business_connection_id=business_connection_id, ) diff --git a/pyrogram/types/messages_and_media/message.py b/pyrogram/types/messages_and_media/message.py index 7cc228db..df9ccb62 100644 --- a/pyrogram/types/messages_and_media/message.py +++ b/pyrogram/types/messages_and_media/message.py @@ -2779,7 +2779,9 @@ class Message(Object, Update): allow_paid_broadcast: bool = None, message_effect_id: int = None, parse_mode: Optional["enums.ParseMode"] = None, - invert_media: bool = None + invert_media: bool = None, + progress: Callable = None, + progress_args: tuple = (), ) -> List["types.Message"]: """Bound method *reply_media_group* of :obj:`~pyrogram.types.Message`. @@ -2840,6 +2842,28 @@ class Message(Object, Update): invert_media (``bool``, *optional*): Inverts the position of the media and caption. + progress (``Callable``, *optional*): + Pass a callback function to view the file transmission progress. + The function must take *(current, total)* as positional arguments (look at Other Parameters below for a + detailed description) and will be called back each time a new file chunk has been successfully + transmitted. + + progress_args (``tuple``, *optional*): + Extra custom arguments for the progress callback function. + You can pass anything you need to be available in the progress callback scope; for example, a Message + object or a Client instance in order to edit the message with the updated progress status. + + Other Parameters: + current (``int``): + The amount of bytes transmitted so far. + + total (``int``): + The total size of the file. + + *args (``tuple``, *optional*): + Extra custom arguments as defined in the ``progress_args`` parameter. + You can either keep ``*args`` or add every single extra argument in your function signature. + Returns: On success, a :obj:`~pyrogram.types.Messages` object is returned containing all the single messages sent. @@ -2878,7 +2902,9 @@ class Message(Object, Update): quote_entities=quote_entities, allow_paid_broadcast=allow_paid_broadcast, message_effect_id=message_effect_id, - invert_media=invert_media + invert_media=invert_media, + progress=progress, + progress_args=progress_args, ) async def reply_photo(