Files
2026-05-21 18:19:27 +07:00

165 lines
9.4 KiB
Python

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
╔═══════════════════════════════════════════════════════════════╗
║ 🤖 YANDEX MUSIC DOWNLOADER v2.0 "MARK XLII" ║
║ Designed by Pavel • Powered by J.A.R.V.I.S. core ║
║ "Sometimes you gotta run before you can walk." — T. Stark ║
╚═══════════════════════════════════════════════════════════════╝
"""
import asyncio
import webbrowser
import sys
from pathlib import Path
from yandex_music import ClientAsync
# ─────────────────────────────────────────────────────────────
# 🎨 TONY STARK UI UTILS
# ─────────────────────────────────────────────────────────────
class StarkUI:
"""Интерфейс в стиле голографического дисплея Тони Старка"""
COLORS = {
'cyan': '\033[96m', 'blue': '\033[94m', 'green': '\033[92m',
'yellow': '\033[93m', 'red': '\033[91m', 'bold': '\033[1m',
'reset': '\033[0m'
}
@staticmethod
def banner():
return f"""
{StarkUI.COLORS['cyan']}
╔═══════════════════════════════════════════════════════════════╗
║ 🤖 YANDEX MUSIC DOWNLOADER v2.0 "MARK XLII" ║
║ ⚡ Just another Tuesday in the workshop ║
║ 🛠️ Powered by J.A.R.V.I.S. core • {StarkUI.COLORS['yellow']}Pavel Edition{StarkUI.COLORS['reset']}
╚═══════════════════════════════════════════════════════════════╝
{StarkUI.COLORS['reset']}"""
@staticmethod
def status(msg, color='cyan'):
print(f"{StarkUI.COLORS.get(color, '')}[✦] {msg}{StarkUI.COLORS['reset']}", flush=True)
@staticmethod
def success(msg):
print(f"{StarkUI.COLORS['green']}[✓] {msg}{StarkUI.COLORS['reset']}", flush=True)
@staticmethod
def warning(msg):
print(f"{StarkUI.COLORS['yellow']}[⚠] {msg}{StarkUI.COLORS['reset']}", flush=True)
@staticmethod
def error(msg):
print(f"{StarkUI.COLORS['red']}[✗] {msg}{StarkUI.COLORS['reset']}", flush=True)
@staticmethod
def progress(current, total, status="Processing"):
percent = (current / total) * 100
bar = "█" * int(percent // 5) + "░" * (20 - int(percent // 5))
print(f"\r{StarkUI.COLORS['blue']}[{bar}] {percent:.1f}% {status}{StarkUI.COLORS['reset']}", end="", flush=True)
# ─────────────────────────────────────────────────────────────
# 🔗 КОНСТАНТЫ
# ─────────────────────────────────────────────────────────────
TOKEN_HELP_URL = "https://chromewebstore.google.com/detail/yandex-music-token/lcbjeookjibfhjjopieifgjnhlegmkib?hl=ru&utm_source=ext_sidebar"
# ─────────────────────────────────────────────────────────────
# 🎵 CORE LOGIC
# ─────────────────────────────────────────────────────────────
async def download_track(client: ClientAsync, short_track, folder: Path):
"""Загрузка трека. Быстро. Эффектно. Без компромиссов."""
try:
full_track = await short_track.fetch_track_async()
artist = full_track.artists[0].name if full_track.artists else "Unknown Artist"
title = full_track.title
# 🛡️ Фильтр "кривых" символов — Тони не любит баги в путях
safe = lambda s: "".join(c for c in s if c not in r'<>:"/\|?*').strip()
filename = folder / f"{safe(artist)} - {safe(title)}.mp3"
await full_track.download_async(filename=str(filename))
return True, f"{artist} - {title}"
except Exception as e:
return False, str(e)
async def main(TOKEN, DOWNLOAD_DIR):
StarkUI.status("Инициализация протоколов связи...", "blue")
DOWNLOAD_DIR.mkdir(parents=True, exist_ok=True)
client = await ClientAsync(TOKEN).init()
StarkUI.success("Авторизация пройдена. Добро пожаловать, сэр.")
status = await client.account_status()
StarkUI.status(f"Пользователь: {status.account.login}", "cyan")
plus_status = "активен" if status.plus.has_plus else "не активен (качество будет стандартным)"
StarkUI.status(f"Яндекс.Плюс: {plus_status}", "green" if status.plus.has_plus else "yellow")
# 🎛️ Настройки (можно вынести в конфиг позже)
USE_LIKES_PLAYLIST = True
USER_ID, PLAYLIST_KIND = "12345678", "567890"
StarkUI.status("Загрузка метаданных плейлиста...", "blue")
if USE_LIKES_PLAYLIST:
tracks_list = await client.users_likes_tracks()
StarkUI.success("Плейлист 'Мне нравится' получен")
else:
playlist = await client.users_playlists(kind=PLAYLIST_KIND, user_id=USER_ID)
if not playlist:
StarkUI.error("Плейлист не найден. Проверьте координаты, сэр.")
return
tracks_list = playlist[0]
tracks = tracks_list.tracks if hasattr(tracks_list, 'tracks') else []
StarkUI.status(f"Обнаружено треков: {len(tracks)}. Начинаем загрузку...", "cyan")
print()
success, failed = 0, 0
for i, short_track in enumerate(tracks, 1):
StarkUI.progress(i, len(tracks), f"Track {i}/{len(tracks)}")
ok, result = await download_track(client, short_track, DOWNLOAD_DIR)
if ok:
success += 1
else:
failed += 1
StarkUI.warning(f"Пропуск: {result[:50]}...")
await asyncio.sleep(0.25) # Не будим спящих драконов Яндекса
print()
StarkUI.success(f"Миссия выполнена! ✅ Успешно: {success} | Пропущено: {failed}")
StarkUI.status(f"Файлы сохранены в: {DOWNLOAD_DIR}", "green")
await client.close()
# ─────────────────────────────────────────────────────────────
# 🚀 ENTRY POINT
# ─────────────────────────────────────────────────────────────
if __name__ == "__main__":
print(StarkUI.banner())
# 🌐 Открываем браузер для получения токена — Тони ценит ваше время
StarkUI.status("Открываю панель получения токена в браузере...", "cyan")
webbrowser.open(TOKEN_HELP_URL)
print(f"{StarkUI.COLORS['yellow']}💡 Совет: скопируйте токен и вернитесь сюда. Я подожду.{StarkUI.COLORS['reset']}\n")
# 🔐 Ввод токена
TOKEN = input(f"{StarkUI.COLORS['bold']}🔐 Введите токен доступа:{StarkUI.COLORS['reset']} ").strip()
if not TOKEN or len(TOKEN) < 20:
StarkUI.error("Токен выглядит подозрительно. Проверьте, сэр.")
input(f"\n{StarkUI.COLORS['yellow']}Нажмите Enter для выхода...{StarkUI.COLORS['reset']}")
sys.exit(1)
# 📁 Ввод пути
folder_input = input(f"{StarkUI.COLORS['bold']}📁 Путь для сохранения (Enter для C:\\mus):{StarkUI.COLORS['reset']} ").strip()
DOWNLOAD_DIR = Path(folder_input if folder_input else r"C:\mus")
# 🚀 Запуск
try:
asyncio.run(main(TOKEN, DOWNLOAD_DIR))
except KeyboardInterrupt:
print(f"\n{StarkUI.COLORS['yellow']}⏹️ Прервано по команде пользователя. Как скажете.{StarkUI.COLORS['reset']}")
except Exception as e:
StarkUI.error(f"Критический сбой: {e}")
StarkUI.warning("Рекомендация: проверьте соединение и попробуйте снова, сэр.")
finally:
print(f"\n{StarkUI.COLORS['green']}🎯 Система готова к следующей операции.{StarkUI.COLORS['reset']}")
input(f"{StarkUI.COLORS['bold']}Нажмите Enter для завершения...{StarkUI.COLORS['reset']}")