├── .github └── workflows │ ├── ci.yml │ └── release.yml ├── .gitignore ├── .vscode └── settings.json ├── LICENSE ├── README.md ├── build.config.ts ├── eslint.config.js ├── package.json ├── pnpm-lock.yaml ├── reference ├── ANCHOR_LOT_AWARD │ └── normal.jsonc ├── ANCHOR_LOT_START │ ├── physical.jsonc │ └── virtual.jsonc ├── DANMU_MSG │ ├── emoticon.jsonc │ ├── 渐变粉丝勋章.jsonc │ └── 灰色粉丝勋章.jsonc ├── DM_INTERACTION │ ├── type-101-1.jsonc │ ├── type-101-2.jsonc │ └── type-102.jsonc ├── ENTRY_EFFECT │ ├── no-fans-medal.jsonc │ └── normal.jsonc ├── GUARD_BUY │ └── normal.jsonc ├── HOT_RANK_CHANGED_V2 │ └── normal.jsonc ├── INTERACT_WORD │ ├── no-medal.jsonc │ └── normal.jsonc ├── LIKE_INFO_V3_CLICK │ └── normal.jsonc ├── LIKE_INFO_V3_UPDATE │ └── normal.jsonc ├── LIVE │ └── normal.jsonc ├── NOTICE_MSG │ ├── 人气榜第一名.jsonc │ └── 舰长一个月no-msg-common.jsonc ├── ONLINE_RANK_COUNT │ ├── no_online_count.jsonc │ └── online_count.jsonc ├── POPULARITY_RED_POCKET_NEW │ └── normal.jsonc ├── POPULARITY_RED_POCKET_START │ └── normal.jsonc ├── POPULARITY_RED_POCKET_WINNER_LIST │ └── normal.jsonc ├── POPULAR_RANK_CHANGED │ └── normal.jsonc ├── PREPARING │ └── normal.jsonc ├── ROOM_CHANGE │ └── normal.jsonc ├── ROOM_REAL_TIME_MESSAGE_UPDATE │ └── normal.jsonc ├── SEND_GIFT │ ├── 干杯 combo.jsonc │ ├── 粉丝团灯牌.jsonc │ └── 辣条.jsonc ├── SUPER_CHAT_MESSAGE │ └── normal.jsonc └── WATCHED_CHANGE │ └── normal.jsonc ├── src ├── BiliLive.ts ├── events │ ├── AnchorLotEnd.ts │ ├── AnchorLotStart.ts │ ├── Danmu.ts │ ├── DanmuInteract.ts │ ├── EntryEffect.ts │ ├── FansCountUpdate.ts │ ├── Gift.ts │ ├── GuardBuy.ts │ ├── HotRankUpdate.ts │ ├── Interact.ts │ ├── LikeCountUpdate.ts │ ├── LiveEnd.ts │ ├── LiveStart.ts │ ├── Notice.ts │ ├── PopularRankUpdate.ts │ ├── RankCountUpdate.ts │ ├── RedPocketEnd.ts │ ├── RedPocketStart.ts │ ├── RoomChange.ts │ ├── SuperChat.ts │ ├── TAMPLATE.ts │ ├── WatchedChange.ts │ └── common.ts ├── index.ts ├── types │ ├── event.ts │ ├── message.ts │ └── response.ts └── utils │ ├── color.ts │ ├── message.ts │ └── request.ts ├── test └── index.test.ts └── tsconfig.json /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | 11 | jobs: 12 | lint: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout 16 | uses: actions/checkout@v4 17 | 18 | - name: Set node 19 | uses: actions/setup-node@v4 20 | with: 21 | node-version: lts/* 22 | 23 | - name: Install @antfu/ni 24 | run: npm i -g @antfu/ni 25 | 26 | - name: Install 27 | run: nci 28 | 29 | - name: Lint 30 | run: nr lint 31 | 32 | - name: Typecheck 33 | run: nr typecheck 34 | 35 | test: 36 | runs-on: ubuntu-latest 37 | 38 | strategy: 39 | matrix: 40 | os: [ubuntu-latest, windows-latest, macos-latest] 41 | node: [lts/*] 42 | fail-fast: false 43 | 44 | steps: 45 | - name: Checkout 46 | uses: actions/checkout@v4 47 | 48 | - name: Set node ${{ matrix.node }} 49 | uses: actions/setup-node@v4 50 | with: 51 | node-version: ${{ matrix.node }} 52 | 53 | - name: Install @antfu/ni 54 | run: npm i -g @antfu/ni 55 | 56 | - name: Install 57 | run: nci 58 | 59 | - name: Build 60 | run: nr build 61 | 62 | - name: Test 63 | run: nr test 64 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | # .github/workflows/release.yml 2 | 3 | name: Release 4 | 5 | permissions: 6 | contents: write 7 | 8 | on: 9 | push: 10 | tags: 11 | - 'v*' 12 | 13 | jobs: 14 | release: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@v3 18 | with: 19 | fetch-depth: 0 20 | 21 | - uses: actions/setup-node@v3 22 | with: 23 | node-version: 16.x 24 | 25 | - run: npx changelogithub # or changelogithub@0.12 if ensure the stable result 26 | env: 27 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .cache 2 | .DS_Store 3 | .idea 4 | *.log 5 | *.tgz 6 | coverage 7 | dist 8 | lib-cov 9 | logs 10 | node_modules 11 | temp 12 | src/start.ts 13 | reference/* 14 | !/reference/**/ 15 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | // Enable the ESlint flat config support 3 | "eslint.experimental.useFlatConfig": true, 4 | 5 | // Disable the default formatter, use eslint instead 6 | "prettier.enable": false, 7 | "editor.formatOnSave": false, 8 | 9 | // Auto fix 10 | "editor.codeActionsOnSave": { 11 | "source.fixAll.eslint": "explicit", 12 | "source.organizeImports": "never" 13 | }, 14 | 15 | // Silent the stylistic rules in you IDE, but still auto fix them 16 | "eslint.rules.customizations": [ 17 | { "rule": "style/*", "severity": "off" }, 18 | { "rule": "*-indent", "severity": "off" }, 19 | { "rule": "*-spacing", "severity": "off" }, 20 | { "rule": "*-spaces", "severity": "off" }, 21 | { "rule": "*-order", "severity": "off" }, 22 | { "rule": "*-dangle", "severity": "off" }, 23 | { "rule": "*-newline", "severity": "off" }, 24 | { "rule": "*quotes", "severity": "off" }, 25 | { "rule": "*semi", "severity": "off" } 26 | ], 27 | 28 | // Enable eslint for all supported languages 29 | "eslint.validate": [ 30 | "javascript", 31 | "javascriptreact", 32 | "typescript", 33 | "typescriptreact", 34 | "vue", 35 | "html", 36 | "markdown", 37 | "json", 38 | "jsonc", 39 | "yaml" 40 | ] 41 | } 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Lu-Jiejie (https://github.com/Lu-Jiejie) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # bili-live-listener 2 | 3 | [![npm version][npm-version-badge]][npm-version-href] 4 | [![minzip][minzip-badge]][minzip-href] 5 | [![jsdocs][jsdocs-badge]][jsdocs-href] 6 | [![license][license-badge]][license-href] 7 | [![bilibili live][bili-live-badge]][bili-live-href] 8 | 9 | Bilibili直播间消息监听库。 10 | 11 | - 更优雅的消息监听方式 12 | - 更丰富的类型导出 13 | - 持续更新的API 14 | - 基于 TCP 协议,~~也许更节省内存~~ 15 | 16 | ## Install 17 | 18 | ```bash 19 | npm i bili-live-listener 20 | ``` 21 | 22 | ## Example 23 | 24 | ```typescript 25 | import { BiliLive } from 'bili-live-listener' 26 | 27 | // 获取直播间长号 28 | const { longRoomId } = getRoomId(510) 29 | 30 | // 实例化一个 BiliLive 对象,传入直播间号 31 | // 需要传入登录状态下获取的 key 以及 uid,详情见下方 Options 32 | const biliLive = new BiliLive(longRoomId, { 33 | key: 'xxxxxxxx', // your key 34 | uid: 12345678, // your logined uid 35 | // isBrowser: true // if you are in browser environment 36 | }) 37 | 38 | // 监听弹幕消息,返回一个移除监听器的函数 39 | const removeHandler = biliLive.onDanmu(({ data }) => { 40 | console.log(`${data.user.uname} : ${data.content}`) 41 | }) 42 | 43 | // 移除监听器 44 | removeHandler() 45 | 46 | // 切断连接 47 | biliLive.close() 48 | ``` 49 | 50 | ## Class Params 51 | 52 | > [!IMPORTANT] 53 | > ~~叔叔~~B站修改了获取直播间数据包的要求,现在必须传入登录状态下获取的 `key` 以及 登录的 `uid` 才能正常接受到数据包。 54 | 55 | BiliLive 的构造函数接受两个参数,分别为 `roomId` 和 `options`。 56 | 57 | ### roomId 58 | 59 | 要监听的直播间号,需要传入长号。 60 | 61 | 在 `node` 环境下,可以使用本库提供的 `getRoomId` 函数获取长号与短号(如果有)。 62 | 63 | ```typescript 64 | import { getRoomId } from 'bili-live-listener' 65 | 66 | console.log(getRoomId(510)) 67 | // => { longRoomId: 80397, shortRoomId: 510 } 68 | 69 | console.log(getRoomId(80397)) 70 | // => { longRoomId: 80397, shortRoomId: 510 } 71 | ``` 72 | 73 | ### options 74 | 75 | #### key 76 | 77 | 登录状态下获取的 `key`。 78 | 79 | 登录状态下,在浏览器中访问链接 https://api.live.bilibili.com/room/v1/Room/mobileRoomInit?id={LONG_ROOM_ID} , 找到响应体中的 `data.token` 即为 `key`。 80 | 81 | 在 `node` 环境下,可以使用本库提供的 `getRoomConf` 函数获取,示例如下: 82 | 83 | ```typescript 84 | import { getRoomConf } from 'bili-live-listener' 85 | 86 | // bilibili.com 中的 cookie,注意不要通过 `document.cookie` 获取,而应该通过浏览器发送请求的请求头中的 `cookie` 属性获取 87 | const cookie = 'buvid3=...' 88 | 89 | // 注意传入长号 90 | const longRoomId = 80397 91 | 92 | const { key } = getRoomConf(longRoomId, cookie) 93 | ``` 94 | 95 | #### uid 96 | 97 | 登录的 `uid`。 98 | 99 | 登录状态下,在浏览器中访问 https://api.bilibili.com/x/web-interface/nav ,找到响应体中的 `data.mid` 即为 `uid`。 100 | 101 | 当然,你也可以直接访问自己的主页,找到 `uid`。 102 | 103 | 在 `node` 环境下,可以使用本库提供的 `getLoginedUid` 函数获取,示例如下: 104 | 105 | ```typescript 106 | import { getLoginedUid } from 'bili-live-listener' 107 | 108 | // bilibili.com 中的 cookie,注意不要通过 `document.cookie` 获取,而应该通过浏览器发送请求的请求头中的 `cookie` 属性获取 109 | const cookie = 'buvid3=...' 110 | 111 | const uid = getLoginedUid(cookie) 112 | ``` 113 | 114 | #### isBrowser 115 | 116 | 是否在浏览器环境下运行,默认为 `false`。 117 | 118 | 浏览器环境下必须传入 `true`。 119 | 120 | ## Instance Methods 121 | 122 | ### Common Methods 123 | 124 | | Instance Method | Callback Params | Trigger When ... | 125 | | --------------- | -------------------- | ---------------------------- | 126 | | onOpen | void | 连接开启 | 127 | | onLive | void | 成功登入房间 | 128 | | onHeartbeat | void | 收到服务器心跳包,约30s一次 | 129 | | onClose | void | 连接关闭 | 130 | | onError | error: any | 连接 error,同时连接也会关闭 | 131 | 132 | ### Listen to Raw Message 133 | 134 | 你可以使用 `onRawMessage` 方法监听原生消息,以应对B站新增的消息类型。一个例子如下: 135 | 136 | ```typescript 137 | // 监听一切消息 138 | biliLive.onRawMessage('msg',(message) => { 139 | console.log(message) 140 | }) 141 | 142 | // 监听指定消息 143 | biliLive.onRawMessage('DANMU_MSG',(message) => { 144 | console.log(message) 145 | }) 146 | ``` 147 | 148 | ### Message Methods 149 | 150 | | Instance Method | Callback Params | Trigger When ... | 151 | | --------------- | -------------------- | ---------------------------- | 152 | | onDanmu | [DanmuData](src/events/Danmu.ts) | 收到弹幕消息 | 153 | | onGuardBuy | [GuardBuyData](src/events/GuardBuy.ts) | 收到上船消息 | 154 | | onSuperChat | [SuperChatData](src/events/SuperChat.ts) | 收到醒目留言消息 | 155 | | onGift | [GiftData](src/events/Gift.ts) | 收到礼物消息 | 156 | | onWatchedChange | [WatchedChangeData](src/events/WatchedChange.ts) | “XX人看过”人数变动 | 157 | | onRankCountChange | [RankCountChangeData](src/events/RankCountChange.ts) | 高能榜人数变动 | 158 | | onLikeCountChange | [LikeCountChangeData](src/events/LikeCountChange.ts) | 点赞数变动 | 159 | | onNotice | [NoticeData](src/events/Notice.ts) | 收到全站广播 | 160 | | onHotRankUpdate | [HotRankUpdateData](src/events/HotRankUpdate.ts) | 当前直播间的分区排名变动 | 161 | | onFansCountUpdate | [FansCountUpdateData](src/events/FansCountUpdate.ts) | 粉丝数量\粉丝团数量变动 | 162 | | onLiveStart | [LiveStartData](src/events/LiveStart.ts) | 直播开启 | 163 | | onLiveEnd | [LiveEndData](src/events/LiveEnd.ts) | 直播结束 | 164 | | onInteract | [InteractData](src/events/Interact.ts) | 互动消息(用户进入、关注、分享、点赞直播间) | 165 | | onEntryEffect | [EntryEffectData](src/events/EntryEffect.ts) | 有入场特效的用户进入直播间(舰长、提督、总督等) | 166 | | onRoomChange | [RoomChangeData](src/events/RoomChange.ts) | 房间信息变动(标题、分区) | 167 | | onAnchorLotStart | [AnchorLotStartData](src/events/AnchorLotStart.ts) | 天选时刻开始 | 168 | | onAnchorLotEnd | [AnchorLotEndData](src/events/AnchorLotEnd.ts) | 天选时刻结束 | 169 | | onRedPocketStart | [RedPocketStartData](src/events/RedPocketStart.ts) | 红包抽奖开始 | 170 | | onRedPocketEnd | [RedPocketEndData](src/events/RedPocketEnd.ts) | 红包抽奖结束 | 171 | | onPopularRankUpdate | [PopularRankUpdateData](src/events/PopularRankUpdate.ts) | 当前直播间的热门排名变动 | 172 | | onDanmuInteract | [DanmuInteractData](src/events/DanmuInteract.ts) | 弹幕互动(弹幕投票信息、“他们都在说”类的弹幕连击) | 173 | 174 | 值得注意的是,这些 `Message Methods` 都会返回一个移除监听器的函数,你可以调用这个函数来移除监听器,参考 [这里](#example) 175 | 176 | ### Message Type 177 | 178 | #### Common 179 | 180 | ```typescript 181 | // 回调函数中的参数类型 182 | export interface Message { 183 | /** 消息原生类型 */ 184 | cmd: string 185 | /** 收到消息的时间戳,由 Date.now() 生成 */ 186 | timestamp: number 187 | /** 类型化后的消息主体 */ 188 | data: T 189 | /** 原生消息 */ 190 | raw: any 191 | } 192 | ``` 193 | 194 | > [!TIP] 195 | > 大部分消息主体 `data` 中都会包含 `timestamp` 字段,表示消息到达的更精确的秒时间戳,建议使用这个字段。 196 | > 而 `Message` 中的 `timestamp` 字段,是由 `Date.now()` 生成的时间戳,也许会有一定的误差。 197 | 198 | #### Data Type 199 | 200 | 你可以在 [src/events](src/events) 查看所有的消息主体类型,它将作为泛型传入回调函数的参数类型 `Message` 中。其中部分类型在 [src/types/message.ts](src/types/message.ts) 中定义。 201 | 202 | 比如,弹幕消息的消息主体类型为 `DanmuData`,定义如下: 203 | 204 | ```typescript 205 | export interface DanmuData { 206 | /** 用户信息 */ 207 | user: User 208 | /** 弹幕内容 */ 209 | content: string 210 | /** 时间戳 */ 211 | timestamp: number 212 | /** 是否为抽奖弹幕 */ 213 | isLottery: boolean 214 | // 弹幕表情 215 | emoticon?: { 216 | id: number 217 | url: string 218 | } 219 | } 220 | ``` 221 | 222 | ## Reference 223 | 224 | [SocialSisterYi/bilibili-API-collect](https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/docs/live/message_stream.md) 225 | 226 | [lovelyyoshino/Bilibili-Live-API](https://github.com/lovelyyoshino/Bilibili-Live-API/blob/master/API.WebSocket.md) 227 | 228 | 229 | [npm-version-badge]: https://img.shields.io/npm/v/bili-live-listener?style=flat&color=skyblue&labelColor=444 230 | [npm-version-href]: https://www.npmjs.com/package/bili-live-listener 231 | [minzip-badge]: https://img.shields.io/bundlephobia/minzip/bili-live-listener?style=flat&color=skyblue&labelColor=444&label=minizip 232 | [minzip-href]: https://bundlephobia.com/result?p=bili-live-listener 233 | [jsdocs-badge]: https://img.shields.io/badge/jsDocs-reference-skyblue?style=flat&color=skyblue&labelColor=444 234 | [jsdocs-href]: https://www.jsdocs.io/package/bili-live-listener 235 | [license-badge]: https://img.shields.io/github/license/Lu-Jiejie/bili-live-listener?style=flat&color=skyblue&labelColor=444 236 | [license-href]: https://github.com/Lu-Jiejie/bili-live-listener/blob/main/LICENSE 237 | [bili-live-badge]: https://img.shields.io/badge/-Bilibili直播-skyblue?style=flat&logo=Bilibili&color=skyblue&labelColor=444&logoColor=white 238 | [bili-live-href]: https://live.bilibili.com/ 239 | -------------------------------------------------------------------------------- /build.config.ts: -------------------------------------------------------------------------------- 1 | import { defineBuildConfig } from 'unbuild' 2 | 3 | export default defineBuildConfig({ 4 | entries: [ 5 | 'src/index', 6 | ], 7 | declaration: true, 8 | clean: true, 9 | rollup: { 10 | emitCJS: true, 11 | }, 12 | }) 13 | -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | import antfu from '@antfu/eslint-config' 2 | 3 | export default antfu( 4 | { 5 | ignores: [ 6 | // eslint ignore globs here 7 | ], 8 | }, 9 | { 10 | rules: { 11 | // override rules here 12 | }, 13 | }, 14 | ) 15 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bili-live-listener", 3 | "type": "module", 4 | "version": "1.0.5", 5 | "packageManager": "pnpm@9.1.1", 6 | "description": "Bilibili直播间消息监听库。", 7 | "author": "Lu-Jiejie ", 8 | "license": "MIT", 9 | "homepage": "https://github.com/Lu-Jiejie/bili-live-listener#readme", 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/Lu-Jiejie/bili-live-listener.git" 13 | }, 14 | "bugs": "https://github.com/Lu-Jiejie/bili-live-listener/issues", 15 | "keywords": [ 16 | "bilibili", 17 | "live", 18 | "danmu" 19 | ], 20 | "sideEffects": false, 21 | "exports": { 22 | ".": { 23 | "types": "./dist/index.d.ts", 24 | "import": "./dist/index.mjs", 25 | "require": "./dist/index.cjs" 26 | } 27 | }, 28 | "main": "./dist/index.mjs", 29 | "module": "./dist/index.mjs", 30 | "types": "./dist/index.d.ts", 31 | "typesVersions": { 32 | "*": { 33 | "*": [ 34 | "./dist/*", 35 | "./dist/index.d.ts" 36 | ] 37 | } 38 | }, 39 | "files": [ 40 | "dist" 41 | ], 42 | "scripts": { 43 | "dev": "unbuild --stub", 44 | "build": "unbuild", 45 | "lint": "eslint .", 46 | "release": "bumpp && npm publish", 47 | "start": "esno src/start.ts", 48 | "test": "vitest", 49 | "typecheck": "tsc --noEmit", 50 | "prepublishOnly": "nr build", 51 | "prepare": "simple-git-hooks" 52 | }, 53 | "dependencies": { 54 | "bilibili-live-ws": "^6.3.1" 55 | }, 56 | "devDependencies": { 57 | "@antfu/eslint-config": "^2.8.2", 58 | "@types/node": "^20.11.27", 59 | "bumpp": "^9.4.0", 60 | "eslint": "^8.57.0", 61 | "esno": "^4.7.0", 62 | "lint-staged": "^15.2.2", 63 | "simple-git-hooks": "^2.10.0", 64 | "typescript": "^5.4.2", 65 | "unbuild": "^2.0.0", 66 | "vitest": "^0.34.6" 67 | }, 68 | "simple-git-hooks": { 69 | "pre-commit": "pnpm lint-staged" 70 | }, 71 | "lint-staged": { 72 | "*": "eslint --fix" 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /reference/ANCHOR_LOT_AWARD/normal.jsonc: -------------------------------------------------------------------------------- 1 | { 2 | "cmd":"ANCHOR_LOT_AWARD", 3 | "data":{ 4 | "award_dont_popup":1, // 待观察 5 | "award_image":"", // 待观察,疑似奖品图片,用于构建样式 6 | "award_name":"情书", // 奖品名称 7 | "award_num":1, // 奖品数量 8 | "award_price_text":"价值52电池", // 奖品价值描述 9 | "award_type":1, // 待观察 10 | "award_users":[ // 中奖者信息 11 | { 12 | "bag_id":6465092, // 待观察 13 | "color":10512625, // 待观察 14 | "face":"https://i1.hdslb.com/bfs/face/fe2a73ec1a4016a60aa69c0053f2835ab644fb4e.jpg", // 中奖者头像 15 | "gift_id":31250, // 礼物id 16 | "level":34, // 直播等级 17 | "num":1, // 数量 18 | "uid":11497234, // uid 19 | "uname":"智障树上智障果灬" // 名称 20 | } 21 | ], 22 | "id":4579146, // id 23 | "lot_status":2, // 待观察 24 | "ruid":3494365156608185,// 待观察 25 | "url":"https://live.bilibili.com/p/html/live-lottery/anchor-join.html?is_live_half_webview=1&hybrid_biz=live-lottery-anchor&hybrid_half_ui=1,5,100p,100p,000000,0,30,0,0,1;2,5,100p,100p,000000,0,30,0,0,1;3,5,100p,100p,000000,0,30,0,0,1;4,5,100p,100p,000000,0,30,0,0,1;5,5,100p,100p,000000,0,30,0,0,1;6,5,100p,100p,000000,0,30,0,0,1;7,5,100p,100p,000000,0,30,0,0,1;8,5,100p,100p,000000,0,30,0,0,1", // 待观察 26 | "web_url":"https://live.bilibili.com/p/html/live-lottery/anchor-join.html" // 待观察 27 | }, 28 | "is_report":false, // 待观察 29 | "msg_id":"146501042967552", // 待观察 30 | "send_time":1688833308564 // 推送时间 31 | } 32 | -------------------------------------------------------------------------------- /reference/ANCHOR_LOT_START/physical.jsonc: -------------------------------------------------------------------------------- 1 | { 2 | "cmd": "ANCHOR_LOT_START", 3 | "data": { 4 | "asset_icon": "https://i0.hdslb.com/bfs/live/627ee2d9e71c682810e7dc4400d5ae2713442c02.png", 5 | "asset_icon_webp": "https://i0.hdslb.com/bfs/live/b47453a0d42f30673b6d030159a96d07905d677a.webp", 6 | "award_image": "", 7 | "award_name": "签名照", 8 | "award_num": 1, 9 | "award_type": 0, 10 | "cur_gift_num": 0, 11 | "current_time": 1684497625, 12 | "danmu": "", 13 | "danmu_new": [ 14 | { 15 | "danmu": "", 16 | "danmu_view": "", 17 | "reject": false 18 | } 19 | ], 20 | "danmu_type": 0, 21 | "gift_id": 0, 22 | "gift_name": "", 23 | "gift_num": 1, 24 | "gift_price": 0, 25 | "goaway_time": 180, 26 | "goods_id": -99998, 27 | "id": 4342335, 28 | "is_broadcast": 1, 29 | "join_type": 0, 30 | "lot_status": 0, 31 | "max_time": 300, 32 | "require_text": "当前主播粉丝勋章至少15级", 33 | "require_type": 2, 34 | "require_value": 15, 35 | "room_id": 21281833, 36 | "send_gift_ensure": 0, 37 | "show_panel": 1, 38 | "start_dont_popup": 0, 39 | "status": 1, 40 | "time": 299, 41 | "url": "https://live.bilibili.com/p/html/live-lottery/anchor-join.html?is_live_half_webview=1&hybrid_biz=live-lottery-anchor&hybrid_half_ui=1,5,100p,100p,000000,0,30,0,0,1;2,5,100p,100p,000000,0,30,0,0,1;3,5,100p,100p,000000,0,30,0,0,1;4,5,100p,100p,000000,0,30,0,0,1;5,5,100p,100p,000000,0,30,0,0,1;6,5,100p,100p,000000,0,30,0,0,1;7,5,100p,100p,000000,0,30,0,0,1;8,5,100p,100p,000000,0,30,0,0,1", 42 | "web_url": "https://live.bilibili.com/p/html/live-lottery/anchor-join.html" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /reference/ANCHOR_LOT_START/virtual.jsonc: -------------------------------------------------------------------------------- 1 | { 2 | "cmd": "ANCHOR_LOT_START", 3 | "data": { 4 | "asset_icon": "https://i0.hdslb.com/bfs/live/627ee2d9e71c682810e7dc4400d5ae2713442c02.png", // 不重要,用来构建样式的图片 5 | "asset_icon_webp": "https://i0.hdslb.com/bfs/live/b47453a0d42f30673b6d030159a96d07905d677a.webp", // 不重要,用来构建样式的图片 6 | "award_image": "", // 待观察,疑似奖品图片,用于构建样式 7 | "award_name": "情书", // 奖品名称 8 | "award_num": 1, // 奖品数量 9 | "award_price_text": "价值52电池", // 奖品价值描述 10 | "award_type": 1, // 待观察 11 | "cur_gift_num": 0, // 待观察 12 | "current_time": 1688832408, // 天选开始时间,结束时间应该是 + max_time 13 | "danmu": "姐姐贴贴", // 需要发送的弹幕 14 | "danmu_new": [ // 待观察 15 | { 16 | "danmu": "姐姐贴贴", 17 | "danmu_view": "", 18 | "reject": false 19 | } 20 | ], 21 | "danmu_type": 0, // 待观察 22 | "gift_id": 0, // 待观察,疑似需要赠送的礼物id,也许是为了需要赠送礼物来参加的天选准备的 23 | "gift_name": "", // 待观察,疑似需要赠送的礼物名称,也许是为了需要赠送礼物来参加的天选准备的 24 | "gift_num": 0, // 待观察,疑似需要赠送的礼物数量,也许是为了需要赠送礼物来参加的天选准备的 25 | "gift_price": 0, // 待观察,疑似需要赠送的价格,也许是为了需要赠送礼物来参加的天选准备的 26 | "goaway_time": 180, // 待观察 27 | "goods_id": -99998, // 待观察 28 | "id": 4579146, // id 29 | "is_broadcast": 1, // 待观察 30 | "join_type": 0, // 待观察 31 | "lot_status": 0, // 待观察 32 | "max_time": 900, // 持续时间,单位秒 33 | "require_text": "关注主播", // 待观察,看起来应该是用于参与条件中的是否关注主播?也可能有其他情况,应该是 require_type 对应的内容 34 | "require_type": 1, // 待观察,目前已知,1:关注主播 35 | "require_value": 0, // 待观察 36 | "room_id": 30118851, // 房间号 37 | "send_gift_ensure": 0, // 待观察 38 | "show_panel": 1, // 待观察 39 | "start_dont_popup": 0, // 待观察 40 | "status": 1, // 待观察 41 | "time": 899, // 剩余时间,单位秒 42 | "url": "https://live.bilibili.com/p/html/live-lottery/anchor-join.html?is_live_half_webview=1&hybrid_biz=live-lottery-anchor&hybrid_half_ui=1,5,100p,100p,000000,0,30,0,0,1;2,5,100p,100p,000000,0,30,0,0,1;3,5,100p,100p,000000,0,30,0,0,1;4,5,100p,100p,000000,0,30,0,0,1;5,5,100p,100p,000000,0,30,0,0,1;6,5,100p,100p,000000,0,30,0,0,1;7,5,100p,100p,000000,0,30,0,0,1;8,5,100p,100p,000000,0,30,0,0,1", // 待观察,看起来像是天选框样式 43 | "web_url": "https://live.bilibili.com/p/html/live-lottery/anchor-join.html" // 待观察,看起来像是天选框样式 44 | }, 45 | "is_report": false, // 待观察 46 | "msg_id": "146029755244032", // id 47 | "send_time": 1688832409654 // 推送时间 48 | } 49 | -------------------------------------------------------------------------------- /reference/DANMU_MSG/emoticon.jsonc: -------------------------------------------------------------------------------- 1 | { 2 | "cmd": "DANMU_MSG", 3 | "info": [ 4 | [ 5 | 0, 6 | 1, 7 | 25, 8 | 5816798, 9 | 1713496477789, 10 | 575878827, 11 | 0, 12 | "5c8de9db", 13 | 0, 14 | 0, 15 | 0, 16 | "", 17 | 1, 18 | { 19 | "bulge_display": 1, 20 | "emoticon_unique": "upower_[乖乖雪糕_哈哈]", 21 | "height": 20, 22 | "in_player_area": 1, 23 | "is_dynamic": 0, 24 | "url": "https://i0.hdslb.com/bfs/garb/0371ead61093a5eec1c884c6d5cfa5deb445d82d.png", 25 | "width": 20 26 | }, 27 | "{}", 28 | { 29 | "mode": 0, 30 | "show_player_type": 0, 31 | "extra": "{\"send_from_me\":false,\"mode\":0,\"color\":5816798,\"dm_type\":1,\"font_size\":25,\"player_mode\":1,\"show_player_type\":0,\"content\":\"[乖乖雪糕_哈哈]\",\"user_hash\":\"1552804315\",\"emoticon_unique\":\"upower_[乖乖雪糕_哈哈]\",\"bulge_display\":1,\"recommend_score\":0,\"main_state_dm_color\":\"\",\"objective_state_dm_color\":\"\",\"direction\":0,\"pk_direction\":0,\"quartet_direction\":0,\"anniversary_crowd\":0,\"yeah_space_type\":\"\",\"yeah_space_url\":\"\",\"jump_to_url\":\"\",\"space_type\":\"\",\"space_url\":\"\",\"animation\":{},\"emots\":null,\"is_audited\":false,\"id_str\":\"5d252e882e0cde8e6a7da73cf16621e131\",\"icon\":null,\"show_reply\":true,\"reply_mid\":0,\"reply_uname\":\"\",\"reply_uname_color\":\"\",\"reply_is_mystery\":false,\"hit_combo\":0}", 32 | "user": { 33 | "uid": 360709723, 34 | "base": { 35 | "name": "Baileywy", 36 | "face": "https://i0.hdslb.com/bfs/face/member/noface.jpg", 37 | "name_color": 0, 38 | "is_mystery": false, 39 | "risk_ctrl_info": null, 40 | "origin_info": { 41 | "name": "Baileywy", 42 | "face": "https://i0.hdslb.com/bfs/face/member/noface.jpg" 43 | }, 44 | "official_info": { 45 | "role": 0, 46 | "title": "", 47 | "desc": "", 48 | "type": -1 49 | }, 50 | "name_color_str": "" 51 | }, 52 | "medal": null, 53 | "wealth": null, 54 | "title": { 55 | "old_title_css_id": "", 56 | "title_css_id": "" 57 | }, 58 | "guard": null, 59 | "uhead_frame": null, 60 | "guard_leader": { 61 | "is_guard_leader": false 62 | } 63 | } 64 | }, 65 | { 66 | "activity_identity": "", 67 | "activity_source": 0, 68 | "not_show": 0 69 | }, 70 | 0 71 | ], 72 | "[乖乖雪糕_哈哈]", 73 | [ 74 | 360709723, 75 | "Baileywy", 76 | 0, 77 | 0, 78 | 0, 79 | 10000, 80 | 1, 81 | "" // 显示的用户名的色号 82 | ], 83 | [ 84 | 12, 85 | "boba", 86 | "帅比笙歌超可爱OvO", 87 | 573893, 88 | 9272486, 89 | "", 90 | 0, 91 | 9272486, 92 | 9272486, 93 | 9272486, 94 | 0, 95 | 1, 96 | 15641218 97 | ], 98 | [ 99 | 0, 100 | 0, 101 | 9868950, 102 | ">50000", 103 | 0 104 | ], 105 | [ 106 | "", 107 | "" 108 | ], 109 | 0, 110 | 0, 111 | null, 112 | { 113 | "ts": 1713496477, 114 | "ct": "12D1E1F7" 115 | }, 116 | 0, 117 | 0, 118 | null, 119 | null, 120 | 0, 121 | 77, 122 | [ 123 | 5 124 | ], 125 | null 126 | ], 127 | "dm_v2": "" 128 | } 129 | -------------------------------------------------------------------------------- /reference/DANMU_MSG/渐变粉丝勋章.jsonc: -------------------------------------------------------------------------------- 1 | { 2 | "cmd": "DANMU_MSG", 3 | "info": [ 4 | [ 5 | 0, 6 | 4, 7 | 25, 8 | 14893055, 9 | 1713493645295, 10 | 760073161, 11 | 0, 12 | "650b13c6", 13 | 0, 14 | 0, 15 | 5, 16 | "#1453BAFF,#4C2263A2,#3353BAFF", 17 | 0, 18 | "{}", 19 | "{}", 20 | { 21 | "mode": 0, 22 | "show_player_type": 0, 23 | "extra": "{\"send_from_me\":false,\"mode\":0,\"color\":14893055,\"dm_type\":0,\"font_size\":25,\"player_mode\":4,\"show_player_type\":0,\"content\":\"粘黄芥末酱\",\"user_hash\":\"1695224774\",\"emoticon_unique\":\"\",\"bulge_display\":0,\"recommend_score\":9,\"main_state_dm_color\":\"\",\"objective_state_dm_color\":\"\",\"direction\":0,\"pk_direction\":0,\"quartet_direction\":0,\"anniversary_crowd\":0,\"yeah_space_type\":\"\",\"yeah_space_url\":\"\",\"jump_to_url\":\"\",\"space_type\":\"\",\"space_url\":\"\",\"animation\":{},\"emots\":null,\"is_audited\":false,\"id_str\":\"23d61cb9ebabf9201bd4507b5b6621d646\",\"icon\":null,\"show_reply\":true,\"reply_mid\":0,\"reply_uname\":\"\",\"reply_uname_color\":\"\",\"reply_is_mystery\":false,\"hit_combo\":0}", 24 | "user": { 25 | "uid": 33948208, 26 | "base": { 27 | "name": "不投币只改名的屑", 28 | "face": "https://i1.hdslb.com/bfs/face/4e8e6c2d9a5e60d926ff04138e0a5ece1b4d7b38.jpg", 29 | "name_color": 0, 30 | "is_mystery": false, 31 | "risk_ctrl_info": null, 32 | "origin_info": { 33 | "name": "不投币只改名的屑", 34 | "face": "https://i1.hdslb.com/bfs/face/4e8e6c2d9a5e60d926ff04138e0a5ece1b4d7b38.jpg" 35 | }, 36 | "official_info": { 37 | "role": 0, 38 | "title": "", 39 | "desc": "", 40 | "type": -1 41 | }, 42 | "name_color_str": "" 43 | }, 44 | "medal": null, 45 | "wealth": null, 46 | "title": { 47 | "old_title_css_id": "", 48 | "title_css_id": "" 49 | }, 50 | "guard": null, 51 | "uhead_frame": null, 52 | "guard_leader": { 53 | "is_guard_leader": false 54 | } 55 | } 56 | }, 57 | { 58 | "activity_identity": "", 59 | "activity_source": 0, 60 | "not_show": 0 61 | }, 62 | 43 63 | ], 64 | "粘黄芥末酱", 65 | [ 66 | 33948208, 67 | "不投币只改名的屑", 68 | 0, 69 | 0, 70 | 0, 71 | 10000, 72 | 1, 73 | "#00D1F1" 74 | ], 75 | [ 76 | 26, 77 | "雪撬犬", 78 | "雪糕cheese", 79 | 27183290, 80 | 398668, 81 | "", 82 | 0, 83 | 6809855, 84 | 398668, 85 | 6850801, 86 | 3, 87 | 1, 88 | 3493139945884106 89 | ], 90 | [ 91 | 19, 92 | 0, 93 | 6406234, 94 | ">50000", 95 | 0 96 | ], 97 | [ 98 | "", 99 | "" 100 | ], 101 | 0, 102 | 3, 103 | null, 104 | { 105 | "ts": 1713493645, 106 | "ct": "60996766" 107 | }, 108 | 0, 109 | 0, 110 | null, 111 | null, 112 | 0, 113 | 663, 114 | [ 115 | 30 116 | ], 117 | null 118 | ], 119 | "dm_v2": "" 120 | } 121 | -------------------------------------------------------------------------------- /reference/DANMU_MSG/灰色粉丝勋章.jsonc: -------------------------------------------------------------------------------- 1 | { 2 | "cmd": "DANMU_MSG", 3 | "info": [ 4 | [ 5 | 0, 6 | 1, 7 | 25, 8 | 5816798, 9 | 1713816585463, 10 | 1713815836, 11 | 0, 12 | "0078af55", 13 | 0, 14 | 0, 15 | 0, 16 | "", 17 | 0, 18 | "{}", 19 | "{}", 20 | { 21 | "mode": 0, 22 | "show_player_type": 0, 23 | "extra": "{\"send_from_me\":false,\"mode\":0,\"color\":5816798,\"dm_type\":0,\"font_size\":25,\"player_mode\":1,\"show_player_type\":0,\"content\":\"享受\",\"user_hash\":\"7909205\",\"emoticon_unique\":\"\",\"bulge_display\":0,\"recommend_score\":7,\"main_state_dm_color\":\"\",\"objective_state_dm_color\":\"\",\"direction\":0,\"pk_direction\":0,\"quartet_direction\":0,\"anniversary_crowd\":0,\"yeah_space_type\":\"\",\"yeah_space_url\":\"\",\"jump_to_url\":\"\",\"space_type\":\"\",\"space_url\":\"\",\"animation\":{},\"emots\":null,\"is_audited\":false,\"id_str\":\"79adef0559567b4a215a8ea5316626c468\",\"icon\":null,\"show_reply\":true,\"reply_mid\":0,\"reply_uname\":\"\",\"reply_uname_color\":\"\",\"reply_is_mystery\":false,\"hit_combo\":0}", 24 | "user": { 25 | "uid": 15253584, 26 | "base": { 27 | "name": "鹿解解", 28 | "face": "https://i0.hdslb.com/bfs/face/9eeb889c3a709a94f3893ceda8e1f0d3c222f698.jpg", 29 | "name_color": 0, 30 | "is_mystery": false, 31 | "risk_ctrl_info": null, 32 | "origin_info": { 33 | "name": "鹿解解", 34 | "face": "https://i0.hdslb.com/bfs/face/9eeb889c3a709a94f3893ceda8e1f0d3c222f698.jpg" 35 | }, 36 | "official_info": { 37 | "role": 0, 38 | "title": "", 39 | "desc": "", 40 | "type": -1 41 | }, 42 | "name_color_str": "" 43 | }, 44 | "medal": null, 45 | "wealth": null, 46 | "title": { 47 | "old_title_css_id": "", 48 | "title_css_id": "" 49 | }, 50 | "guard": null, 51 | "uhead_frame": null, 52 | "guard_leader": { 53 | "is_guard_leader": false 54 | } 55 | } 56 | }, 57 | { 58 | "activity_identity": "", 59 | "activity_source": 0, 60 | "not_show": 0 61 | }, 62 | 0 63 | ], 64 | "享受", 65 | [ 66 | 15253584, 67 | "鹿解解", 68 | 0, 69 | 0, 70 | 0, 71 | 10000, 72 | 1, 73 | "" 74 | ], 75 | [ 76 | 22, 77 | "小孩梓", 78 | "阿梓从小就很可爱", 79 | 80397, 80 | 1725515, 81 | "", 82 | 0, 83 | 12632256, 84 | 12632256, 85 | 12632256, 86 | 0,// guard type 87 | 0,// is lighted 88 | 7706705 // anchor uid 89 | ], 90 | [ 91 | 14, 92 | 0, 93 | 6406234, 94 | ">50000", 95 | 0 96 | ], 97 | [ 98 | "", 99 | "" 100 | ], 101 | 0, 102 | 0, 103 | null, 104 | { 105 | "ts": 1713816585, 106 | "ct": "BED26F0A" 107 | }, 108 | 0, 109 | 0, 110 | null, 111 | null, 112 | 0, 113 | 234, 114 | [ 115 | 17 116 | ], 117 | null 118 | ], 119 | "dm_v2": "" 120 | } 121 | -------------------------------------------------------------------------------- /reference/DM_INTERACTION/type-101-1.jsonc: -------------------------------------------------------------------------------- 1 | { 2 | "cmd": "DM_INTERACTION", 3 | "data": { 4 | "data": "{\"question\":\"你支持哪支战队\",\"options\":[{\"idx\":1,\"desc\":\"TTG\",\"cnt\":919,\"percent\":0.6708029197080292},{\"idx\":2,\"desc\":\"BOA\",\"cnt\":451,\"percent\":0.3291970802919708}],\"vote_id\":11404452,\"cnt\":1370,\"duration\":180000,\"left_duration\":0,\"fade_duration\":1000,\"waiting_duration\":-1,\"result\":2,\"result_text\":\"蓝获胜\",\"component\":\"https://live.bilibili.com/p/html/live-app-guessing-game/vote.html?is_live_half_webview=1\\u0026hybrid_half_ui=1,3,100p,245,0,0,30,100,12,0;2,2,375,100p,0,0,30,100,12,0;3,3,100p,245,0,0,30,100,12,0;4,2,375,100p,0,0,30,100,12,0;5,3,100p,70p,0,0,30,100,12,0;6,3,100p,70p,0,0,30,100,12,0;7,3,100p,70p,0,0,30,100,12,0;8,3,100p,70p,0,0,30,100,12,0\",\"natural_die_duration\":30000,\"my_vote\":0,\"component_anchor\":\"https://live.bilibili.com/p/html/live-app-guessing-game/anchor_vote.html?pc_ui=390,428,0,3\\u0026is_live_half_webview=1\\u0026hybrid_half_ui=1,3,100p,448,0,0,30,0,12,0;2,2,375,100p,0,0,30,0,12,0;3,3,100p,448,0,0,30,0,12,0;4,2,375,100p,0,0,30,0,12,0;5,3,100p,448,0,0,30,0,12,0;6,2,320,100p,0,0,30,0,12,0;7,2,320,100p,0,0,30,0,12,0;8,2,320,100p,0,0,30,0,12,0#/\",\"audit_reason\":\"\",\"combo\":[{\"id\":1,\"status\":5,\"content\":\"TTG\",\"cnt\":919,\"guide\":\"\",\"left_duration\":0,\"fade_duration\":0,\"prefix_icon\":\"http://i0.hdslb.com/bfs/dm/7d7e3682c9116aa3503418abe3cde6b45ed2e91e.png\"},{\"id\":2,\"status\":5,\"content\":\"BOA\",\"cnt\":451,\"guide\":\"\",\"left_duration\":0,\"fade_duration\":0,\"prefix_icon\":\"http://i0.hdslb.com/bfs/dm/f83c7280b2a90b4f58a68fd8c594ea7d5667e3cb.png\"}]}", 5 | "dmscore": 36, 6 | "id": 31335934626816, 7 | "status": 5, 8 | "type": 101 9 | } 10 | } 11 | 12 | /** 13 | { 14 | "audit_reason": "", 15 | "cnt": 1370, 16 | "combo": [ 17 | { 18 | "cnt": 919, 19 | "content": "TTG", 20 | "fade_duration": 0, 21 | "guide": "", 22 | "id": 1, 23 | "left_duration": 0, 24 | "prefix_icon": "http://i0.hdslb.com/bfs/dm/7d7e3682c9116aa3503418abe3cde6b45ed2e91e.png", 25 | "status": 5, 26 | }, 27 | { 28 | "cnt": 451, 29 | "content": "BOA", 30 | "fade_duration": 0, 31 | "guide": "", 32 | "id": 2, 33 | "left_duration": 0, 34 | "prefix_icon": "http://i0.hdslb.com/bfs/dm/f83c7280b2a90b4f58a68fd8c594ea7d5667e3cb.png", 35 | "status": 5, 36 | }, 37 | ], 38 | "component": "https://live.bilibili.com/p/html/live-app-guessing-game/vote.html?is_live_half_webview=1&hybrid_half_ui=1,3,100p,245,0,0,30,100,12,0;2,2,375,100p,0,0,30,100,12,0;3,3,100p,245,0,0,30,100,12,0;4,2,375,100p,0,0,30,100,12,0;5,3,100p,70p,0,0,30,100,12,0;6,3,100p,70p,0,0,30,100,12,0;7,3,100p,70p,0,0,30,100,12,0;8,3,100p,70p,0,0,30,100,12,0", 39 | "component_anchor": "https://live.bilibili.com/p/html/live-app-guessing-game/anchor_vote.html?pc_ui=390,428,0,3&is_live_half_webview=1&hybrid_half_ui=1,3,100p,448,0,0,30,0,12,0;2,2,375,100p,0,0,30,0,12,0;3,3,100p,448,0,0,30,0,12,0;4,2,375,100p,0,0,30,0,12,0;5,3,100p,448,0,0,30,0,12,0;6,2,320,100p,0,0,30,0,12,0;7,2,320,100p,0,0,30,0,12,0;8,2,320,100p,0,0,30,0,12,0#/", 40 | "duration": 180000, 41 | "fade_duration": 1000, 42 | "left_duration": 0, 43 | "my_vote": 0, 44 | "natural_die_duration": 30000, 45 | "options": [ 46 | { 47 | "cnt": 919, 48 | "desc": "TTG", 49 | "idx": 1, 50 | "percent": 0.6708029197080292, 51 | }, 52 | { 53 | "cnt": 451, 54 | "desc": "BOA", 55 | "idx": 2, 56 | "percent": 0.3291970802919708, 57 | }, 58 | ], 59 | "question": "你支持哪支战队", 60 | "result": 2, 61 | "result_text": "蓝获胜", 62 | "vote_id": 11404452, 63 | "waiting_duration": -1, 64 | } 65 | */ 66 | -------------------------------------------------------------------------------- /reference/DM_INTERACTION/type-101-2.jsonc: -------------------------------------------------------------------------------- 1 | { 2 | "cmd": "DM_INTERACTION", 3 | "data": { 4 | "data": "{\"question\":\"你支持哪支战队\",\"options\":[{\"idx\":1,\"desc\":\"TTG\",\"cnt\":722,\"percent\":0.6747663551401869},{\"idx\":2,\"desc\":\"BOA\",\"cnt\":345,\"percent\":0.32242990654205606}],\"vote_id\":11404452,\"cnt\":1070,\"duration\":180000,\"left_duration\":88000,\"fade_duration\":1000,\"waiting_duration\":-1,\"result\":2,\"result_text\":\"蓝领先\",\"component\":\"https://live.bilibili.com/p/html/live-app-guessing-game/vote.html?is_live_half_webview=1\\u0026hybrid_half_ui=1,3,100p,245,0,0,30,100,12,0;2,2,375,100p,0,0,30,100,12,0;3,3,100p,245,0,0,30,100,12,0;4,2,375,100p,0,0,30,100,12,0;5,3,100p,70p,0,0,30,100,12,0;6,3,100p,70p,0,0,30,100,12,0;7,3,100p,70p,0,0,30,100,12,0;8,3,100p,70p,0,0,30,100,12,0\",\"natural_die_duration\":30000,\"my_vote\":0,\"component_anchor\":\"https://live.bilibili.com/p/html/live-app-guessing-game/anchor_vote.html?pc_ui=390,428,0,3\\u0026is_live_half_webview=1\\u0026hybrid_half_ui=1,3,100p,448,0,0,30,0,12,0;2,2,375,100p,0,0,30,0,12,0;3,3,100p,448,0,0,30,0,12,0;4,2,375,100p,0,0,30,0,12,0;5,3,100p,448,0,0,30,0,12,0;6,2,320,100p,0,0,30,0,12,0;7,2,320,100p,0,0,30,0,12,0;8,2,320,100p,0,0,30,0,12,0#/\",\"audit_reason\":\"\",\"combo\":[{\"id\":1,\"status\":4,\"content\":\"TTG\",\"cnt\":722,\"guide\":\"\",\"left_duration\":88000,\"fade_duration\":0,\"prefix_icon\":\"http://i0.hdslb.com/bfs/dm/7d7e3682c9116aa3503418abe3cde6b45ed2e91e.png\"},{\"id\":2,\"status\":4,\"content\":\"BOA\",\"cnt\":345,\"guide\":\"\",\"left_duration\":88000,\"fade_duration\":0,\"prefix_icon\":\"http://i0.hdslb.com/bfs/dm/f83c7280b2a90b4f58a68fd8c594ea7d5667e3cb.png\"}]}", 5 | "dmscore": 36, 6 | "id": 31335934626816, 7 | "status": 4, 8 | "type": 101 9 | } 10 | } 11 | 12 | /** 13 | { 14 | "audit_reason": "", 15 | "cnt": 1070, 16 | "combo": [ 17 | { 18 | "cnt": 722, 19 | "content": "TTG", 20 | "fade_duration": 0, 21 | "guide": "", 22 | "id": 1, 23 | "left_duration": 88000, 24 | "prefix_icon": "http://i0.hdslb.com/bfs/dm/7d7e3682c9116aa3503418abe3cde6b45ed2e91e.png", 25 | "status": 4, 26 | }, 27 | { 28 | "cnt": 345, 29 | "content": "BOA", 30 | "fade_duration": 0, 31 | "guide": "", 32 | "id": 2, 33 | "left_duration": 88000, 34 | "prefix_icon": "http://i0.hdslb.com/bfs/dm/f83c7280b2a90b4f58a68fd8c594ea7d5667e3cb.png", 35 | "status": 4, 36 | }, 37 | ], 38 | "component": "https://live.bilibili.com/p/html/live-app-guessing-game/vote.html?is_live_half_webview=1&hybrid_half_ui=1,3,100p,245,0,0,30,100,12,0;2,2,375,100p,0,0,30,100,12,0;3,3,100p,245,0,0,30,100,12,0;4,2,375,100p,0,0,30,100,12,0;5,3,100p,70p,0,0,30,100,12,0;6,3,100p,70p,0,0,30,100,12,0;7,3,100p,70p,0,0,30,100,12,0;8,3,100p,70p,0,0,30,100,12,0", 39 | "component_anchor": "https://live.bilibili.com/p/html/live-app-guessing-game/anchor_vote.html?pc_ui=390,428,0,3&is_live_half_webview=1&hybrid_half_ui=1,3,100p,448,0,0,30,0,12,0;2,2,375,100p,0,0,30,0,12,0;3,3,100p,448,0,0,30,0,12,0;4,2,375,100p,0,0,30,0,12,0;5,3,100p,448,0,0,30,0,12,0;6,2,320,100p,0,0,30,0,12,0;7,2,320,100p,0,0,30,0,12,0;8,2,320,100p,0,0,30,0,12,0#/", 40 | "duration": 180000, 41 | "fade_duration": 1000, 42 | "left_duration": 88000, 43 | "my_vote": 0, 44 | "natural_die_duration": 30000, 45 | "options": [ 46 | { 47 | "cnt": 722, 48 | "desc": "TTG", 49 | "idx": 1, 50 | "percent": 0.6747663551401869, 51 | }, 52 | { 53 | "cnt": 345, 54 | "desc": "BOA", 55 | "idx": 2, 56 | "percent": 0.32242990654205606, 57 | }, 58 | ], 59 | "question": "你支持哪支战队", 60 | "result": 2, 61 | "result_text": "蓝领先", 62 | "vote_id": 11404452, 63 | "waiting_duration": -1, 64 | } 65 | */ 66 | -------------------------------------------------------------------------------- /reference/DM_INTERACTION/type-102.jsonc: -------------------------------------------------------------------------------- 1 | { 2 | "cmd": "DM_INTERACTION", 3 | "data": { 4 | "data": "{\"combo\":[{\"id\":30125982785536,\"status\":4,\"content\":\"?\",\"cnt\":11,\"guide\":\"他们都在说:\",\"left_duration\":10000,\"fade_duration\":10000,\"prefix_icon\":\"\"},{\"id\":30125997491200,\"status\":4,\"content\":\"哈 哈哈\",\"cnt\":6,\"guide\":\"他们都在说:\",\"left_duration\":12000,\"fade_duration\":10000,\"prefix_icon\":\"\"},{\"id\":30126003773952,\"status\":4,\"content\":\"哈哈哈哈哈\",\"cnt\":5,\"guide\":\"他们都在说:\",\"left_duration\":11000,\"fade_duration\":10000,\"prefix_icon\":\"\"},{\"id\":30126003736576,\"status\":4,\"content\":\"哈哈哈哈\",\"cnt\":4,\"guide\":\"他们都在说:\",\"left_duration\":5000,\"fade_duration\":10000,\"prefix_icon\":\"\"},{\"id\":30126005846528,\"status\":4,\"content\":\"哈哈\",\"cnt\":3,\"guide\":\"他们都在说:\",\"left_duration\":0,\"fade_duration\":10000,\"prefix_icon\":\"\"}],\"merge_interval\":1000,\"card_appear_interval\":1000,\"send_interval\":1000}", 5 | "dmscore": 36, 6 | "id": 30125982785536, 7 | "status": 4, 8 | "type": 102 9 | } 10 | } 11 | 12 | /** 13 | { 14 | "card_appear_interval": 1000, 15 | "combo": [ 16 | { 17 | "cnt": 11, 18 | "content": "?", 19 | "fade_duration": 10000, 20 | "guide": "他们都在说:", 21 | "id": 30125982785536, 22 | "left_duration": 10000, 23 | "prefix_icon": "", 24 | "status": 4, 25 | }, 26 | { 27 | "cnt": 6, 28 | "content": "哈 哈哈", 29 | "fade_duration": 10000, 30 | "guide": "他们都在说:", 31 | "id": 30125997491200, 32 | "left_duration": 12000, 33 | "prefix_icon": "", 34 | "status": 4, 35 | }, 36 | { 37 | "cnt": 5, 38 | "content": "哈哈哈哈哈", 39 | "fade_duration": 10000, 40 | "guide": "他们都在说:", 41 | "id": 30126003773952, 42 | "left_duration": 11000, 43 | "prefix_icon": "", 44 | "status": 4, 45 | }, 46 | { 47 | "cnt": 4, 48 | "content": "哈哈哈哈", 49 | "fade_duration": 10000, 50 | "guide": "他们都在说:", 51 | "id": 30126003736576, 52 | "left_duration": 5000, 53 | "prefix_icon": "", 54 | "status": 4, 55 | }, 56 | { 57 | "cnt": 3, 58 | "content": "哈哈", 59 | "fade_duration": 10000, 60 | "guide": "他们都在说:", 61 | "id": 30126005846528, 62 | "left_duration": 0, 63 | "prefix_icon": "", 64 | "status": 4, 65 | }, 66 | ], 67 | "merge_interval": 1000, 68 | "send_interval": 1000, 69 | } 70 | */ 71 | -------------------------------------------------------------------------------- /reference/ENTRY_EFFECT/no-fans-medal.jsonc: -------------------------------------------------------------------------------- 1 | { 2 | "cmd": "ENTRY_EFFECT", 3 | "data": { 4 | "id": 4, 5 | "uid": 3108739, 6 | "target_id": 8739477, 7 | "mock_effect": 0, 8 | "face": "https://i1.hdslb.com/bfs/face/5275dcfb158087ed02232de9e952a8b87317a70c.jpg", 9 | "privilege_type": 3, 10 | "copy_writing": "欢迎舰长 <%大井盖儿%> 进入直播间", 11 | "copy_color": "#ffffff", 12 | "highlight_color": "#E6FF00", 13 | "priority": 1, 14 | "basemap_url": "https://i0.hdslb.com/bfs/live/mlive/11a6e8eb061c3e715d0a6a2ac0ddea2faa15c15e.png", 15 | "show_avatar": 1, 16 | "effective_time": 2, 17 | "web_basemap_url": "https://i0.hdslb.com/bfs/live/mlive/11a6e8eb061c3e715d0a6a2ac0ddea2faa15c15e.png", 18 | "web_effective_time": 2, 19 | "web_effect_close": 0, 20 | "web_close_time": 0, 21 | "business": 1, 22 | "copy_writing_v2": "欢迎舰长 <%大井盖儿%> 进入直播间", 23 | "icon_list": [], 24 | "max_delay_time": 7, 25 | "trigger_time": 1713631024570116400, 26 | "identities": 6, 27 | "effect_silent_time": 0, 28 | "effective_time_new": 0, 29 | "web_dynamic_url_webp": "", 30 | "web_dynamic_url_apng": "", 31 | "mobile_dynamic_url_webp": "", 32 | "wealthy_info": null, 33 | "new_style": 0, 34 | "is_mystery": false, 35 | "uinfo": { 36 | "uid": 3108739, 37 | "base": { 38 | "name": "大井盖儿", 39 | "face": "https://i1.hdslb.com/bfs/face/5275dcfb158087ed02232de9e952a8b87317a70c.jpg", 40 | "name_color": 0, 41 | "is_mystery": false, 42 | "risk_ctrl_info": null, 43 | "origin_info": null, 44 | "official_info": null, 45 | "name_color_str": "#00D1F1" 46 | }, 47 | "medal": null, 48 | "wealth": { 49 | "level": 50, 50 | "dm_icon_key": "ChronosWealth_5.png" 51 | }, 52 | "title": null, 53 | "guard": { 54 | "level": 3, 55 | "expired_str": "2024-05-03 23:59:59" 56 | }, 57 | "uhead_frame": null, 58 | "guard_leader": null 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /reference/ENTRY_EFFECT/normal.jsonc: -------------------------------------------------------------------------------- 1 | { 2 | "cmd": "ENTRY_EFFECT", 3 | "data": { 4 | "id": 2, 5 | "uid": 278449453, 6 | "target_id": 8739477, 7 | "mock_effect": 0, 8 | "face": "https://i1.hdslb.com/bfs/face/c03c2aa4545d1804d4e6fce6150ab8f8138375af.jpg", 9 | "privilege_type": 2, 10 | "copy_writing": "欢迎提督 <%飞哥飞飞飞飞飞...%> 进入直播间", 11 | "copy_color": "#ffffff", 12 | "highlight_color": "#FFF100", 13 | "priority": 1, 14 | "basemap_url": "https://i0.hdslb.com/bfs/live/mlive/74a41c65e422116d230d433042881fa5556f7870.png", 15 | "show_avatar": 1, 16 | "effective_time": 3, 17 | "web_basemap_url": "https://i0.hdslb.com/bfs/live/mlive/74a41c65e422116d230d433042881fa5556f7870.png", 18 | "web_effective_time": 3, 19 | "web_effect_close": 0, 20 | "web_close_time": 0, 21 | "business": 1, 22 | "copy_writing_v2": "欢迎提督 <%飞哥飞飞飞飞…%> 进入直播间", 23 | "icon_list": [], 24 | "max_delay_time": 7, 25 | "trigger_time": 1713630018063956200, 26 | "identities": 7, 27 | "effect_silent_time": 0, 28 | "effective_time_new": 0, 29 | "web_dynamic_url_webp": "", 30 | "web_dynamic_url_apng": "", 31 | "mobile_dynamic_url_webp": "", 32 | "wealthy_info": null, 33 | "new_style": 0, 34 | "is_mystery": false, 35 | "uinfo": { 36 | "uid": 278449453, 37 | "base": { 38 | "name": "飞哥飞飞飞飞飞飞", 39 | "face": "https://i1.hdslb.com/bfs/face/c03c2aa4545d1804d4e6fce6150ab8f8138375af.jpg", 40 | "name_color": 0, 41 | "is_mystery": false, 42 | "risk_ctrl_info": null, 43 | "origin_info": null, 44 | "official_info": null, 45 | "name_color_str": "#E17AFF" 46 | }, 47 | "medal": { 48 | "name": "德云色", 49 | "level": 29, 50 | "color_start": 2951253, 51 | "color_end": 10329087, 52 | "color_border": 16771156, 53 | "color": 2951253, 54 | "id": 0, 55 | "typ": 0, 56 | "is_light": 1, 57 | "ruid": 8739477, 58 | "guard_level": 2, 59 | "score": 50549164, 60 | "guard_icon": "", 61 | "honor_icon": "" 62 | }, 63 | "wealth": { 64 | "level": 39, 65 | "dm_icon_key": "" 66 | }, 67 | "title": null, 68 | "guard": { 69 | "level": 2, 70 | "expired_str": "2024-09-24 23:59:59" 71 | }, 72 | "uhead_frame": null, 73 | "guard_leader": null 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /reference/GUARD_BUY/normal.jsonc: -------------------------------------------------------------------------------- 1 | { 2 | "cmd": "GUARD_BUY", 3 | "data": { 4 | "uid": 151862454, 5 | "username": "慧儿面", 6 | "guard_level": 3, 7 | "num": 1, 8 | "price": 198000, 9 | "gift_id": 10003, 10 | "gift_name": "舰长", 11 | "start_time": 1713630642, 12 | "end_time": 1713630642 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /reference/HOT_RANK_CHANGED_V2/normal.jsonc: -------------------------------------------------------------------------------- 1 | { 2 | "cmd": "HOT_RANK_CHANGED_V2", 3 | "data": { 4 | "rank": 32, 5 | "trend": 0, 6 | "countdown": 50, 7 | "timestamp": 1654975750, 8 | "web_url": "https://live.bilibili.com/p/html/live-app-hotrank/index.html?clientType=2&area_id=1&parent_area_id=1&second_area_id=145", 9 | "live_url": "https://live.bilibili.com/p/html/live-app-hotrank/index.html?clientType=1&area_id=1&parent_area_id=1&second_area_id=145&is_live_half_webview=1&hybrid_rotate_d=1&hybrid_half_ui=1,3,100p,70p,ffffff,0,30,100,12,0;2,2,375,100p,ffffff,0,30,100,0,0;3,3,100p,70p,ffffff,0,30,100,12,0;4,2,375,100p,ffffff,0,30,100,0,0;5,3,100p,70p,ffffff,0,30,100,0,0;6,3,100p,70p,ffffff,0,30,100,0,0;7,3,100p,70p,ffffff,0,30,100,0,0;8,3,100p,70p,ffffff,0,30,100,0,0", 10 | "blink_url": "https://live.bilibili.com/p/html/live-app-hotrank/index.html?clientType=3&area_id=1&parent_area_id=1&second_area_id=145&is_live_half_webview=1&hybrid_rotate_d=1&is_cling_player=1&hybrid_half_ui=1,3,100p,70p,ffffff,0,30,100,0,0;2,2,375,100p,ffffff,0,30,100,0,0;3,3,100p,70p,ffffff,0,30,100,0,0;4,2,375,100p,ffffff,0,30,100,0,0;5,3,100p,70p,ffffff,0,30,100,0,0;6,3,100p,70p,ffffff,0,30,100,0,0;7,3,100p,70p,ffffff,0,30,100,0,0;8,3,100p,70p,ffffff,0,30,100,0,0", 11 | "live_link_url": "https://live.bilibili.com/p/html/live-app-hotrank/index.html?clientType=5&area_id=1&parent_area_id=1&second_area_id=145&is_live_half_webview=1&hybrid_rotate_d=1&is_cling_player=1&hybrid_half_ui=1,3,100p,70p,f4eefa,0,30,100,0,0;2,2,375,100p,f4eefa,0,30,100,0,0;3,3,100p,70p,f4eefa,0,30,100,0,0;4,2,375,100p,f4eefa,0,30,100,0,0;5,3,100p,70p,f4eefa,0,30,100,0,0;6,3,100p,70p,f4eefa,0,30,100,0,0;7,3,100p,70p,f4eefa,0,30,100,0,0;8,3,100p,70p,f4eefa,0,30,100,0,0", 12 | "pc_link_url": "https://live.bilibili.com/p/html/live-app-hotrank/index.html?clientType=4&is_live_half_webview=1&area_id=1&parent_area_id=1&second_area_id=145&pc_ui=338,465,f4eefa,0", 13 | "icon": "https://i0.hdslb.com/bfs/live/cb2e160ac4f562b347bb5ae6e635688ebc69580f.png", 14 | "area_name": "视频聊天", 15 | "rank_desc": "视频聊天top50" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /reference/INTERACT_WORD/no-medal.jsonc: -------------------------------------------------------------------------------- 1 | { 2 | "cmd": "INTERACT_WORD", 3 | "data": { 4 | "contribution": { 5 | "grade": 0 6 | }, 7 | "contribution_v2": { 8 | "grade": 0, 9 | "rank_type": "", 10 | "text": "" 11 | }, 12 | "core_user_type": 0, 13 | "dmscore": 51, 14 | "fans_medal": null, 15 | "group_medal": null, 16 | "identities": [ 17 | 6, 18 | 1 19 | ], 20 | "is_mystery": false, 21 | "is_spread": 0, 22 | "msg_type": 1, 23 | "privilege_type": 3, 24 | "roomid": 545068, 25 | "score": 1713627272750, 26 | "spread_desc": "", 27 | "spread_info": "", 28 | "tail_icon": 0, 29 | "tail_text": "", 30 | "timestamp": 1713627272, 31 | "trigger_time": 1713627271604058000, 32 | "uid": 289470390, 33 | "uinfo": { 34 | "base": { 35 | "face": "https://i0.hdslb.com/bfs/face/c36e369dd7e359a6ae9a855d728d5db8f6575b3c.jpg", 36 | "is_mystery": false, 37 | "name": "薯门-异羽高", 38 | "name_color": 0, 39 | "name_color_str": "#00D1F1", 40 | "official_info": null, 41 | "origin_info": null, 42 | "risk_ctrl_info": null 43 | }, 44 | "guard": { 45 | "expired_str": "2024-05-20 23:59:59", 46 | "level": 3 47 | }, 48 | "guard_leader": null, 49 | "medal": null, 50 | "title": null, 51 | "uhead_frame": null, 52 | "uid": 289470390, 53 | "wealth": { 54 | "dm_icon_key": "", 55 | "level": 30 56 | } 57 | }, 58 | "uname": "薯门-异羽高", 59 | "uname_color": "#00D1F1" 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /reference/INTERACT_WORD/normal.jsonc: -------------------------------------------------------------------------------- 1 | { 2 | "cmd": "INTERACT_WORD", 3 | "data": { 4 | "contribution": { 5 | "grade": 0 6 | }, 7 | "contribution_v2": { 8 | "grade": 0, 9 | "rank_type": "", 10 | "text": "" 11 | }, 12 | "core_user_type": 0, 13 | "dmscore": 27, 14 | "fans_medal": { 15 | "anchor_roomid": 25150536, 16 | "guard_level": 0, 17 | "icon_id": 0, 18 | "is_lighted": 1, 19 | "medal_color": 1725515, 20 | "medal_color_border": 1725515, 21 | "medal_color_end": 5414290, 22 | "medal_color_start": 1725515, 23 | "medal_level": 22, 24 | "medal_name": "辅导原", 25 | "score": 50002196, 26 | "special": "", 27 | "target_id": 1204426852 28 | }, 29 | "group_medal": null, 30 | "identities": [ 31 | 3, 32 | 1 33 | ], 34 | "is_mystery": false, 35 | "is_spread": 0, 36 | "msg_type": 1, 37 | "privilege_type": 0, 38 | "roomid": 545068, 39 | "score": 1763639456612, 40 | "spread_desc": "", 41 | "spread_info": "", 42 | "tail_icon": 0, 43 | "tail_text": "", 44 | "timestamp": 1713627260, 45 | "trigger_time": 1713627259524730600, 46 | "uid": 278517010, 47 | "uinfo": { 48 | "base": { 49 | "face": "https://i1.hdslb.com/bfs/face/90f9f7a95f01aea24678fdd52bb69297381f7139.jpg", 50 | "is_mystery": false, 51 | "name": "牡丹江腕力金少", 52 | "name_color": 0, 53 | "name_color_str": "", 54 | "official_info": null, 55 | "origin_info": null, 56 | "risk_ctrl_info": null 57 | }, 58 | "guard": { 59 | "expired_str": "", 60 | "level": 0 61 | }, 62 | "guard_leader": null, 63 | "medal": { 64 | "color": 1725515, 65 | "color_border": 1725515, 66 | "color_end": 5414290, 67 | "color_start": 1725515, 68 | "guard_icon": "", 69 | "guard_level": 0, 70 | "honor_icon": "", 71 | "id": 0, 72 | "is_light": 1, 73 | "level": 22, 74 | "name": "辅导原", 75 | "ruid": 1204426852, 76 | "score": 50002196, 77 | "typ": 0 78 | }, 79 | "title": null, 80 | "uhead_frame": null, 81 | "uid": 278517010, 82 | "wealth": { 83 | "dm_icon_key": "", 84 | "level": 26 85 | } 86 | }, 87 | "uname": "牡丹江腕力金少", 88 | "uname_color": "" 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /reference/LIKE_INFO_V3_CLICK/normal.jsonc: -------------------------------------------------------------------------------- 1 | { 2 | "cmd": "LIKE_INFO_V3_CLICK", 3 | "data": { 4 | "contribution_info": { 5 | "grade": 0 6 | }, 7 | "dmscore": 36, 8 | "fans_medal": { 9 | "anchor_roomid": 0, 10 | "guard_level": 0, 11 | "icon_id": 0, 12 | "is_lighted": 0, 13 | "medal_color": 1725515, 14 | "medal_color_border": 12632256, 15 | "medal_color_end": 12632256, 16 | "medal_color_start": 12632256, 17 | "medal_level": 22, 18 | "medal_name": "小孩梓", 19 | "score": 50003171, 20 | "special": "", 21 | "target_id": 7706705 22 | }, 23 | "group_medal": null, 24 | "identities": [ 25 | 1 26 | ], 27 | "is_mystery": false, 28 | "like_icon": "https://i0.hdslb.com/bfs/live/23678e3d90402bea6a65251b3e728044c21b1f0f.png", 29 | "like_text": "为主播点赞了", 30 | "msg_type": 6, 31 | "show_area": 0, 32 | "uid": 15253584, 33 | "uinfo": { 34 | "base": { 35 | "face": "https://i0.hdslb.com/bfs/face/9eeb889c3a709a94f3893ceda8e1f0d3c222f698.jpg", 36 | "is_mystery": false, 37 | "name": "鹿解解", 38 | "name_color": 0, 39 | "name_color_str": "", 40 | "official_info": { 41 | "desc": "", 42 | "role": 0, 43 | "title": "", 44 | "type": -1 45 | }, 46 | "origin_info": { 47 | "face": "https://i0.hdslb.com/bfs/face/9eeb889c3a709a94f3893ceda8e1f0d3c222f698.jpg", 48 | "name": "鹿解解" 49 | }, 50 | "risk_ctrl_info": null 51 | }, 52 | "guard": { 53 | "expired_str": "", 54 | "level": 0 55 | }, 56 | "guard_leader": null, 57 | "medal": { 58 | "color": 1725515, 59 | "color_border": 12632256, 60 | "color_end": 12632256, 61 | "color_start": 12632256, 62 | "guard_icon": "", 63 | "guard_level": 0, 64 | "honor_icon": "", 65 | "id": 0, 66 | "is_light": 0, 67 | "level": 22, 68 | "name": "小孩梓", 69 | "ruid": 7706705, 70 | "score": 50003171, 71 | "typ": 0 72 | }, 73 | "title": null, 74 | "uhead_frame": null, 75 | "uid": 15253584, 76 | "wealth": null 77 | }, 78 | "uname": "鹿解解", 79 | "uname_color": "" 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /reference/LIKE_INFO_V3_UPDATE/normal.jsonc: -------------------------------------------------------------------------------- 1 | { 2 | "cmd": "LIKE_INFO_V3_UPDATE", 3 | "data": { 4 | "click_count": 470 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /reference/LIVE/normal.jsonc: -------------------------------------------------------------------------------- 1 | { 2 | "cmd": "LIVE", 3 | "live_key": "234304209915761953", 4 | "voice_background": "", 5 | "sub_session_key": "234304209915761953sub_time:1651036923", 6 | "live_platform": "pc", 7 | "live_model": 0, 8 | "live_time": 1651036923, 9 | "roomid": 23614753 10 | } 11 | -------------------------------------------------------------------------------- /reference/NOTICE_MSG/人气榜第一名.jsonc: -------------------------------------------------------------------------------- 1 | { 2 | "cmd": "NOTICE_MSG", 3 | "id": 804, 4 | "name": "人气榜第一名", 5 | "full": { 6 | "head_icon": "https://i0.hdslb.com/bfs/live/f74b09c7fb83123a0dd66c536b6d5b143d271b08.png", 7 | "tail_icon": "https://i0.hdslb.com/bfs/live/822da481fdaba986d738db5d8fd469ffa95a8fa1.webp", 8 | "head_icon_fa": "https://i0.hdslb.com/bfs/live/f74b09c7fb83123a0dd66c536b6d5b143d271b08.png", 9 | "tail_icon_fa": "https://i0.hdslb.com/bfs/live/38cb2a9f1209b16c0f15162b0b553e3b28d9f16f.png", 10 | "head_icon_fan": 1, 11 | "tail_icon_fan": 4, 12 | "background": "#FFE6BD", 13 | "color": "#9D5412", 14 | "highlight": "#FF6933", 15 | "time": 20 16 | }, 17 | "half": { 18 | "head_icon": "https://i0.hdslb.com/bfs/live/f74b09c7fb83123a0dd66c536b6d5b143d271b08.png", 19 | "tail_icon": "https://i0.hdslb.com/bfs/live/822da481fdaba986d738db5d8fd469ffa95a8fa1.webp", 20 | "background": "#FFE6BD", 21 | "color": "#9D5412", 22 | "highlight": "#FF6933", 23 | "time": 0 24 | }, 25 | "side": { 26 | "head_icon": "", 27 | "background": "", 28 | "color": "", 29 | "highlight": "", 30 | "border": "" 31 | }, 32 | "roomid": 6136246, 33 | "real_roomid": 6136246, 34 | "msg_common": "恭喜主播<%凉哈皮%>荣获上小时人气榜第<%1%>名!点击传送查看精彩内容!", 35 | "msg_self": "恭喜主播<%凉哈皮%>荣获上小时人气榜第<%1%>名!", 36 | "link_url": "https://live.bilibili.com/6136246?broadcast_type=0&is_room_feed=1&from=28003&extra_jump_from=28003", 37 | "msg_type": 1, 38 | "shield_uid": -1, 39 | "business_id": "", 40 | "scatter": { 41 | "min": 0, 42 | "max": 0 43 | }, 44 | "marquee_id": "", 45 | "notice_type": 0 46 | } 47 | -------------------------------------------------------------------------------- /reference/NOTICE_MSG/舰长一个月no-msg-common.jsonc: -------------------------------------------------------------------------------- 1 | { 2 | "cmd": "NOTICE_MSG", 3 | "id": 968, 4 | "name": "舰长1个月", 5 | "full": { 6 | "head_icon": "https://i0.hdslb.com/bfs/live/82665c9d263c8673f3f934e23d09c1d0f6bc8f50.png", 7 | "tail_icon": "", 8 | "head_icon_fa": "https://i0.hdslb.com/bfs/live/82665c9d263c8673f3f934e23d09c1d0f6bc8f50.png", 9 | "tail_icon_fa": "", 10 | "head_icon_fan": 1, 11 | "tail_icon_fan": 0, 12 | "background": "#FFE2B2", 13 | "color": "#B87436", 14 | "highlight": "#E37921", 15 | "time": 10 16 | }, 17 | "half": { 18 | "head_icon": "", 19 | "tail_icon": "", 20 | "background": "", 21 | "color": "", 22 | "highlight": "", 23 | "time": 0 24 | }, 25 | "side": { 26 | "head_icon": "https://i0.hdslb.com/bfs/live/82665c9d263c8673f3f934e23d09c1d0f6bc8f50.png", 27 | "background": "#FFE9C8FF", 28 | "color": "#EF903AFF", 29 | "highlight": "#D54900FF", 30 | "border": "#FFCFA4FF" 31 | }, 32 | "roomid": 545068, 33 | "real_roomid": 545068, 34 | "msg_common": "", 35 | "msg_self": "<%--为什么--%> 在主播 <%老实憨厚的笑笑%>的直播间开通了舰长,感谢上船陪伴", 36 | "link_url": "", 37 | "msg_type": 4, 38 | "shield_uid": -1, 39 | "business_id": "xuser-guard", 40 | "scatter": { 41 | "min": 0, 42 | "max": 0 43 | }, 44 | "marquee_id": "", 45 | "notice_type": 0 46 | } 47 | -------------------------------------------------------------------------------- /reference/ONLINE_RANK_COUNT/no_online_count.jsonc: -------------------------------------------------------------------------------- 1 | { 2 | "cmd": "ONLINE_RANK_COUNT", 3 | "data": { 4 | "count": 3203 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /reference/ONLINE_RANK_COUNT/online_count.jsonc: -------------------------------------------------------------------------------- 1 | { 2 | "cmd": "ONLINE_RANK_COUNT", 3 | "data": { 4 | "count": 3205, 5 | "online_count": 4265 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /reference/POPULARITY_RED_POCKET_NEW/normal.jsonc: -------------------------------------------------------------------------------- 1 | { 2 | "cmd": "POPULARITY_RED_POCKET_NEW", 3 | "data": { 4 | "lot_id": 8445764, 5 | "start_time": 1673684631, 6 | "current_time": 1673684631, 7 | "wait_num": 0, 8 | "uname": "我的0019", 9 | "uid": 38554435, 10 | "action": "送出", 11 | "num": 1, 12 | "gift_name": "红包", 13 | "gift_id": 13000, 14 | "price": 20,// 单位:1电池=100金瓜子=1/10元 15 | "name_color": "", 16 | "medal_info": { 17 | "target_id": 400963649, 18 | "special": "", 19 | "icon_id": 0, 20 | "anchor_uname": "", 21 | "anchor_roomid": 0, 22 | "medal_level": 21, 23 | "medal_name": "憨憨酥", 24 | "medal_color": 1725515, 25 | "medal_color_start": 12632256, 26 | "medal_color_end": 12632256, 27 | "medal_color_border": 12632256, 28 | "is_lighted": 0, 29 | "guard_level": 0 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /reference/POPULARITY_RED_POCKET_START/normal.jsonc: -------------------------------------------------------------------------------- 1 | { 2 | "cmd": "POPULARITY_RED_POCKET_START", 3 | "data": { 4 | "lot_id": 8445764, 5 | "sender_uid": 38554435, 6 | "sender_name": "我的0019", 7 | "sender_face": "https://i1.hdslb.com/bfs/face/282c3055de94c74d69094fad91a56f9ed73a270a.jpg", 8 | "join_requirement": 1, 9 | "danmu": "点点红包,关注主播抽礼物~", 10 | "current_time": 1673684632, 11 | "start_time": 1673684631, 12 | "end_time": 1673684811, 13 | "last_time": 180, 14 | "remove_time": 1673684826, 15 | "replace_time": 1673684821, 16 | "lot_status": 1, 17 | "h5_url": "https://live.bilibili.com/p/html/live-app-red-envelope/popularity.html?is_live_half_webview=1&hybrid_half_ui=1,5,100p,100p,000000,0,50,0,0,1;2,5,100p,100p,000000,0,50,0,0,1;3,5,100p,100p,000000,0,50,0,0,1;4,5,100p,100p,000000,0,50,0,0,1;5,5,100p,100p,000000,0,50,0,0,1;6,5,100p,100p,000000,0,50,0,0,1;7,5,100p,100p,000000,0,50,0,0,1;8,5,100p,100p,000000,0,50,0,0,1&hybrid_rotate_d=1&hybrid_biz=popularityRedPacket&lotteryId=8445764", 18 | "user_status": 2, 19 | "awards": [ 20 | { 21 | "gift_id": 31212, 22 | "gift_name": "打call", 23 | "gift_pic": "https://s1.hdslb.com/bfs/live/461be640f60788c1d159ec8d6c5d5cf1ef3d1830.png", 24 | "num": 2 25 | }, 26 | { 27 | "gift_id": 31214, 28 | "gift_name": "牛哇", 29 | "gift_pic": "https://s1.hdslb.com/bfs/live/91ac8e35dd93a7196325f1e2052356e71d135afb.png", 30 | "num": 3 31 | }, 32 | { 33 | "gift_id": 31216, 34 | "gift_name": "i了i了", 35 | "gift_pic": "https://s1.hdslb.com/bfs/live/1157a445487b39c0b7368d91b22290c60fa665b2.png", 36 | "num": 3 37 | } 38 | ], 39 | "lot_config_id": 3, 40 | "total_price": 1600, 41 | "wait_num": 0 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /reference/POPULARITY_RED_POCKET_WINNER_LIST/normal.jsonc: -------------------------------------------------------------------------------- 1 | { 2 | "cmd": "POPULARITY_RED_POCKET_WINNER_LIST", 3 | "data": { 4 | "lot_id": 8445764, 5 | "total_num": 8, 6 | "winner_info": [ 7 | [ 8 | 38554435, 9 | "我的0019", 10 | 4581509, 11 | 31212 12 | ], 13 | [ 14 | 516174930, 15 | "云来海遛鸟大爷", 16 | 4606389, 17 | 31212 18 | ] 19 | ], 20 | "awards": { 21 | "31212": { 22 | "award_type": 1, 23 | "award_name": "打call", 24 | "award_pic": "https://s1.hdslb.com/bfs/live/461be640f60788c1d159ec8d6c5d5cf1ef3d1830.png", 25 | "award_big_pic": "https://i0.hdslb.com/bfs/live/9e6521c57f24c7149c054d265818d4b82059f2ef.png", 26 | "award_price": 500 27 | }, 28 | "31214": { 29 | "award_type": 1, 30 | "award_name": "牛哇", 31 | "award_pic": "https://s1.hdslb.com/bfs/live/91ac8e35dd93a7196325f1e2052356e71d135afb.png", 32 | "award_big_pic": "https://i0.hdslb.com/bfs/live/3b74c117b4f265edcea261bc5608a58d3a7c300a.png", 33 | "award_price": 100 34 | }, 35 | "31216": { 36 | "award_type": 1, 37 | "award_name": "i了i了", 38 | "award_pic": "https://s1.hdslb.com/bfs/live/1157a445487b39c0b7368d91b22290c60fa665b2.png", 39 | "award_big_pic": "https://i0.hdslb.com/bfs/live/cfb9c3d9bdd2c25c95b7d859ebaa590ca9362adb.png", 40 | "award_price": 100 41 | } 42 | }, 43 | "version": 1 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /reference/POPULAR_RANK_CHANGED/normal.jsonc: -------------------------------------------------------------------------------- 1 | { 2 | "cmd": "POPULAR_RANK_CHANGED", 3 | "data": { 4 | "uid": 780791, 5 | "rank": 36, 6 | "countdown": 1927, 7 | "timestamp": 1702578474, 8 | "cache_key": "rank_change:91a4e81ba3034ae894d61e432aa13081" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /reference/PREPARING/normal.jsonc: -------------------------------------------------------------------------------- 1 | { 2 | "cmd": "PREPARING", 3 | "round": 1, 4 | "roomid": "6032530" 5 | } 6 | -------------------------------------------------------------------------------- /reference/ROOM_CHANGE/normal.jsonc: -------------------------------------------------------------------------------- 1 | { 2 | "cmd": "ROOM_CHANGE", 3 | "data": { 4 | "title": "开始白给CS", 5 | "area_id": 371, 6 | "parent_area_id": 9, 7 | "area_name": "虚拟主播", 8 | "parent_area_name": "虚拟主播", 9 | "live_key": "320830629635915849", 10 | "sub_session_key": "320830629635915849sub_time:1673690546" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /reference/ROOM_REAL_TIME_MESSAGE_UPDATE/normal.jsonc: -------------------------------------------------------------------------------- 1 | { 2 | "cmd": "ROOM_REAL_TIME_MESSAGE_UPDATE", 3 | "data": { 4 | "roomid": 545068, 5 | "fans": 2702428, 6 | "red_notice": -1, 7 | "fans_club": 59676 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /reference/SEND_GIFT/干杯 combo.jsonc: -------------------------------------------------------------------------------- 1 | { 2 | "cmd": "SEND_GIFT", 3 | "data": { 4 | "action": "投喂", 5 | "bag_gift": null, 6 | "batch_combo_id": "batch:gift:combo_id:26782850:8739477:31049:1713667359.2101", 7 | "batch_combo_send": { 8 | "action": "投喂", 9 | "batch_combo_id": "batch:gift:combo_id:26782850:8739477:31049:1713667359.2101", 10 | "batch_combo_num": 2, 11 | "blind_gift": null, 12 | "gift_id": 31049, 13 | "gift_name": "干杯", 14 | "gift_num": 1, 15 | "send_master": null, 16 | "uid": 26782850, 17 | "uname": "来把RPG吧" 18 | }, 19 | "beatId": "", 20 | "biz_source": "live", 21 | "blind_gift": null, 22 | "broadcast_id": 0, 23 | "coin_type": "gold", 24 | "combo_resources_id": 1, 25 | "combo_send": null, 26 | "combo_stay_time": 5, 27 | "combo_total_coin": 13200, 28 | "crit_prob": 0, 29 | "demarcation": 2, 30 | "discount_price": 6600, 31 | "dmscore": 280, 32 | "draw": 0, 33 | "effect": 0, 34 | "effect_block": 0, 35 | "face": "https://i0.hdslb.com/bfs/face/member/noface.jpg", 36 | "face_effect_id": 0, 37 | "face_effect_type": 0, 38 | "float_sc_resource_id": 0, 39 | "giftId": 31049, 40 | "giftName": "干杯", 41 | "giftType": 0, 42 | "gift_tag": [], 43 | "gold": 0, 44 | "group_medal": null, 45 | "guard_level": 0, 46 | "is_first": false, 47 | "is_join_receiver": false, 48 | "is_naming": false, 49 | "is_special_batch": 0, 50 | "magnification": 1, 51 | "medal_info": { 52 | "anchor_roomid": 0, 53 | "anchor_uname": "", 54 | "guard_level": 0, 55 | "icon_id": 0, 56 | "is_lighted": 1, 57 | "medal_color": 1725515, 58 | "medal_color_border": 1725515, 59 | "medal_color_end": 5414290, 60 | "medal_color_start": 1725515, 61 | "medal_level": 22, 62 | "medal_name": "德云色", 63 | "special": "", 64 | "target_id": 8739477 65 | }, 66 | "name_color": "", 67 | "num": 1, 68 | "original_gift_name": "", 69 | "price": 6600, 70 | "rcost": 315123382, 71 | "receive_user_info": { 72 | "uid": 8739477, 73 | "uname": "老实憨厚的笑笑" 74 | }, 75 | "receiver_uinfo": { 76 | "base": { 77 | "face": "https://i0.hdslb.com/bfs/face/59da329a536b148b0e8d19143b686d2da06dad93.jpg", 78 | "is_mystery": false, 79 | "name": "老实憨厚的笑笑", 80 | "name_color": 0, 81 | "name_color_str": "", 82 | "official_info": { 83 | "desc": "", 84 | "role": 2, 85 | "title": "bilibili 2022百大UP主、2022直播年度弹幕人气奖UP主、职业游戏解说孙亚龙", 86 | "type": 0 87 | }, 88 | "origin_info": { 89 | "face": "https://i0.hdslb.com/bfs/face/59da329a536b148b0e8d19143b686d2da06dad93.jpg", 90 | "name": "老实憨厚的笑笑" 91 | }, 92 | "risk_ctrl_info": { 93 | "face": "https://i0.hdslb.com/bfs/face/59da329a536b148b0e8d19143b686d2da06dad93.jpg", 94 | "name": "老实憨厚的笑笑" 95 | } 96 | }, 97 | "guard": null, 98 | "guard_leader": null, 99 | "medal": null, 100 | "title": null, 101 | "uhead_frame": null, 102 | "uid": 8739477, 103 | "wealth": null 104 | }, 105 | "remain": 0, 106 | "rnd": "4499326070192884224", 107 | "send_master": null, 108 | "sender_uinfo": { 109 | "base": { 110 | "face": "https://i0.hdslb.com/bfs/face/member/noface.jpg", 111 | "is_mystery": false, 112 | "name": "来把RPG吧", 113 | "name_color": 0, 114 | "name_color_str": "", 115 | "official_info": { 116 | "desc": "", 117 | "role": 0, 118 | "title": "", 119 | "type": -1 120 | }, 121 | "origin_info": { 122 | "face": "https://i0.hdslb.com/bfs/face/member/noface.jpg", 123 | "name": "来把RPG吧" 124 | }, 125 | "risk_ctrl_info": { 126 | "face": "https://i0.hdslb.com/bfs/face/member/noface.jpg", 127 | "name": "来把RPG吧" 128 | } 129 | }, 130 | "guard": null, 131 | "guard_leader": null, 132 | "medal": null, 133 | "title": null, 134 | "uhead_frame": null, 135 | "uid": 26782850, 136 | "wealth": null 137 | }, 138 | "silver": 0, 139 | "super": 0, 140 | "super_batch_gift_num": 2, 141 | "super_gift_num": 2, 142 | "svga_block": 0, 143 | "switch": true, 144 | "tag_image": "", 145 | "tid": "4499326070192884224", 146 | "timestamp": 1713667359, 147 | "top_list": null, 148 | "total_coin": 6600, 149 | "uid": 26782850, 150 | "uname": "来把RPG吧", 151 | "wealth_level": 12 152 | }, 153 | "msg_id": "13166696010898944:1000:1000", 154 | "p_is_ack": true, 155 | "p_msg_type": 1, 156 | "send_time": 1713667359635 157 | } 158 | -------------------------------------------------------------------------------- /reference/SEND_GIFT/粉丝团灯牌.jsonc: -------------------------------------------------------------------------------- 1 | { 2 | "cmd": "SEND_GIFT", 3 | "data": { 4 | "action": "投喂", 5 | "bag_gift": null, 6 | "batch_combo_id": "batch:gift:combo_id:477844045:8739477:31164:1713627768.7318", 7 | "batch_combo_send": { 8 | "action": "投喂", 9 | "batch_combo_id": "batch:gift:combo_id:477844045:8739477:31164:1713627768.7318", 10 | "batch_combo_num": 1, 11 | "blind_gift": null, 12 | "gift_id": 31164, 13 | "gift_name": "粉丝团灯牌", 14 | "gift_num": 1, 15 | "send_master": null, 16 | "uid": 477844045, 17 | "uname": "wasp111" 18 | }, 19 | "beatId": "", 20 | "biz_source": "live", 21 | "blind_gift": null, 22 | "broadcast_id": 0, 23 | "coin_type": "gold", 24 | "combo_resources_id": 1, 25 | "combo_send": { 26 | "action": "投喂", 27 | "combo_id": "gift:combo_id:477844045:8739477:31164:1713627768.7306", 28 | "combo_num": 1, 29 | "gift_id": 31164, 30 | "gift_name": "粉丝团灯牌", 31 | "gift_num": 1, 32 | "send_master": null, 33 | "uid": 477844045, 34 | "uname": "wasp111" 35 | }, 36 | "combo_stay_time": 5, 37 | "combo_total_coin": 1000, 38 | "crit_prob": 0, 39 | "demarcation": 2, 40 | "discount_price": 1000, 41 | "dmscore": 14, 42 | "draw": 0, 43 | "effect": 3, 44 | "effect_block": 0, 45 | "face": "https://i1.hdslb.com/bfs/face/56d3445ad8f437bf975182de1e45232e8b8968e7.jpg", 46 | "face_effect_id": 0, 47 | "face_effect_type": 0, 48 | "float_sc_resource_id": 0, 49 | "giftId": 31164, 50 | "giftName": "粉丝团灯牌", 51 | "giftType": 0, 52 | "gift_tag": [], 53 | "gold": 0, 54 | "group_medal": null, 55 | "guard_level": 0, 56 | "is_first": true, 57 | "is_join_receiver": false, 58 | "is_naming": false, 59 | "is_special_batch": 0, 60 | "magnification": 1, 61 | "medal_info": { 62 | "anchor_roomid": 0, 63 | "anchor_uname": "", 64 | "guard_level": 0, 65 | "icon_id": 0, 66 | "is_lighted": 0, 67 | "medal_color": 0, 68 | "medal_color_border": 0, 69 | "medal_color_end": 0, 70 | "medal_color_start": 0, 71 | "medal_level": 0, 72 | "medal_name": "", 73 | "special": "", 74 | "target_id": 0 75 | }, 76 | "name_color": "", 77 | "num": 1, 78 | "original_gift_name": "", 79 | "price": 1000, 80 | "rcost": 315105790, 81 | "receive_user_info": { 82 | "uid": 8739477, 83 | "uname": "老实憨厚的笑笑" 84 | }, 85 | "receiver_uinfo": { 86 | "base": { 87 | "face": "https://i0.hdslb.com/bfs/face/59da329a536b148b0e8d19143b686d2da06dad93.jpg", 88 | "is_mystery": false, 89 | "name": "老实憨厚的笑笑", 90 | "name_color": 0, 91 | "name_color_str": "", 92 | "official_info": { 93 | "desc": "", 94 | "role": 2, 95 | "title": "bilibili 2022百大UP主、2022直播年度弹幕人气奖UP主、职业游戏解说孙亚龙", 96 | "type": 0 97 | }, 98 | "origin_info": { 99 | "face": "https://i0.hdslb.com/bfs/face/59da329a536b148b0e8d19143b686d2da06dad93.jpg", 100 | "name": "老实憨厚的笑笑" 101 | }, 102 | "risk_ctrl_info": { 103 | "face": "https://i0.hdslb.com/bfs/face/59da329a536b148b0e8d19143b686d2da06dad93.jpg", 104 | "name": "老实憨厚的笑笑" 105 | } 106 | }, 107 | "guard": null, 108 | "guard_leader": null, 109 | "medal": null, 110 | "title": null, 111 | "uhead_frame": null, 112 | "uid": 8739477, 113 | "wealth": null 114 | }, 115 | "remain": 0, 116 | "rnd": "4499160014027534848", 117 | "send_master": null, 118 | "sender_uinfo": { 119 | "base": { 120 | "face": "https://i1.hdslb.com/bfs/face/56d3445ad8f437bf975182de1e45232e8b8968e7.jpg", 121 | "is_mystery": false, 122 | "name": "wasp111", 123 | "name_color": 0, 124 | "name_color_str": "", 125 | "official_info": { 126 | "desc": "", 127 | "role": 0, 128 | "title": "", 129 | "type": -1 130 | }, 131 | "origin_info": { 132 | "face": "https://i1.hdslb.com/bfs/face/56d3445ad8f437bf975182de1e45232e8b8968e7.jpg", 133 | "name": "wasp111" 134 | }, 135 | "risk_ctrl_info": { 136 | "face": "https://i1.hdslb.com/bfs/face/56d3445ad8f437bf975182de1e45232e8b8968e7.jpg", 137 | "name": "wasp111" 138 | } 139 | }, 140 | "guard": null, 141 | "guard_leader": null, 142 | "medal": null, 143 | "title": null, 144 | "uhead_frame": null, 145 | "uid": 477844045, 146 | "wealth": null 147 | }, 148 | "silver": 0, 149 | "super": 0, 150 | "super_batch_gift_num": 1, 151 | "super_gift_num": 1, 152 | "svga_block": 0, 153 | "switch": true, 154 | "tag_image": "", 155 | "tid": "4499160014027534848", 156 | "timestamp": 1713627768, 157 | "top_list": null, 158 | "total_coin": 1000, 159 | "uid": 477844045, 160 | "uname": "wasp111", 161 | "wealth_level": 0 162 | }, 163 | "msg_id": "13145938966630401:100:1000", 164 | "p_is_ack": true, 165 | "p_msg_type": 1, 166 | "send_time": 1713627768715 167 | } 168 | -------------------------------------------------------------------------------- /reference/SEND_GIFT/辣条.jsonc: -------------------------------------------------------------------------------- 1 | { 2 | "cmd": "SEND_GIFT", 3 | "data": { 4 | "action": "投喂", 5 | "bag_gift": null, 6 | "batch_combo_id": "", 7 | "batch_combo_send": null, 8 | "beatId": "", 9 | "biz_source": "live", 10 | "blind_gift": null, 11 | "broadcast_id": 0, 12 | "coin_type": "silver", 13 | "combo_resources_id": 1, 14 | "combo_send": null, 15 | "combo_stay_time": 5, 16 | "combo_total_coin": 0, 17 | "crit_prob": 0, 18 | "demarcation": 1, 19 | "discount_price": 0, 20 | "dmscore": 50, 21 | "draw": 0, 22 | "effect": 0, 23 | "effect_block": 1, 24 | "face": "http://i2.hdslb.com/bfs/face/64167b0c938adafa6f45bc2185df2f4fb8b58583.jpg", 25 | "face_effect_id": 0, 26 | "face_effect_type": 0, 27 | "float_sc_resource_id": 0, 28 | "giftId": 1, 29 | "giftName": "辣条", 30 | "giftType": 5, 31 | "gift_tag": [], 32 | "gold": 0, 33 | "group_medal": null, 34 | "guard_level": 0, 35 | "is_first": true, 36 | "is_join_receiver": false, 37 | "is_naming": false, 38 | "is_special_batch": 0, 39 | "magnification": 1, 40 | "medal_info": { 41 | "anchor_roomid": 0, 42 | "anchor_uname": "", 43 | "guard_level": 0, 44 | "icon_id": 0, 45 | "is_lighted": 1, 46 | "medal_color": 13081892, 47 | "medal_color_border": 13081892, 48 | "medal_color_end": 13081892, 49 | "medal_color_start": 13081892, 50 | "medal_level": 19, 51 | "medal_name": "德云色", 52 | "special": "", 53 | "target_id": 8739477 54 | }, 55 | "name_color": "", 56 | "num": 1, 57 | "original_gift_name": "", 58 | "price": 100, 59 | "rcost": 315105719, 60 | "receive_user_info": { 61 | "uid": 8739477, 62 | "uname": "老实憨厚的笑笑" 63 | }, 64 | "receiver_uinfo": { 65 | "base": { 66 | "face": "https://i0.hdslb.com/bfs/face/59da329a536b148b0e8d19143b686d2da06dad93.jpg", 67 | "is_mystery": false, 68 | "name": "老实憨厚的笑笑", 69 | "name_color": 0, 70 | "name_color_str": "", 71 | "official_info": { 72 | "desc": "", 73 | "role": 2, 74 | "title": "bilibili 2022百大UP主、2022直播年度弹幕人气奖UP主、职业游戏解说孙亚龙", 75 | "type": 0 76 | }, 77 | "origin_info": { 78 | "face": "https://i0.hdslb.com/bfs/face/59da329a536b148b0e8d19143b686d2da06dad93.jpg", 79 | "name": "老实憨厚的笑笑" 80 | }, 81 | "risk_ctrl_info": { 82 | "face": "https://i0.hdslb.com/bfs/face/59da329a536b148b0e8d19143b686d2da06dad93.jpg", 83 | "name": "老实憨厚的笑笑" 84 | } 85 | }, 86 | "guard": null, 87 | "guard_leader": null, 88 | "medal": null, 89 | "title": null, 90 | "uhead_frame": null, 91 | "uid": 8739477, 92 | "wealth": null 93 | }, 94 | "remain": 0, 95 | "rnd": "4499158310355815936", 96 | "send_master": null, 97 | "sender_uinfo": { 98 | "base": { 99 | "face": "http://i2.hdslb.com/bfs/face/64167b0c938adafa6f45bc2185df2f4fb8b58583.jpg", 100 | "is_mystery": false, 101 | "name": "冰川和企鹅", 102 | "name_color": 0, 103 | "name_color_str": "", 104 | "official_info": { 105 | "desc": "", 106 | "role": 0, 107 | "title": "", 108 | "type": -1 109 | }, 110 | "origin_info": { 111 | "face": "http://i2.hdslb.com/bfs/face/64167b0c938adafa6f45bc2185df2f4fb8b58583.jpg", 112 | "name": "冰川和企鹅" 113 | }, 114 | "risk_ctrl_info": { 115 | "face": "http://i2.hdslb.com/bfs/face/64167b0c938adafa6f45bc2185df2f4fb8b58583.jpg", 116 | "name": "冰川和企鹅" 117 | } 118 | }, 119 | "guard": null, 120 | "guard_leader": null, 121 | "medal": null, 122 | "title": null, 123 | "uhead_frame": null, 124 | "uid": 9639461, 125 | "wealth": null 126 | }, 127 | "silver": 0, 128 | "super": 0, 129 | "super_batch_gift_num": 0, 130 | "super_gift_num": 0, 131 | "svga_block": 0, 132 | "switch": true, 133 | "tag_image": "", 134 | "tid": "4499158310355815936", 135 | "timestamp": 1713627362, 136 | "top_list": null, 137 | "total_coin": 100, 138 | "uid": 9639461, 139 | "uname": "冰川和企鹅", 140 | "wealth_level": 4 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /reference/SUPER_CHAT_MESSAGE/normal.jsonc: -------------------------------------------------------------------------------- 1 | { 2 | "cmd": "SUPER_CHAT_MESSAGE", 3 | "data": { 4 | "background_bottom_color": "#427D9E", // 主色调 sc文字下面的背景色 5 | "background_color": "#DBFFFD", // sc文字上面的背景色 6 | "background_color_end": "#29718B", 7 | "background_color_start": "#4EA4C5", 8 | "background_icon": "", 9 | "background_image": "https://i0.hdslb.com/bfs/live/a712efa5c6ebc67bafbe8352d3e74b820a00c13e.png", 10 | "background_price_color": "#7DA4BD", 11 | "color_point": 0.7, 12 | "dmscore": 120, 13 | "end_time": 1664465187, 14 | "gift": { 15 | "gift_id": 12000, 16 | "gift_name": "醒目留言", 17 | "num": 1 18 | }, 19 | "id": 5179121, 20 | "is_ranked": 0, 21 | "is_send_audit": 1, 22 | "medal_info": { 23 | "anchor_roomid": 7777, 24 | "anchor_uname": "老实憨厚的笑笑", 25 | "guard_level": 3, 26 | "icon_id": 0, 27 | "is_lighted": 1, 28 | "medal_color": "#2d0855", // original 29 | "medal_color_border": 6809855, 30 | "medal_color_end": 10329087, 31 | "medal_color_start": 2951253, 32 | "medal_level": 30, 33 | "medal_name": "德云色", 34 | "special": "", 35 | "target_id": 8739477 36 | }, 37 | "message": "可以喊车神和光神拍sdm和lsl吗我想看", 38 | "message_font_color": "#A3F6FF", 39 | "message_trans": "", 40 | "price": 50, 41 | "rate": 1000, 42 | "start_time": 1664465067, 43 | "time": 120, 44 | "token": "35647666", 45 | "trans_mark": 0, 46 | "ts": 1664465067, 47 | "uid": 12777723, 48 | "user_info": { 49 | "face": "http://i0.hdslb.com/bfs/face/fefbebbe3b9bb409475d5a40d9db16f2f0305f85.jpg", 50 | "face_frame": "https://i0.hdslb.com/bfs/live/80f732943cc3367029df65e267960d56736a82ee.png", 51 | "guard_level": 3, 52 | "is_main_vip": 1, 53 | "is_svip": 0, 54 | "is_vip": 0, 55 | "level_color": "#5896de", 56 | "manager": 0, 57 | "name_color": "#00D1F1", 58 | "title": "title-635-1", 59 | "uname": "卡纸哥我宣你", 60 | "user_level": 30 61 | } 62 | }, 63 | "roomid": 545068 64 | } 65 | -------------------------------------------------------------------------------- /reference/WATCHED_CHANGE/normal.jsonc: -------------------------------------------------------------------------------- 1 | { 2 | "cmd": "WATCHED_CHANGE", 3 | "data": { 4 | "num": 169882, 5 | "text_small": "16.9万", 6 | "text_large": "16.9万人看过" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/BiliLive.ts: -------------------------------------------------------------------------------- 1 | import { KeepLiveTCP } from 'bilibili-live-ws' 2 | import { KeepLiveWS } from 'bilibili-live-ws/browser' 3 | 4 | import type { Message } from './types/message' 5 | 6 | // common events 7 | import { CloseEvent, ErrorEvent, HeartbeatEvent, LiveEvent, OpenEvent } from './events/common' 8 | 9 | // events 10 | import { type DanmuData, DanmuEvent } from './events/Danmu' 11 | import { type GuardBuyData, GuardBuyEvent } from './events/GuardBuy' 12 | import { type SuperChatData, SuperChatEvent } from './events/SuperChat' 13 | import { type GiftData, GiftEvent } from './events/Gift' 14 | import { type WatchedChangeData, WatchedChangeEvent } from './events/WatchedChange' 15 | import { type RankCountUpdateData, RankCountUpdateEvent } from './events/RankCountUpdate' 16 | import { type LikeCountUpdateData, LikeCountUpdateEvent } from './events/LikeCountUpdate' 17 | import { type NoticeData, NoticeEvent } from './events/Notice' 18 | import { type HotRankUpdateData, HotRankUpdateEvent } from './events/HotRankUpdate' 19 | import { type FansCountUpdateData, FansCountUpdateEvent } from './events/FansCountUpdate' 20 | import { type LiveStartData, LiveStartEvent } from './events/LiveStart' 21 | import { type LiveEndData, LiveEndEvent } from './events/LiveEnd' 22 | import { type InteractData, InteractEvent } from './events/Interact' 23 | import { type EntryEffectData, EntryEffectEvent } from './events/EntryEffect' 24 | import { type RoomChangeData, RoomChangeEvent } from './events/RoomChange' 25 | import { type AnchorLotStartData, AnchorLotStartEvent } from './events/AnchorLotStart' 26 | import { type AnchorLotEndData, AnchorLotEndEvent } from './events/AnchorLotEnd' 27 | import { type RedPocketStartData, RedPocketStartEvent } from './events/RedPocketStart' 28 | import { type RedPocketEndData, RedPocketEndEvent } from './events/RedPocketEnd' 29 | import { type PopularRankUpdateData, PopularRankUpdateEvent } from './events/PopularRankUpdate' 30 | import { type DanmuInteractData, DanmuInteractEvent } from './events/DanmuInteract' 31 | 32 | const commonEvents = [ 33 | OpenEvent, 34 | LiveEvent, 35 | HeartbeatEvent, 36 | CloseEvent, 37 | ErrorEvent, 38 | ] 39 | 40 | const events = [ 41 | DanmuEvent, 42 | GuardBuyEvent, 43 | SuperChatEvent, 44 | GiftEvent, 45 | WatchedChangeEvent, 46 | RankCountUpdateEvent, 47 | LikeCountUpdateEvent, 48 | NoticeEvent, 49 | HotRankUpdateEvent, 50 | FansCountUpdateEvent, 51 | LiveStartEvent, 52 | LiveEndEvent, 53 | InteractEvent, 54 | EntryEffectEvent, 55 | RoomChangeEvent, 56 | AnchorLotStartEvent, 57 | AnchorLotEndEvent, 58 | RedPocketStartEvent, 59 | RedPocketEndEvent, 60 | PopularRankUpdateEvent, 61 | DanmuInteractEvent, 62 | ] 63 | 64 | export interface BiliLiveOptions { 65 | /** 登录状态下获取的key */ 66 | key: string 67 | /** 登录状态下的uid */ 68 | uid: number 69 | /** 是否在浏览器环境下 */ 70 | isBrowser?: boolean 71 | } 72 | 73 | /** 移除监听器 */ 74 | export type RemoveHandler = () => void 75 | 76 | export default class BiliLive { 77 | private live: KeepLiveTCP | KeepLiveWS 78 | private handlers: Record = {} 79 | private handlerIdCounter = 0 80 | 81 | // common events handler 82 | public onOpen!: (callback: () => void) => RemoveHandler 83 | public onLive!: (callback: () => void) => RemoveHandler 84 | public onHeartbeat!: (callback: () => void) => RemoveHandler 85 | public onClose!: (callback: () => void) => RemoveHandler 86 | public onError!: (callback: (error: any) => void) => RemoveHandler 87 | 88 | // event handler 89 | public onDanmu!: (callback: (message: Message) => void) => RemoveHandler 90 | public onGuardBuy!: (callback: (message: Message) => void) => RemoveHandler 91 | public onSuperChat!: (callback: (message: Message) => void) => RemoveHandler 92 | public onGift!: (callback: (message: Message) => void) => RemoveHandler 93 | public onWatchedChange!: (callback: (message: Message) => void) => RemoveHandler 94 | public onRankCountChange!: (callback: (message: Message) => void) => RemoveHandler 95 | public onLikeCountChange!: (callback: (message: Message) => void) => RemoveHandler 96 | public onNotice!: (callback: (message: Message) => void) => RemoveHandler 97 | public onHotRankUpdate!: (callback: (message: Message) => void) => RemoveHandler 98 | public onFansCountUpdate!: (callback: (message: Message) => void) => RemoveHandler 99 | public onLiveStart!: (callback: (message: Message) => void) => RemoveHandler 100 | public onLiveEnd!: (callback: (message: Message) => void) => RemoveHandler 101 | public onInteract!: (callback: (message: Message) => void) => RemoveHandler 102 | public onEntryEffect!: (callback: (message: Message) => void) => RemoveHandler 103 | public onRoomChange!: (callback: (message: Message) => void) => RemoveHandler 104 | public onAnchorLotStart!: (callback: (message: Message) => void) => RemoveHandler 105 | public onAnchorLotEnd!: (callback: (message: Message) => void) => RemoveHandler 106 | public onRedPocketStart!: (callback: (message: Message) => void) => RemoveHandler 107 | public onRedPocketEnd!: (callback: (message: Message) => void) => RemoveHandler 108 | public onPopularRankUpdate!: (callback: (message: Message) => void) => RemoveHandler 109 | public onDanmuInteract!: (callback: (message: Message) => void) => RemoveHandler 110 | 111 | constructor(roomId: number, options: BiliLiveOptions) { 112 | const { key, uid, isBrowser } = options 113 | 114 | if (isBrowser) 115 | this.live = new KeepLiveWS(roomId, { key, uid }) 116 | else 117 | this.live = new KeepLiveTCP(roomId, { key, uid }) 118 | 119 | this.initEvents() 120 | } 121 | 122 | private initEvents() { 123 | [...commonEvents.map(item => ({ ...item, dataProcessor: undefined })), ...events].forEach((event) => { 124 | const { cmdName, handlerName, dataProcessor } = event 125 | const cmdNames = Array.isArray(cmdName) ? cmdName : [cmdName] 126 | cmdNames.forEach((cmdName) => { 127 | this.live.on(cmdName, (data) => { 128 | if (this.handlers[handlerName]) { 129 | this.handlers[handlerName].forEach(({ callback }) => { 130 | if (!dataProcessor) { 131 | callback(data) 132 | } 133 | else { 134 | const message = dataProcessor(data) 135 | // no message means the data is not should be processed and returned 136 | if (message) 137 | callback(message) 138 | } 139 | }) 140 | } 141 | }) 142 | }) 143 | // eslint-disable-next-line ts/ban-ts-comment 144 | // @ts-expect-error 145 | this[handlerName] = function (callback: Function): RemoveHandler { 146 | return this.addHandler(handlerName, callback) 147 | } 148 | }) 149 | } 150 | 151 | private addHandler(handlerName: string, callback: Function): RemoveHandler { 152 | const id = this.handlerIdCounter++ 153 | if (!this.handlers[handlerName]) 154 | this.handlers[handlerName] = [] 155 | this.handlers[handlerName].push({ id, callback }) 156 | return () => this.removeHandler(handlerName, id) 157 | } 158 | 159 | private removeHandler(handlerName: string, id: number) { 160 | if (this.handlers[handlerName]) 161 | this.handlers[handlerName] = this.handlers[handlerName].filter(handler => handler.id !== id) 162 | } 163 | 164 | public close() { 165 | this.live.close() 166 | } 167 | 168 | public onRawMessage: (...args: Parameters) => void = (...args) => { 169 | this.live.on(...args) 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /src/events/AnchorLotEnd.ts: -------------------------------------------------------------------------------- 1 | import type { EventInfo } from '../types/event' 2 | import type { AnchorLotAward, Message } from '../types/message' 3 | import { normalizeMessage } from '../utils/message' 4 | 5 | export interface AnchorLotEndData { 6 | /** 天选时刻Id */ 7 | id: number 8 | /** 奖品 */ 9 | award: AnchorLotAward 10 | /** 中奖用户信息 */ 11 | winners: { 12 | /** 用户Id */ 13 | uid: number 14 | /** 用户名 */ 15 | uname: string 16 | /** 用户头像 */ 17 | face: string 18 | /** 直播等级 */ 19 | level: number 20 | /** 中奖数量 */ 21 | num: number 22 | }[] 23 | } 24 | 25 | function dataProcessor(rawData: any): Message { 26 | const { data } = rawData 27 | const newData: AnchorLotEndData = { 28 | id: data.id, 29 | award: { 30 | name: data.award_name, 31 | num: data.award_num, 32 | type: data.award_type, 33 | image: data.award_image, 34 | priceText: data.award_price_text, 35 | }, 36 | winners: data.award_users.map((user: any) => ({ 37 | uid: user.uid, 38 | uname: user.uname, 39 | face: user.face, 40 | level: user.level, 41 | num: user.num, 42 | })), 43 | } 44 | return normalizeMessage(rawData.cmd, newData, rawData) 45 | } 46 | 47 | export const AnchorLotEndEvent: EventInfo = { 48 | cmdName: 'ANCHOR_LOT_AWARD', 49 | handlerName: 'onAnchorLotEnd', 50 | dataProcessor, 51 | } 52 | -------------------------------------------------------------------------------- /src/events/AnchorLotStart.ts: -------------------------------------------------------------------------------- 1 | import type { EventInfo } from '../types/event' 2 | import type { AnchorLotAward, AnchorLotUserType, Message } from '../types/message' 3 | import { normalizeMessage } from '../utils/message' 4 | 5 | export interface AnchorLotStartData { 6 | /** 天选时刻Id */ 7 | id: number 8 | /** 奖品 */ 9 | award: AnchorLotAward 10 | /** 弹幕口令 */ 11 | danmu: string 12 | /** 需要赠送的礼物信息,用于参与天选时刻 */ 13 | gift?: { 14 | /** 礼物Id */ 15 | id: number 16 | /** 礼物名称 */ 17 | name: string 18 | /** 礼物数量 */ 19 | num: number 20 | /** 礼物价格 */ 21 | price: number 22 | } 23 | /** 天选时刻参与用户要求 */ 24 | require: { 25 | /** 参与用户要求描述 */ 26 | text: string 27 | /** 参与的用户类型 */ 28 | userType: AnchorLotUserType 29 | /** 用户类型对应等级 粉丝勋章等级或大航海类型 */ 30 | value: number 31 | } 32 | /** 天选时刻开始时间 */ 33 | startTime: number 34 | /** 天选时刻持续时间,单位:秒 */ 35 | duration: number 36 | } 37 | 38 | function dataProcessor(rawData: any): Message { 39 | const { data } = rawData 40 | const newData: AnchorLotStartData = { 41 | id: data.id, 42 | award: { 43 | name: data.award_name, 44 | num: data.award_num, 45 | type: data.award_type, 46 | image: data.award_image, 47 | priceText: data.award_price_text, 48 | }, 49 | danmu: data.danmu, 50 | gift: data.gift_id 51 | ? { 52 | id: data.gift_id, 53 | name: data.gift_name, 54 | num: data.gift_num, 55 | price: data.gift_price, 56 | } 57 | : undefined, 58 | require: { 59 | text: data.require_text, 60 | userType: data.require_type, 61 | value: data.require_value, 62 | }, 63 | startTime: data.current_time, 64 | duration: data.max_time, 65 | } 66 | return normalizeMessage(rawData.cmd, newData, rawData) 67 | } 68 | 69 | export const AnchorLotStartEvent: EventInfo = { 70 | cmdName: 'ANCHOR_LOT_START', 71 | handlerName: 'onAnchorLotStart', 72 | dataProcessor, 73 | } 74 | -------------------------------------------------------------------------------- /src/events/Danmu.ts: -------------------------------------------------------------------------------- 1 | import type { Message, User } from '../types/message' 2 | import { int2ColorHex } from '../utils/color' 3 | import { normalizeMessage } from '../utils/message' 4 | import type { EventInfo } from '../types/event' 5 | 6 | export interface DanmuData { 7 | /** 用户信息 */ 8 | user: User 9 | /** 弹幕内容 */ 10 | content: string 11 | /** 时间戳 */ 12 | timestamp: number 13 | /** 是否为抽奖弹幕 */ 14 | isLottery: boolean 15 | // 弹幕表情 16 | emoticon?: { 17 | id: number 18 | url: string 19 | } 20 | } 21 | 22 | function dataProcessor(rawData: any): Message { 23 | const { info } = rawData 24 | 25 | const newData: DanmuData = { 26 | user: { 27 | uid: info[0][15].user.uid, 28 | uname: info[0][15].user.base.name, 29 | face: info[0][15].user.base.face, 30 | fansMedal: info[3].length > 0 31 | ? { 32 | name: info[3][1], 33 | level: info[3][0], 34 | guardType: info[3][10], 35 | color: { 36 | original: int2ColorHex(info[3][4]), 37 | border: int2ColorHex(info[3][7]), 38 | start: int2ColorHex(info[3][8]), 39 | end: int2ColorHex(info[3][9]), 40 | }, 41 | isLighted: info[3][11], 42 | anchor: { 43 | uid: info[3][12], 44 | uname: info[3][2], 45 | roomId: info[3][3], 46 | }, 47 | } 48 | : undefined, 49 | giftRank: info[4][4], 50 | guardType: info[7], 51 | isRoomAdmin: info[2][2] === 1, 52 | }, 53 | content: info[1], 54 | timestamp: info[0][4], 55 | isLottery: info[0][9] !== 0, 56 | emoticon: info[0][13]?.emoticon_unique 57 | ? { 58 | id: info[0][13].emoticon_unique.id, 59 | url: info[0][13].url, 60 | } 61 | : undefined, 62 | } 63 | 64 | return normalizeMessage(rawData.cmd, newData, rawData) 65 | } 66 | 67 | export const DanmuEvent: EventInfo = { 68 | cmdName: 'DANMU_MSG', 69 | handlerName: 'onDanmu', 70 | dataProcessor, 71 | } 72 | -------------------------------------------------------------------------------- /src/events/DanmuInteract.ts: -------------------------------------------------------------------------------- 1 | import type { EventInfo } from '../types/event' 2 | import type { Message } from '../types/message' 3 | import { normalizeMessage } from '../utils/message' 4 | 5 | export interface DanmuInteractData { 6 | /** 弹幕互动类型;Vote:弹幕投票 Combo:弹幕连击 */ 7 | type: 'Vote' | 'Combo' 8 | /** 弹幕互动Id */ 9 | id: number 10 | /** 弹幕互动状态:进行中或结束;进行中表示该id的弹幕互动仍会更新 */ 11 | status: 'Ongoing' | 'Ended' 12 | /** 弹幕投票 */ 13 | vote?: { 14 | /** 投票Id */ 15 | id: number 16 | /** 投票问题 */ 17 | question: string 18 | /** 投票选项 */ 19 | options: { 20 | /** 选项序号 */ 21 | id: number 22 | /** 选项描述 */ 23 | description: string 24 | /** 投票人数 */ 25 | count: number 26 | /** 投票比例 */ 27 | percent: number 28 | }[] 29 | /** 投票总数 */ 30 | count: number 31 | } 32 | /** 弹幕连击 */ 33 | combo?: { 34 | /** Id */ 35 | id: number 36 | /** 内容 */ 37 | content: string 38 | /** 弹幕前缀图标 */ 39 | prefixIcon: string 40 | /** 引导;比如“他们都在说:” */ 41 | guide: string 42 | /** 状态:进行中或结束;进行中表示该id的`弹幕连击`仍会更新 */ 43 | status: 'Ongoing' | 'Ended' 44 | /** `弹幕连击`数量 */ 45 | count: number 46 | }[] 47 | } 48 | 49 | function dataProcessorVote(rawData: any): Message { 50 | const { data: { data } } = rawData 51 | const newData: DanmuInteractData = { 52 | type: 'Vote', 53 | id: rawData.data.id, 54 | status: rawData.data.status === 5 ? 'Ended' : 'Ongoing', 55 | vote: { 56 | id: data.vote_id, 57 | question: data.question, 58 | options: data.options.map((option: any) => ({ 59 | id: option.idx, 60 | description: option.desc, 61 | count: option.cnt, 62 | percent: option.percent, 63 | })), 64 | count: data.cnt, 65 | }, 66 | combo: data.combo 67 | ? data.combo.map((c: any) => ({ 68 | id: c.id, 69 | content: c.content, 70 | prefixIcon: c.prefix_icon, 71 | guide: c.guide, 72 | status: c.status === 5 ? 'Ended' : 'Ongoing', 73 | count: c.cnt, 74 | })) 75 | : undefined, 76 | } 77 | return normalizeMessage(rawData.cmd, newData, rawData) 78 | } 79 | 80 | function dataProcessorCombo(rawData: any): Message { 81 | const { data: { data } } = rawData 82 | const newData: DanmuInteractData = { 83 | type: 'Combo', 84 | id: rawData.data.id, 85 | status: rawData.data.status === 5 ? 'Ended' : 'Ongoing', 86 | combo: data.combo.map((c: any) => ({ 87 | id: c.id, 88 | content: c.content, 89 | prefixIcon: c.prefix_icon, 90 | guide: c.guide, 91 | status: c.status === 5 ? 'Ended' : 'Ongoing', 92 | count: c.cnt, 93 | })), 94 | } 95 | return normalizeMessage(rawData.cmd, newData, rawData) 96 | } 97 | 98 | function dataProcessor(rawData: any): Message | undefined { 99 | switch (rawData.data.type) { 100 | case 101: 101 | return dataProcessorVote(rawData) 102 | case 102: 103 | return dataProcessorCombo(rawData) 104 | default: 105 | return undefined 106 | } 107 | } 108 | 109 | export const DanmuInteractEvent: EventInfo = { 110 | cmdName: 'DM_INTERACTION', 111 | handlerName: 'onDanmuInteract', 112 | dataProcessor, 113 | } 114 | -------------------------------------------------------------------------------- /src/events/EntryEffect.ts: -------------------------------------------------------------------------------- 1 | import type { EventInfo } from '../types/event' 2 | import type { Message, User } from '../types/message' 3 | import { normalizeMessage } from '../utils/message' 4 | 5 | export interface EntryEffectData { 6 | /** 用户信息 */ 7 | user: User 8 | /** 入场特效文本内容 */ 9 | content: string 10 | /** 时间戳 */ 11 | timestamp: number 12 | } 13 | 14 | function dataProcessor(rawData: any): Message { 15 | const { data } = rawData 16 | const { medal } = data.uinfo 17 | const newData: EntryEffectData = { 18 | user: { 19 | uid: data.uid, 20 | uname: data.uinfo.base.name, 21 | face: data.face, 22 | fansMedal: medal && medal.name 23 | ? { 24 | name: medal.name, 25 | level: medal.level, 26 | guardType: medal.guard_level, 27 | color: { 28 | original: medal.color, 29 | border: medal.color_border, 30 | start: medal.color_start, 31 | end: medal.color_end, 32 | }, 33 | isLighted: medal.is_light, 34 | anchor: { 35 | uid: medal.ruid, 36 | uname: '', 37 | roomId: 0, 38 | }, 39 | } 40 | : undefined, 41 | guardType: data.uinfo.guard.level, 42 | }, 43 | content: data.copy_writting, 44 | timestamp: Math.round(data.trigger_time / 1000), 45 | } 46 | return normalizeMessage(rawData.cmd, newData, rawData) 47 | } 48 | 49 | export const EntryEffectEvent: EventInfo = { 50 | cmdName: 'ENTRY_EFFECT', 51 | handlerName: 'onEntryEffect', 52 | dataProcessor, 53 | } 54 | -------------------------------------------------------------------------------- /src/events/FansCountUpdate.ts: -------------------------------------------------------------------------------- 1 | import type { EventInfo } from '../types/event' 2 | import type { Message } from '../types/message' 3 | import { normalizeMessage } from '../utils/message' 4 | 5 | export interface FansCountUpdateData { 6 | /** 粉丝数量 */ 7 | fansCount: number 8 | /** 粉丝团数量 */ 9 | fansClubCount: number 10 | } 11 | 12 | function dataProcessor(rawData: any): Message { 13 | const { data } = rawData 14 | const newData: FansCountUpdateData = { 15 | fansCount: data.fans, 16 | fansClubCount: data.fans_club, 17 | } 18 | return normalizeMessage(rawData.cmd, newData, rawData) 19 | } 20 | 21 | export const FansCountUpdateEvent: EventInfo = { 22 | cmdName: 'ROOM_REAL_TIME_MESSAGE_UPDATE', 23 | handlerName: 'onFansCountUpdate', 24 | dataProcessor, 25 | } 26 | -------------------------------------------------------------------------------- /src/events/Gift.ts: -------------------------------------------------------------------------------- 1 | import type { EventInfo } from '../types/event' 2 | import type { CoinType, Message, User } from '../types/message' 3 | 4 | import { normalizeMessage } from '../utils/message' 5 | 6 | export interface GiftData { 7 | /** 用户信息 */ 8 | user: User 9 | /** 礼物Id */ 10 | giftId: number 11 | /** 礼物名称 */ 12 | giftName: string 13 | /** 礼物瓜子类型;1金瓜子=1/1000元=1/100电池;银瓜子无金钱价值 */ 14 | coinType: CoinType 15 | /** 礼物价格(单价);单位:1金瓜子=1/1000元=1/100电池 */ 16 | price: number 17 | /** 礼物数量 */ 18 | num: number 19 | /** 赠送动作(例如:投喂) */ 20 | action: string 21 | /** 礼物连击 */ 22 | combo?: { 23 | /** 连击Id */ 24 | id: number 25 | /** 连击次数 */ 26 | num: number 27 | /** 连击总价;单位:1金瓜子=1/1000元=1/100电池 */ 28 | totalPrice: number 29 | } 30 | /** 时间戳 */ 31 | timestamp: number 32 | } 33 | 34 | function dataProcessorGift(rawData: any): Message { 35 | const { data } = rawData 36 | const { medal_info } = data 37 | const newData: GiftData = { 38 | user: { 39 | uid: data.uid, 40 | uname: data.uname, 41 | face: data.face, 42 | fansMedal: medal_info && medal_info.medal_name 43 | ? { 44 | name: medal_info.medal_name, 45 | level: medal_info.medal_level, 46 | guardType: medal_info.guard_level, 47 | color: { 48 | original: medal_info.medal_color, 49 | border: medal_info.medal_color_border, 50 | start: medal_info.medal_color_start, 51 | end: medal_info.medal_color_end, 52 | }, 53 | isLighted: medal_info.is_lighted, 54 | anchor: { 55 | uid: medal_info.target_id, 56 | uname: medal_info.anchor_uname, // "" 57 | roomId: medal_info.anchor_roomid, // 0 58 | }, 59 | } 60 | : undefined, 61 | }, 62 | giftId: data.giftId, 63 | giftName: data.giftName, 64 | coinType: data.coin_type, 65 | price: data.price, 66 | num: data.num, 67 | action: data.action, 68 | combo: data.batch_combo_id 69 | ? { 70 | id: data.batch_combo_id, 71 | num: data.batch_combo_num, 72 | totalPrice: data.combo_total_coin, 73 | } 74 | : undefined, 75 | timestamp: data.timestamp, 76 | } 77 | return normalizeMessage(rawData.cmd, newData, rawData) 78 | } 79 | 80 | function dataProcessorRedPocket(rawData: any): Message { 81 | const { data } = rawData 82 | const { medal_info } = data 83 | const newData: GiftData = { 84 | user: { 85 | uid: data.uid, 86 | uname: data.uname, 87 | fansMedal: medal_info && medal_info.medal_name 88 | ? { 89 | name: medal_info.medal_name, 90 | // 没有 face 字段 91 | level: medal_info.medal_level, 92 | guardType: medal_info.guard_level, 93 | color: { 94 | original: medal_info.medal_color, 95 | border: medal_info.medal_color_border, 96 | start: medal_info.medal_color_start, 97 | end: medal_info.medal_color_end, 98 | }, 99 | isLighted: medal_info.is_lighted, 100 | anchor: { 101 | uid: medal_info.target_id, 102 | uname: medal_info.anchor_uname, // "" 103 | roomId: medal_info.anchor_roomid, // 0 104 | }, 105 | } 106 | : undefined, 107 | }, 108 | giftId: data.gift_id, 109 | giftName: data.gift_name, 110 | coinType: 'gold', 111 | price: data.price * 100, // 原单位是电池,为单位统一,转换为金瓜子 112 | num: data.num, 113 | action: data.action, 114 | combo: data.batch_combo_id 115 | ? { 116 | id: data.batch_combo_id, 117 | num: data.batch_combo_num, 118 | totalPrice: data.combo_total_coin, 119 | } 120 | : undefined, 121 | timestamp: data.current_time, 122 | } 123 | return normalizeMessage(rawData.cmd, newData, rawData) 124 | } 125 | 126 | function dataProcessor(rawData: any): Message { 127 | switch (rawData.cmd) { 128 | case 'POPULARITY_RED_POCKET_NEW': 129 | return dataProcessorRedPocket(rawData) 130 | default: 131 | return dataProcessorGift(rawData) 132 | } 133 | } 134 | 135 | export const GiftEvent: EventInfo = { 136 | cmdName: ['SEND_GIFT', 'POPULARITY_RED_POCKET_NEW'], 137 | handlerName: 'onGift', 138 | dataProcessor, 139 | } 140 | -------------------------------------------------------------------------------- /src/events/GuardBuy.ts: -------------------------------------------------------------------------------- 1 | import type { EventInfo } from '../types/event' 2 | import type { GuardType, Message, User } from '../types/message' 3 | import { normalizeMessage } from '../utils/message' 4 | 5 | export interface GuardBuyData { 6 | /** 用户信息 */ 7 | user: User 8 | /** 大航海类型 */ 9 | guardType: GuardType 10 | /** 价格;单位:1金瓜子=1/1000元=1/100电池 */ 11 | price: number 12 | /** 数量 */ 13 | num: number 14 | /** 礼物名称 */ 15 | giftName: string 16 | /** 礼物Id */ 17 | giftId: number 18 | /** 开始时间 */ 19 | startTime: number 20 | /** 结束时间 */ 21 | endTime: number 22 | } 23 | 24 | function dataProcessor(rawData: any): Message { 25 | const { data } = rawData 26 | const newData: GuardBuyData = { 27 | user: { 28 | uid: data.uid, 29 | uname: data.username, 30 | }, 31 | guardType: data.guard_level, 32 | price: data.price, 33 | num: data.num, 34 | giftName: data.gift_name, 35 | giftId: data.gift_id, 36 | startTime: data.start_time, 37 | endTime: data.end_time, 38 | } 39 | return normalizeMessage(rawData.cmd, newData, rawData) 40 | } 41 | 42 | export const GuardBuyEvent: EventInfo = { 43 | cmdName: 'GUARD_BUY', 44 | handlerName: 'onGuardBuy', 45 | dataProcessor, 46 | } 47 | -------------------------------------------------------------------------------- /src/events/HotRankUpdate.ts: -------------------------------------------------------------------------------- 1 | import type { EventInfo } from '../types/event' 2 | import type { Message } from '../types/message' 3 | import { normalizeMessage } from '../utils/message' 4 | 5 | export interface HotRankUpdateData { 6 | /** 分区名称;比如“虚拟主播” */ 7 | areaName: string 8 | /** 分区热门排名描述;比如“虚拟主播top50” */ 9 | description: string 10 | /** 分区热门排名 */ 11 | rank: number 12 | /** 结榜倒计时 */ 13 | countdown: number 14 | /** 时间戳 */ 15 | timestamp: number 16 | } 17 | 18 | function dataProcessor(rawData: any): Message { 19 | const { data } = rawData 20 | const newData: HotRankUpdateData = { 21 | areaName: data.area_name, 22 | description: data.rank_desc, 23 | rank: data.rank, 24 | countdown: data.countdown, 25 | timestamp: data.timestamp, 26 | } 27 | return normalizeMessage(rawData.cmd, newData, rawData) 28 | } 29 | 30 | export const HotRankUpdateEvent: EventInfo = { 31 | cmdName: 'HOT_RANK_CHANGED_V2', 32 | handlerName: 'onHotRankUpdate', 33 | dataProcessor, 34 | } 35 | -------------------------------------------------------------------------------- /src/events/Interact.ts: -------------------------------------------------------------------------------- 1 | import type { EventInfo } from '../types/event' 2 | import { InteractType } from '../types/message' 3 | import type { Message, User } from '../types/message' 4 | import { normalizeMessage } from '../utils/message' 5 | 6 | export interface InteractData { 7 | /** 互动类型 */ 8 | type: InteractType 9 | /** 用户信息 */ 10 | user: User 11 | /** 时间戳 */ 12 | timestamp: number 13 | } 14 | 15 | function dataProcessorInteract(rawData: any): Message { 16 | const { data } = rawData 17 | const { fans_medal } = data 18 | const newData: InteractData = { 19 | type: data.msg_type, 20 | user: { 21 | uid: data.uid, 22 | uname: data.uname, 23 | face: data.uinfo.base.face, 24 | fansMedal: fans_medal && fans_medal.medal_name 25 | ? { 26 | name: fans_medal.medal_name, 27 | level: fans_medal.medal_level, 28 | guardType: fans_medal.is_lighted, 29 | color: { 30 | original: fans_medal.medal_color, 31 | border: fans_medal.medal_color_border, 32 | start: fans_medal.medal_color_start, 33 | end: fans_medal.medal_color_end, 34 | }, 35 | isLighted: !!fans_medal.is_lighted, 36 | anchor: { 37 | uid: fans_medal.target_id, 38 | uname: '', 39 | roomId: fans_medal.anchor_roomid, 40 | }, 41 | } 42 | : undefined, 43 | guardType: data.uinfo.guard.level, 44 | }, 45 | timestamp: data.timestamp, 46 | } 47 | return normalizeMessage(rawData.cmd, newData, rawData) 48 | } 49 | 50 | function dataProcessorLike(rawData: any): Message { 51 | const { data } = rawData 52 | const { fans_medal } = data 53 | const newData: InteractData = { 54 | type: InteractType.Like, 55 | user: { 56 | uid: data.uid, 57 | uname: data.uname, 58 | face: data.uinfo.base.face, 59 | fansMedal: fans_medal && fans_medal.medal_name 60 | ? { 61 | name: fans_medal.medal_name, 62 | level: fans_medal.medal_level, 63 | guardType: fans_medal.is_lighted, 64 | color: { 65 | original: fans_medal.medal_color, 66 | border: fans_medal.medal_color_border, 67 | start: fans_medal.medal_color_start, 68 | end: fans_medal.medal_color_end, 69 | }, 70 | isLighted: !!fans_medal.is_lighted, 71 | anchor: { 72 | uid: fans_medal.target_id, 73 | uname: '', 74 | roomId: fans_medal.anchor_roomid, 75 | }, 76 | } 77 | : undefined, 78 | guardType: data.uinfo.guard.level, 79 | }, 80 | timestamp: Date.now(), // 点赞没有时间戳,使用当前时间 81 | } 82 | return normalizeMessage(rawData.cmd, newData, rawData) 83 | } 84 | 85 | function dataProcessor(rawData: any): Message { 86 | switch (rawData.cmd) { 87 | case 'LIKE_INFO_V3_CLICK': 88 | return dataProcessorLike(rawData) 89 | default: 90 | return dataProcessorInteract(rawData) 91 | } 92 | } 93 | 94 | export const InteractEvent: EventInfo = { 95 | cmdName: ['INTERACT_WORD', 'LIKE_INFO_V3_CLICK'], 96 | handlerName: 'onInteract', 97 | dataProcessor, 98 | } 99 | -------------------------------------------------------------------------------- /src/events/LikeCountUpdate.ts: -------------------------------------------------------------------------------- 1 | import type { EventInfo } from '../types/event' 2 | import type { Message } from '../types/message' 3 | import { normalizeMessage } from '../utils/message' 4 | 5 | export interface LikeCountUpdateData { 6 | num: number 7 | } 8 | 9 | function dataProcessor(rawData: any): Message { 10 | const { data } = rawData 11 | const newData: LikeCountUpdateData = { 12 | num: data.click_count, 13 | } 14 | return normalizeMessage(rawData.cmd, newData, rawData) 15 | } 16 | 17 | export const LikeCountUpdateEvent: EventInfo = { 18 | cmdName: 'LIKE_INFO_V3_UPDATE', 19 | handlerName: 'onLikeCountUpdate', 20 | dataProcessor, 21 | } 22 | -------------------------------------------------------------------------------- /src/events/LiveEnd.ts: -------------------------------------------------------------------------------- 1 | import type { EventInfo } from '../types/event' 2 | import type { Message } from '../types/message' 3 | import { normalizeMessage } from '../utils/message' 4 | 5 | export interface LiveEndData { 6 | roomId: number 7 | } 8 | 9 | function dataProcessor(rawData: any): Message { 10 | const newData: LiveEndData = { 11 | roomId: rawData.roomid, 12 | } 13 | return normalizeMessage(rawData.cmd, newData, rawData) 14 | } 15 | 16 | export const LiveEndEvent: EventInfo = { 17 | cmdName: 'PREPARING', 18 | handlerName: 'onLiveEnd', 19 | dataProcessor, 20 | } 21 | -------------------------------------------------------------------------------- /src/events/LiveStart.ts: -------------------------------------------------------------------------------- 1 | import type { EventInfo } from '../types/event' 2 | import type { Message } from '../types/message' 3 | import { normalizeMessage } from '../utils/message' 4 | 5 | export interface LiveStartData { 6 | /** 开播平台 */ 7 | platform: string 8 | /** 开播时间戳 */ 9 | timestamp: number 10 | /** 房间Id */ 11 | roomId: number 12 | } 13 | 14 | function dataProcessor(rawData: any): Message { 15 | const newData: LiveStartData = { 16 | platform: rawData.live_platform, 17 | timestamp: rawData.live_time, 18 | roomId: rawData.roomid, 19 | } 20 | return normalizeMessage(rawData.cmd, newData, rawData) 21 | } 22 | 23 | export const LiveStartEvent: EventInfo = { 24 | cmdName: 'LIVE', 25 | handlerName: 'onLiveStart', 26 | dataProcessor, 27 | } 28 | -------------------------------------------------------------------------------- /src/events/Notice.ts: -------------------------------------------------------------------------------- 1 | import type { EventInfo } from '../types/event' 2 | import type { Message } from '../types/message' 3 | import { normalizeMessage } from '../utils/message' 4 | 5 | export interface NoticeData { 6 | /** 广播Id */ 7 | id: number 8 | /** 广播名称/类型 */ 9 | name: string 10 | /** 目标直播间Id;如果有短Id则为短Id */ 11 | roomId: number 12 | /** 目标直播间长Id */ 13 | longRoomId: number 14 | /** 广播相关链接;如果广播只有本直播间可见则无 */ 15 | url?: string 16 | /** 广播内容(他人直播间);如果广播只有本直播间可见则无 */ 17 | messageCommon?: string 18 | /** 广播内容(自己直播间) */ 19 | messageSelf: string 20 | } 21 | 22 | function dataProcessor(rawData: any): Message { 23 | const newData: NoticeData = { 24 | id: rawData.id, 25 | name: rawData.name, 26 | roomId: rawData.roomid, 27 | longRoomId: rawData.real_roomid, 28 | url: rawData.link_url || undefined, 29 | messageCommon: rawData.msg_common || undefined, 30 | messageSelf: rawData.msg_self, 31 | } 32 | return normalizeMessage(rawData.cmd, newData, rawData) 33 | } 34 | 35 | export const NoticeEvent: EventInfo = { 36 | cmdName: 'NOTICE_MSG', 37 | handlerName: 'onNotice', 38 | dataProcessor, 39 | } 40 | -------------------------------------------------------------------------------- /src/events/PopularRankUpdate.ts: -------------------------------------------------------------------------------- 1 | import type { EventInfo } from '../types/event' 2 | import type { Message } from '../types/message' 3 | import { normalizeMessage } from '../utils/message' 4 | 5 | export interface PopularRankUpdateData { 6 | /** 房间主播uid */ 7 | uid: number 8 | /** 人气榜排名 */ 9 | rank: number 10 | /** 结榜倒计时 */ 11 | countdown: number 12 | /** 时间戳 */ 13 | timestamp: number 14 | } 15 | 16 | function dataProcessor(rawData: any): Message { 17 | const { data } = rawData 18 | const newData: PopularRankUpdateData = { 19 | uid: data.uid, 20 | rank: data.rank, 21 | countdown: data.countdown, 22 | timestamp: data.timestamp, 23 | } 24 | return normalizeMessage(rawData.cmd, newData, rawData) 25 | } 26 | 27 | export const PopularRankUpdateEvent: EventInfo = { 28 | cmdName: 'PopularRankUpdate', 29 | handlerName: 'onPopularRankUpdate', 30 | dataProcessor, 31 | } 32 | -------------------------------------------------------------------------------- /src/events/RankCountUpdate.ts: -------------------------------------------------------------------------------- 1 | import type { EventInfo } from '../types/event' 2 | import type { Message } from '../types/message' 3 | import { normalizeMessage } from '../utils/message' 4 | 5 | export interface RankCountUpdateData { 6 | /** 高能榜有贡献值的人数 */ 7 | count: number 8 | /** 高能榜总人数,即在线总人数; 该属性会按照 有-无-有-无 的情况出现 */ 9 | onlineCount?: number 10 | } 11 | 12 | function dataProcessor(rawData: any): Message { 13 | const { data } = rawData 14 | const newData: RankCountUpdateData = { 15 | count: data.count, 16 | } 17 | return normalizeMessage(rawData.cmd, newData, rawData) 18 | } 19 | 20 | export const RankCountUpdateEvent: EventInfo = { 21 | cmdName: 'ONLINE_RANK_COUNT', 22 | handlerName: 'onRankCountUpdate', 23 | dataProcessor, 24 | } 25 | -------------------------------------------------------------------------------- /src/events/RedPocketEnd.ts: -------------------------------------------------------------------------------- 1 | import type { EventInfo } from '../types/event' 2 | import type { Message } from '../types/message' 3 | import { normalizeMessage } from '../utils/message' 4 | 5 | export interface RedPocketEndData { 6 | /** 红包Id */ 7 | id: number 8 | /** 红包礼物总数 */ 9 | totalNum: number 10 | /** 中奖用户信息 */ 11 | winners: { 12 | /** 用户Id */ 13 | uid: number 14 | /** 用户名 */ 15 | uname: string 16 | /** 获得的奖品Id */ 17 | giftId: number 18 | }[] 19 | /** 红包内礼物信息 */ 20 | awards: { 21 | /** 礼物Id */ 22 | giftId: number 23 | /** 礼物名称 */ 24 | giftName: number 25 | /** 礼物价格 */ 26 | price: number 27 | /** 奖品图片 */ 28 | giftPic: string 29 | /** 礼物图片(大) */ 30 | giftBigPic: string 31 | }[] 32 | } 33 | 34 | function dataProcessor(rawData: any): Message { 35 | const { data } = rawData 36 | const newData: RedPocketEndData = { 37 | id: data.lot_id, 38 | totalNum: data.total_num, 39 | winners: data.winner_info.map((winner: any) => ({ 40 | uid: winner[0], 41 | uname: winner[1], 42 | giftId: winner[3], 43 | })), 44 | awards: Object.entries(data.awards).map(([giftId, giftInfo]: [string, any]) => ({ 45 | giftId: Number.parseInt(giftId), 46 | giftName: giftInfo.award_name, 47 | price: giftInfo.award_price, 48 | giftPic: giftInfo.award_pic, 49 | giftBigPic: giftInfo.award_big_pic, 50 | })), 51 | } 52 | return normalizeMessage(rawData.cmd, newData, rawData) 53 | } 54 | 55 | export const RedPocketEndEvent: EventInfo = { 56 | cmdName: 'POPULARITY_RED_POCKET_WINNER_LIST', 57 | handlerName: 'onRedPocketEnd', 58 | dataProcessor, 59 | } 60 | -------------------------------------------------------------------------------- /src/events/RedPocketStart.ts: -------------------------------------------------------------------------------- 1 | import type { EventInfo } from '../types/event' 2 | import type { Message } from '../types/message' 3 | import { normalizeMessage } from '../utils/message' 4 | 5 | export interface RedPocketStartData { 6 | /** 红包Id */ 7 | id: number 8 | /** 红包奖品 */ 9 | awards: { 10 | /** 礼物Id */ 11 | giftId: number 12 | /** 礼物名称 */ 13 | giftName: number 14 | /** 礼物数量 */ 15 | num: number 16 | /** 奖品图片 */ 17 | giftPic: string 18 | }[] 19 | /** 奖品总价值;单位:1金瓜子=1/1000元=1/100电池 */ 20 | totalPrice: number 21 | /** 弹幕口令 */ 22 | danmu: string 23 | /** 红包赠送者信息 */ 24 | sender: { 25 | uid: number 26 | uname: string 27 | face: string 28 | } 29 | /** 时间戳 */ 30 | timestamp: number 31 | /** 红包可以开始抽奖的时间戳 */ 32 | startTime: number 33 | /** 红包抽奖持续时间,单位:秒 */ 34 | duration: number 35 | /** 等待开始抽奖的红包数量 */ 36 | waitNum: number 37 | } 38 | 39 | function dataProcessor(rawData: any): Message { 40 | const { data } = rawData 41 | const newData: RedPocketStartData = { 42 | id: data.lot_id, 43 | awards: data.awards.map((award: any) => ({ 44 | giftId: award.gift_id, 45 | giftName: award.gift_name, 46 | num: award.num, 47 | giftPic: award.gift_pic, 48 | })), 49 | totalPrice: data.total_price, 50 | danmu: data.danmu, 51 | sender: { 52 | uid: data.sender_uid, 53 | uname: data.sender_name, 54 | face: data.sender_face, 55 | }, 56 | timestamp: data.current_time, 57 | startTime: data.start_time, 58 | duration: data.last_time, 59 | waitNum: data.wait_num, 60 | } 61 | return normalizeMessage(rawData.cmd, newData, rawData) 62 | } 63 | 64 | export const RedPocketStartEvent: EventInfo = { 65 | cmdName: 'POPULARITY_RED_POCKET_START', 66 | handlerName: 'onRedPocketStart', 67 | dataProcessor, 68 | } 69 | -------------------------------------------------------------------------------- /src/events/RoomChange.ts: -------------------------------------------------------------------------------- 1 | import type { EventInfo } from '../types/event' 2 | import type { Message } from '../types/message' 3 | import { normalizeMessage } from '../utils/message' 4 | 5 | export interface RoomChangeData { 6 | /** 直播间标题 */ 7 | title: string 8 | /** 一级分区Id */ 9 | parentAreaId: number 10 | /** 一级分区名称 */ 11 | parentAreaName: string 12 | /** 二级分区Id */ 13 | areaId: number 14 | /** 二级分区名称 */ 15 | areaName: string 16 | /** 时间戳 */ 17 | timestamp: number 18 | } 19 | 20 | function dataProcessor(rawData: any): Message { 21 | const { data } = rawData 22 | const newData: RoomChangeData = { 23 | title: data.title, 24 | parentAreaId: data.parent_area_id, 25 | parentAreaName: data.parent_area_name, 26 | areaId: data.area_id, 27 | areaName: data.area_name, 28 | timestamp: Number.parseInt(data.sub_session_key.split(':')[1]), 29 | } 30 | return normalizeMessage(rawData.cmd, newData, rawData) 31 | } 32 | 33 | export const RoomChangeEvent: EventInfo = { 34 | cmdName: 'ROOM_CHANGE', 35 | handlerName: 'onRoomChange', 36 | dataProcessor, 37 | } 38 | -------------------------------------------------------------------------------- /src/events/SuperChat.ts: -------------------------------------------------------------------------------- 1 | import type { EventInfo } from '../types/event' 2 | import type { Message, User } from '../types/message' 3 | import { int2ColorHex } from '../utils/color' 4 | import { normalizeMessage } from '../utils/message' 5 | 6 | export interface SuperChatData { 7 | /** 用户信息 */ 8 | user: User 9 | /** SC内容 */ 10 | content: string 11 | /** SC价格 单位:元 */ 12 | price: number 13 | /** SC颜色 */ 14 | color: { 15 | main: string 16 | background: string 17 | } 18 | /** SC持续时间 */ 19 | duration: number 20 | /** SC开始时间 */ 21 | startTime: number 22 | /** SC结束时间 */ 23 | endTime: number 24 | /** 礼物名称 */ 25 | giftName: string 26 | /** 礼物Id */ 27 | giftId: number 28 | } 29 | 30 | function dataProcessor(rawData: any): Message { 31 | const { data } = rawData 32 | const { user_info, medal_info } = data 33 | const newData: SuperChatData = { 34 | user: { 35 | uid: data.uid, 36 | uname: user_info.uname, 37 | face: user_info.face, 38 | faceFrame: user_info.face_frame, 39 | fansMedal: medal_info && medal_info.medal_name 40 | ? { 41 | name: medal_info.medal_name, 42 | level: medal_info.medal_level, 43 | guardType: medal_info.guard_level, 44 | color: { 45 | original: medal_info.medal_color, 46 | border: int2ColorHex(medal_info.medal_color_border), 47 | start: int2ColorHex(medal_info.medal_color_start), 48 | end: int2ColorHex(medal_info.medal_color_end), 49 | }, 50 | isLighted: medal_info.is_lighted, 51 | anchor: { 52 | uid: medal_info.target_id, 53 | uname: medal_info.anchor_uname, 54 | roomId: medal_info.anchor_roomid, 55 | }, 56 | } 57 | : undefined, 58 | guardType: user_info.guard_level, 59 | isRoomAdmin: !!user_info.manager, 60 | }, 61 | content: data.message, 62 | price: data.price, 63 | color: { 64 | main: data.background_bottom_color, 65 | background: data.background_color, 66 | }, 67 | duration: data.time, 68 | startTime: data.start_time, 69 | endTime: data.end_time, 70 | giftId: data.gift.gift_id, 71 | giftName: data.gift.gift_name, 72 | } 73 | return normalizeMessage(rawData.cmd, newData, rawData) 74 | } 75 | 76 | export const SuperChatEvent: EventInfo = { 77 | cmdName: 'SUPER_CHAT_MESSAGE', 78 | handlerName: 'onSuperChat', 79 | dataProcessor, 80 | } 81 | -------------------------------------------------------------------------------- /src/events/TAMPLATE.ts: -------------------------------------------------------------------------------- 1 | import type { EventInfo } from '../types/event' 2 | import type { Message } from '../types/message' 3 | import { normalizeMessage } from '../utils/message' 4 | 5 | export interface TAMPLATEData { 6 | 7 | } 8 | 9 | function dataProcessor(rawData: any): Message { 10 | const newData: TAMPLATEData = { 11 | 12 | } 13 | return normalizeMessage(rawData.cmd, newData, rawData) 14 | } 15 | 16 | export const TAMPLATEEvent: EventInfo = { 17 | cmdName: 'TAMPLATE', 18 | handlerName: 'onTAMPLATE', 19 | dataProcessor, 20 | } 21 | -------------------------------------------------------------------------------- /src/events/WatchedChange.ts: -------------------------------------------------------------------------------- 1 | import type { EventInfo } from '../types/event' 2 | import type { Message } from '../types/message' 3 | import { normalizeMessage } from '../utils/message' 4 | 5 | export interface WatchedChangeData { 6 | /** 累计观看人数详细数据;如168253 */ 7 | num: number 8 | /** 格式化文本;如16.8万 */ 9 | textSmall: string 10 | /** 更完整的格式化文本;如16.8万人看过 */ 11 | textLarge: string 12 | } 13 | 14 | function dataProcessor(rawData: any): Message { 15 | const { data } = rawData 16 | const newData: WatchedChangeData = { 17 | num: data.num, 18 | textSmall: data.text_small, 19 | textLarge: data.text_large, 20 | } 21 | return normalizeMessage(rawData.cmd, newData, rawData) 22 | } 23 | 24 | export const WatchedChangeEvent: EventInfo = { 25 | cmdName: 'WATCHED_CHANGE', 26 | handlerName: 'onWatchedChange', 27 | dataProcessor, 28 | } 29 | -------------------------------------------------------------------------------- /src/events/common.ts: -------------------------------------------------------------------------------- 1 | import type { CommonEventInfo } from '../types/event' 2 | 3 | export const OpenEvent: CommonEventInfo = { 4 | cmdName: 'open', 5 | handlerName: 'onOpen', 6 | } 7 | 8 | export const LiveEvent: CommonEventInfo = { 9 | cmdName: 'live', 10 | handlerName: 'onLive', 11 | 12 | } 13 | 14 | export const HeartbeatEvent: CommonEventInfo = { 15 | cmdName: 'heartbeat', 16 | handlerName: 'onHeartbeat', 17 | } 18 | 19 | export const CloseEvent: CommonEventInfo = { 20 | cmdName: 'close', 21 | handlerName: 'onClose', 22 | } 23 | 24 | export const ErrorEvent: CommonEventInfo = { 25 | cmdName: 'error', 26 | handlerName: 'onError', 27 | } 28 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export { default as BiliLive, type BiliLiveOptions, type RemoveHandler } from './BiliLive' 2 | export { getRoomId, getRoomConf, getLoginedUid } from './utils/request' 3 | 4 | // message type 5 | export * from './types/message' 6 | 7 | // events types 8 | export type { DanmuData } from './events/Danmu' 9 | export type { GuardBuyData } from './events/GuardBuy' 10 | export type { SuperChatData } from './events/SuperChat' 11 | export type { GiftData } from './events/Gift' 12 | export type { WatchedChangeData } from './events/WatchedChange' 13 | export type { RankCountUpdateData } from './events/RankCountUpdate' 14 | export type { LikeCountUpdateData } from './events/LikeCountUpdate' 15 | export type { NoticeData } from './events/Notice' 16 | export type { HotRankUpdateData } from './events/HotRankUpdate' 17 | export type { FansCountUpdateData } from './events/FansCountUpdate' 18 | export type { LiveStartData } from './events/LiveStart' 19 | export type { LiveEndData } from './events/LiveEnd' 20 | export type { InteractData } from './events/Interact' 21 | export type { EntryEffectData } from './events/EntryEffect' 22 | export type { RoomChangeData } from './events/RoomChange' 23 | export type { AnchorLotStartData } from './events/AnchorLotStart' 24 | export type { AnchorLotEndData } from './events/AnchorLotEnd' 25 | export type { RedPocketStartData } from './events/RedPocketStart' 26 | export type { RedPocketEndData } from './events/RedPocketEnd' 27 | export type { PopularRankUpdateData } from './events/PopularRankUpdate' 28 | export type { DanmuInteractData } from './events/DanmuInteract' 29 | -------------------------------------------------------------------------------- /src/types/event.ts: -------------------------------------------------------------------------------- 1 | import type { Message } from './message' 2 | 3 | export interface EventInfo { 4 | cmdName: string | string[] 5 | handlerName: string 6 | dataProcessor: (data: any) => Message | undefined 7 | } 8 | 9 | export interface CommonEventInfo { 10 | cmdName: string 11 | handlerName: string 12 | } 13 | -------------------------------------------------------------------------------- /src/types/message.ts: -------------------------------------------------------------------------------- 1 | export interface Message { 2 | /** 消息原生类型 */ 3 | cmd: string 4 | /** 收到消息的时间戳 */ 5 | timestamp: number 6 | /** 类型化后的消息主体 */ 7 | data: T 8 | /** 原生消息 */ 9 | raw: any 10 | } 11 | 12 | export interface User { 13 | /** 用户Id */ 14 | uid: number 15 | /** 用户名 */ 16 | uname: string 17 | /** 用户头像 */ 18 | face?: string 19 | /** 用户头像框 */ 20 | faceFrame?: string 21 | /** 用户当前佩戴的粉丝勋章 */ 22 | fansMedal?: FansMedal 23 | /** 高能榜排名 */ 24 | giftRank?: GiftRank 25 | /** 大航海类型 */ 26 | guardType?: GuardType 27 | // 是否为房管 28 | isRoomAdmin?: boolean 29 | } 30 | 31 | export interface FansMedal { 32 | /** 粉丝勋章名称 */ 33 | name: string 34 | /** 粉丝勋章等级 */ 35 | level: number 36 | /** 粉丝勋章的大航海类型 */ 37 | guardType: GuardType 38 | /** 粉丝勋章颜色 */ 39 | color: { 40 | /** 原始颜色 */ 41 | original: string 42 | /** 边框色 */ 43 | border: string 44 | /** 渐变色开始 */ 45 | start: string 46 | /** 渐变色结束 */ 47 | end: string 48 | } 49 | /** 粉丝勋章是否点亮 */ 50 | isLighted: boolean 51 | /** 相关主播信息 */ 52 | anchor: { 53 | /** 主播用户Id */ 54 | uid: number 55 | /** 主播用户名 */ 56 | uname: string 57 | /** 主播房间号 */ 58 | roomId: number 59 | } 60 | } 61 | 62 | /** 高能榜排名 */ 63 | export enum GiftRank { 64 | /** 未上榜 */ 65 | None, 66 | /** 榜1 */ 67 | First, 68 | /** 榜2 */ 69 | Second, 70 | /** 榜3 */ 71 | Third, 72 | } 73 | 74 | /** 大航海类型 */ 75 | export enum GuardType { 76 | /** 未上舰 */ 77 | None, 78 | /** 总督 */ 79 | ZongDu, 80 | /** 提督 */ 81 | TiDu, 82 | /** 舰长 */ 83 | JianZhang, 84 | } 85 | 86 | /** 互动类型 */ 87 | export enum InteractType { 88 | Enter = 1, 89 | Follow, 90 | Share, 91 | Like, 92 | } 93 | 94 | /** 天选时刻奖品信息 */ 95 | export interface AnchorLotAward { 96 | /** 奖品名称 */ 97 | name: string 98 | /** 奖品数量 */ 99 | num: number 100 | /** 奖品图片 */ 101 | image: string 102 | /** 奖品类型 */ 103 | type: AnchorLotAwardType 104 | /** 奖品价值描述 */ 105 | priceText: string 106 | } 107 | 108 | /** 天选时刻奖品类型 */ 109 | export enum AnchorLotAwardType { 110 | /** 实物奖品 */ 111 | PHYSICAL = 0, 112 | /** 虚拟奖品 */ 113 | VIRTUAL = 1, 114 | } 115 | 116 | /** 天选时刻参与用户要求的类型 */ 117 | export enum AnchorLotUserType { 118 | /** 无要求 */ 119 | None = 0, 120 | /** 关注主播 */ 121 | Follow = 1, 122 | /** 粉丝勋章 */ 123 | FansMedal = 2, 124 | /** 大航海 */ 125 | Guard = 3, 126 | } 127 | 128 | /** 礼物瓜子类型;1金瓜子=1/1000元=1/100电池;银瓜子无金钱价值 */ 129 | export type CoinType = 'silver' | 'gold' 130 | -------------------------------------------------------------------------------- /src/types/response.ts: -------------------------------------------------------------------------------- 1 | export interface MobileRoomInitResponse { 2 | code: number 3 | msg: string 4 | message: string 5 | data: { 6 | room_id: number 7 | short_id: number 8 | uid: number 9 | } 10 | } 11 | 12 | export interface DanmuInfoResponse { 13 | code: number 14 | message: string 15 | ttl: number 16 | data: { 17 | group: string 18 | business_id: number 19 | refresh_row_factor: number 20 | refresh_rate: number 21 | max_delay: number 22 | token: string 23 | host_list: { 24 | host: string 25 | port: number 26 | wss_port: number 27 | ws_port: number 28 | }[] 29 | } 30 | } 31 | 32 | export interface NavResponse { 33 | code: number 34 | message: string 35 | ttl: number 36 | data: { 37 | mid: number 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/utils/color.ts: -------------------------------------------------------------------------------- 1 | export function int2ColorHex(int: number) { 2 | return `#${int.toString(16).padStart(6, '0')}` 3 | } 4 | 5 | export function colorHex2Int(hex: string) { 6 | return Number.parseInt(hex.slice(1), 16) 7 | } 8 | 9 | export function colorHex2Rgb(hex: string) { 10 | const int = colorHex2Int(hex) 11 | return { 12 | r: int >> 16 & 0xFF, 13 | g: int >> 8 & 0xFF, 14 | b: int & 0xFF, 15 | } 16 | } 17 | 18 | export function rgb2Int(r: number, g: number, b: number) { 19 | return r << 16 | g << 8 | b 20 | } 21 | -------------------------------------------------------------------------------- /src/utils/message.ts: -------------------------------------------------------------------------------- 1 | import type { Message } from '../types/message' 2 | 3 | export function normalizeMessage(cmd: string, data: T, rawData: any): Message { 4 | return { 5 | cmd, 6 | timestamp: Date.now(), 7 | data, 8 | raw: rawData, 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/utils/request.ts: -------------------------------------------------------------------------------- 1 | import type { DanmuInfoResponse, MobileRoomInitResponse, NavResponse } from '../types/response' 2 | 3 | export function request(url: string, options?: RequestInit): Promise { 4 | return fetch(url, options).then(res => res.json()) as Promise 5 | } 6 | 7 | export async function getRoomId(roomid: number) { 8 | const { data: { 9 | room_id: longRoomId, 10 | short_id: shortRoomId, 11 | }, 12 | } = await request(`https://api.live.bilibili.com/room/v1/Room/mobileRoomInit?id=${roomid}`) 13 | 14 | return { longRoomId, shortRoomId } 15 | } 16 | 17 | export async function getRoomConf(roomid: number, cookie: string) { 18 | const { 19 | data: { 20 | token: key, 21 | }, 22 | } = await request(`https://api.live.bilibili.com/xlive/web-room/v1/index/getDanmuInfo?id=${roomid}`, { headers: { cookie } }) 23 | 24 | return { key } 25 | } 26 | 27 | export async function getLoginedUid(cookie: string) { 28 | const { 29 | data: { 30 | mid: uid, 31 | }, 32 | } = await request('https://api.bilibili.com/x/web-interface/nav', { headers: { cookie } }) 33 | 34 | return uid 35 | } 36 | -------------------------------------------------------------------------------- /test/index.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest' 2 | import { colorHex2Rgb, int2ColorHex } from '../src/utils/color' 3 | 4 | describe('test', () => { 5 | it('int2Hex should work', () => { 6 | const hexs = ['#2A60B2', '#EDF5FF', '#405D85', '#3171D2', '#7497CD'] 7 | const rgbs = hexs.map(colorHex2Rgb).map(rgbObj => `${rgbObj.r}, ${rgbObj.g}, ${rgbObj.b}`) 8 | expect(rgbs).toMatchInlineSnapshot(` 9 | [ 10 | "42, 96, 178", 11 | "237, 245, 255", 12 | "64, 93, 133", 13 | "49, 113, 210", 14 | "116, 151, 205", 15 | ] 16 | `) 17 | }) 18 | it('colorHex2Int should work', () => { 19 | expect(int2ColorHex(2951253)).toMatchInlineSnapshot('"#2d0855"') 20 | }) 21 | it('parse data', () => { 22 | const data = '{"question":"你支持哪支战队","options":[{"idx":1,"desc":"TTG","cnt":722,"percent":0.6747663551401869},{"idx":2,"desc":"BOA","cnt":345,"percent":0.32242990654205606}],"vote_id":11404452,"cnt":1070,"duration":180000,"left_duration":88000,"fade_duration":1000,"waiting_duration":-1,"result":2,"result_text":"蓝领先","component":"https://live.bilibili.com/p/html/live-app-guessing-game/vote.html?is_live_half_webview=1\\u0026hybrid_half_ui=1,3,100p,245,0,0,30,100,12,0;2,2,375,100p,0,0,30,100,12,0;3,3,100p,245,0,0,30,100,12,0;4,2,375,100p,0,0,30,100,12,0;5,3,100p,70p,0,0,30,100,12,0;6,3,100p,70p,0,0,30,100,12,0;7,3,100p,70p,0,0,30,100,12,0;8,3,100p,70p,0,0,30,100,12,0","natural_die_duration":30000,"my_vote":0,"component_anchor":"https://live.bilibili.com/p/html/live-app-guessing-game/anchor_vote.html?pc_ui=390,428,0,3\\u0026is_live_half_webview=1\\u0026hybrid_half_ui=1,3,100p,448,0,0,30,0,12,0;2,2,375,100p,0,0,30,0,12,0;3,3,100p,448,0,0,30,0,12,0;4,2,375,100p,0,0,30,0,12,0;5,3,100p,448,0,0,30,0,12,0;6,2,320,100p,0,0,30,0,12,0;7,2,320,100p,0,0,30,0,12,0;8,2,320,100p,0,0,30,0,12,0#/","audit_reason":"","combo":[{"id":1,"status":4,"content":"TTG","cnt":722,"guide":"","left_duration":88000,"fade_duration":0,"prefix_icon":"http://i0.hdslb.com/bfs/dm/7d7e3682c9116aa3503418abe3cde6b45ed2e91e.png"},{"id":2,"status":4,"content":"BOA","cnt":345,"guide":"","left_duration":88000,"fade_duration":0,"prefix_icon":"http://i0.hdslb.com/bfs/dm/f83c7280b2a90b4f58a68fd8c594ea7d5667e3cb.png"}]}' 23 | const parsed = JSON.parse(data) 24 | expect(parsed).toMatchInlineSnapshot(` 25 | { 26 | "audit_reason": "", 27 | "cnt": 1070, 28 | "combo": [ 29 | { 30 | "cnt": 722, 31 | "content": "TTG", 32 | "fade_duration": 0, 33 | "guide": "", 34 | "id": 1, 35 | "left_duration": 88000, 36 | "prefix_icon": "http://i0.hdslb.com/bfs/dm/7d7e3682c9116aa3503418abe3cde6b45ed2e91e.png", 37 | "status": 4, 38 | }, 39 | { 40 | "cnt": 345, 41 | "content": "BOA", 42 | "fade_duration": 0, 43 | "guide": "", 44 | "id": 2, 45 | "left_duration": 88000, 46 | "prefix_icon": "http://i0.hdslb.com/bfs/dm/f83c7280b2a90b4f58a68fd8c594ea7d5667e3cb.png", 47 | "status": 4, 48 | }, 49 | ], 50 | "component": "https://live.bilibili.com/p/html/live-app-guessing-game/vote.html?is_live_half_webview=1&hybrid_half_ui=1,3,100p,245,0,0,30,100,12,0;2,2,375,100p,0,0,30,100,12,0;3,3,100p,245,0,0,30,100,12,0;4,2,375,100p,0,0,30,100,12,0;5,3,100p,70p,0,0,30,100,12,0;6,3,100p,70p,0,0,30,100,12,0;7,3,100p,70p,0,0,30,100,12,0;8,3,100p,70p,0,0,30,100,12,0", 51 | "component_anchor": "https://live.bilibili.com/p/html/live-app-guessing-game/anchor_vote.html?pc_ui=390,428,0,3&is_live_half_webview=1&hybrid_half_ui=1,3,100p,448,0,0,30,0,12,0;2,2,375,100p,0,0,30,0,12,0;3,3,100p,448,0,0,30,0,12,0;4,2,375,100p,0,0,30,0,12,0;5,3,100p,448,0,0,30,0,12,0;6,2,320,100p,0,0,30,0,12,0;7,2,320,100p,0,0,30,0,12,0;8,2,320,100p,0,0,30,0,12,0#/", 52 | "duration": 180000, 53 | "fade_duration": 1000, 54 | "left_duration": 88000, 55 | "my_vote": 0, 56 | "natural_die_duration": 30000, 57 | "options": [ 58 | { 59 | "cnt": 722, 60 | "desc": "TTG", 61 | "idx": 1, 62 | "percent": 0.6747663551401869, 63 | }, 64 | { 65 | "cnt": 345, 66 | "desc": "BOA", 67 | "idx": 2, 68 | "percent": 0.32242990654205606, 69 | }, 70 | ], 71 | "question": "你支持哪支战队", 72 | "result": 2, 73 | "result_text": "蓝领先", 74 | "vote_id": 11404452, 75 | "waiting_duration": -1, 76 | } 77 | `) 78 | }) 79 | }) 80 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2018", 4 | "lib": ["esnext"], 5 | "module": "esnext", 6 | "moduleResolution": "node", 7 | "resolveJsonModule": true, 8 | "strict": true, 9 | "strictNullChecks": true, 10 | "esModuleInterop": true, 11 | "skipDefaultLibCheck": true, 12 | "skipLibCheck": true 13 | } 14 | } 15 | --------------------------------------------------------------------------------