From c5f6da31ba20e14fc41b436e6c8cf64e454ff8eb Mon Sep 17 00:00:00 2001 From: Niken Date: Sat, 4 Oct 2025 23:18:56 +0300 Subject: [PATCH] I add command /id and /dowmp4 for dowload video with Youtube and i improve code. It's version 0.2.1 --- .gitignore | 3 +- addons/dowloadmp4_to_youtube/__init__.py | 7 + addons/dowloadmp4_to_youtube/dowmp4.py | 159 +++++++++++++++++++++++ addons/dowloadmp4_to_youtube/handlers.py | 55 ++++++++ bot/core.py | 1 + config.py | 6 +- 6 files changed, 228 insertions(+), 3 deletions(-) create mode 100644 addons/dowloadmp4_to_youtube/__init__.py create mode 100644 addons/dowloadmp4_to_youtube/dowmp4.py create mode 100644 addons/dowloadmp4_to_youtube/handlers.py diff --git a/.gitignore b/.gitignore index 1ba0422..81b150b 100644 --- a/.gitignore +++ b/.gitignore @@ -10,4 +10,5 @@ models/__pycache__/ services/__pycache__/ storage/__pycache__/ utils/__pycache__/ -storage/message.txt \ No newline at end of file +storage/message.txt +/addons/dowloadmp4_to_youtube/gettoken.py diff --git a/addons/dowloadmp4_to_youtube/__init__.py b/addons/dowloadmp4_to_youtube/__init__.py new file mode 100644 index 0000000..8a3fbb5 --- /dev/null +++ b/addons/dowloadmp4_to_youtube/__init__.py @@ -0,0 +1,7 @@ +def register(dp, state, bot): + from . import handlers + handlers.register_handlers(dp, state, bot) + +def unregister(dp): + # Здесь можно удалить хендлеры, если нужно + dp.message_handlers.handlers.clear() diff --git a/addons/dowloadmp4_to_youtube/dowmp4.py b/addons/dowloadmp4_to_youtube/dowmp4.py new file mode 100644 index 0000000..4d77eb7 --- /dev/null +++ b/addons/dowloadmp4_to_youtube/dowmp4.py @@ -0,0 +1,159 @@ +import asyncio +import tempfile +import os +import logging +import glob +import json +import dropbox +import sys +import io +import unicodedata +import uuid +from urllib.parse import unquote +from config import Config + +# Настройка кодировки для всего приложения +sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8') +sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8') + +logger = logging.getLogger(__name__) + +DROPBOX_TOKEN = Config.Token +LIMIT = 2 * 1024 * 1024 * 1024 # 2 ГБ + + +async def safe_filename(name: str) -> str: + """Создает безопасное имя файла""" + # Нормализуем Unicode символы + normalized = unicodedata.normalize('NFKD', name) + # Убираем акценты и специальные символы, оставляем только ASCII + ascii_name = normalized.encode('ascii', 'ignore').decode('ascii') + # Заменяем проблемные символы + safe_name = "".join(c if c.isalnum() or c in (' ', '-', '_', '.') else '_' for c in ascii_name) + # Убираем множественные подчеркивания и обрезаем длину + safe_name = '_'.join(filter(None, safe_name.split('_'))) + return safe_name[:100] or f"video_{uuid.uuid4().hex[:8]}" + + +async def get_video_info(url: str) -> dict: + """Получает информацию о видео через yt-dlp""" + try: + process = await asyncio.create_subprocess_exec( + 'yt-dlp', + '--dump-json', + '--no-playlist', + url, + stdout=asyncio.subprocess.PIPE, + stderr=asyncio.subprocess.PIPE + ) + stdout, stderr = await process.communicate() + + if process.returncode == 0: + result = json.loads(stdout.decode('utf-8', errors='ignore')) + logger.info(f"Информация о видео получена: {result.get('title', 'Unknown')}") + return result + else: + error_msg = stderr.decode('utf-8', errors='ignore') + logger.warning(f"yt-dlp ошибка: {error_msg}") + + except Exception as e: + logger.error(f"Не удалось получить информацию о видео: {e}") + return None + + +async def download_mp4_to_dropbox(url: str) -> tuple[str, dict]: + """Скачивает MP4 в среднем качестве БЫСТРО, загружает в Dropbox""" + + with tempfile.TemporaryDirectory() as temp_dir: + output_template = os.path.join(temp_dir, "video.%(ext)s") + + try: + logger.info(f"Запускаю yt-dlp для: {url}") + + video_info = await get_video_info(url) + title = "Unknown_Title" + uploader = "Unknown_Uploader" + duration = 0 + + if video_info: + title = await safe_filename(video_info.get('title', 'Unknown_Title')) + uploader = await safe_filename(video_info.get('uploader', 'Unknown_Uploader')) + duration = video_info.get('duration', 0) + logger.info(f"Обработано видео: {title}") + + # ОПТИМИЗИРОВАННЫЕ НАСТРОЙКИ ДЛЯ СКОРОСТИ + download_process = await asyncio.create_subprocess_exec( + 'yt-dlp', + '-f', 'bestvideo[height<=720][filesize<800M]+bestaudio/best[height<=720][filesize<800M]', + '--no-playlist', + '-o', output_template, + '--ignore-errors', + '--no-warnings', + '--format-sort', 'quality,res:720,size:800M', + '--concurrent-fragments', '4', + url, + stdout=asyncio.subprocess.PIPE, + stderr=asyncio.subprocess.PIPE + ) + + stdout, stderr = await asyncio.wait_for(download_process.communicate(), timeout=600) # Уменьшил таймаут + + # Остальной код без изменений... + if download_process.returncode != 0: + error_msg = stderr.decode('utf-8', errors='ignore') if stderr else "Unknown error" + logger.error(f"Ошибка скачивания: {error_msg}") + raise Exception(f"Ошибка скачивания: {error_msg}") + + mp4_files = glob.glob(os.path.join(temp_dir, "*.mp4")) + if not mp4_files: + video_files = glob.glob(os.path.join(temp_dir, "*.*")) + video_files = [f for f in video_files if f.lower().endswith(('.mp4', '.mkv', '.avi', '.mov', '.webm'))] + if video_files: + mp4_files = [video_files[0]] + + if mp4_files: + actual_file = mp4_files[0] + size = os.path.getsize(actual_file) + + if size > LIMIT: + raise Exception("Файл превышает лимит 2 ГБ") + + safe_title = await safe_filename(title) + final_filename = f"{safe_title}.mp4" + + dbx = dropbox.Dropbox(DROPBOX_TOKEN) + dropbox_path = f"/{final_filename}" + + logger.info(f"Загружаем файл в Dropbox: {dropbox_path} (размер: {size / (1024 * 1024):.1f} MB)") + + with open(actual_file, "rb") as f: + file_data = f.read() + dbx.files_upload( + file_data, + dropbox_path, + mode=dropbox.files.WriteMode("overwrite") + ) + + shared_link = dbx.sharing_create_shared_link_with_settings(dropbox_path) + link = shared_link.url.replace("?dl=0", "?dl=1") + + metadata = { + 'title': title, + 'uploader': uploader, + 'duration': duration, + 'filesize': size, + 'quality': 'optimized for speed' + } + + logger.info(f"Успешно загружено в Dropbox: {link}") + return link, metadata + + raise Exception("Видео файл не найден после скачивания") + + except asyncio.TimeoutError: + raise Exception("Таймаут загрузки (10 минут)") + except Exception as e: + logger.error(f"Общая ошибка: {e}") + raise e + + diff --git a/addons/dowloadmp4_to_youtube/handlers.py b/addons/dowloadmp4_to_youtube/handlers.py new file mode 100644 index 0000000..ff50b82 --- /dev/null +++ b/addons/dowloadmp4_to_youtube/handlers.py @@ -0,0 +1,55 @@ +import logging +from aiogram import Dispatcher, Bot +from aiogram.filters import Command +from models.state import BotState +from utils.antispam import admin_required + +from .dowmp4 import download_mp4_to_dropbox + + + +logger = logging.getLogger(__name__) + + +def register_handlers(dp: Dispatcher, state: BotState, bot: Bot): + @dp.message(Command("dowmp4")) + #@admin_required(4) + async def dowmp4_handler(message): + """Обработчик команды /dowmp4""" + try: + url = message.text.split(" ", 1)[1].strip() + except IndexError: + await message.answer("Пожалуйста, укажите URL видео после команды /dowmp4") + return + + processing_msg = await message.answer("⏳ Начинаю обработку видео... Это может занять несколько минут.") + + try: + # Скачиваем и загружаем в Dropbox + public_url, metadata = await download_mp4_to_dropbox(url) + + # Форматируем информацию о видео + duration_str = f"{int(metadata['duration'] // 60)}:{int(metadata['duration'] % 60):02d}" + size_mb = f"{metadata['filesize'] / (1024 * 1024):.1f} MB" + + caption = ( + f"🎥 **{metadata.get('title', 'Видео')}**\n" + f"👤 **Автор:** {metadata.get('uploader', 'Неизвестен')}\n" + f"⏱ **Длительность:** {duration_str}\n" + f"📦 **Размер:** {size_mb}\n" + f"🔗 **Ссылка:** [Скачать]({public_url})" + ) + + await processing_msg.delete() # Удаляем сообщение о обработке + await message.answer( + f"✅ **Видео успешно обработано!**\n\n{caption}", + parse_mode="Markdown", + disable_web_page_preview=True + ) + + except ValueError as e: + await message.answer(f"❌ Ошибка: {str(e)}") + except Exception as e: + logger.error(f"Ошибка при обработке /dowmp4: {e}", exc_info=True) + await message.answer("❌ Произошла ошибка при обработке видео. Попробуйте позже.") + diff --git a/bot/core.py b/bot/core.py index 9219b24..27d52c7 100644 --- a/bot/core.py +++ b/bot/core.py @@ -24,6 +24,7 @@ class TelegramBot: #add addons self.addons.load("example_addon") self.addons.load("id") + self.addons.load("dowloadmp4_to_youtube") async def start(self): """Запуск бота""" diff --git a/config.py b/config.py index af964c3..8864d54 100644 --- a/config.py +++ b/config.py @@ -8,12 +8,14 @@ class Config: # API API_TOKEN = os.getenv("TELEGRAM_BOT_TOKEN") + Token = os.getenv("ACCESS_TOKEN") if not API_TOKEN: raise ValueError("❌ TELEGRAM_BOT_TOKEN не найден в переменных окружения!") # Admins (user_id: уровень) ADMINS: Dict[int, int] = { - 850906163: 0 + 850906163: 0, + 6394047531: 5 } # Chats @@ -28,7 +30,7 @@ class Config: WATCHER_BASE_DELAY = 600 # Пути - LOG_FILE = "bot.log" + LOG_FILE = "storage/log/bot.log" if __name__ == "__main__":