I add command /id and /dowmp4 for dowload video with Youtube and i improve code.
It's version 0.2.1
This commit is contained in:
+2
-1
@@ -10,4 +10,5 @@ models/__pycache__/
|
|||||||
services/__pycache__/
|
services/__pycache__/
|
||||||
storage/__pycache__/
|
storage/__pycache__/
|
||||||
utils/__pycache__/
|
utils/__pycache__/
|
||||||
storage/message.txt
|
storage/message.txt
|
||||||
|
/addons/dowloadmp4_to_youtube/gettoken.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()
|
||||||
@@ -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
|
||||||
|
|
||||||
|
|
||||||
@@ -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("❌ Произошла ошибка при обработке видео. Попробуйте позже.")
|
||||||
|
|
||||||
@@ -24,6 +24,7 @@ class TelegramBot:
|
|||||||
#add addons
|
#add addons
|
||||||
self.addons.load("example_addon")
|
self.addons.load("example_addon")
|
||||||
self.addons.load("id")
|
self.addons.load("id")
|
||||||
|
self.addons.load("dowloadmp4_to_youtube")
|
||||||
|
|
||||||
async def start(self):
|
async def start(self):
|
||||||
"""Запуск бота"""
|
"""Запуск бота"""
|
||||||
|
|||||||
@@ -8,12 +8,14 @@ class Config:
|
|||||||
|
|
||||||
# API
|
# API
|
||||||
API_TOKEN = os.getenv("TELEGRAM_BOT_TOKEN")
|
API_TOKEN = os.getenv("TELEGRAM_BOT_TOKEN")
|
||||||
|
Token = os.getenv("ACCESS_TOKEN")
|
||||||
if not API_TOKEN:
|
if not API_TOKEN:
|
||||||
raise ValueError("❌ TELEGRAM_BOT_TOKEN не найден в переменных окружения!")
|
raise ValueError("❌ TELEGRAM_BOT_TOKEN не найден в переменных окружения!")
|
||||||
|
|
||||||
# Admins (user_id: уровень)
|
# Admins (user_id: уровень)
|
||||||
ADMINS: Dict[int, int] = {
|
ADMINS: Dict[int, int] = {
|
||||||
850906163: 0
|
850906163: 0,
|
||||||
|
6394047531: 5
|
||||||
}
|
}
|
||||||
|
|
||||||
# Chats
|
# Chats
|
||||||
@@ -28,7 +30,7 @@ class Config:
|
|||||||
WATCHER_BASE_DELAY = 600
|
WATCHER_BASE_DELAY = 600
|
||||||
|
|
||||||
# Пути
|
# Пути
|
||||||
LOG_FILE = "bot.log"
|
LOG_FILE = "storage/log/bot.log"
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
Reference in New Issue
Block a user