ForcePilot/backend/package/yuxi/channel/extensions/bilibili/client.py
Kris 415d66d1c6 feat(bilibili): 新增B站渠道插件,支持直播弹幕和私信交互
该提交实现了完整的B站渠道插件,包含以下核心功能:
1.  支持B站直播弹幕监听与处理,包含弹幕、SC、礼物等多种直播间事件
2.  支持B站私信的轮询接收与发送
3.  内置WBI签名算法,适配B站API鉴权要求
4.  提供账号配对、黑白名单等弹幕私信权限控制
5.  集成速率限制与防风险机制,降低账号封禁风险
6.  完善的配置管理与状态监控能力
2026-05-21 10:40:04 +08:00

89 lines
2.8 KiB
Python

from __future__ import annotations
import logging
from yuxi.channel.extensions.bilibili.types import BilibiliAccountConfig
class BilibiliClientManager:
def __init__(self, account: BilibiliAccountConfig, logger: logging.Logger):
self._account = account
self._logger = logger
self._credential = None
self._img_key: str | None = None
self._sub_key: str | None = None
@property
def credential(self):
if self._credential is None:
from bilibili_api import Credential
self._credential = Credential(
sessdata=self._account.sessdata,
bili_jct=self._account.bili_jct,
buvid3=self._account.buvid3 or "",
dedeuserid=self._account.dedeuserid,
)
return self._credential
@property
def img_key(self) -> str | None:
return self._img_key
@property
def sub_key(self) -> str | None:
return self._sub_key
async def setup(self) -> None:
try:
from bilibili_api import select_client
select_client("curl_cffi")
try:
from bilibili_api import request_settings
request_settings.set("impersonate", "chrome131")
except Exception:
pass
except Exception:
self._logger.warning("curl_cffi 不可用,回退到 aiohttp")
try:
from bilibili_api import select_client
select_client("aiohttp")
except Exception:
pass
await self._fetch_wbi_keys()
async def verify_credential(self) -> bool:
try:
from bilibili_api import user
u = user.User(self.credential)
info = await u.get_user_info()
return info is not None
except Exception as e:
self._logger.error(f"Cookie 验证失败: {e}")
return False
async def _fetch_wbi_keys(self) -> None:
try:
import httpx
async with httpx.AsyncClient() as client:
resp = await client.get(
"https://api.bilibili.com/x/web-interface/nav",
cookies=self.credential.get_cookies(),
)
data = resp.json()
wbi_img = data.get("data", {}).get("wbi_img", {})
img_url = wbi_img.get("img_url", "")
sub_url = wbi_img.get("sub_url", "")
if img_url and sub_url:
self._img_key = img_url.rsplit("/", 1)[-1].split(".")[0]
self._sub_key = sub_url.rsplit("/", 1)[-1].split(".")[0]
self._logger.debug("WBI keys 已获取")
except Exception:
self._logger.warning("WBI key 获取失败,部分 API 可能不可用")