"""Build frontier-im WebSocket URL (DouYin_Spider douyin_recv_msg logic).""" import hashlib import logging from typing import Optional import requests from .auth import DouyinAuth from .dy_util import generate_a_bogus, generate_msToken, generate_webid, splice_url from .session import DouyinImSession, is_frontier_ws_url logger = logging.getLogger("douyin_im.frontier") APP_KEY = "e1bd35ec9db7b8d846de66ed140b1ad9" FP_ID = "9" def build_frontier_ws_url(session: DouyinImSession, device_id: str) -> Optional[str]: token = session.cookies.get("sessionid") or session.cookies.get("sessionid_ss") or "" if not token or not device_id: return None access_key_raw = f"{FP_ID}{APP_KEY}{device_id}f8a69f1719916z" access_key = hashlib.md5(access_key_raw.encode("utf-8")).hexdigest() params = { "aid": "6383", "device_platform": "douyin_pc", "fpid": FP_ID, "device_id": device_id, "token": token, "access_key": access_key, } query = "&".join(f"{k}={v}" for k, v in params.items()) return f"wss://frontier-im.douyin.com/ws/v2?{query}" def fetch_device_id(session: DouyinImSession) -> str: """Call Douyin user query API to obtain device/web id.""" auth = DouyinAuth() auth.perepare_auth( session.cookie_header(), session.web_protect_str, session.keys_str, ) url = "https://www.douyin.com/aweme/v1/web/query/user" headers = { "User-Agent": session.user_agent, "Referer": "https://www.douyin.com/discover", "Accept": "application/json, text/plain, */*", "Cookie": session.cookie_header(), } params = { "device_platform": "webapp", "aid": "6383", "channel": "channel_pc_web", "publish_video_strategy_type": "2", "verifyFp": auth.cookie.get("s_v_web_id", "") if auth.cookie else "", "fp": auth.cookie.get("s_v_web_id", "") if auth.cookie else "", "webid": generate_webid(auth, "https://www.douyin.com/discover"), "msToken": generate_msToken(), } query = splice_url(params) params["a_bogus"] = generate_a_bogus(query, user_agent=session.user_agent) try: resp = requests.get( url, params=params, headers=headers, cookies=auth.cookie, verify=False, timeout=15, ) data = resp.json() device_id = str(data.get("id") or data.get("device_id") or "") if device_id.isdigit(): logger.info(f"Fetched device_id: {device_id[:20]}...") return device_id logger.warning(f"query/user returned non-numeric id: {device_id[:32]!r}") except Exception as e: logger.warning(f"fetch_device_id failed: {e}") return "" def resolve_frontier_device_id(session: DouyinImSession) -> str: """Frontier WS requires numeric device_id from Douyin query/user API.""" current = str(session.device_id or session.web_id or "") if current.isdigit(): return current fetched = fetch_device_id(session) if fetched and str(fetched).isdigit(): session.device_id = str(fetched) logger.info(f"Using numeric device_id for frontier WS: {fetched[:16]}...") return str(fetched) logger.warning( f"Invalid frontier device_id={current[:24]!r}; " "expected numeric id from query/user API" ) return "" def ensure_frontier_ws(session: DouyinImSession) -> Optional[str]: """Ensure session has a usable frontier WebSocket URL.""" session.sanitize_ws_urls() # 方案 A:若已有浏览器抓到的“真实” frontier URL(带 sdk_cert/ts_sign), # 直接沿用,不要用我们自己推导 token/access_key 的版本去覆盖它。 for url in session.ws_urls: if is_frontier_ws_url(url) and "sdk_cert=" in url: session.ws_urls = [url] logger.info("Using captured real frontier WS URL (with sdk_cert)") return url device_id = resolve_frontier_device_id(session) if not device_id: session.ws_urls = [] return None built = build_frontier_ws_url(session, device_id) if built: session.ws_urls = [built] logger.info("Built frontier WS URL from cookie session") return built return None