{"id":50761936,"url":"https://github.com/qinant/dycast-desktop","last_synced_at":"2026-06-11T11:00:37.811Z","repository":{"id":362236817,"uuid":"1257879669","full_name":"qinant/dycast-desktop","owner":"qinant","description":"Dycast Desktop - 抖音直播弹幕桌面端工具，支持房间号连接、弹幕记录和 WebSocket 转发。","archived":false,"fork":false,"pushed_at":"2026-06-07T12:10:18.000Z","size":68811,"stargazers_count":5,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-07T14:08:37.129Z","etag":null,"topics":["danmu","desktop","douyin","dycast-desktop","live-stream","tauri2"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":false,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/qinant.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":"NOTICE","maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-03T04:56:54.000Z","updated_at":"2026-06-07T12:25:31.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/qinant/dycast-desktop","commit_stats":null,"previous_names":["qinantong/dycast-desktop","qinant/dycast-desktop"],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/qinant/dycast-desktop","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/qinant%2Fdycast-desktop","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/qinant%2Fdycast-desktop/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/qinant%2Fdycast-desktop/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/qinant%2Fdycast-desktop/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/qinant","download_url":"https://codeload.github.com/qinant/dycast-desktop/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/qinant%2Fdycast-desktop/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34195117,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-11T02:00:06.485Z","response_time":57,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["danmu","desktop","douyin","dycast-desktop","live-stream","tauri2"],"created_at":"2026-06-11T11:00:22.892Z","updated_at":"2026-06-11T11:00:37.785Z","avatar_url":"https://github.com/qinant.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Dycast Desktop\n\nDycast Desktop（dycast-desktop）是一个基于 [skmcj/dycast](https://github.com/skmcj/dycast) 独立维护的 Tauri 桌面端弹幕工具，用于连接抖音直播间、实时读取直播间弹幕与房间状态，并可将解析后的弹幕数据转发到外部 WebSocket 服务，方便接入弹幕互动、直播工具、数据采集与本地调试流程。\n\n本项目最初 fork 自 [skmcj/dycast](https://github.com/skmcj/dycast)。感谢原作者所做的开源工作。\n\n## 功能\n\n- 输入抖音直播间房间号并连接直播间。\n- 实时展示聊天、礼物、点赞、关注、进入直播间等弹幕消息。\n- 展示直播间基础信息和在线状态。\n- 支持断线重连，提升长时间运行稳定性。\n- 支持将解析后的弹幕消息以 JSON 形式转发到 `ws://` 或 `wss://` 服务端。\n- 支持将弹幕实时记录到本地 JSONL 文件，适合长时间运行和后续数据处理。\n- 弹幕列表保留最近消息，避免长时间运行时内存持续增长。\n\n## 技术栈\n\n本项目是 Tauri 2 桌面应用，前后端分工如下：\n\n- 桌面容器：Tauri 2\n- 前端框架：Vue 3\n- 构建工具：Vite 6\n- 开发语言：TypeScript、Rust\n- 样式：SCSS\n- 虚拟列表：`vue-virtual-scroller`\n- 数据处理：`pako`、自定义 protobuf 解析模型\n- Rust 后端：负责 Tauri 命令、HTTP 请求、WebSocket 中继、弹幕记录和桌面端运行能力\n\n关键目录：\n\n- `src/`：Vue 前端界面、弹幕解析、转发逻辑和工具函数。\n- `src/core/dycast.ts`：直播间连接、弹幕消息解析和事件分发核心逻辑。\n- `src/core/model/`：抖音直播弹幕相关 protobuf 结构的 TypeScript 解析模型。\n- `src/platform/`：浏览器环境与 Tauri 环境的 HTTP / WebSocket 适配层。\n- `src-tauri/`：Tauri 2 Rust 工程、桌面端配置和原生能力实现。\n- `public/`：静态资源。\n\n## 环境要求\n\n请先安装以下环境：\n\n- Node.js 22 或兼容版本\n- npm\n- Rust\n- Tauri 2 所需系统依赖\n\nTauri 环境安装可参考官方文档：[Tauri prerequisites](https://v2.tauri.app/start/prerequisites/)\n\n## 开发运行\n\n安装前端依赖：\n\n```sh\nnpm install\n```\n\n启动 Tauri 桌面端开发模式：\n\n```sh\nnpm run tauri-dev\n```\n\n仅启动 Web 前端调试：\n\n```sh\nnpm run dev\n```\n\n仅 Web 前端模式主要用于界面和基础逻辑调试。完整桌面能力请使用 `npm run tauri-dev`。\n\n## 构建\n\n构建前端资源：\n\n```sh\nnpm run build\n```\n\n构建桌面端安装包：\n\n```sh\nnpm run tauri-build\n```\n\n构建产物位于：\n\n- Windows / macOS / Linux 可执行文件：`src-tauri/target/release/`\n- 安装包：`src-tauri/target/release/bundle/`\n\n具体产物类型取决于当前操作系统和 Tauri bundle 配置。\n\n## 使用方式\n\n1. 启动应用。\n2. 在「房间号」输入框填写抖音直播间房间号。\n3. 点击「连接」，等待应用获取直播间信息并建立弹幕连接。\n4. 连接成功后，中间列表展示聊天和礼物弹幕，右侧列表展示点赞、关注、进入等其它消息。\n5. 如需转发弹幕，在「WS地址」输入框填写外部 WebSocket 服务地址，例如 `ws://127.0.0.1:8080`，然后点击「转发」。\n6. 如需记录弹幕，点击左侧工具区的记录按钮，选择 `.jsonl` 文件保存位置；再次点击可停止记录。\n\n### WebSocket 转发数据结构\n\n转发端每次发送的是一个 WebSocket text message，内容是一段 JSON 字符串。接收端需要先 `JSON.parse`，解析后有两类数据：\n\n- 连接转发成功后先发送一次直播间信息对象 `DyLiveInfo`。\n- 后续每次收到弹幕批次时发送一个数组 `DyMessage[]`，数组里可能包含多条消息，也可能包含不同 `method` 类型的消息。\n\n接收端可用 `Array.isArray(payload)` 区分两类数据。\n\n直播间信息对象结构：\n\n```ts\ninterface DyLiveInfo {\n  roomNum?: string;\n  roomId: string;\n  uniqueId: string;\n  avatar: string;\n  cover: string;\n  nickname: string;\n  title: string;\n  status: number;\n}\n```\n\n弹幕批次结构。注意：一次 WebSocket message 对应一个消息数组，不是一条单独弹幕；接收端应遍历数组逐条处理。\n\n```ts\ntype RelayMessagePayload = DyMessage[];\n\ninterface DyMessage {\n  id?: string;\n  method?: CastMethod;\n  user?: CastUser;\n  toUser?: CastUser;\n  gift?: CastGift;\n  content?: string;\n  rtfContent?: CastRtfContent[];\n  room?: LiveRoom;\n  rank?: LiveRankItem[];\n}\n\ninterface CastUser {\n  id?: string;\n  name?: string;\n  avatar?: string;\n  gender?: number;\n}\n\ninterface CastGift {\n  id?: string;\n  name?: string;\n  price?: number;\n  type?: number;\n  desc?: string;\n  icon?: string;\n  count?: number | string;\n  repeatEnd?: number;\n}\n\ninterface LiveRoom {\n  audienceCount?: number | string;\n  likeCount?: number | string;\n  followCount?: number | string;\n  totalUserCount?: number | string;\n  status?: number;\n}\n\ninterface LiveRankItem {\n  nickname: string;\n  avatar: string;\n  rank: number | string;\n}\n```\n\n常见 `method` 值：\n\n| method | 含义 | 常见字段 |\n| --- | --- | --- |\n| `WebcastChatMessage` | 聊天弹幕 | `user`、`content`、`rtfContent` |\n| `WebcastEmojiChatMessage` | 表情弹幕 | `user`、`content` |\n| `WebcastGiftMessage` | 礼物消息 | `user`、`toUser`、`gift` |\n| `WebcastLikeMessage` | 点赞消息 | `user`、`content`、`room.likeCount` |\n| `WebcastMemberMessage` | 用户进入直播间 | `user`、`content`、`room.audienceCount` |\n| `WebcastSocialMessage` | 关注消息 | `user`、`content`、`room.followCount` |\n| `WebcastRoomUserSeqMessage` | 在线人数和榜单 | `room.audienceCount`、`room.totalUserCount`、`rank` |\n| `WebcastRoomStatsMessage` | 房间统计 | `room.audienceCount` |\n| `WebcastControlMessage` | 房间状态控制 | `content`、`room.status` |\n\n示例：\n\n```json\n[\n  {\n    \"id\": \"7649725134995427337\",\n    \"method\": \"WebcastMemberMessage\",\n    \"user\": {\n      \"id\": \"MS4wLjABAAAA...\",\n      \"name\": \"之乎者也\",\n      \"avatar\": \"https://p11.douyinpic.com/...\"\n    },\n    \"content\": \"进入直播间\",\n    \"room\": {\n      \"audienceCount\": \"21497\"\n    }\n  },\n  {\n    \"id\": \"7649725129967285311\",\n    \"method\": \"WebcastChatMessage\",\n    \"user\": {\n      \"id\": \"MS4wLjABAAAA...\",\n      \"name\": \"吃柠檬\",\n      \"gender\": 1,\n      \"avatar\": \"https://p11.douyinpic.com/...\"\n    },\n    \"content\": \"点点关注，点点赞\"\n  }\n]\n```\n\nPython 接收示例：\n\n```py\nimport json\n\nasync for message in websocket:\n    payload = json.loads(message)\n    if isinstance(payload, list):\n        for item in payload:\n            print(item.get(\"method\"), item.get(\"content\"))\n    else:\n        print(\"live info:\", payload.get(\"roomNum\"), payload.get(\"title\"))\n```\n\n记录文件为 JSON Lines 格式，即每行一条弹幕 JSON。\n\n## 弹幕记录与内存策略\n\n为保证长时间运行稳定性，界面弹幕列表只保留最近 3000 条消息，因此应用不会在内存中默认保存全量弹幕。\n\n需要长期保存弹幕时，请使用记录功能。记录开启后，应用会将接收到的弹幕按批追加写入本地 `.jsonl` 文件，而不是累积在前端内存中。JSONL 文件可用文本编辑器逐行查看，也便于后续用脚本、数据库或数据分析工具处理。\n\n## 免责声明\n\n本项目仅用于学习交流、桌面端工具开发和合法的直播辅助场景。请遵守相关平台规则、法律法规和数据使用边界。因不当使用造成的风险与后果由使用者自行承担。\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fqinant%2Fdycast-desktop","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fqinant%2Fdycast-desktop","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fqinant%2Fdycast-desktop/lists"}