This commit is contained in:
Ling-ex 2025-05-08 07:25:05 +00:00 committed by GitHub
commit b09643518a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 206 additions and 89 deletions

View file

@ -22,7 +22,12 @@ import os
import re import re
from datetime import datetime from datetime import datetime
from pymediainfo import MediaInfo from pymediainfo import MediaInfo
from typing import Union, List, Optional from typing import (
Union,
List,
Optional,
Callable,
)
import pyrogram import pyrogram
from pyrogram import enums from pyrogram import enums
@ -35,7 +40,6 @@ log = logging.getLogger(__name__)
class SendMediaGroup: class SendMediaGroup:
# TODO: Add progress parameter
async def send_media_group( async def send_media_group(
self: "pyrogram.Client", self: "pyrogram.Client",
chat_id: Union[int, str], chat_id: Union[int, str],
@ -59,7 +63,9 @@ class SendMediaGroup:
protect_content: bool = None, protect_content: bool = None,
allow_paid_broadcast: bool = None, allow_paid_broadcast: bool = None,
message_effect_id: int = None, message_effect_id: int = None,
invert_media: bool = None invert_media: bool = None,
progress: Callable = None,
progress_args: tuple = (),
) -> List["types.Message"]: ) -> List["types.Message"]:
"""Send a group of photos or videos as an album. """Send a group of photos or videos as an album.
@ -126,6 +132,28 @@ class SendMediaGroup:
invert_media (``bool``, *optional*): invert_media (``bool``, *optional*):
Inverts the position of the media and caption. 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: Returns:
List of :obj:`~pyrogram.types.Message`: On success, a list of the sent messages is returned. 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, reply_to_chat_id=reply_to_chat_id,
quote_text=quote_text, quote_text=quote_text,
quote_entities=quote_entities, quote_entities=quote_entities,
parse_mode=parse_mode parse_mode=parse_mode,
) )
for i in media: for i in media:
if isinstance(i, types.InputMediaPhoto): if isinstance(i, types.InputMediaPhoto):
if isinstance(i.media, str): if isinstance(i.media, str):
if os.path.isfile(i.media): if os.path.isfile(i.media):
file = await self.save_file(i.media, progress=progress, progress_args=progress_args)
media = await self.invoke( media = await self.invoke(
raw.functions.messages.UploadMedia( raw.functions.messages.UploadMedia(
peer=await self.resolve_peer(chat_id), peer=await self.resolve_peer(chat_id),
media=raw.types.InputMediaUploadedPhoto( media=raw.types.InputMediaUploadedPhoto(
file=await self.save_file(i.media), file=file,
spoiler=i.has_spoiler spoiler=i.has_spoiler,
) ),
) ),
) )
media = raw.types.InputMediaPhoto( media = raw.types.InputMediaPhoto(
@ -177,7 +206,7 @@ class SendMediaGroup:
access_hash=media.photo.access_hash, access_hash=media.photo.access_hash,
file_reference=media.photo.file_reference file_reference=media.photo.file_reference
), ),
spoiler=i.has_spoiler spoiler=i.has_spoiler,
) )
elif re.match("^https?://", i.media): elif re.match("^https?://", i.media):
media = await self.invoke( media = await self.invoke(
@ -185,9 +214,9 @@ class SendMediaGroup:
peer=await self.resolve_peer(chat_id), peer=await self.resolve_peer(chat_id),
media=raw.types.InputMediaPhotoExternal( media=raw.types.InputMediaPhotoExternal(
url=i.media, url=i.media,
spoiler=i.has_spoiler spoiler=i.has_spoiler,
) ),
) ),
) )
media = raw.types.InputMediaPhoto( media = raw.types.InputMediaPhoto(
@ -196,19 +225,20 @@ class SendMediaGroup:
access_hash=media.photo.access_hash, access_hash=media.photo.access_hash,
file_reference=media.photo.file_reference file_reference=media.photo.file_reference
), ),
spoiler=i.has_spoiler spoiler=i.has_spoiler,
) )
else: else:
media = utils.get_input_media_from_file_id(i.media, FileType.PHOTO) media = utils.get_input_media_from_file_id(i.media, FileType.PHOTO)
else: else:
file = await self.save_file(i.media, progress=progress, progress_args=progress_args)
media = await self.invoke( media = await self.invoke(
raw.functions.messages.UploadMedia( raw.functions.messages.UploadMedia(
peer=await self.resolve_peer(chat_id), peer=await self.resolve_peer(chat_id),
media=raw.types.InputMediaUploadedPhoto( media=raw.types.InputMediaUploadedPhoto(
file=await self.save_file(i.media), file=file,
spoiler=i.has_spoiler spoiler=i.has_spoiler,
) ),
) ),
) )
media = raw.types.InputMediaPhoto( media = raw.types.InputMediaPhoto(
@ -217,7 +247,7 @@ class SendMediaGroup:
access_hash=media.photo.access_hash, access_hash=media.photo.access_hash,
file_reference=media.photo.file_reference file_reference=media.photo.file_reference
), ),
spoiler=i.has_spoiler spoiler=i.has_spoiler,
) )
elif ( elif (
isinstance(i, types.InputMediaVideo) isinstance(i, types.InputMediaVideo)
@ -241,22 +271,25 @@ class SendMediaGroup:
w=i.width, w=i.width,
h=i.height 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: if is_animation:
attributes.append(raw.types.DocumentAttributeAnimated()) 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( media = await self.invoke(
raw.functions.messages.UploadMedia( raw.functions.messages.UploadMedia(
peer=await self.resolve_peer(chat_id), peer=await self.resolve_peer(chat_id),
media=raw.types.InputMediaUploadedDocument( media=raw.types.InputMediaUploadedDocument(
file=await self.save_file(i.media), file=file,
thumb=await self.save_file(i.thumb), thumb=thumb,
spoiler=i.has_spoiler, spoiler=i.has_spoiler,
mime_type=self.guess_mime_type(i.media) or "video/mp4", mime_type=self.guess_mime_type(i.media) or "video/mp4",
nosound_video=is_animation, nosound_video=is_animation,
attributes=attributes attributes=attributes,
) ),
) ),
) )
media = raw.types.InputMediaDocument( media = raw.types.InputMediaDocument(
@ -265,7 +298,7 @@ class SendMediaGroup:
access_hash=media.document.access_hash, access_hash=media.document.access_hash,
file_reference=media.document.file_reference file_reference=media.document.file_reference
), ),
spoiler=i.has_spoiler spoiler=i.has_spoiler,
) )
elif re.match("^https?://", i.media): elif re.match("^https?://", i.media):
media = await self.invoke( media = await self.invoke(
@ -273,9 +306,9 @@ class SendMediaGroup:
peer=await self.resolve_peer(chat_id), peer=await self.resolve_peer(chat_id),
media=raw.types.InputMediaDocumentExternal( media=raw.types.InputMediaDocumentExternal(
url=i.media, url=i.media,
spoiler=i.has_spoiler spoiler=i.has_spoiler,
) ),
) ),
) )
media = raw.types.InputMediaDocument( media = raw.types.InputMediaDocument(
@ -284,17 +317,19 @@ class SendMediaGroup:
access_hash=media.document.access_hash, access_hash=media.document.access_hash,
file_reference=media.document.file_reference file_reference=media.document.file_reference
), ),
spoiler=i.has_spoiler spoiler=i.has_spoiler,
) )
else: else:
media = utils.get_input_media_from_file_id(i.media, FileType.VIDEO) media = utils.get_input_media_from_file_id(i.media, FileType.VIDEO)
else: 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( media = await self.invoke(
raw.functions.messages.UploadMedia( raw.functions.messages.UploadMedia(
peer=await self.resolve_peer(chat_id), peer=await self.resolve_peer(chat_id),
media=raw.types.InputMediaUploadedDocument( media=raw.types.InputMediaUploadedDocument(
file=await self.save_file(i.media), file=file,
thumb=await self.save_file(i.thumb), thumb=thumb,
spoiler=i.has_spoiler, spoiler=i.has_spoiler,
mime_type=self.guess_mime_type(getattr(i.media, "name", "video.mp4")) or "video/mp4", mime_type=self.guess_mime_type(getattr(i.media, "name", "video.mp4")) or "video/mp4",
attributes=[ attributes=[
@ -304,10 +339,10 @@ class SendMediaGroup:
w=i.width, w=i.width,
h=i.height 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( media = raw.types.InputMediaDocument(
@ -316,127 +351,135 @@ class SendMediaGroup:
access_hash=media.document.access_hash, access_hash=media.document.access_hash,
file_reference=media.document.file_reference file_reference=media.document.file_reference
), ),
spoiler=i.has_spoiler spoiler=i.has_spoiler,
) )
elif isinstance(i, types.InputMediaAudio): elif isinstance(i, types.InputMediaAudio):
if isinstance(i.media, str): if isinstance(i.media, str):
if os.path.isfile(i.media): 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( media = await self.invoke(
raw.functions.messages.UploadMedia( raw.functions.messages.UploadMedia(
peer=await self.resolve_peer(chat_id), peer=await self.resolve_peer(chat_id),
media=raw.types.InputMediaUploadedDocument( media=raw.types.InputMediaUploadedDocument(
mime_type=self.guess_mime_type(i.media) or "audio/mpeg", mime_type=self.guess_mime_type(i.media) or "audio/mpeg",
file=await self.save_file(i.media), file=file,
thumb=await self.save_file(i.thumb), thumb=thumb,
attributes=[ attributes=[
raw.types.DocumentAttributeAudio( raw.types.DocumentAttributeAudio(
duration=i.duration, duration=i.duration,
performer=i.performer, performer=i.performer,
title=i.title 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( media = raw.types.InputMediaDocument(
id=raw.types.InputDocument( id=raw.types.InputDocument(
id=media.document.id, id=media.document.id,
access_hash=media.document.access_hash, access_hash=media.document.access_hash,
file_reference=media.document.file_reference file_reference=media.document.file_reference,
) ),
) )
elif re.match("^https?://", i.media): elif re.match("^https?://", i.media):
media = await self.invoke( media = await self.invoke(
raw.functions.messages.UploadMedia( raw.functions.messages.UploadMedia(
peer=await self.resolve_peer(chat_id), peer=await self.resolve_peer(chat_id),
media=raw.types.InputMediaDocumentExternal( media=raw.types.InputMediaDocumentExternal(
url=i.media url=i.media,
) ),
) ),
) )
media = raw.types.InputMediaDocument( media = raw.types.InputMediaDocument(
id=raw.types.InputDocument( id=raw.types.InputDocument(
id=media.document.id, id=media.document.id,
access_hash=media.document.access_hash, access_hash=media.document.access_hash,
file_reference=media.document.file_reference file_reference=media.document.file_reference,
) ),
) )
else: else:
media = utils.get_input_media_from_file_id(i.media, FileType.AUDIO) media = utils.get_input_media_from_file_id(i.media, FileType.AUDIO)
else: 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( media = await self.invoke(
raw.functions.messages.UploadMedia( raw.functions.messages.UploadMedia(
peer=await self.resolve_peer(chat_id), peer=await self.resolve_peer(chat_id),
media=raw.types.InputMediaUploadedDocument( media=raw.types.InputMediaUploadedDocument(
mime_type=self.guess_mime_type(getattr(i.media, "name", "audio.mp3")) or "audio/mpeg", mime_type=self.guess_mime_type(getattr(i.media, "name", "audio.mp3")) or "audio/mpeg",
file=await self.save_file(i.media), file=file,
thumb=await self.save_file(i.thumb), thumb=thumb,
attributes=[ attributes=[
raw.types.DocumentAttributeAudio( raw.types.DocumentAttributeAudio(
duration=i.duration, duration=i.duration,
performer=i.performer, performer=i.performer,
title=i.title 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( media = raw.types.InputMediaDocument(
id=raw.types.InputDocument( id=raw.types.InputDocument(
id=media.document.id, id=media.document.id,
access_hash=media.document.access_hash, access_hash=media.document.access_hash,
file_reference=media.document.file_reference file_reference=media.document.file_reference,
) ),
) )
elif isinstance(i, types.InputMediaDocument): elif isinstance(i, types.InputMediaDocument):
if isinstance(i.media, str): if isinstance(i.media, str):
if os.path.isfile(i.media): 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( media = await self.invoke(
raw.functions.messages.UploadMedia( raw.functions.messages.UploadMedia(
peer=await self.resolve_peer(chat_id), peer=await self.resolve_peer(chat_id),
media=raw.types.InputMediaUploadedDocument( media=raw.types.InputMediaUploadedDocument(
mime_type=self.guess_mime_type(i.media) or "application/zip", mime_type=self.guess_mime_type(i.media) or "application/zip",
file=await self.save_file(i.media), file=file,
thumb=await self.save_file(i.thumb), thumb=thumb,
attributes=[ 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( media = raw.types.InputMediaDocument(
id=raw.types.InputDocument( id=raw.types.InputDocument(
id=media.document.id, id=media.document.id,
access_hash=media.document.access_hash, access_hash=media.document.access_hash,
file_reference=media.document.file_reference file_reference=media.document.file_reference,
) ),
) )
elif re.match("^https?://", i.media): elif re.match("^https?://", i.media):
media = await self.invoke( media = await self.invoke(
raw.functions.messages.UploadMedia( raw.functions.messages.UploadMedia(
peer=await self.resolve_peer(chat_id), peer=await self.resolve_peer(chat_id),
media=raw.types.InputMediaDocumentExternal( media=raw.types.InputMediaDocumentExternal(
url=i.media url=i.media,
) ),
) ),
) )
media = raw.types.InputMediaDocument( media = raw.types.InputMediaDocument(
id=raw.types.InputDocument( id=raw.types.InputDocument(
id=media.document.id, id=media.document.id,
access_hash=media.document.access_hash, access_hash=media.document.access_hash,
file_reference=media.document.file_reference file_reference=media.document.file_reference,
) ),
) )
else: else:
media = utils.get_input_media_from_file_id(i.media, FileType.DOCUMENT) media = utils.get_input_media_from_file_id(i.media, FileType.DOCUMENT)
else: 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( media = await self.invoke(
raw.functions.messages.UploadMedia( raw.functions.messages.UploadMedia(
peer=await self.resolve_peer(chat_id), peer=await self.resolve_peer(chat_id),
@ -444,21 +487,21 @@ class SendMediaGroup:
mime_type=self.guess_mime_type( mime_type=self.guess_mime_type(
getattr(i.media, "name", "file.zip") getattr(i.media, "name", "file.zip")
) or "application/zip", ) or "application/zip",
file=await self.save_file(i.media), file=file,
thumb=await self.save_file(i.thumb), thumb=thumb,
attributes=[ 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( media = raw.types.InputMediaDocument(
id=raw.types.InputDocument( id=raw.types.InputDocument(
id=media.document.id, id=media.document.id,
access_hash=media.document.access_hash, access_hash=media.document.access_hash,
file_reference=media.document.file_reference file_reference=media.document.file_reference,
) ),
) )
else: else:
raise ValueError(f"{i.__class__.__name__} is not a supported type for send_media_group") raise ValueError(f"{i.__class__.__name__} is not a supported type for send_media_group")
@ -467,8 +510,8 @@ class SendMediaGroup:
raw.types.InputSingleMedia( raw.types.InputSingleMedia(
media=media, media=media,
random_id=self.rnd_id(), 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( rpc = raw.functions.messages.SendMultiMedia(
@ -480,7 +523,7 @@ class SendMediaGroup:
noforwards=protect_content, noforwards=protect_content,
allow_paid_floodskip=allow_paid_broadcast, allow_paid_floodskip=allow_paid_broadcast,
effect=message_effect_id, effect=message_effect_id,
invert_media=invert_media invert_media=invert_media,
) )
if business_connection_id is not None: if business_connection_id is not None:
@ -489,7 +532,7 @@ class SendMediaGroup:
connection_id=business_connection_id, connection_id=business_connection_id,
query=rpc query=rpc
), ),
sleep_threshold=60 sleep_threshold=60,
) )
else: else:
r = await self.invoke(rpc, sleep_threshold=60) r = await self.invoke(rpc, sleep_threshold=60)
@ -505,7 +548,7 @@ class SendMediaGroup:
r.updates r.updates
)], )],
users=r.users, users=r.users,
chats=r.chats chats=r.chats,
), ),
business_connection_id=business_connection_id business_connection_id=business_connection_id,
) )

View file

@ -19,6 +19,7 @@
from .add_handler import AddHandler from .add_handler import AddHandler
from .export_session_string import ExportSessionString from .export_session_string import ExportSessionString
from .ping import Ping
from .remove_handler import RemoveHandler from .remove_handler import RemoveHandler
from .remove_error_handler import RemoveErrorHandler from .remove_error_handler import RemoveErrorHandler
from .restart import Restart from .restart import Restart
@ -32,6 +33,7 @@ from .stop_transmission import StopTransmission
class Utilities( class Utilities(
AddHandler, AddHandler,
ExportSessionString, ExportSessionString,
Ping,
RemoveHandler, RemoveHandler,
RemoveErrorHandler, RemoveErrorHandler,
Restart, Restart,

View file

@ -0,0 +1,46 @@
# Pyrofork - Telegram MTProto API Client Library for Python
# Copyright (C) 2017-present Dan <https://github.com/delivrance>
# Copyright (C) 2022-present Mayuri-Chan <https://github.com/Mayuri-Chan>
#
# This file is part of Pyrofork.
#
# Pyrofork is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Pyrofork is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with Pyrofork. If not, see <http://www.gnu.org/licenses/>.
from time import time
import pyrogram
from pyrogram import raw
class Ping:
async def ping(self: "pyrogram.Client"):
"""Measure the round-trip time (RTT) to the Telegram server.
The ping method sends a request to the Telegram server and measures the time it takes to receive a response.
This can be useful for monitoring network latency and ensuring a stable connection to the server.
Returns:
float: The round-trip time in milliseconds (ms).
Example:
.. code-block:: python
latency = await app.ping()
print(f"Ping: {latency} ms")
"""
start_time = time()
await self.invoke(
raw.functions.ping.Ping(ping_id=self.rnd_id()),
)
return round((time() - start_time) * 1000.0, 3)

View file

@ -2779,7 +2779,9 @@ class Message(Object, Update):
allow_paid_broadcast: bool = None, allow_paid_broadcast: bool = None,
message_effect_id: int = None, message_effect_id: int = None,
parse_mode: Optional["enums.ParseMode"] = None, parse_mode: Optional["enums.ParseMode"] = None,
invert_media: bool = None invert_media: bool = None,
progress: Callable = None,
progress_args: tuple = (),
) -> List["types.Message"]: ) -> List["types.Message"]:
"""Bound method *reply_media_group* of :obj:`~pyrogram.types.Message`. """Bound method *reply_media_group* of :obj:`~pyrogram.types.Message`.
@ -2840,6 +2842,28 @@ class Message(Object, Update):
invert_media (``bool``, *optional*): invert_media (``bool``, *optional*):
Inverts the position of the media and caption. 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: Returns:
On success, a :obj:`~pyrogram.types.Messages` object is returned containing all the On success, a :obj:`~pyrogram.types.Messages` object is returned containing all the
single messages sent. single messages sent.
@ -2878,7 +2902,9 @@ class Message(Object, Update):
quote_entities=quote_entities, quote_entities=quote_entities,
allow_paid_broadcast=allow_paid_broadcast, allow_paid_broadcast=allow_paid_broadcast,
message_effect_id=message_effect_id, message_effect_id=message_effect_id,
invert_media=invert_media invert_media=invert_media,
progress=progress,
progress_args=progress_args,
) )
async def reply_photo( async def reply_photo(