feat(auth): separate read and write bilibili accounts
This commit is contained in:
1 parent
5c2ab7b187
commit
178ab64b18
17 files changed
+671
-707
No files matched your search
+39
-32
@@ -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
|
||||
|
||||
|
||||
Reference in new issue
Block a user