Spaces:
Running
Running
| # Thunder/utils/custom_dl.py | |
| import asyncio | |
| from typing import Any, AsyncGenerator, Awaitable, Callable, Dict, Optional | |
| from pyrogram import Client | |
| from pyrogram.errors import FloodWait | |
| from pyrogram.types import Message | |
| from Thunder.server.exceptions import FileNotFound | |
| from Thunder.utils.file_properties import get_media | |
| from Thunder.utils.logger import logger | |
| from Thunder.vars import Var | |
| class ByteStreamer: | |
| __slots__ = ('client', 'chat_id') | |
| def __init__(self, client: Client) -> None: | |
| self.client = client | |
| self.chat_id = int(Var.BIN_CHANNEL) | |
| async def get_message(self, message_id: int) -> Message: | |
| while True: | |
| try: | |
| message = await self.client.get_messages(self.chat_id, message_id) | |
| break | |
| except FloodWait as e: | |
| logger.debug(f"FloodWait: get_message, sleep {e.value}s") | |
| await asyncio.sleep(e.value) | |
| except Exception as e: | |
| logger.debug(f"Error fetching message {message_id}: {e}", exc_info=True) | |
| raise FileNotFound(f"Message {message_id} not found") from e | |
| if not message or not message.media: | |
| raise FileNotFound(f"Message {message_id} not found") | |
| return message | |
| async def stream_file( | |
| self, | |
| media_ref: int | Message, | |
| offset: int = 0, | |
| limit: int = 0, | |
| fallback_message_id: int | None = None, | |
| on_fallback_message: Optional[Callable[[Message], Awaitable[None]]] = None | |
| ) -> AsyncGenerator[bytes, None]: | |
| chunk_offset = offset // (1024 * 1024) | |
| chunk_limit = 0 | |
| if limit > 0: | |
| chunk_limit = ((limit + (1024 * 1024) - 1) // (1024 * 1024)) + 1 | |
| refs: list[int | Message] = [media_ref] | |
| media_id = media_ref if isinstance(media_ref, int) else None | |
| if isinstance(media_ref, Message): | |
| media_id = getattr(media_ref, "id", getattr(media_ref, "message_id", None)) | |
| if fallback_message_id is not None and (media_id is None or fallback_message_id != media_id): | |
| refs.append(fallback_message_id) | |
| last_error: Exception | None = None | |
| for ref in refs: | |
| started_stream = False | |
| while True: | |
| try: | |
| target = await self.get_message(ref) if isinstance(ref, int) else ref | |
| if ( | |
| on_fallback_message is not None and | |
| fallback_message_id is not None and | |
| ref == fallback_message_id and | |
| isinstance(target, Message) | |
| ): | |
| await on_fallback_message(target) | |
| async for chunk in self.client.stream_media( | |
| target, offset=chunk_offset, limit=chunk_limit | |
| ): | |
| started_stream = True | |
| yield chunk | |
| return | |
| except FloodWait as e: | |
| logger.debug(f"FloodWait: stream_file, sleep {e.value}s") | |
| await asyncio.sleep(e.value) | |
| except Exception as e: | |
| last_error = e | |
| logger.debug(f"Error streaming media ref {ref}: {e}", exc_info=True) | |
| if started_stream: | |
| raise | |
| break | |
| raise FileNotFound(f"Unable to stream file: {last_error}") | |
| def get_file_info_sync(self, message: Message) -> Dict[str, Any]: | |
| media = get_media(message) | |
| if not media: | |
| return {"message_id": message.id, "error": "No media"} | |
| media_type = type(media).__name__.lower() | |
| file_name = getattr(media, 'file_name', None) | |
| mime_type = getattr(media, 'mime_type', None) | |
| if not file_name: | |
| ext_map = { | |
| "photo": "jpg", | |
| "audio": "mp3", | |
| "voice": "ogg", | |
| "video": "mp4", | |
| "animation": "mp4", | |
| "videonote": "mp4", | |
| "sticker": "webp", | |
| } | |
| ext = ext_map.get(media_type, "bin") | |
| file_name = f"Thunder_{message.id}.{ext}" | |
| if not mime_type: | |
| mime_map = { | |
| "photo": "image/jpeg", | |
| "voice": "audio/ogg", | |
| "videonote": "video/mp4", | |
| } | |
| mime_type = mime_map.get(media_type) | |
| return { | |
| "message_id": message.id, | |
| "file_size": getattr(media, 'file_size', 0) or 0, | |
| "file_name": file_name, | |
| "mime_type": mime_type, | |
| "unique_id": getattr(media, 'file_unique_id', None), | |
| "media_type": media_type | |
| } | |
| async def get_file_info(self, message_id: int) -> Dict[str, Any]: | |
| try: | |
| message = await self.get_message(message_id) | |
| return self.get_file_info_sync(message) | |
| except Exception as e: | |
| logger.debug(f"Error getting file info for {message_id}: {e}", exc_info=True) | |
| return {"message_id": message_id, "error": str(e)} | |