diff --git a/core/huya/app_login.py b/core/huya/app_login.py index afe29d4..b638331 100644 --- a/core/huya/app_login.py +++ b/core/huya/app_login.py @@ -392,7 +392,17 @@ class HuyaAppPasswordLogin: self.device_info = device_info or get_profile(self.username, force_new=force_new_device) def login(self) -> HuyaLoginResult: - """执行完整 App 登录获取 Cookie 流程。""" + """执行完整 App 登录获取 Cookie 流程 (成功/失败均记录登录时间 → 设备绑定页)。""" + result = self._login_impl() + try: + from .device_profile import record_login + record_login(self.username, result.success, result.message) + except Exception: # 元数据记录失败不影响登录结果 + pass + return result + + def _login_impl(self) -> HuyaLoginResult: + """登录主体 (原 login)。""" acct = self.username logger.info(f"[huya-app] 开始登录账号 {acct} (机型: {self.device_info.get('model')})...") diff --git a/core/huya/device_profile.py b/core/huya/device_profile.py index 07254a5..2d502c4 100644 --- a/core/huya/device_profile.py +++ b/core/huya/device_profile.py @@ -78,6 +78,23 @@ def generate_profile(model_pick=None) -> dict: } +def record_login(account: str, ok: bool, message: str = "") -> None: + """记录该账号环境的一次登录结果 (最后登录时间/成败)。 + + 幂等元数据: bound_at 只在首次出现时写入; 失败也记录 (设备绑定页要展示)。 + 供 app_login 登录流程调用, GUI 设备绑定页读取展示。 + """ + import time as _time + db = _load_db() + rec = db.get(account) + if rec is None: + return # 尚无环境的账号 (纯 Cookie 导入) 不生成记录 + now = int(_time.time()) + rec.setdefault("bound_at", now) + rec["last_login"] = {"ok": bool(ok), "msg": str(message or "")[:120], "at": now} + _save_db(db) + + def _load_db() -> dict: if PRIMARY_PROFILE_DB.exists(): try: @@ -100,34 +117,35 @@ def _save_db(db: dict) -> None: pass -def _enrich_profile(profile: dict) -> dict: - """补齐 dfp 一致性字段 (幂等): guid32 / Hebe_D1-D5。 +def _enrich_profile(profile: dict) -> tuple[dict, bool]: + """补齐 dfp 一致性字段 (幂等): guid32 / Hebe_D1-D5, 返回 (新画像, 是否有变更)。 来源: R36 解密的真实 dfp 指纹 JSON 结构 (deviceinfo.guid + deviceinfo.Hebe_D1-D5)。 一号一设备: 每账号独立生成后终身不变, 供后续真实载荷构建与 GUI 设备绑定页展示。 + 注意: 返回新字典 (不原地改), 调用方据此判断是否需要回写。 """ + out = dict(profile) changed = False - if not profile.get("guid32"): - profile["guid32"] = hashlib.sha256(os.urandom(16) + b"guid").hexdigest() + if not out.get("guid32"): + out["guid32"] = hashlib.sha256(os.urandom(16) + b"guid").hexdigest() changed = True - hebe = profile.get("hebe") or {} - if len(hebe) < 5: - profile["hebe"] = {f"Hebe_D{i}": hashlib.sha256(os.urandom(16) + f"hebe{i}".encode()).hexdigest() - for i in range(1, 6)} + if len(out.get("hebe") or {}) < 5: + out["hebe"] = {f"Hebe_D{i}": hashlib.sha256(os.urandom(16) + f"hebe{i}".encode()).hexdigest() + for i in range(1, 6)} changed = True - return profile if changed else profile + return out, changed def get_profile(account: str, force_new: bool = False) -> dict: """按账号获取或创建画像(幂等:同账号复用同一套, 自动补齐缺失一致性字段)。""" db = _load_db() if not force_new and account in db: - enriched = _enrich_profile(db[account]) - if enriched is not db[account]: + enriched, changed = _enrich_profile(db[account]) + if changed: db[account] = enriched _save_db(db) return enriched - p = _enrich_profile(generate_profile()) + p, _ = _enrich_profile(generate_profile()) db[account] = p _save_db(db) return p