Add source

This commit is contained in:
2025-12-04 00:12:56 +03:00
parent b75875df5e
commit 0cb7045e7a
75 changed files with 9055 additions and 0 deletions

View File

@@ -0,0 +1,270 @@
"""
Sending files to users
"""
from pathlib import Path
from pyrogram import Client
from pyrogram.types import Message
from typing import Optional
import aiohttp
import aiofiles
import logging
logger = logging.getLogger(__name__)
async def download_thumbnail(url: str, output_path: str) -> Optional[str]:
"""
Download thumbnail from URL
Args:
url: Thumbnail URL
output_path: Path to save
Returns:
Path to downloaded file or None
"""
try:
async with aiohttp.ClientSession() as session:
async with session.get(url) as response:
if response.status == 200:
async with aiofiles.open(output_path, 'wb') as f:
async for chunk in response.content.iter_chunked(8192):
await f.write(chunk)
return output_path
except Exception as e:
logger.warning(f"Failed to download thumbnail: {e}")
return None
async def send_file_to_user(
client: Client,
chat_id: int,
file_path: str,
caption: Optional[str] = None,
thumbnail: Optional[str] = None
) -> bool:
"""
Send file to user
Args:
client: Pyrogram client
chat_id: Chat ID
file_path: Path to file
caption: File caption
thumbnail: Path to thumbnail or URL
Returns:
True if successful, False otherwise
"""
thumbnail_path = None
try:
file = Path(file_path)
if not file.exists():
logger.error(f"File not found: {file_path}")
return False
# Maximum file size for Telegram (2GB)
max_size = 2 * 1024 * 1024 * 1024
file_size = file.stat().st_size
# If file is larger than 2GB, split into parts
if file_size > max_size:
logger.info(f"File too large ({file_size / (1024*1024*1024):.2f} GB), splitting into parts...")
return await send_large_file_in_parts(
client, chat_id, file_path, caption, thumbnail
)
# Process thumbnail (can be URL or file path)
if thumbnail:
if thumbnail.startswith(('http://', 'https://')):
# This is a URL - download thumbnail
thumbnail_path = f"downloads/thumb_{file.stem}.jpg"
downloaded = await download_thumbnail(thumbnail, thumbnail_path)
if downloaded:
thumbnail_path = downloaded
else:
thumbnail_path = None # Don't use thumbnail if download failed
else:
# This is a file path
thumb_file = Path(thumbnail)
if thumb_file.exists():
thumbnail_path = thumbnail
else:
thumbnail_path = None
# Determine file type
if file.suffix.lower() in ['.mp4', '.avi', '.mov', '.mkv', '.webm']:
# Video - if no thumbnail, try to generate one
if not thumbnail_path:
from bot.utils.file_processor import generate_thumbnail
thumbnail_path_temp = f"downloads/thumb_{file.stem}.jpg"
if await generate_thumbnail(str(file), thumbnail_path_temp):
thumbnail_path = thumbnail_path_temp
await client.send_video(
chat_id=chat_id,
video=str(file),
caption=caption,
thumb=thumbnail_path
)
elif file.suffix.lower() in ['.jpg', '.jpeg', '.png', '.gif', '.webp']:
# Image
await client.send_photo(
chat_id=chat_id,
photo=str(file),
caption=caption
)
elif file.suffix.lower() in ['.mp3', '.wav', '.ogg', '.m4a', '.flac']:
# Audio
await client.send_audio(
chat_id=chat_id,
audio=str(file),
caption=caption,
thumb=thumbnail_path
)
else:
# Document
await client.send_document(
chat_id=chat_id,
document=str(file),
caption=caption,
thumb=thumbnail_path
)
logger.info(f"File sent to user {chat_id}: {file_path}")
return True
except Exception as e:
logger.error(f"Error sending file: {e}", exc_info=True)
return False
finally:
# Delete temporary thumbnail if it was downloaded
if thumbnail_path and thumbnail_path.startswith("downloads/thumb_"):
try:
thumb_file = Path(thumbnail_path)
if thumb_file.exists():
thumb_file.unlink()
logger.debug(f"Temporary thumbnail deleted: {thumbnail_path}")
except Exception as e:
logger.warning(f"Failed to delete temporary thumbnail: {e}")
async def send_large_file_in_parts(
client: Client,
chat_id: int,
file_path: str,
caption: Optional[str] = None,
thumbnail: Optional[str] = None
) -> bool:
"""
Send large file in parts
Args:
client: Pyrogram client
chat_id: Chat ID
file_path: Path to file
caption: File caption
thumbnail: Path to thumbnail or URL
Returns:
True if successful, False otherwise
"""
from bot.utils.file_splitter import split_file, delete_file_parts, get_part_info
parts = []
try:
# Split file into parts
parts = await split_file(file_path)
part_info = get_part_info(parts)
total_parts = part_info["total_parts"]
logger.info(f"Sending file in parts: {total_parts} parts")
# Send each part
for part_num, part_path in enumerate(parts, 1):
part_caption = None
if caption:
part_caption = f"{caption}\n\n📦 Part {part_num} of {total_parts}"
else:
part_caption = f"📦 Part {part_num} of {total_parts}"
# Send thumbnail only with first part
part_thumbnail = thumbnail if part_num == 1 else None
try:
await client.send_document(
chat_id=chat_id,
document=str(part_path),
caption=part_caption,
thumb=part_thumbnail
)
logger.info(f"Sent part {part_num}/{total_parts}")
except Exception as e:
logger.error(f"Error sending part {part_num}: {e}", exc_info=True)
# Continue sending other parts
continue
logger.info(f"File sent in parts to user {chat_id}")
return True
except Exception as e:
logger.error(f"Error sending large file in parts: {e}", exc_info=True)
return False
finally:
# Delete file parts after sending
if parts:
await delete_file_parts(parts)
logger.debug("File parts deleted")
async def delete_file(file_path: str, max_retries: int = 3) -> bool:
"""
Delete file with retries
Args:
file_path: Path to file
max_retries: Maximum number of retries
Returns:
True if successful
"""
import asyncio
file = Path(file_path)
if not file.exists():
return True # File already doesn't exist
for attempt in range(max_retries):
try:
file.unlink()
logger.info(f"File deleted: {file_path}")
return True
except PermissionError as e:
# File may be locked by another process
if attempt < max_retries - 1:
wait_time = (attempt + 1) * 0.5 # Exponential backoff
logger.warning(f"File locked, retrying in {wait_time}s: {file_path}")
await asyncio.sleep(wait_time)
else:
logger.error(f"Failed to delete file after {max_retries} attempts (locked): {file_path}")
# Add to cleanup queue for background cleanup task
from bot.utils.file_cleanup import add_file_to_cleanup_queue
add_file_to_cleanup_queue(str(file_path))
return False
except Exception as e:
if attempt < max_retries - 1:
wait_time = (attempt + 1) * 0.5
logger.warning(f"Error deleting file, retrying in {wait_time}s: {e}")
await asyncio.sleep(wait_time)
else:
logger.error(f"Error deleting file after {max_retries} attempts: {e}", exc_info=True)
# Add to cleanup queue for background cleanup task
from bot.utils.file_cleanup import add_file_to_cleanup_queue
add_file_to_cleanup_queue(str(file_path))
return False
return False