61 lines
4.0 KiB
Python
61 lines
4.0 KiB
Python
"""Validate YouTube-only cookies and attach them to an in-memory yt-dlp jar."""
|
|
from __future__ import annotations
|
|
import math
|
|
import re
|
|
from http.cookiejar import Cookie
|
|
|
|
DOMAIN = re.compile(r"^(?:[a-z0-9-]+\.)*youtube\.com$", re.I)
|
|
NAME = re.compile(r"^[!#$%&'*+.^_`|~0-9A-Za-z-]{1,256}$")
|
|
|
|
|
|
def validate_cookies(value):
|
|
if value is None:
|
|
return []
|
|
if not isinstance(value, list) or len(value) > 300:
|
|
raise ValueError("Некорректные данные входа YouTube.")
|
|
result, size = [], 0
|
|
for entry in value:
|
|
if not isinstance(entry, dict):
|
|
raise ValueError("Некорректные данные входа YouTube.")
|
|
name, content, domain, path = (entry.get(key) for key in ("name", "value", "domain", "path"))
|
|
if not isinstance(domain, str) or len(domain) > 253 or not DOMAIN.fullmatch(domain.lstrip(".")) or domain.startswith(".."):
|
|
raise ValueError("Данные входа разрешены только для YouTube.")
|
|
if (not isinstance(name, str) or not NAME.fullmatch(name) or not isinstance(content, str) or len(content) > 16384
|
|
or any(char in content for char in "\r\n\x00") or not isinstance(path, str) or not path.startswith("/")
|
|
or len(path) > 2048 or any(char in path for char in "\r\n\x00")):
|
|
raise ValueError("Некорректные данные входа YouTube.")
|
|
expires = entry.get("expirationDate")
|
|
if expires is not None and (isinstance(expires, bool) or not isinstance(expires, (int, float)) or not math.isfinite(expires) or not 0 <= expires < 2**63):
|
|
raise ValueError("Некорректный срок действия входа YouTube.")
|
|
size += len((name + content + domain + path).encode("utf-8"))
|
|
if size > 512 * 1024:
|
|
raise ValueError("Превышен размер данных входа YouTube.")
|
|
result.append({"name": name, "value": content, "domain": domain.lower(), "path": path,
|
|
"secure": bool(entry.get("secure", True)), "httpOnly": bool(entry.get("httpOnly")),
|
|
"hostOnly": bool(entry.get("hostOnly", not domain.startswith("."))), "expirationDate": expires})
|
|
return result
|
|
|
|
|
|
def apply_cookies(ydl, cookies):
|
|
for entry in validate_cookies(cookies):
|
|
expires = entry["expirationDate"]
|
|
ydl.cookiejar.set_cookie(Cookie(
|
|
version=0, name=entry["name"], value=entry["value"], port=None, port_specified=False,
|
|
domain=entry["domain"], domain_specified=not entry["hostOnly"], domain_initial_dot=entry["domain"].startswith("."),
|
|
path=entry["path"], path_specified=True, secure=entry["secure"],
|
|
expires=int(expires) if expires is not None else None, discard=expires is None,
|
|
comment=None, comment_url=None, rest={"HttpOnly": None} if entry["httpOnly"] else {}, rfc2109=False))
|
|
|
|
|
|
def is_access_error(error):
|
|
return bool(re.search(r"sign in to confirm|not a bot|cookies.*authentication|login required|log in to|HTTP Error (?:403|410)", str(error), re.I))
|
|
|
|
|
|
def public_error(error, authenticated=False):
|
|
message = str(error)
|
|
if re.search(r"sign in to confirm|not a bot|cookies.*authentication|login required|log in to", message, re.I):
|
|
if authenticated:
|
|
return "YouTube отклонил вход браузера или просит проверку. Откройте этот ролик на YouTube, пройдите проверку, затем нажмите «Проверить вход» и повторите загрузку."
|
|
return "YouTube отклонил запрос загрузчика и просит проверку на бота. Повторите запрос или обновите yt-dlp в компонентах. Дополнительно можно подключить вход кнопкой «Использовать вход в YouTube». Помощник работает; переустановка не нужна."
|
|
return message[:2000]
|