FileToLink / Thunder /utils /custom_dl.py
Fyaz Mohammed
3
e7261d3
Raw
History Blame Contribute Delete
5.14 kB
# 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)}