import asyncio import json import logging import random import sys from pathlib import Path from datetime import datetime from bilibili_api import Credential, sync logging.basicConfig( level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s", datefmt="%Y-%m-%d %H:%M:%S", ) logger = logging.getLogger(__name__) COOKIE_FILE = Path("cookies.txt") def load_credential() -> Credential: """从 KEY=VALUE 格式的 txt 文件加载 Credential 对象""" if not COOKIE_FILE.exists(): logger.error("Cookie file not found: %s", COOKIE_FILE) sys.exit(1) try: data = {} with open(COOKIE_FILE, "r") as f: for line in f: line = line.strip() if not line: continue if "=" in line: key, _, value = line.partition("=") data[key.strip()] = value.strip() # 构建 Credential 对象(key 名大小写不敏感) cred = Credential( sessdata=data.get("SESSDATA") or data.get("sessdata"), bili_jct=data.get("bili_jct") or data.get("BILI_JCT"), buvid3=data.get("buvid3") or data.get("BUVID3"), dedeuserid=data.get("dedeuserid") or data.get("DEDEUSERID"), ac_time_value=data.get("ac_time_value") or data.get("AC_TIME_VALUE"), ) logger.info("Credential loaded from %s", COOKIE_FILE) return cred except Exception as e: logger.error("Failed to load credential: %s", e) sys.exit(1) async def check_and_refresh(cred: Credential) -> bool: """检查有效性,如果有效则刷新 token。返回是否成功刷新(或仍有效)""" try: valid = await cred.check_valid() if not valid: logger.warning("Credential is invalid (maybe expired). Refresh may fail.") else: logger.info("Credential is valid, proceeding to refresh...") # 无论 valid 状态如何,都尝试 refresh(refresh 接口本身会处理) await cred.refresh() logger.info("Token refresh successful. New SESSDATA: %s...", cred.sessdata[:20]) # 保存更新后的凭证到文件 save_credential(cred) return True except Exception as e: logger.error("Refresh failed: %s", e) return False def save_credential(cred: Credential): """将 credential 的关键字段保存回 txt 文件(KEY=VALUE 格式)""" lines = [] if cred.sessdata: lines.append(f"SESSDATA={cred.sessdata}") if cred.bili_jct: lines.append(f"bili_jct={cred.bili_jct}") if cred.buvid3: lines.append(f"buvid3={cred.buvid3}") if cred.dedeuserid: lines.append(f"dedeuserid={cred.dedeuserid}") if cred.ac_time_value: lines.append(f"ac_time_value={cred.ac_time_value}") with open(COOKIE_FILE, "w") as f: f.write("\n".join(lines) + "\n") logger.info("Credential saved to %s", COOKIE_FILE) async def main_loop(): logger.info("Starting token refresh daemon...") cred = load_credential() while True: # 随机间隔 50~70 分钟(单位:秒) interval = random.randint(50 * 60, 70 * 60) next_refresh = datetime.now().timestamp() + interval logger.info("Next refresh in %d minutes (at %s)", interval // 60, datetime.fromtimestamp(next_refresh).strftime("%Y-%m-%d %H:%M:%S")) await asyncio.sleep(interval) logger.info("Refreshing token...") success = await check_and_refresh(cred) if not success: logger.error("Refresh failed, will retry in next cycle.") else: logger.info("Refresh cycle completed.") if __name__ == "__main__": try: asyncio.run(main_loop()) except KeyboardInterrupt: logger.info("Daemon stopped by user.") except Exception as e: logger.exception("Unexpected error: %s", e) sys.exit(1)