from __future__ import annotations import logging from yuxi.channel.extensions.bilibili.types import ( BilibiliAccountConfig, BilibiliConfig, is_account_configured, ) logger = logging.getLogger(__name__) _MERGE_FIELDS = ( "sessdata", "bili_jct", "dedeuserid", "buvid3", "buvid4", "bili_ticket", "require_mention", "rate_limit_multiplier", ) class BilibiliConfigAdapter: def __init__(self): self._config: dict = {} @property def _bl_cfg(self) -> dict: return self._config.get("channels", {}).get("bilibili", {}) def list_account_ids(self, config: dict) -> list[str]: self._config = config accounts = self._bl_cfg.get("accounts", {}) if accounts: return list(accounts.keys()) return ["default"] async def resolve_account(self, account_id: str) -> dict: return self._build_account(account_id) def is_configured(self, account: dict) -> bool: acct = self._dict_to_account_config(account) return is_account_configured(acct) def is_enabled(self, account: dict) -> bool: return account.get("enabled", True) def describe_account(self, account: dict) -> dict: acct = self._dict_to_account_config(account) return { "account_id": acct.dedeuserid or "", "uid": acct.dedeuserid, "room_count": len(acct.room_ids), "dm_enabled": acct.dm_enabled, "configured": is_account_configured(acct), } def default_account_id(self) -> str: return self._bl_cfg.get("default_account", "default") def get_account_config(self, config: dict, account_id: str = "default") -> BilibiliAccountConfig: cfg = self._parse_config(config) account = cfg.accounts.get(account_id) if account is None: return BilibiliAccountConfig() if account_id == "default": merged = account.model_copy() for field_name in _MERGE_FIELDS: base_val = getattr(cfg, field_name, None) is_empty = False if isinstance(base_val, str) and not base_val: is_empty = True elif isinstance(base_val, list) and not base_val: is_empty = True if base_val is None or is_empty: continue setattr(merged, field_name, base_val) return merged return account def get_anti_risk_level(self, config: dict) -> str: cfg = self._parse_config(config) return cfg.anti_risk_level def config_schema(self) -> dict: return { "$schema": "https://json-schema.org/draft-07/schema#", "type": "object", "title": "B站 渠道配置", "properties": { "sessdata": { "type": "string", "title": "SESSDATA", "description": "B站 Cookie SESSDATA", "x-ui-password": True, }, "bili_jct": { "type": "string", "title": "bili_jct (CSRF Token)", "description": "B站 CSRF Token", "x-ui-password": True, }, "dedeuserid": { "type": "string", "title": "DedeUserID", "description": "B站用户 UID", }, "room_ids": { "type": "array", "items": {"type": "integer"}, "title": "直播间 ID 列表", }, "dm_policy": { "type": "string", "title": "DM 策略", "enum": ["pairing", "allowlist", "open", "disabled"], "default": "pairing", }, "comment_enabled": { "type": "boolean", "title": "评论功能", "default": False, "x-ui-hidden": True, }, "comment_oids": { "type": "array", "items": {"type": "integer"}, "title": "评论 OID 列表", "x-ui-hidden": True, }, "require_mention": { "type": "boolean", "title": "弹幕提及要求", "default": True, }, }, } def _parse_config(self, config_dict: dict | None) -> BilibiliConfig: if config_dict is None: return BilibiliConfig() channel_cfg = config_dict.get("channels", {}).get("bilibili", {}) if isinstance(channel_cfg, dict): accounts_raw = channel_cfg.get("accounts", {}) accounts = {} for aid, acct in accounts_raw.items(): if isinstance(acct, BilibiliAccountConfig): accounts[aid] = acct elif isinstance(acct, dict): accounts[aid] = BilibiliAccountConfig(**acct) return BilibiliConfig( enabled=channel_cfg.get("enabled", True), name=channel_cfg.get("name"), default_account=channel_cfg.get("default_account", "default"), accounts=accounts, anti_risk_level=channel_cfg.get("anti_risk_level", "moderate"), ) if isinstance(channel_cfg, BilibiliConfig): return channel_cfg return BilibiliConfig() def _build_account(self, account_id: str) -> dict: return self._get_account_or_default(account_id).model_dump() def _get_account_or_default(self, account_id: str) -> BilibiliAccountConfig: cfg = self._parse_config(self._config) return cfg.accounts.get(account_id) or BilibiliAccountConfig() @staticmethod def _dict_to_account_config(account: dict) -> BilibiliAccountConfig: if isinstance(account, BilibiliAccountConfig): return account return BilibiliAccountConfig(**account) if account else BilibiliAccountConfig()