该提交实现了完整的B站渠道插件,包含以下核心功能: 1. 支持B站直播弹幕监听与处理,包含弹幕、SC、礼物等多种直播间事件 2. 支持B站私信的轮询接收与发送 3. 内置WBI签名算法,适配B站API鉴权要求 4. 提供账号配对、黑白名单等弹幕私信权限控制 5. 集成速率限制与防风险机制,降低账号封禁风险 6. 完善的配置管理与状态监控能力
177 lines
6.1 KiB
Python
177 lines
6.1 KiB
Python
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()
|