Files
StudioLift/skills/yt-studio-auth-prep/scripts/login_profile.py

143 lines
6.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# -*- coding: utf-8 -*-
# /// script
# requires-python = ">=3.10"
# dependencies = [
# "playwright>=1.40",
# ]
# ///
r"""
调试用户目录 .profile 生成 + YouTube 账号登录脚本(yt-studio-auth-prep 技能)。
职责单一:在指定目录创建(或复用)一个独立的浏览器用户数据目录,打开可见浏览器
窗口交给用户手动完成 Google/YouTube 登录,轮询确认 studio.youtube.com 不再跳转
登录页后关闭窗口退出。登录态持久化在该目录,供下游脚本复用:
- youtube-studio-csv-download : --user-data-dir "<place>/.profile" --channel <brand>
- yt-studio-groupid-lookup : 同上(在线捕获套件时)
用法(cwd 任意;目录不存在会自动创建):
python login_profile.py --channel chrome --user-data-dir "<place>/.profile"
python login_profile.py --channel msedge --user-data-dir "<place>/.profile" --timeout-seconds 600
注意:
- channel 一经选定,整个工作区所有下游命令必须用同一品牌(chrome/msedge);
换品牌 = 删除 .profile 后重跑本脚本,并重新捕获 bundles.json。
- 登录态失效(下游报跳 Google 登录页)时直接重跑本脚本补登录,.profile 保留原数据。
"""
from __future__ import annotations
import argparse
import os
import time
STUDIO_URL = "https://studio.youtube.com/"
DEFAULT_TIMEOUT_SECONDS = 600
def classify_url(url: str) -> str:
"""当前页面 URL 归类:google=登录页,studio=已进 Studio,other=其他。"""
u = (url or "").lower()
if "accounts.google" in u:
return "google"
if "studio.youtube.com" in u:
return "studio"
return "other"
def selftest():
assert classify_url("https://accounts.google.com/v3/signin/identifier?x=1") == "google"
assert classify_url("https://studio.youtube.com/channel/UCxx/analytics") == "studio"
assert classify_url("https://www.youtube.com/") == "other"
assert classify_url("") == "other"
print("selftest OK:URL 分类逻辑通过")
def wait_login(page, start_url: str, timeout_seconds: int) -> bool:
"""轮询等待用户完成登录;首次到达 studio 后回访一次,防会话弹回登录页。"""
page.goto(start_url, wait_until="domcontentloaded")
deadline = time.time() + timeout_seconds
hinted = False
last_report = time.time()
while time.time() < deadline:
kind = classify_url(page.url)
if kind == "studio":
page.wait_for_timeout(3000)
page.goto(start_url, wait_until="domcontentloaded")
if classify_url(page.url) == "studio":
return True
elif kind == "google":
if not hinted:
print("[登录] 请在打开的浏览器窗口中完成 Google/YouTube 登录"
"(支持两步验证),完成后本脚本自动检测……")
hinted = True
page.wait_for_timeout(2000)
else:
page.wait_for_timeout(3000)
page.goto(start_url, wait_until="domcontentloaded")
if time.time() - last_report >= 30:
print(f"[登录] 仍在等待登录……剩余 {max(0, int(deadline - time.time()))} 秒")
last_report = time.time()
return False
def main(argv=None):
ap = argparse.ArgumentParser(
description="创建调试用户目录 .profile 并完成 YouTube 账号登录")
ap.add_argument("--selftest", action="store_true",
help="仅跑 URL 分类自测(无需浏览器)")
ap.add_argument("--user-data-dir", help="调试用户目录路径,如 <place>/.profile")
ap.add_argument("--channel", choices=["chrome", "msedge"], default="chrome",
help="浏览器品牌,默认 %(default)s;选定后全工作区统一")
ap.add_argument("--start-url", default=STUDIO_URL,
help="登录检测起始页,默认 %(default)s")
ap.add_argument("--timeout-seconds", type=int, default=DEFAULT_TIMEOUT_SECONDS,
help="等待登录的最长秒数,默认 %(default)s")
args = ap.parse_args(argv)
if args.selftest:
selftest()
return
if not args.user_data_dir:
ap.error("--user-data-dir 必填,如 <place>/.profile")
os.makedirs(args.user_data_dir, exist_ok=True)
print(f"[登录] 打开 {args.channel}(profile={args.user_data_dir}),"
f"最长等待 {args.timeout_seconds} 秒……")
try:
from playwright.sync_api import sync_playwright
except ImportError:
raise SystemExit("[!] 缺少 playwright:在工作区(yt-download-place)内用"
" uv run python 重试,或先 uv sync。")
with sync_playwright() as p:
try:
context = p.chromium.launch_persistent_context(
user_data_dir=args.user_data_dir,
channel=args.channel,
headless=False,
args=["--disable-blink-features=AutomationControlled"],
)
except Exception as e: # noqa: BLE001
raise SystemExit(
f"[!] 启动浏览器失败: {e}\n"
f" 多为 {args.channel} 正被占用(上次窗口未关/手动开过 .profile)。"
"任务管理器彻底退出该品牌浏览器进程后重跑。")
try:
page = context.new_page()
ok = wait_login(page, args.start_url, args.timeout_seconds)
finally:
context.close()
if not ok:
raise SystemExit(
f"[!] {args.timeout_seconds} 秒内未完成登录。.profile 已保留进度,"
"直接重跑同一命令继续;等待不够就加大 --timeout-seconds。")
print(f"[登录] 登录成功:{args.user_data_dir}(channel={args.channel})")
print(f"[i] 该工作区所有下游命令统一使用 --channel {args.channel} "
f"--user-data-dir {args.user_data_dir}")
if __name__ == "__main__":
main()