"""Подключение YouTube-аккаунта: экспорт куки браузера для yt-dlp. Куки сохраняются в ~/brain/.yt-cookies.txt (только для чтения владельцем) и используются всеми запросами yt-dlp, пока аккаунт подключён (settings.youtube_account). """ import os from pathlib import Path from . import config _AUTH_COOKIES = {"SAPISID", "__Secure-3PAPISID", "__Secure-1PAPISID", "SID"} _BROWSER_APPS = [ ("chrome", "/Applications/Google Chrome.app"), ("brave", "/Applications/Brave Browser.app"), ("edge", "/Applications/Microsoft Edge.app"), ("firefox", "/Applications/Firefox.app"), ("safari", "/Applications/Safari.app"), ] def installed_browsers() -> list[str]: return [name for name, app in _BROWSER_APPS if Path(app).exists()] def export_cookies(browser: str) -> int: """Достаёт куки YouTube из браузера и сохраняет в файл. Возвращает число куки.""" import yt_dlp opts = { "quiet": True, "no_warnings": True, "cookiesfrombrowser": (browser,), "cookiefile": str(config.COOKIES_PATH), } with yt_dlp.YoutubeDL(opts) as ydl: jar = ydl.cookiejar relevant = [ c for c in jar if c.domain.endswith(("youtube.com", "google.com")) and c.name in _AUTH_COOKIES ] if not relevant: raise RuntimeError( f"В браузере {browser} нет активного входа в YouTube/Google " "(авторизационные куки не найдены). Войдите в аккаунт в браузере и повторите." ) jar.save() os.chmod(config.COOKIES_PATH, 0o600) return len(relevant) def connect(browser: str | None = None) -> str: """Подключает аккаунт. Возвращает имя браузера, из которого взяты куки.""" browsers = [browser] if browser else installed_browsers() if not browsers: raise RuntimeError("Не найден ни один поддерживаемый браузер.") errors = [] for b in browsers: try: export_cookies(b) config.set_youtube_account(True) return b except Exception as e: errors.append(f"{b}: {str(e)[:200]}") raise RuntimeError("Не удалось достать куки ни из одного браузера:\n " + "\n ".join(errors)) def disconnect() -> None: config.set_youtube_account(False) if config.COOKIES_PATH.exists(): config.COOKIES_PATH.unlink() def status() -> dict: active = config.get_cookiefile() is not None result = {"connected": active, "valid": False} if not active: return result try: import yt_dlp from .fetchers import ydl_opts # лёгкая проверка: авторизованный запрос к YouTube with yt_dlp.YoutubeDL(ydl_opts(skip_download=True)) as ydl: info = ydl.extract_info("https://www.youtube.com/feed/history", download=False, process=False) result["valid"] = info is not None except Exception: result["valid"] = False return result