BiliDM 是一个 直接连接 B 站直播弹幕协议 的桌面客户端,用 Tauri 2 打包,前端 React 19 + TypeScript + Tailwind 4,后端 Rust + Tokio。
它做了哪些事:
- 解析 B 站直播弹幕的 WSS 二进制协议(Header/Body、JSON/Zlib/Brotli 三种载荷)
- 处理风控:
buvid3暖身 + WBI 签名(不签getDanmuInfo直接返回-352) - 二维码扫码登录,登录后写真实
SESSDATA进 cookie,热门间不再被匿名节流 - 提供普通模式与 迷你悬浮模式(透明、置顶、可调透明度、可拖动)
- 弹幕类型过滤、缓存上限、启动自动重连等本地设置
⚠️ 仅供个人学习交流。请遵守 B 站用户协议,不要用于刷弹幕、机器人、商用聚合等场景。
主界面:弹幕、礼物、SC、上舰、互动统一时间线,左侧侧栏管理多个直播间。
迷你模式:右上角悬浮、半透明、置顶;可拖动、可调透明度,看弹幕的同时打游戏/写代码两不误。
扫码登录:第一次启动后扫码登录 B 站账号,凭据本地加密保存,下次自动恢复。
设置面板:可控制要显示哪些类型的消息、缓存条数上限、是否启动时自动连接上次的房间。
截图由
pnpm dev+?demo模式渲染(src/lib/demo.ts中定义了样例事件),方便随时重新生成。
- ✅ 解析弹幕 / 礼物 / SuperChat / 大航海 / 互动(进场·关注·点赞·分享)
- ✅ 二维码登录,凭证持久化到本地(
~/Library/Application Support/com.bilidm.desktop/auth.json等) - ✅ 迷你悬浮窗:透明背景、置顶、滑块调透明度、整窗拖动
- ✅ 房间多开(清单持久化)+ 启动自动重连
- ✅ 设置面板:按类型过滤、缓存上限、自动重连开关
- ✅ 内置插件:弹幕语音 TTS / OBS 文件 / 抽奖投票 / Webhook 上报(使用文档)
- ✅ 外部 JS 插件:从插件目录加载第三方插件(开发文档)
- 🚧 弹幕历史归档(路线图)
数据流向是单向的:Rust 拿到一帧字节流 → 解包 → 解析成 DanmakuEvent → 通过 Tauri Event Bus 发到前端 → 前端按设置过滤后入列表与统计。
📐 mermaid 源(可直接编辑)
flowchart TB
subgraph FE["Frontend · Tauri WebView<br/>React 19 + TS + Tailwind 4 + Vite 7"]
direction LR
APP["App.tsx<br/>全局状态 / 事件订阅"]
UI["Sidebar · TopBar · StatsBar<br/>DanmakuStream · CompactView"]
DLG["LoginDialog (QR)<br/>SettingsDialog"]
end
subgraph CORE["Tauri Core · Rust + Tokio"]
direction LR
CMDS["#tauri::command<br/>connect_room · disconnect_room<br/>set_compact · auth_qr_*"]
subgraph BILI_MOD["bili::"]
direction TB
CONN["connection<br/>WSS 客户端 + 心跳 + 状态机"]
PROTO["proto<br/>16B header · Zlib · Brotli"]
PARSER["parser<br/>CMD → DanmakuEvent"]
CONF["conf<br/>reqwest + buvid3 暖身"]
WBI["wbi<br/>img/sub_key → mixin → MD5"]
AUTH["auth<br/>QR 生成 / 轮询 / cookie 解析"]
STORE["store<br/>Credentials JSON @ AppData"]
end
end
subgraph EXT["External · Bilibili"]
direction TB
HTTP["api.bilibili.com / api.live.bilibili.com<br/>(HTTPS, WBI 签名)"]
WSS["broadcastlive.chat.bilibili.com<br/>(WSS, 二进制弹幕协议)"]
end
APP -- "invoke()" --> CMDS
CMDS -- "emit('danmaku-event')" --> APP
APP --> UI
APP --> DLG
CMDS --> CONN
CMDS --> AUTH
CMDS --> CONF
CONN --> PROTO --> PARSER
PARSER -- "DanmakuEvent" --> CMDS
CONF --> WBI
AUTH --> STORE
CONF -. "cookie" .-> CONN
CONF -- "HTTPS" --> HTTP
AUTH -- "HTTPS" --> HTTP
CONN -- "WSS" --> WSS
sequenceDiagram
autonumber
participant U as 用户
participant FE as React UI
participant TC as Tauri Command
participant H as bili::conf (HTTPS)
participant W as bili::connection (WSS)
participant B as Bilibili
U->>FE: 输入房间号 → 点击连接
FE->>TC: invoke('connect_room', { roomId })
TC->>H: GET /finger/spi (拿 buvid3)
H-->>TC: Set-Cookie: buvid3
TC->>H: GET /web-interface/nav
H-->>TC: img_key, sub_key
TC->>H: WBI 签 → getDanmuInfo(roomId)
H-->>TC: token + WSS host 列表
TC->>W: WSS connect
W->>B: OP=7 鉴权包 (uid, roomId, token)
B-->>W: OP=8 鉴权 OK
loop 30s
W->>B: OP=2 心跳
B-->>W: OP=3 心跳回复 (人气值)
end
B-->>W: OP=5 消息帧 (Zlib/Brotli)
W->>W: proto 解包 → parser 拆 cmd
W->>FE: emit('danmaku-event', DanmakuEvent)
FE->>FE: 过滤 + 入队 + 渲染
flowchart LR
RAW["WSS 帧<br/>(bytes)"] --> HDR{"16B header<br/>pktLen / hdrLen / ver / op / seq"}
HDR -->|"op=3 心跳回复"| POP["弹出在线人气"]
HDR -->|"op=5 消息<br/>ver=0 (JSON)"| J["UTF-8 → JSON"]
HDR -->|"op=5 消息<br/>ver=2 (Zlib)"| Z["flate2 解压"]
HDR -->|"op=5 消息<br/>ver=3 (Brotli)"| BR["brotli 解压"]
HDR -->|"op=8 鉴权 OK"| OK["进入 5s 心跳循环"]
Z & BR --> SPLIT["递归拆分子帧<br/>(再走一次 header)"]
SPLIT --> J
J --> CMD{"cmd 字段"}
CMD -->|DANMU_MSG| C1[Comment]
CMD -->|SEND_GIFT| C2[Gift]
CMD -->|SUPER_CHAT_MESSAGE| C3[SuperChat]
CMD -->|GUARD_BUY / USER_TOAST_MSG| C4[Guard]
CMD -->|INTERACT_WORD / ENTRY_EFFECT| C5[Interact]
C1 & C2 & C3 & C4 & C5 --> EVT["DanmakuEvent<br/>(serde::Serialize)"]
EVT --> EMIT["app.emit('danmaku-event', evt)"]
| 层 | 选型 | 为什么 |
|---|---|---|
| 桌面壳 | Tauri 2 | 二进制小(~10MB)、原生 WebView、Rust 后端可直连 WSS,不像 Electron 那样要走 Node 子进程 |
| 前端 | React 19 + TS + Vite 7 | 团队最熟、HMR 快;Tauri 官方模板默认即此 |
| 样式 | Tailwind CSS 4 | 配色 / 间距统一靠 token,迭代快 |
| 图标 | lucide-react | 风格统一、tree-shake 友好 |
| 二维码 | qrcode.react | 纯 SVG 渲染,登录弹窗需要 |
| HTTP | reqwest (rustls + cookies + gzip) | 自带 cookie store;rustls 不依赖系统 OpenSSL,跨平台一致 |
| WSS | tokio-tungstenite | tokio 生态原生,rustls-tls-webpki-roots 免系统证书 |
| 解压 | flate2 (Zlib) + brotli | B 站协议两种压缩都要 |
| 签名 | md-5 + hex + percent-encoding | WBI 签名标准实现 |
| 时间/日志 | chrono + tracing | 标准做法 |
| 持久化 | 纯 JSON 落到 app data dir | 不引入数据库;本地配置压根不需要 |
- 包体大、内存占用大
- 弹幕协议解析放在 Node 端要再绕一层 Native Addon 才能高效解 Brotli/Zlib,得不偿失
- Tauri 用系统 WebView,启动 < 200 ms
- 浏览器没法直接发 WSS 到
broadcastlive.chat.bilibili.com:443之外的端口、改 UA、带SESSDATAcookie - 没本地存储登录态的隔离能力(CSRF / XSS 风险)
- 桌面侧才能实现"置顶悬浮窗"
- 风控暖身:先访问
https://api.bilibili.com/x/frontend/finger/spi拿buvid3进 cookie jar,再调任何/x/...接口才不会-352 - WBI 签名:拉
/x/web-interface/nav解出img_url、sub_url,按官方getMixinKey索引表组合 32 位 mixin key,配合wts时间戳做 MD5 得到w_rid - 协议解包:16 字节 BE 头(
pktLen | hdrLen | ver | op | seq),op=5 时按 ver 走 JSON / Zlib / Brotli - 心跳:30 秒一次空 op=2 包,op=3 是回复,里面带在线人数
- 匿名 vs 登录:匿名 token 可以连上,但热门间几乎只推
WATCHED_CHANGE系统事件;带SESSDATA后才有真实弹幕流 - 迷你模式:Rust 端
set_decorations(false)+set_always_on_top(true)+ 显式定位到右上;前端getCurrentWindow().startDragging()实现整窗拖动 - StrictMode 双订阅:
onDanmakuEvent异步注册,用alive哨兵确保 cleanup 不漏 unlisten
- Node.js ≥ 18
- pnpm ≥ 9
- Rust stable(
rustup default stable) - 平台依赖:
- macOS:Xcode Command Line Tools
- Windows:Microsoft C++ Build Tools + WebView2
- Linux:
webkit2gtk-4.1libssl-devlibrsvg2-devlibayatana-appindicator3-dev(详见 Tauri 官方前置)
pnpm install
pnpm tauri dev桌面窗口会自动起来,前端走 Vite HMR,Rust 改动会触发 cargo 重编。
pnpm tauri build产物在 src-tauri/target/release/bundle/:
- macOS:
*.app、*.dmg - Windows:
*.msi、*.exe(NSIS) - Linux:
*.AppImage、*.deb、*.rpm
如果改了 assets/logo.svg:
pnpm gen:iconspnpm dev
# 然后用浏览器分别打开下面四个 URL,按 Cmd+Shift+4 截图后替换 docs/screenshots/*.png
http://localhost:1420/?demo # 主界面
http://localhost:1420/?demo&compact # 迷你模式
http://localhost:1420/?demo&login # 扫码登录弹窗
http://localhost:1420/?demo&settings # 设置面板?demo 是只在浏览器(非 Tauri)才生效的开发模式:注入样例事件并跳过所有 Tauri RPC,用来快速给 README 截图,不会影响真实使用。
会重新渲染 1024 PNG 并调用 tauri icon 派生全套(mac/win/linux/ios/android)。
- 登录:右上角"扫码登录" → 用 B 站手机 app 扫码 → 在手机上确认。登录后凭证写入本地,下次启动自动恢复
- 添加房间:左侧
+,输入直播间号(短号或长号都可以) - 切换房间:点击侧栏房间,切到新房间会自动断开旧的
- 迷你模式:右上角"迷你模式"按钮 → 进入透明悬浮窗
- 拖动:按住头部空白区
- 透明度:左上滑块
- 关闭:× 按钮(断开 + 退出迷你)
- 回到主窗:▢ 按钮(保持连接)
- 设置:左下角齿轮
- 显示哪些消息(默认关掉"互动",避免热门间被进场刷屏)
- 缓存条数上限
- 启动时自动重连上次房间
- 插件:在「设置 → 插件」启用 4 个内置插件,或把第三方 JS 插件丢到插件目录
- macOS:
~/Library/Application Support/com.bilidm.desktop/ - Windows:
%APPDATA%\com.bilidm.desktop\ - Linux:
~/.local/share/com.bilidm.desktop/
auth.json 存放登录凭证(包含 SESSDATA),不要 把这个目录提交到任何地方。
- 内置插件:TTS / OBS 文件 / 抽奖投票 / Webhook 上报
- 外部 JS 插件加载(从
app_local_data_dir/plugins/扫描 + 动态 import) - InteractWord V2 协议适配(B 站新版互动事件)
- 弹幕历史归档(SQLite + 导出 csv)
- 多直播间并行订阅
- 主题切换
- 自动更新(tauri-plugin-updater)
欢迎 Issue / PR。前端遵循 仓库内 eslint 默认规则,Rust 走 cargo fmt + cargo clippy。
提 PR 前请:
pnpm tsc --noEmit
cargo check --manifest-path src-tauri/Cargo.toml- 协议参考:bilibili-API-collect
- 老牌 Windows 版:lovelyyoshino/Bilibili-Live-API、copyliu/bililive_dm
- 框架:Tauri、React、Tokio