feat(auth): separate read and write bilibili accounts

This commit is contained in:
kairee committed 2026-10-31 20:01:21 +08:00
1 parent 5c2ab7b187
commit 178ab64b18
17 files changed
+671 -707

No files matched your search

+39 -32
View File
@@ -1,13 +1,8 @@
# -*- coding: utf-8 -*-
"""运行期配置加载、校验与热加载。
支持两种写法:
* 新格式:``{"bilibili": {"mid": ..., "cookie": ...}, ...}``;
* 旧 ``favsort.py`` 格式:顶层 ``mid`` / ``cookie`` / ``csrf``。
旧的扁平字段仍然被读取,因此 ``bili-fav auth`` 写回的 config.json 无需迁移即可
被看板使用。敏感值只保存在配置对象中,日志输出必须先经过 ``logging_json`` 脱敏。
B 站凭据必须采用 ``bilibili.read`` / ``bilibili.write`` 双账号格式。敏感值只
保存在配置对象中,日志输出必须先经过 ``logging_json`` 脱敏。
"""
from __future__ import annotations
@@ -43,7 +38,10 @@ DEFAULT_HEAT_WEIGHTS: dict[str, float] = {
}
DEFAULT_CONFIG: dict[str, Any] = {
"bilibili": {"mid": 0, "csrf": "", "cookie": ""},
"bilibili": {
"read": {"mid": 0, "csrf": "", "cookie": ""},
"write": {"mid": 0, "csrf": "", "cookie": ""},
},
"telegram": {"enabled": False, "bot_token": "", "chat_id": 0},
"web": {
"host": "127.0.0.1",
@@ -73,9 +71,12 @@ DEFAULT_CONFIG: dict[str, Any] = {
# 环境变量只作为 CI / 临时调试的覆盖入口,不作为主路径。
ENV_OVERRIDES: dict[str, tuple[tuple[str, ...], type]] = {
"BILI_COOKIE": (("bilibili", "cookie"), str),
"BILI_CSRF": (("bilibili", "csrf"), str),
"BILI_MID": (("bilibili", "mid"), int),
"BILI_READ_COOKIE": (("bilibili", "read", "cookie"), str),
"BILI_READ_CSRF": (("bilibili", "read", "csrf"), str),
"BILI_READ_MID": (("bilibili", "read", "mid"), int),
"BILI_WRITE_COOKIE": (("bilibili", "write", "cookie"), str),
"BILI_WRITE_CSRF": (("bilibili", "write", "csrf"), str),
"BILI_WRITE_MID": (("bilibili", "write", "mid"), int),
"TG_BOT_TOKEN": (("telegram", "bot_token"), str),
"TG_CHAT_ID": (("telegram", "chat_id"), int),
"DASHBOARD_AUTH_TOKEN": (("web", "auth_token"), str),
@@ -91,12 +92,18 @@ class ConfigError(ValueError):
@dataclass(frozen=True)
class BilibiliConfig:
class BilibiliCredentials:
mid: int
csrf: str
cookie: str
@dataclass(frozen=True)
class BilibiliConfig:
read: BilibiliCredentials
write: BilibiliCredentials
@dataclass(frozen=True)
class TelegramConfig:
enabled: bool
@@ -234,18 +241,6 @@ def _as_str_list(value: Any, name: str) -> list[str]:
return out
def _legacy_to_current(raw: Mapping[str, Any]) -> dict[str, Any]:
"""把 favsort 的扁平字段映射到新格式,不修改调用方对象。"""
data = copy.deepcopy(dict(raw))
if "bilibili" not in data:
data["bilibili"] = {
"mid": raw.get("mid", 0),
"csrf": raw.get("csrf", ""),
"cookie": raw.get("cookie", ""),
}
return data
def _validate_folders(value: Any) -> tuple[FolderConfig, ...]:
if not isinstance(value, list):
raise ConfigError("folders 必须是数组")
@@ -273,11 +268,17 @@ def _build_config(data: Mapping[str, Any], path: Path) -> AppConfig:
b = data["bilibili"]
if not isinstance(b, Mapping):
raise ConfigError("bilibili 必须是对象")
bilibili = BilibiliConfig(
mid=_as_int(b.get("mid", 0), "bilibili.mid", minimum=0),
csrf=_as_str(b.get("csrf", "") or "", "bilibili.csrf"),
cookie=_as_str(b.get("cookie", "") or "", "bilibili.cookie"),
)
def credentials(account: str) -> BilibiliCredentials:
value = b.get(account)
if not isinstance(value, Mapping):
raise ConfigError(f"bilibili.{account} 必须是对象")
return BilibiliCredentials(
mid=_as_int(value.get("mid", 0), f"bilibili.{account}.mid", minimum=0),
csrf=_as_str(value.get("csrf", "") or "", f"bilibili.{account}.csrf"),
cookie=_as_str(value.get("cookie", "") or "", f"bilibili.{account}.cookie"),
)
bilibili = BilibiliConfig(read=credentials("read"), write=credentials("write"))
tg = data["telegram"]
if not isinstance(tg, Mapping):
@@ -431,8 +432,14 @@ def load_config(
raise ConfigError(f"无法读取配置文件: {config_path}: {exc}") from exc
if not isinstance(raw, Mapping):
raise ConfigError("配置文件顶层必须是 JSON 对象")
raw_bilibili = raw.get("bilibili")
if not isinstance(raw_bilibili, Mapping):
raise ConfigError("缺少 bilibili.read / bilibili.write 双账号配置")
for account in ("read", "write"):
if account not in raw_bilibili:
raise ConfigError(f"缺少 bilibili.{account} 账号配置")
data = _deep_merge(DEFAULT_CONFIG, _legacy_to_current(raw))
data = _deep_merge(DEFAULT_CONFIG, raw)
env_map = os.environ if env is None else env
for env_name, (target, caster) in ENV_OVERRIDES.items():
@@ -447,9 +454,9 @@ def load_config(
_set_path(data, target, value)
cfg = _build_config(data, config_path)
if require_auth and (not cfg.bilibili.cookie or not cfg.bilibili.csrf):
if require_auth and (not cfg.bilibili.read.cookie or not cfg.bilibili.read.csrf):
raise ConfigError(
"config.json 中的 cookie / csrf 不能为空(可先运行 bili-fav auth)"
"config.json 中的 bilibili.read.cookie / csrf 不能为空(可先运行 python app.py auth)"
)
return cfg