#!/usr/bin/env python3 """ ๐Ÿ” AuthManager โ€” ็™ปๅฝ•็Šถๆ€็ฎก็†ๅŽŸๅญๅŒ–ๆจกๅ— (v1.0.0) ๅŽŸๅญๆ“ไฝœ: check_login_by_cookie(context) โ†’ bool # Cookie ๆฃ€ๆต‹๏ผˆไธปๆ–นๆกˆ๏ผ‰ check_login_by_dom(page) โ†’ bool # DOM ๆฃ€ๆต‹๏ผˆๅค‡้€‰๏ผŒไป…็งปๅŠจ็ซฏ่ง†ๅฃ๏ผ‰ get_login_status(context, page) โ†’ dict # ๅคš็ปด็ปผๅˆๆฃ€ๆต‹ export_cookies(context, path) โ†’ int # ๅฏผๅ‡บ Cookie ๅˆฐๆ–‡ไปถ inject_cookies(context, path) โ†’ int # ไปŽๆ–‡ไปถๆณจๅ…ฅ Cookie get_session_id(cookies) โ†’ str # ๆๅ– sessionid ่ฎพ่ฎกๅŽŸๅˆ™: 1. Cookie ๆฃ€ๆต‹ไธบไธปๆ–นๆกˆ๏ผˆไธไพ่ต– UI ๅธƒๅฑ€๏ผ‰ 2. DOM ๆฃ€ๆต‹ไธบๅค‡้€‰๏ผˆไป…ๅœจ mobile=True ่ง†ๅฃไธ‹ๆœ‰ๆ•ˆ๏ผ‰ 3. ๆ‰€ๆœ‰ๅ‡ฝๆ•ฐๆ— ๅ‰ฏไฝœ็”จ๏ผˆไธๅ†™ DB/ๆ–‡ไปถ๏ผŒ้™ค้žๅ‡ฝๆ•ฐๅๆ˜Ž็กฎ๏ผ‰ 4. ็ปŸไธ€ๅผ‚ๅธธๅค„็†๏ผˆๆ‰€ๆœ‰ๅผ‚ๅธธ่ฟ”ๅ›ž False/็ฉบๅ€ผ๏ผŒไธๆŠ›ๅ‡บ๏ผ‰ ๆœ€ๅŽๆ›ดๆ–ฐ: 2026-05-01 """ import json import asyncio from pathlib import Path from typing import Optional __version__ = "1.0.0" # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• # Cookie ๆฃ€ๆต‹๏ผˆๆ ธๅฟƒๆ–นๆกˆ๏ผ‰ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• # โ”€โ”€ ๅ„ๅนณๅฐ็™ปๅฝ•ๆ€ Cookie ่ง„ๅˆ™ โ”€โ”€ LOGIN_COOKIE_RULES = { "douyin": { "domain": "douyin", "names": ("sessionid", "sid_guard"), }, "xiaohongshu": { "domain": "xiaohongshu", "names": ("a1", "web_session"), }, "kuaishou": { "domain": "kuaishou", "names": ("kuaishou.login",), }, "zhihu": { "domain": "zhihu", "names": ("z_c0", "d_c0"), }, } async def check_login_by_cookie(context, platform: str = "douyin") -> bool: """้€š่ฟ‡ Cookie ๆฃ€ๆต‹็™ปๅฝ•็Šถๆ€ ๆฃ€ๆต‹ๅนณๅฐ็‰นๆœ‰็š„ session cookie ๆ˜ฏๅฆๅญ˜ๅœจใ€‚ ่ฟ™ๆ˜ฏไธปๆ–นๆกˆ๏ผŒไธไพ่ต–้กต้ข DOM ๅธƒๅฑ€ใ€‚ ๅ‚ๆ•ฐ: context: Playwright BrowserContext platform: ๅนณๅฐๅ็งฐ (douyin/xiaohongshu/kuaishou/zhihu) ่ฟ”ๅ›ž: bool: True=ๅทฒ็™ปๅฝ•, False=ๆœช็™ปๅฝ• """ try: cookies = await context.cookies() except Exception: return False rule = LOGIN_COOKIE_RULES.get(platform) if not rule: return False for c in cookies: domain = c.get("domain", "") name = c.get("name", "") if rule["domain"] in domain and name in rule["names"]: return True return False def check_login_by_cookie_sync(cookies: list, platform: str = "douyin") -> bool: """ๅŒๆญฅ็‰ˆ Cookie ๆฃ€ๆต‹๏ผˆไปŽๅทฒ่Žทๅ–็š„ Cookie ๅˆ—่กจๅˆคๆ–ญ๏ผ‰ ๅ‚ๆ•ฐ: cookies: Cookie ๅˆ—่กจ๏ผˆๆฏไธชไธบ dict๏ผŒๅซ domain/name ๅญ—ๆฎต๏ผ‰ platform: ๅนณๅฐๅ็งฐ (douyin/xiaohongshu/...) ่ฟ”ๅ›ž: bool: True=ๅทฒ็™ปๅฝ• """ rule = LOGIN_COOKIE_RULES.get(platform) if not rule: return False for c in cookies: domain = c.get("domain", "") name = c.get("name", "") if rule["domain"] in domain and name in rule["names"]: return True return False def get_session_id(cookies: list, platform: str = "douyin") -> Optional[str]: """ไปŽ Cookie ๅˆ—่กจไธญๆๅ– session cookie ๅ€ผ ๅ‚ๆ•ฐ: cookies: Cookie ๅˆ—่กจ platform: ๅนณๅฐๅ็งฐ ่ฟ”ๅ›ž: str/None: session cookie ๅ€ผๆˆ– None """ rule = LOGIN_COOKIE_RULES.get(platform) if not rule: return None for c in cookies: if c.get("name") in rule["names"]: return c.get("value") return None def count_platform_cookies(cookies: list, platform: str = "douyin") -> int: """็ปŸ่ฎกๆŒ‡ๅฎšๅนณๅฐ็š„ Cookie ๆ•ฐ้‡""" rule = LOGIN_COOKIE_RULES.get(platform) if not rule: return 0 domain_hint = rule["domain"] return sum(1 for c in cookies if domain_hint in c.get("domain", "")) # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• # DOM ๆฃ€ๆต‹๏ผˆๅค‡้€‰ๆ–นๆกˆ๏ผ‰ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• # ๅ„ๅนณๅฐ็™ปๅฝ•ๆ€ DOM ๆŒ‡็คบๅ™จ LOGIN_INDICATORS = { "douyin": [ "[data-e2e='user-avatar']", # ไธปๆŒ‡็คบๅ™จ "[data-e2e='user-detail']", # ็”จๆˆท่ฏฆๆƒ… ], "xiaohongshu": [ ".user-avatar", ".reds-count", ], "kuaishou": [ ".avatar-container", ], "zhihu": [ ".AppHeader-profile", ], } async def check_login_by_dom(page, platform: str = "douyin") -> bool: """้€š่ฟ‡ DOM ๅ…ƒ็ด ๆฃ€ๆต‹็™ปๅฝ•็Šถๆ€๏ผˆๅค‡้€‰ๆ–นๆกˆ๏ผ‰ ๆณจๆ„: ไป…ๅœจ็งปๅŠจ็ซฏ/ๅนณๆฟ่ง†ๅฃ (mobile=True) ไธ‹ๆœ‰ๆ•ˆใ€‚ ๆกŒ้ข็ซฏ่ง†ๅฃไธ‹ๆŠ–้ŸณไธๆธฒๆŸ“่ฟ™ไบ›ๅ…ƒ็ด ใ€‚ ๅ‚ๆ•ฐ: page: Playwright Page platform: ๅนณๅฐๅ็งฐ๏ผˆdouyin/xiaohongshu/kuaishou/zhihu๏ผ‰ ่ฟ”ๅ›ž: bool: True=ๅทฒ็™ปๅฝ• """ selectors = LOGIN_INDICATORS.get(platform, []) if not selectors: return False try: for sel in selectors: el = await page.query_selector(sel) if el: return True except Exception: pass return False # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• # ็ปผๅˆๆฃ€ๆต‹ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• async def get_login_status(context, page=None, platform: str = "douyin") -> dict: """ๅคš็ปด็ปผๅˆ็™ปๅฝ•ๆฃ€ๆต‹ ๅŒๆ—ถไฝฟ็”จ Cookie ๆฃ€ๆต‹ + DOM ๆฃ€ๆต‹๏ผŒ่ฟ”ๅ›ž่ฏฆ็ป†่ฏŠๆ–ญไฟกๆฏใ€‚ ๅ‚ๆ•ฐ: context: Playwright BrowserContext page: Playwright Page๏ผˆๅฏ้€‰๏ผŒไธไผ ๅˆ™่ทณ่ฟ‡ DOM ๆฃ€ๆต‹๏ผ‰ platform: ๅนณๅฐๅ็งฐ ่ฟ”ๅ›ž: dict: logged_in: bool # ๆœ€็ปˆๅˆคๆ–ญ๏ผˆไปปๆ„ไธ€ไธชๆ–นๆณ•็กฎ่ฎคๅณๅฏ๏ผ‰ cookie_ok: bool # Cookie ๆฃ€ๆต‹็ป“ๆžœ dom_ok: bool/None # DOM ๆฃ€ๆต‹็ป“ๆžœ๏ผˆๆœชไผ  page ไธบ None๏ผ‰ cookie_count: int # douyin.com Cookie ๆ•ฐ้‡ session_id: str/None # sessionid ๅ€ผ method: str # ็กฎ่ฎคๆ–นๆณ•๏ผˆcookie/dom/both๏ผ‰ """ result = { "logged_in": False, "cookie_ok": False, "dom_ok": None, "cookie_count": 0, "session_id": None, "method": "", } # Cookie ๆฃ€ๆต‹ try: cookies = await context.cookies() result["cookie_ok"] = check_login_by_cookie_sync(cookies, platform) result["cookie_count"] = count_platform_cookies(cookies, platform) result["session_id"] = get_session_id(cookies, platform) except Exception: result["cookie_ok"] = False # DOM ๆฃ€ๆต‹ if page: try: result["dom_ok"] = await check_login_by_dom(page, platform) except Exception: result["dom_ok"] = False # ็ปผๅˆๅˆคๆ–ญ if result["cookie_ok"] and result["dom_ok"]: result["logged_in"] = True result["method"] = "both" elif result["cookie_ok"]: result["logged_in"] = True result["method"] = "cookie" elif result["dom_ok"]: result["logged_in"] = True result["method"] = "dom" return result # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• # Cookie ๅฏผๅ‡บ / ๆณจๅ…ฅ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• async def export_cookies(context, file_path: Path) -> int: """ๅฏผๅ‡บๅฝ“ๅ‰ๆต่งˆๅ™จ็š„ๆ‰€ๆœ‰ Cookie ๅˆฐ JSON ๆ–‡ไปถ ๅ‚ๆ•ฐ: context: Playwright BrowserContext file_path: ๅฏผๅ‡บๆ–‡ไปถ่ทฏๅพ„ ่ฟ”ๅ›ž: int: ๅฏผๅ‡บ็š„ Cookie ๆ•ฐ้‡๏ผˆ0 ่กจ็คบๅคฑ่ดฅ๏ผ‰ """ try: cookies = await context.cookies() if not cookies: return 0 file_path = Path(file_path) file_path.parent.mkdir(parents=True, exist_ok=True) with open(file_path, "w", encoding="utf-8") as f: json.dump(cookies, f, indent=2, ensure_ascii=False) return len(cookies) except Exception: return 0 async def inject_cookies(context, file_path: Path) -> int: """ไปŽ JSON ๆ–‡ไปถๆณจๅ…ฅ Cookie ๅˆฐๆต่งˆๅ™จ ๅ‚ๆ•ฐ: context: Playwright BrowserContext file_path: Cookie JSON ๆ–‡ไปถ่ทฏๅพ„ ่ฟ”ๅ›ž: int: ๆˆๅŠŸๆณจๅ…ฅ็š„ Cookie ๆ•ฐ้‡๏ผˆ0 ่กจ็คบๅคฑ่ดฅ๏ผ‰ """ try: file_path = Path(file_path) if not file_path.exists(): return 0 cookies = json.loads(file_path.read_text(encoding="utf-8")) if not cookies: return 0 success = 0 for c in cookies: try: await context.add_cookies([{ "name": c["name"], "value": c["value"], "domain": c.get("domain", ""), "path": c.get("path", "/"), "httpOnly": c.get("httpOnly", False), "secure": c.get("secure", False), "sameSite": c.get("sameSite", "Lax"), }]) success += 1 except Exception: pass return success except Exception: return 0 async def wait_for_login(context, page, platform: str = "douyin", timeout: int = 300, interval: int = 5) -> dict: """็ญ‰ๅพ…็”จๆˆทๆ‰‹ๅŠจ็™ปๅฝ•๏ผˆ่ฝฎ่ฏข Cookie + DOM๏ผ‰ ็”จไบŽ่‡ชๅŠจๅŒ–ๆต็จ‹ไธญ็ญ‰ๅพ…็”จๆˆทๆ‰ซ็ ็™ปๅฝ•็š„ๅœบๆ™ฏใ€‚ ไธ้˜ปๅกž็ปˆ็ซฏ๏ผŒ่‡ชๅŠจๆฃ€ๆต‹ๅˆฐ็™ปๅฝ•ๅŽ่ฟ”ๅ›žใ€‚ ๅ‚ๆ•ฐ: context: Playwright BrowserContext page: Playwright Page platform: ๅนณๅฐๅ็งฐ timeout: ่ถ…ๆ—ถ็ง’ๆ•ฐ๏ผˆ้ป˜่ฎค 300s = 5ๅˆ†้’Ÿ๏ผ‰ interval: ่ฝฎ่ฏข้—ด้š”็ง’ๆ•ฐ๏ผˆ้ป˜่ฎค 5s๏ผ‰ ่ฟ”ๅ›ž: dict: get_login_status() ็š„่ฟ”ๅ›ž็ป“ๆžœ ๅฆ‚ๆžœ่ถ…ๆ—ถ๏ผŒ่ฟ”ๅ›ž logged_in=False """ for i in range(timeout // interval): await asyncio.sleep(interval) status = await get_login_status(context, page, platform) if status["logged_in"]: return status print(f" โณ ็ญ‰ๅพ…็™ปๅฝ•... ({i * interval + interval}s/{timeout}s)") # ่ถ…ๆ—ถ print(f" โฐ ็ญ‰ๅพ…่ถ…ๆ—ถ ({timeout}s)") return {"logged_in": False, "cookie_ok": False, "dom_ok": False, "cookie_count": 0, "session_id": None, "method": ""} # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• # ็‹ฌ็ซ‹ CLI ๆต‹่ฏ•ๅ…ฅๅฃ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• async def _test_check(port: int = 9222): """ๆต‹่ฏ•่ฟžๆŽฅๆŒ‡ๅฎš็ซฏๅฃ็š„ๆต่งˆๅ™จๅนถๆฃ€ๆต‹็™ปๅฝ•็Šถๆ€""" import urllib.request from patchright.async_api import async_playwright try: opener = urllib.request.build_opener(urllib.request.ProxyHandler({})) with opener.open(f"http://localhost:{port}/json/version", timeout=5) as r: ws_url = json.loads(r.read())["webSocketDebuggerUrl"] except Exception as e: print(f"โŒ ๆ— ๆณ•่ฟžๆŽฅ็ซฏๅฃ {port}: {e}") return pw = await async_playwright().start() try: browser = await pw.chromium.connect_over_cdp(ws_url) page = browser.contexts[0].pages[0] context = browser.contexts[0] await page.goto("https://www.douyin.com/", wait_until="domcontentloaded", timeout=15000) await asyncio.sleep(3) status = await get_login_status(context, page, platform="douyin") domain = "douyin" print(f"\n๐Ÿ“Š ็™ปๅฝ•็Šถๆ€ๆฃ€ๆต‹ (็ซฏๅฃ {port})") print(f" Cookie OK: {'โœ…' if status['cookie_ok'] else 'โŒ'}") print(f" DOM OK: {'โœ…' if status['dom_ok'] else 'โŒ'}") print(f" {domain} Cookie: {status['cookie_count']} ไธช") print(f" session: {status['session_id'][:20] + '...' if status['session_id'] else 'โŒ'}") print(f" ๆœ€็ปˆๅˆคๆ–ญ: {'โœ… ๅทฒ็™ปๅฝ• (via ' + status['method'] + ')' if status['logged_in'] else 'โŒ ๆœช็™ปๅฝ•'}") await pw.stop() except Exception as e: print(f"โŒ ๆฃ€ๆต‹ๅคฑ่ดฅ: {e}") if __name__ == "__main__": import sys port = int(sys.argv[1]) if len(sys.argv) > 1 else 9222 asyncio.run(_test_check(port))