Files
umb/bot/routers/yadisk.py
T
Галингер Р.С. 0f674f8832 refactor: review fixes and code improvements
- Add shared proxy module (bot/utils/proxy.py) to eliminate duplicate SOCKS5 parsing
- Fix AI client: escape HTML before Markdown→HTML conversion, unify timeouts,
  make health check optional and disabled by default, handle 429 retries
- Fix ai.py: correct forwarded message handling (aiogram 3.x forward_origin),
  pass relevant summaries via extra_system_content
- Fix dialogue.py: only respond to Astra's messages, use system context instead
  of prompt injection, answer on the limit message before phase transition
- Fix voice.py: load Whisper model in thread pool, safe WAV path generation
- Improve database.py: composite indexes, Boolean is_active, upsert file_id cache,
  add context cleanup helper
- Update weather.py and yadisk_download.py to use shared proxy connector
- Update yadisk.py: validate URL before cache clear, add download size limit,
  wrap sync file ops in to_thread
- Reuse S3 client via lru_cache
- Update setup_commands with /aiclear and /aiuser
- Update README, Dockerfile (Python 3.11), docker-compose (mount models)
- Pin dependency versions, remove unused httpx[socks]
- Add basic pytest tests for layout converter and voice normalization
2026-07-07 18:42:10 +07:00

115 lines
4.1 KiB
Python

import asyncio
import logging
import os
from aiogram import Router
from aiogram.filters import Command
from aiogram.types import Message, FSInputFile
from bot.utils.database import get_file_id, save_file_id
from bot.utils.yadisk_download import download_yandex_file
from bot.utils.s3_client import upload_file
router = Router()
logger = logging.getLogger("yadisk")
MAX_TELEGRAM_FILE_SIZE = 50 * 1024 * 1024
MAX_DOWNLOAD_SIZE = 2 * 1024 * 1024 * 1024 # 2 GB safety limit
def is_valid_yandex_public_link(value: str) -> bool:
value = value.strip().lower()
return (
value.startswith("https://disk.yandex.")
or value.startswith("http://disk.yandex.")
or value.startswith("https://yadi.sk/")
or value.startswith("http://yadi.sk/")
)
@router.message(Command("ydf"))
async def yandex_download_handler(message: Message):
text = (message.text or "").strip()
parts = text.split(maxsplit=1)
if len(parts) < 2 or not parts[1].strip():
await message.answer("Укажите ссылку: /ydf https://yadi.sk/...")
return
raw_arg = parts[1].strip()
force_refresh = raw_arg.startswith("(new)")
url = raw_arg.replace("(new)", "", 1).strip() if force_refresh else raw_arg
if not url:
await message.answer("После (new) укажите ссылку.")
return
if not is_valid_yandex_public_link(url):
await message.answer("Это не похоже на публичную ссылку Яндекс.Диска.")
return
if force_refresh:
await save_file_id(url, None)
cached_id = await get_file_id(url)
if cached_id:
await message.answer_document(
cached_id,
caption="Файл из кеша Telegram. /ydf (new) [ссылка] для обновления.",
)
return
status_msg = await message.answer("Начинаю загрузку с Яндекс.Диска...")
last_text = "Начинаю загрузку с Яндекс.Диска..."
async def progress(downloaded: int, total: int):
nonlocal last_text
if total <= 0:
return
if total > MAX_DOWNLOAD_SIZE:
raise RuntimeError("Файл слишком большой для загрузки.")
pct = int(downloaded / total * 100)
new_text = f"Загрузка: {pct}%"
if new_text != last_text and pct % 5 == 0:
try:
await status_msg.edit_text(new_text)
last_text = new_text
except Exception:
pass
file_path = None
try:
file_path = await download_yandex_file(url, progress_callback=progress)
except Exception as exc:
logger.exception("Ошибка скачивания с Яндекс.Диска")
await status_msg.edit_text(f"Не удалось скачать файл: {exc}")
return
try:
file_size = await asyncio.to_thread(os.path.getsize, file_path)
if file_size > MAX_DOWNLOAD_SIZE:
await status_msg.edit_text("Файл превышает максимально допустимый размер.")
return
if file_size < MAX_TELEGRAM_FILE_SIZE:
doc = FSInputFile(file_path)
sent = await message.answer_document(doc)
if sent.document and sent.document.file_id:
await save_file_id(url, sent.document.file_id)
await status_msg.delete()
else:
await status_msg.edit_text("Файл больше 50MB, загружаю в облако...")
s3_url = await asyncio.to_thread(upload_file, file_path)
if s3_url:
await message.answer(f"Файл доступен для скачивания: {s3_url}")
await status_msg.delete()
else:
await status_msg.edit_text("Не удалось загрузить файл в облако.")
finally:
if file_path:
try:
await asyncio.to_thread(os.remove, file_path)
except OSError:
pass