Skip to content

Repository files navigation

BiliDM logo

BiliDM · 弹幕姬

跨平台的 B 站直播弹幕桌面客户端
Cross-platform desktop danmaku client for Bilibili Live

中文 · English

platform tauri react rust license


简介

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 插件:从插件目录加载第三方插件(开发文档
  • 🚧 弹幕历史归档(路线图)

架构

系统总览

BiliDM 系统架构图

数据流向是单向的: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
Loading

房间连接时序

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: 过滤 + 入队 + 渲染
Loading

协议解包流水线

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)"]
Loading

技术选型

选型 为什么
桌面壳 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 不引入数据库;本地配置压根不需要

为什么不是 Electron

  • 包体大、内存占用大
  • 弹幕协议解析放在 Node 端要再绕一层 Native Addon 才能高效解 Brotli/Zlib,得不偿失
  • Tauri 用系统 WebView,启动 < 200 ms

为什么不是纯 Web 版

  • 浏览器没法直接发 WSS 到 broadcastlive.chat.bilibili.com:443 之外的端口、改 UA、带 SESSDATA cookie
  • 没本地存储登录态的隔离能力(CSRF / XSS 风险)
  • 桌面侧才能实现"置顶悬浮窗"

关键实现细节

  • 风控暖身:先访问 https://api.bilibili.com/x/frontend/finger/spibuvid3 进 cookie jar,再调任何 /x/... 接口才不会 -352
  • WBI 签名:拉 /x/web-interface/nav 解出 img_urlsub_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.1 libssl-dev librsvg2-dev libayatana-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:icons

重新生成 README 截图

pnpm 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)。

使用说明

  1. 登录:右上角"扫码登录" → 用 B 站手机 app 扫码 → 在手机上确认。登录后凭证写入本地,下次启动自动恢复
  2. 添加房间:左侧 +,输入直播间号(短号或长号都可以)
  3. 切换房间:点击侧栏房间,切到新房间会自动断开旧的
  4. 迷你模式:右上角"迷你模式"按钮 → 进入透明悬浮窗
    • 拖动:按住头部空白区
    • 透明度:左上滑块
    • 关闭:× 按钮(断开 + 退出迷你)
    • 回到主窗:▢ 按钮(保持连接)
  5. 设置:左下角齿轮
    • 显示哪些消息(默认关掉"互动",避免热门间被进场刷屏)
    • 缓存条数上限
    • 启动时自动重连上次房间
  6. 插件:在「设置 → 插件」启用 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

License

MIT

鸣谢

About

哔哩哔哩弹幕姬 for mac/win/linux

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages