├── .npmrc ├── deno.json ├── src ├── frontend │ ├── .npmrc │ ├── src │ │ ├── scss │ │ │ └── styles.scss │ │ ├── main.ts │ │ ├── env.d.ts │ │ ├── types.d.ts │ │ ├── App.vue │ │ ├── components │ │ │ ├── Display.vue │ │ │ ├── Header.vue │ │ │ ├── DeploymentList.vue │ │ │ └── RoomList.vue │ │ ├── utils │ │ │ └── index.ts │ │ └── stores │ │ │ ├── room.ts │ │ │ └── ws.ts │ ├── public │ │ ├── assets │ │ │ ├── js │ │ │ │ └── footer_note.js │ │ │ ├── img-placeholder.png │ │ │ └── styles │ │ │ │ ├── reset.css │ │ │ │ └── footer_note.css │ │ ├── weread │ │ │ ├── __init__.js │ │ │ └── 8.js │ │ ├── vite.svg │ │ └── lib │ │ │ └── base64js.min.js │ ├── backup │ │ ├── 404.html │ │ ├── stability │ │ │ ├── sse.html │ │ │ ├── index.html │ │ │ └── style.css │ │ ├── css │ │ │ ├── main.css │ │ │ └── loading.css │ │ ├── style.html │ │ └── index.html │ ├── vite.config.js │ ├── index.html │ ├── .eslintrc.cjs │ ├── tsconfig.json │ └── package.json └── backend │ ├── live │ ├── manage.ts │ ├── web.ts │ ├── area.ts │ ├── message.ts │ ├── redpocket.ts │ ├── user.ts │ ├── room.ts │ └── stream.ts │ ├── messages.d.ts │ ├── deps.ts │ ├── message-parser.ts │ ├── apis │ ├── staticsSSE.ts │ ├── sse.ts │ └── info.ts │ ├── router.ts │ ├── common │ ├── message-parser.ts │ └── request.ts │ ├── server.ts │ ├── config.ts │ └── types.d.ts ├── assets ├── demo.png ├── roomId.png ├── roomId2.png ├── auth-packet.png ├── deploy-step1.png ├── deploy-step2.png ├── deploy-step3.png ├── heartbeat-body.png ├── heartbeat-packet.png ├── packet-struct-1.png ├── auth-reply-packet.png └── heartbeat-reply-packet.png ├── test ├── persistence │ ├── config.ts │ ├── message.bin │ ├── write.ts │ └── read.ts ├── send.ts ├── proxy.ts ├── message │ └── danmu.ts ├── api.ts └── ws.ts ├── resources └── anatomy │ ├── img.png │ ├── img_1.png │ ├── js │ ├── sw.js.LICENSE.txt │ ├── bili-fonts.js │ └── vendors.js.LICENSE.txt │ ├── sandbox │ └── twitter.html │ ├── modules │ ├── x9fc.js │ ├── FFSN.js │ ├── jM0N.js │ ├── R6Co.js │ ├── zRQJ.js │ ├── yABZ.js │ ├── Rr2x.js │ ├── Qw13.js │ ├── fk22.js │ ├── BuDO.js │ ├── BEmL.js │ ├── kyLC.js │ ├── wHWR.js │ ├── GlyE.js │ ├── wPfs.js │ ├── YMnC.js │ ├── Qsz6.js │ ├── DfF7.js │ ├── V6Hm.js │ ├── hY1T.js │ ├── KaYZ.js │ ├── NwTf.js │ ├── cQRo.js │ ├── hSGw.js │ ├── jLTy.js │ ├── weyG.js │ ├── U3yD.js │ ├── jYwk.js │ ├── qhQO.js │ ├── p0fG.js │ ├── HmVg.js │ ├── ZYDn.js │ ├── z0Gg.js │ ├── gT7a.js │ ├── lmPI.js │ ├── FT5O.js │ ├── JKnM.js │ ├── QivA.js │ ├── z9tw.js │ ├── EORf.js │ ├── E90K.js │ ├── oQm6.js │ ├── KGHo.js │ ├── JkDX.js │ ├── H7b7.js │ ├── MwY8.js │ ├── jGuH.js │ ├── RYhb.js │ ├── lCVI.js │ ├── MY3w.js │ ├── SCHR.js │ ├── E7iI.js │ ├── blC7.js │ ├── ryqy.js │ ├── NApx.js │ ├── cPXi.js │ ├── ywxt.js │ ├── Klvu.js │ ├── PTXm.js │ ├── rPeE.js │ └── PFwM.js │ ├── message-parser.js │ ├── readme.md │ └── parser.js ├── docs ├── apis │ ├── heartBeat.md │ ├── getLastMonthSignDays.md │ ├── get_ab.md │ ├── unread.md │ ├── fetch_client_resource.md │ ├── getIpInfo.md │ ├── WebGetSignInfo.md │ ├── getLotteryInfoWeb.md │ ├── getRoomPlayInfo.md │ ├── send.md │ ├── getDanmuInfo.md │ ├── get_anchor_in_room.md │ └── nav.md └── refactor.md ├── package.json ├── .github └── workflows │ └── deploy.yml ├── LICENSE ├── examples └── example.js └── .gitignore /.npmrc: -------------------------------------------------------------------------------- 1 | registry=https://registry.npmmirror.com/ 2 | -------------------------------------------------------------------------------- /deno.json: -------------------------------------------------------------------------------- 1 | { 2 | "unstable": ["kv", "cron"] 3 | } 4 | -------------------------------------------------------------------------------- /src/frontend/.npmrc: -------------------------------------------------------------------------------- 1 | registry=https://registry.npmmirror.com/ 2 | -------------------------------------------------------------------------------- /src/frontend/src/scss/styles.scss: -------------------------------------------------------------------------------- 1 | @import "bootstrap/scss/bootstrap"; 2 | -------------------------------------------------------------------------------- /src/backend/live/manage.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 直播间管理 3 | * 4 | * 需要认证信息 5 | */ 6 | -------------------------------------------------------------------------------- /assets/demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/champkeh/blive-ws/HEAD/assets/demo.png -------------------------------------------------------------------------------- /assets/roomId.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/champkeh/blive-ws/HEAD/assets/roomId.png -------------------------------------------------------------------------------- /src/frontend/public/assets/js/footer_note.js: -------------------------------------------------------------------------------- 1 | // footer_note.js 2 | console.log(123) 3 | -------------------------------------------------------------------------------- /src/frontend/public/weread/__init__.js: -------------------------------------------------------------------------------- 1 | (() => { 2 | window.weread = {} 3 | })(); 4 | -------------------------------------------------------------------------------- /test/persistence/config.ts: -------------------------------------------------------------------------------- 1 | export const FILE_NAME = 'test/persistence/message.bin' 2 | -------------------------------------------------------------------------------- /assets/roomId2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/champkeh/blive-ws/HEAD/assets/roomId2.png -------------------------------------------------------------------------------- /assets/auth-packet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/champkeh/blive-ws/HEAD/assets/auth-packet.png -------------------------------------------------------------------------------- /assets/deploy-step1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/champkeh/blive-ws/HEAD/assets/deploy-step1.png -------------------------------------------------------------------------------- /assets/deploy-step2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/champkeh/blive-ws/HEAD/assets/deploy-step2.png -------------------------------------------------------------------------------- /assets/deploy-step3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/champkeh/blive-ws/HEAD/assets/deploy-step3.png -------------------------------------------------------------------------------- /assets/heartbeat-body.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/champkeh/blive-ws/HEAD/assets/heartbeat-body.png -------------------------------------------------------------------------------- /resources/anatomy/img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/champkeh/blive-ws/HEAD/resources/anatomy/img.png -------------------------------------------------------------------------------- /assets/heartbeat-packet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/champkeh/blive-ws/HEAD/assets/heartbeat-packet.png -------------------------------------------------------------------------------- /assets/packet-struct-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/champkeh/blive-ws/HEAD/assets/packet-struct-1.png -------------------------------------------------------------------------------- /resources/anatomy/img_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/champkeh/blive-ws/HEAD/resources/anatomy/img_1.png -------------------------------------------------------------------------------- /assets/auth-reply-packet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/champkeh/blive-ws/HEAD/assets/auth-reply-packet.png -------------------------------------------------------------------------------- /test/persistence/message.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/champkeh/blive-ws/HEAD/test/persistence/message.bin -------------------------------------------------------------------------------- /assets/heartbeat-reply-packet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/champkeh/blive-ws/HEAD/assets/heartbeat-reply-packet.png -------------------------------------------------------------------------------- /src/backend/messages.d.ts: -------------------------------------------------------------------------------- 1 | export interface DanmuMessage { 2 | uid: number 3 | uname: string 4 | text: string 5 | } 6 | -------------------------------------------------------------------------------- /src/frontend/public/assets/img-placeholder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/champkeh/blive-ws/HEAD/src/frontend/public/assets/img-placeholder.png -------------------------------------------------------------------------------- /resources/anatomy/js/sw.js.LICENSE.txt: -------------------------------------------------------------------------------- 1 | /*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/facebook/regenerator/blob/main/LICENSE */ 2 | -------------------------------------------------------------------------------- /src/frontend/backup/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 404 6 | 7 | 8 | 404 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/backend/deps.ts: -------------------------------------------------------------------------------- 1 | export * as brotli from "https://deno.land/x/brotli@v0.1.4/mod.ts" 2 | export * as fs from "https://deno.land/std@0.194.0/http/file_server.ts" 3 | export * as dotenv from "https://deno.land/std@0.202.0/dotenv/mod.ts"; 4 | -------------------------------------------------------------------------------- /src/backend/message-parser.ts: -------------------------------------------------------------------------------- 1 | import {DanmuMessage} from "./messages.d.ts"; 2 | 3 | export function parseMessage(data: any): DanmuMessage { 4 | return { 5 | uid: data.info[2][0], 6 | uname: data.info[2][1], 7 | text: data.info[1], 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /resources/anatomy/sandbox/twitter.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Title 6 | 7 | 8 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/frontend/src/main.ts: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue' 2 | import { createPinia } from 'pinia' 3 | import App from './App.vue' 4 | import './scss/styles.scss' 5 | import 'bootstrap' 6 | import 'bootstrap-icons/font/bootstrap-icons.css' 7 | 8 | const app = createApp(App) 9 | 10 | app.use(createPinia()) 11 | 12 | app.mount('#app') 13 | -------------------------------------------------------------------------------- /src/frontend/src/env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | declare module '*.vue' { 4 | import { DefineComponent } from 'vue' 5 | // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types 6 | const component: DefineComponent<{}, {}, any> 7 | export default component 8 | } 9 | 10 | interface Window { 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/frontend/vite.config.js: -------------------------------------------------------------------------------- 1 | import { fileURLToPath, URL } from 'node:url' 2 | 3 | import { defineConfig } from 'vite' 4 | import vue from '@vitejs/plugin-vue' 5 | 6 | // https://vitejs.dev/config/ 7 | export default defineConfig({ 8 | plugins: [vue()], 9 | resolve: { 10 | alias: { 11 | '@': fileURLToPath(new URL('./src', import.meta.url)) 12 | } 13 | } 14 | }) 15 | -------------------------------------------------------------------------------- /resources/anatomy/modules/x9fc.js: -------------------------------------------------------------------------------- 1 | /** 2 | * id: x9fc 3 | * path: ./type 4 | */ 5 | 6 | (function(require,module,exports) { 7 | "use strict";var e;Object.defineProperty(exports,"__esModule",{value:!0}),exports.EMenuType=void 0,function(e){e.DANMU="danmu",e.LINE="line",e.LOGGER="logger",e.VIDEO_INFO="videoInfo",e.VERSION="version",e.COPYRIGHT="copyright",e.CLOSE="close"}(e=exports.EMenuType||(exports.EMenuType={})); 8 | })() -------------------------------------------------------------------------------- /resources/anatomy/modules/FFSN.js: -------------------------------------------------------------------------------- 1 | /** 2 | * id: FFSN 3 | * path: ./events/user-operation 4 | */ 5 | 6 | (function(require,module,exports) { 7 | "use strict";function e(e,t){var n=e.onChange(function(e,n){if("subtitle"===e)for(var r in n)t({path:"ctrl.subtitle.".concat(r),value:n[r]})});return function(){n()}}Object.defineProperty(exports,"__esModule",{value:!0}),exports.bindUserOperation=void 0,exports.bindUserOperation=e; 8 | })() -------------------------------------------------------------------------------- /resources/anatomy/modules/jM0N.js: -------------------------------------------------------------------------------- 1 | /** 2 | * id: jM0N 3 | * path: requestidlecallback-polyfill 4 | */ 5 | 6 | (function(require,module,exports) { 7 | window.requestIdleCallback=window.requestIdleCallback||function(e){var n=Date.now();return setTimeout(function(){e({didTimeout:!1,timeRemaining:function(){return Math.max(0,50-(Date.now()-n))}})},1)},window.cancelIdleCallback=window.cancelIdleCallback||function(e){clearTimeout(e)}; 8 | })() -------------------------------------------------------------------------------- /docs/apis/heartBeat.md: -------------------------------------------------------------------------------- 1 | # heartBeat 2 | 3 | ## 接口地址 4 | 5 | `https://api.live.bilibili.com/relation/v1/Feed/heartBeat` 6 | 7 | ## 方法 8 | 9 | `GET` 10 | 11 | ## 参数 12 | 13 | 无 14 | 15 | ## 返回示例 16 | 17 | ```json 18 | { 19 | "code": 0, 20 | "msg": "success", 21 | "message": "success", 22 | "data": { 23 | "open": 1, 24 | "has_new": 0, 25 | "count": 0 26 | } 27 | } 28 | ``` 29 | 30 | ## 接口作用 31 | 32 | 心跳检测 33 | -------------------------------------------------------------------------------- /test/send.ts: -------------------------------------------------------------------------------- 1 | import {send} from "../src/apis/live/web.ts"; 2 | import {config} from "../src/deno/const.ts" 3 | 4 | const roomid = 7734200 5 | const msg = 'hello' 6 | 7 | send(roomid, msg, `SESSDATA=${config.sessdata}`).then(resp => resp.json()).then(data => { 8 | if (data.code === 0) { 9 | console.log('发送成功') 10 | } else { 11 | console.log(data) 12 | } 13 | }).catch(err => { 14 | console.error(err) 15 | }) 16 | -------------------------------------------------------------------------------- /test/persistence/write.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 消息持久化测试 3 | */ 4 | import ws from "../ws.ts" 5 | import {writeMessageToFile} from "../../src/deno/utils.ts" 6 | import {FILE_NAME} from './config.ts' 7 | 8 | let count = 1 9 | 10 | ws.addEventListener('message', (event: MessageEvent) => { 11 | writeMessageToFile(FILE_NAME, event.data) 12 | console.log(count) 13 | if (count === 30) { 14 | ws.close() 15 | } 16 | count++ 17 | }) 18 | -------------------------------------------------------------------------------- /src/frontend/src/types.d.ts: -------------------------------------------------------------------------------- 1 | export interface Room { 2 | // uuid 3 | id: string 4 | 5 | // 短房间号 6 | rid: number 7 | 8 | // 长房间号 9 | roomId: number 10 | 11 | // 连接状态 12 | status: 'online' | 'offline' 13 | 14 | // 监听的事件 15 | events: string[] 16 | 17 | active: boolean 18 | 19 | logs: MsgLog[] 20 | } 21 | 22 | export interface MsgLog { 23 | rid: number 24 | payload: Record 25 | } 26 | -------------------------------------------------------------------------------- /resources/anatomy/modules/R6Co.js: -------------------------------------------------------------------------------- 1 | /** 2 | * id: R6Co 3 | * path: @bilibili-live/web-player-p2p 4 | */ 5 | 6 | (function(require,module,exports) { 7 | "use strict";var e=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(exports,"__esModule",{value:!0});var r=require("./bili-p2p"),i=e(require("./qvb-p2p"));exports.default={bindBili:r.bindBili,getTrackerParamsString:r.getTrackerParamsString,bindQVB:i.default}; 8 | })() -------------------------------------------------------------------------------- /src/frontend/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | B站直播间弹幕服务 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /docs/apis/getLastMonthSignDays.md: -------------------------------------------------------------------------------- 1 | # getLastMonthSignDays 2 | 3 | ## 接口地址 4 | 5 | `https://api.live.bilibili.com/sign/getLastMonthSignDays` 6 | 7 | ## 方法 8 | 9 | `GET` 10 | 11 | ## 参数 12 | 13 | 无 14 | 15 | ## 返回示例 16 | 17 | ```json 18 | { 19 | "code": 0, 20 | "message": "0", 21 | "ttl": 1, 22 | "data": { 23 | "month": 6, 24 | "days": 30, 25 | "hadSignDays": 0, 26 | "signDaysList": [], 27 | "signBonusDaysList": [] 28 | } 29 | } 30 | ``` 31 | 32 | ## 接口目的 33 | -------------------------------------------------------------------------------- /resources/anatomy/modules/zRQJ.js: -------------------------------------------------------------------------------- 1 | /** 2 | * id: zRQJ 3 | * path: @bilibili-live/gold-miner 4 | */ 5 | 6 | (function(require,module,exports) { 7 | "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.GoldMiner=exports.multitask=void 0;const e=require("./GoldMiner");Object.defineProperty(exports,"GoldMiner",{enumerable:!0,get:function(){return e.GoldMiner}}),Object.defineProperty(exports,"multitask",{enumerable:!0,get:function(){return e.multitask}}),exports.default=e.GoldMiner; 8 | })() -------------------------------------------------------------------------------- /resources/anatomy/modules/yABZ.js: -------------------------------------------------------------------------------- 1 | /** 2 | * id: yABZ 3 | * path: ./live-player-tool 4 | */ 5 | 6 | (function(require,module,exports) { 7 | "use strict";function e(e,r){if(r.some(function(r){return r.value===e.code.toString()}))return e.code.toString();for(var t=r[r.length-1],n=r.length-1;n>=0;n-=1){if(Number(r[n].value)>e.code&&null!=t)return String(t.value);t=r[n]}return String(t.value)}Object.defineProperty(exports,"__esModule",{value:!0}),exports.getHEVCDowngradeQn=void 0,exports.getHEVCDowngradeQn=e; 8 | })() -------------------------------------------------------------------------------- /src/frontend/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | require('@rushstack/eslint-patch/modern-module-resolution') 3 | 4 | module.exports = { 5 | root: true, 6 | 'extends': [ 7 | 'plugin:vue/vue3-essential', 8 | 'eslint:recommended', 9 | '@vue/eslint-config-typescript', 10 | '@vue/eslint-config-prettier/skip-formatting' 11 | ], 12 | parserOptions: { 13 | ecmaVersion: 'latest' 14 | }, 15 | rules: { 16 | "vue/multi-word-component-names": ["off"], // 关闭组件名多单词 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /resources/anatomy/modules/Rr2x.js: -------------------------------------------------------------------------------- 1 | /** 2 | * id: Rr2x 3 | * path: ./biz-interface 4 | */ 5 | 6 | (function(require,module,exports) { 7 | "use strict";var o,t;Object.defineProperty(exports,"__esModule",{value:!0}),exports.EPlatform=exports.RoomStatus=void 0,function(o){o[o.Preparing=0]="Preparing",o[o.Live=1]="Live",o[o.Round=2]="Round"}(o=exports.RoomStatus||(exports.RoomStatus={})),function(o){o[o.Web=1]="Web",o[o.Android=2]="Android",o[o.Ios=3]="Ios",o[o.H5=4]="H5"}(t=exports.EPlatform||(exports.EPlatform={})); 8 | })() -------------------------------------------------------------------------------- /docs/apis/get_ab.md: -------------------------------------------------------------------------------- 1 | # get_ab 2 | 3 | ## 接口地址 4 | 5 | `https://api.live.bilibili.com/xlive/open-interface/v1/get_ab?keys=web_danmu_face` 6 | 7 | ## 方法 8 | 9 | `GET` 10 | 11 | ## 参数 12 | 13 | ### query 14 | 15 | - keys: web_danmu_face 16 | 17 | ## 返回示例 18 | 19 | ```json 20 | { 21 | "code": 0, 22 | "message": "0", 23 | "ttl": 1, 24 | "data": { 25 | "list": [ 26 | { 27 | "exp_key": "web_danmu_face", 28 | "exp_val": 1, 29 | "is_match": true 30 | } 31 | ] 32 | } 33 | } 34 | ``` 35 | -------------------------------------------------------------------------------- /resources/anatomy/modules/Qw13.js: -------------------------------------------------------------------------------- 1 | /** 2 | * id: Qw13 3 | * path: ./media-action 4 | */ 5 | 6 | (function(require,module,exports) { 7 | "use strict";Object.defineProperty(exports,"__esModule",{value:!0});var e=require("./event-bus"),n=new e.EventBus;function t(e,t){return n.on(e,t)}function i(){n.destroy(),n=new e.EventBus}null!=navigator.mediaSession&&(navigator.mediaSession.setActionHandler("pause",function(){n.emit("pause")}),navigator.mediaSession.setActionHandler("play",function(){n.emit("play")})),exports.default={on:t,destroy:i}; 8 | })() -------------------------------------------------------------------------------- /resources/anatomy/modules/fk22.js: -------------------------------------------------------------------------------- 1 | /** 2 | * id: fk22 3 | * path: ./create-element 4 | */ 5 | 6 | (function(require,module,exports) { 7 | "use strict";function t(t,e){void 0===e&&(e={});var s=document.createElement(t),r=Object.assign({classname:"",attributes:[],text:"",textAsHtml:!1},e);return s.className=r.classname,s[r.textAsHtml?"innerHTML":"textContent"]=r.text,r.attributes.forEach(function(t){var e=t[0],r=t[1];s.setAttribute(e,r)}),s}Object.defineProperty(exports,"__esModule",{value:!0}),exports.createElement=void 0,exports.createElement=t; 8 | })() -------------------------------------------------------------------------------- /src/frontend/backup/stability/sse.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SSE 6 | 7 | 8 | 9 | 10 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /docs/apis/unread.md: -------------------------------------------------------------------------------- 1 | # unread 2 | 3 | ## 接口地址 4 | 5 | `https://api.bilibili.com/x/msgfeed/unread` 6 | 7 | ## 方法 8 | 9 | `GET` 10 | 11 | ## 参数 12 | 13 | ### query 14 | 15 | - build: 0 16 | - mobi_app: web 17 | - captcha: 3e7b5667ddd18966ec1c521fcab05b93 18 | - ts: 1657418268423 19 | 20 | ## 返回示例 21 | 22 | ```json 23 | { 24 | "code": 0, 25 | "message": "0", 26 | "ttl": 1, 27 | "data": { 28 | "at": 0, 29 | "chat": 0, 30 | "like": 0, 31 | "reply": 0, 32 | "sys_msg": 0, 33 | "up": 0 34 | } 35 | } 36 | ``` 37 | 38 | ## 接口目的 39 | 40 | 查询未读消息数 41 | -------------------------------------------------------------------------------- /docs/apis/fetch_client_resource.md: -------------------------------------------------------------------------------- 1 | # fetch_client_resource 2 | 3 | ## 接口地址 4 | 5 | `https://api.live.bilibili.com/xlive/open-interface/v1/fetch_client_resource` 6 | 7 | ## 方法 8 | 9 | `GET` 10 | 11 | ## 参数 12 | 13 | ### query 14 | 15 | - business: pc_blink:0 16 | 17 | ## 返回示例 18 | 19 | ```json 20 | { 21 | "code": 0, 22 | "message": "0", 23 | "ttl": 1, 24 | "data": { 25 | "kv_list": [ 26 | { 27 | "hash": "3e67", 28 | "key": "pc_blink", 29 | "value": "{\"isOpen\":\"1\"}" 30 | } 31 | ] 32 | } 33 | } 34 | ``` 35 | 36 | ## 接口目的 37 | -------------------------------------------------------------------------------- /src/frontend/src/App.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 19 | -------------------------------------------------------------------------------- /docs/apis/getIpInfo.md: -------------------------------------------------------------------------------- 1 | # getIpInfo 2 | 3 | ## 接口地址 4 | 5 | `https://api.live.bilibili.com/xlive/web-room/v1/index/getIpInfo` 6 | 7 | ## 方法 8 | 9 | `GET` 10 | 11 | ## 参数 12 | 13 | 无 14 | 15 | ## 返回示例 16 | 17 | ```json 18 | { 19 | "code": 0, 20 | "message": "0", 21 | "ttl": 1, 22 | "data": { 23 | "addr": "2409:8a1e:76fb:a810:9082:2c9f:2233:1c85", 24 | "country": "中国", 25 | "province": "上海", 26 | "city": "上海", 27 | "isp": "移动", 28 | "latitude": "31.224349", 29 | "longitude": "121.476753" 30 | } 31 | } 32 | ``` 33 | 34 | ## 接口作用 35 | 36 | 获取直播间观看者的IP信息。 37 | -------------------------------------------------------------------------------- /resources/anatomy/modules/BuDO.js: -------------------------------------------------------------------------------- 1 | /** 2 | * id: BuDO 3 | * path: @bilibili-live/live-web-track 4 | */ 5 | 6 | (function(require,module,exports) { 7 | "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.createDirective=i,Object.defineProperty(exports,"Tracker",{enumerable:!0,get:function(){return e.Tracker}}),Object.defineProperty(exports,"BizCommonPerf",{enumerable:!0,get:function(){return t.BizCommonPerf}});var e=require("./core"),r=require("./directives/directives"),t=require("./perf");function i(t){var i=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=new e.Tracker(t,i);return(0,r.genDirectives)(n)} 8 | })() -------------------------------------------------------------------------------- /docs/apis/WebGetSignInfo.md: -------------------------------------------------------------------------------- 1 | # WebGetSignInfo 2 | 3 | ## 接口地址 4 | 5 | `https://api.live.bilibili.com/xlive/web-ucenter/v1/sign/WebGetSignInfo` 6 | 7 | ## 方法 8 | 9 | `GET` 10 | 11 | ## 参数 12 | 13 | 无 14 | 15 | ## 返回示例 16 | 17 | ```json 18 | { 19 | "code": 0, 20 | "message": "0", 21 | "ttl": 1, 22 | "data": { 23 | "text": "", 24 | "specialText": "", 25 | "status": 0, 26 | "allDays": 31, 27 | "curMonth": 7, 28 | "curYear": 2022, 29 | "curDay": 10, 30 | "curDate": "2022-7-10", 31 | "hadSignDays": 0, 32 | "newTask": 0, 33 | "signDaysList": [], 34 | "signBonusDaysList": [] 35 | } 36 | } 37 | ``` 38 | 39 | ## 接口作用 40 | -------------------------------------------------------------------------------- /test/persistence/read.ts: -------------------------------------------------------------------------------- 1 | import {readMessageFromFile, parseArrayBuffer} from "../../src/deno/utils.ts" 2 | import {FILE_NAME} from './config.ts' 3 | 4 | const messages = readMessageFromFile(FILE_NAME) 5 | console.log(`共读取到 ${messages.length} 条消息\n`) 6 | 7 | messages.forEach((message, idx) => { 8 | console.log(`message #${idx+1}:`) 9 | const packets = parseArrayBuffer(message) 10 | console.log(`include ${packets.length} packet`) 11 | 12 | // packets.forEach(packet => { 13 | // console.log(packet.op) 14 | // }) 15 | 16 | packets.forEach((packet, idx) => { 17 | console.log(`packet #${idx+1}`) 18 | console.log(packet) 19 | }) 20 | console.log() 21 | }) 22 | -------------------------------------------------------------------------------- /resources/anatomy/message-parser.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const fs = require('fs') 3 | 4 | const buffer = fs.readFileSync(path.resolve(__dirname, '../message-1693353700.bin')) 5 | 6 | const messages = [] 7 | let pos = 0 8 | 9 | while (pos < buffer.byteLength) { 10 | const header = buffer.subarray(pos, pos + 16) 11 | const packetSize = header.readInt32BE() 12 | const body = buffer.subarray(pos + 16, pos + packetSize) 13 | pos += packetSize 14 | 15 | const decode = JSON.parse(new TextDecoder().decode(body)) 16 | 17 | messages.push({ 18 | // header, 19 | // packetSize, 20 | // body, 21 | decode, 22 | }) 23 | } 24 | 25 | console.log(messages) 26 | -------------------------------------------------------------------------------- /src/backend/live/web.ts: -------------------------------------------------------------------------------- 1 | import {postFormData} from '../common/request.ts' 2 | 3 | /** 4 | * 发送弹幕 5 | * @param roomid 直播间id 6 | * @param msg 弹幕内容 7 | * @param cookie 用户cookie 8 | */ 9 | export function send(roomid: number, msg: string, cookie = '') { 10 | return postFormData('https://api.live.bilibili.com/msg/send', { 11 | msg, 12 | roomid, 13 | bubble: 0, 14 | mode: 1, 15 | room_type: 0, 16 | jumpfrom: 0, 17 | color: 16777215, 18 | fontsize: 25, 19 | csrf: '80b65a122df2585a015e3195e2bccc0a', 20 | csrf_token: '80b65a122df2585a015e3195e2bccc0a', 21 | rnd: 1693809581, 22 | }, { 23 | Cookie: cookie, 24 | }) 25 | } 26 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "blive-ws", 3 | "version": "2.0.0", 4 | "repository": "git@github.com:champkeh/blive-ws.git", 5 | "author": "champkeh ", 6 | "description": "B站直播间 WebSocket 服务,可用于实时获取直播间弹幕", 7 | "keywords": [ 8 | "danmaku", 9 | "B站弹幕", 10 | "直播间弹幕", 11 | "弹幕抓取", 12 | "弹幕游戏", 13 | "B站WebSocket" 14 | ], 15 | "license": "MIT", 16 | "scripts": { 17 | "dev:server": "deno run -A src/backend/server.ts", 18 | "dev:front": "vite src/frontend", 19 | "dev": "run-p dev:server dev:front", 20 | "dep": "pnpm i && pnpm --prefix src/frontend i", 21 | "build": "vite build src/frontend" 22 | }, 23 | "devDependencies": { 24 | "npm-run-all": "^4.1.5", 25 | "vite": "^5.0.12" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /resources/anatomy/modules/BEmL.js: -------------------------------------------------------------------------------- 1 | /** 2 | * id: BEmL 3 | * path: ./player-debug 4 | */ 5 | 6 | (function(require,module,exports) { 7 | "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.DebugUrlParamKeys=void 0;var e,o=new URL(location.href).searchParams;!function(e){e.P2PType="wpdP2PType",e.SupportHEVC="wpdSupportHEVC",e.Log2Console="wpdLog2Console",e.SocketMsg2Console="wpdSocketMsg2Console",e.DMRenderType="wpdDMRenderType"}(e=exports.DebugUrlParamKeys||(exports.DebugUrlParamKeys={}));var r=o.get(e.P2PType),p=o.get(e.Log2Console),l=o.get(e.SupportHEVC),s=o.get(e.SocketMsg2Console);exports.default={P2PType:null==r?null:Number(r),Log2Console:null!=p&&"0"!==p,SupportHEVC:null!=l&&"0"!==l,SocketMsg2Console:null!=s&&"0"!==s,DMRenderType:o.get(e.DMRenderType)}; 8 | })() -------------------------------------------------------------------------------- /src/frontend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowJs": true, 4 | "allowSyntheticDefaultImports": true, 5 | "target": "esnext", 6 | "module": "esnext", 7 | "moduleResolution": "node", 8 | "isolatedModules": true, 9 | "useDefineForClassFields": true, 10 | "preserveConstEnums": true, 11 | "strict": true, 12 | "jsx": "preserve", 13 | "skipLibCheck": true, 14 | "sourceMap": true, 15 | "resolveJsonModule": true, 16 | "esModuleInterop": true, 17 | "lib": ["esnext", "dom"], 18 | "baseUrl": ".", 19 | "types": ["vite/client"], 20 | "paths": { 21 | "@/*": ["src/*"] 22 | } 23 | }, 24 | "include": [ 25 | "src/**/*.ts", 26 | "src/**/*.d.ts", 27 | "src/**/*.vue", 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /docs/apis/getLotteryInfoWeb.md: -------------------------------------------------------------------------------- 1 | # getLotteryInfoWeb 2 | 3 | ## 接口地址 4 | 5 | `https://api.live.bilibili.com/xlive/lottery-interface/v1/lottery/getLotteryInfoWeb?roomid=25433884` 6 | 7 | ## 方法 8 | 9 | `GET` 10 | 11 | ## 参数 12 | 13 | ### query 14 | 15 | - roomid: 房间id 16 | 17 | ## 返回示例 18 | 19 | ```json 20 | { 21 | "code": 0, 22 | "message": "0", 23 | "ttl": 1, 24 | "data": { 25 | "pk": null, 26 | "guard": null, 27 | "gift": null, 28 | "storm": null, 29 | "silver": null, 30 | "activity_box": { 31 | "ACTIVITY_ID": 0, 32 | "ACTIVITY_PIC": "" 33 | }, 34 | "danmu": null, 35 | "anchor": null, 36 | "red_pocket": null, 37 | "popularity_red_pocket": null, 38 | "activity_box_info": null 39 | } 40 | } 41 | ``` 42 | 43 | ## 接口目的 44 | -------------------------------------------------------------------------------- /src/backend/apis/staticsSSE.ts: -------------------------------------------------------------------------------- 1 | import {getStatus} from "../client.ts" 2 | 3 | const encoder = new TextEncoder() 4 | 5 | export function staticsSSE(_: Request): Response { 6 | let timer: number 7 | const body = new ReadableStream({ 8 | start(controller) { 9 | timer = setInterval(() => { 10 | const statics = getStatus() 11 | controller.enqueue(encoder.encode(`event: statics\ndata: ${JSON.stringify(statics)}\n\n`)) 12 | }, 3000) 13 | }, 14 | cancel() { 15 | clearInterval(timer) 16 | } 17 | }) 18 | 19 | return new Response(body, { 20 | headers: { 21 | "Content-Type": "text/event-stream", 22 | "Access-Control-Allow-Origin": "*", 23 | } 24 | }) 25 | } 26 | -------------------------------------------------------------------------------- /src/backend/live/area.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 直播间分区 3 | * 4 | * 无需认证 5 | */ 6 | 7 | import {get} from '../common/request.ts' 8 | 9 | 10 | /** 11 | * 获取全部直播间分区列表 12 | * 13 | * @description https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/docs/live/live_area.md#%E8%8E%B7%E5%8F%96%E5%85%A8%E9%83%A8%E7%9B%B4%E6%92%AD%E9%97%B4%E5%88%86%E5%8C%BA%E5%88%97%E8%A1%A8 14 | */ 15 | export async function getAreaList() { 16 | const resp = await get('https://api.live.bilibili.com/room/v1/Area/getList') 17 | return await resp.json() 18 | } 19 | 20 | 21 | /** 22 | * 获取web平台全部直播间分区列表 23 | */ 24 | export async function getWebAreaList() { 25 | const resp = await get('https://api.live.bilibili.com/xlive/web-interface/v1/index/getWebAreaList?source_id=1') 26 | return await resp.json() 27 | } 28 | -------------------------------------------------------------------------------- /resources/anatomy/modules/kyLC.js: -------------------------------------------------------------------------------- 1 | /** 2 | * id: kyLC 3 | * path: ../utils 4 | */ 5 | 6 | (function(require,module,exports) { 7 | "use strict";function o(t){return(o="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(o){return typeof o}:function(o){return o&&"function"==typeof Symbol&&o.constructor===Symbol&&o!==Symbol.prototype?"symbol":typeof o})(t)}function t(o){var t,e;return null!==(e=null===(t=RegExp("[;\\s]*".concat(o,"=([^;\\s]+)")).exec(document.cookie))||void 0===t?void 0:t[1])&&void 0!==e?e:""}function e(){return/ipad|iphone os|android/i.test(navigator.userAgent)}function n(){}function r(t,e){if(t===e)return!0;if("object"===o(t)){for(var n in t)if(!r(t[n],e[n]))return!1;return!0}return!1}Object.defineProperty(exports,"__esModule",{value:!0}),exports.getCookie=t,exports.isMobile=e,exports.noop=n,exports.deepEqual=r; 8 | })() -------------------------------------------------------------------------------- /resources/anatomy/readme.md: -------------------------------------------------------------------------------- 1 | # 分析记录 2 | 3 | ## 关于文件名 4 | 5 | socket 目录下的文件名格式为 `ModuleID.ModuleName.js`,对应`room-player.min.js`中相关模块的代码。 6 | 7 | ## 关于研究对象 8 | 9 | `room-player.min.js`中关于 WebSocket 的模块总共有 2 个: 10 | 11 | - EfpE: 封装 WebSocket 连接的核心包 12 | - MWvE: 采用上面的包连接弹幕服务器 13 | 14 | 我们主要关注这 2 个模块。`MWvE`用来关注协议是否升级,`EfpE`用来关注 WebSocket 连接细节。 15 | 16 | ## 分析结果 17 | 18 | `MWvE`模块我们关注协议版本是否发生变化,以及其他一些连接参数是否有改动,如下图所示: 19 | ![img.png](img.png) 20 | 21 | 目前弹幕协议版本为 3 22 | 23 | `EfpE`模块主要关注websocket 连接细节,如下图所示,入口为索引1的函数: 24 | ![img_1.png](img_1.png) 25 | 26 | 这6个函数的说明: 27 | 28 | - 0:ws 连接用到的一些常量 29 | - 1:入口(直接导出2) 30 | - 2:入口(包含一些构建信息) 31 | - 3:ws 连接核心代码 32 | - 4:ws 消息头的编码结构配置 33 | - 5:工具类 34 | 35 | 当前分析的代码的构建信息如下: 36 | ```json5 37 | { 38 | version: "1.4.5", 39 | gitHash: "cbde3454", 40 | build: "41", 41 | bundleType: "release", 42 | } 43 | ``` 44 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: Deploy 2 | run-name: ${{ github.actor }} is running Deploy Actions 🚀 3 | 4 | on: 5 | push: 6 | branches: master 7 | 8 | jobs: 9 | deploy: 10 | name: Deploy 11 | runs-on: ubuntu-latest 12 | permissions: 13 | id-token: write # Needed for auth with Deno Deploy 14 | contents: read # Needed to clone the repository 15 | 16 | steps: 17 | - name: 拉取代码 18 | uses: actions/checkout@v4 19 | 20 | - name: 设置 pnpm 21 | uses: pnpm/action-setup@v2 22 | with: 23 | version: 7.x 24 | 25 | - name: 安装依赖并执行构建 26 | run: pnpm i && pnpm --prefix src/frontend i && npm run build 27 | 28 | - name: 部署到 Deno Deploy 29 | uses: denoland/deployctl@v1 30 | with: 31 | project: blive 32 | entrypoint: src/backend/server.ts 33 | -------------------------------------------------------------------------------- /src/backend/live/message.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 直播间信息流 3 | */ 4 | 5 | 6 | import {get} from '../common/request.ts' 7 | import {UserInfo, UserAgent} from "../config.ts" 8 | 9 | 10 | /** 11 | * 获取信息流认证秘钥 12 | * 13 | * @description https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/docs/live/message_stream.md#%E8%8E%B7%E5%8F%96%E4%BF%A1%E6%81%AF%E6%B5%81%E8%AE%A4%E8%AF%81%E7%A7%98%E9%92%A5 14 | */ 15 | export async function getDanmuInfo(rid: string | number) { 16 | const resp = await get('https://api.live.bilibili.com/xlive/web-room/v1/index/getDanmuInfo', { 17 | id: rid, 18 | type: 0, 19 | }, { 20 | cookie: UserInfo.cookie, 21 | Referer: 'https://live.bilibili.com/blanc/7734200?liteVersion=true', 22 | Origin: 'https://live.bilibili.com', 23 | 'User-Agent': UserAgent, 24 | }) 25 | return resp.json() 26 | } 27 | -------------------------------------------------------------------------------- /src/backend/live/redpocket.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 直播间人气红包 3 | * 4 | * 需要认证 Cookie(SESSDATA) 5 | */ 6 | 7 | import {get} from '../common/request.ts' 8 | 9 | const cookie = 'SESSDATA=d6a5dc76%2C1708351697%2Cd232c%2A81lwARvZDSBkcR813wNCoP0oqsnKAXCflyofNfrq0P8zRnp4czn2nxaOyvcIYa_MXwAUl4kAAAKwA' 10 | 11 | /** 12 | * 获取指定直播间的红包信息 13 | * 14 | * @description https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/docs/live/redpocket.md#%E8%8E%B7%E5%8F%96%E6%8C%87%E5%AE%9A%E7%9B%B4%E6%92%AD%E9%97%B4%E7%9A%84%E7%BA%A2%E5%8C%85%E4%BF%A1%E6%81%AF 15 | * @param rid 直播间id (支持短id) 16 | */ 17 | export async function getLotteryInfoWeb(rid: string | number) { 18 | const resp = await get('https://api.live.bilibili.com/xlive/lottery-interface/v1/lottery/getLotteryInfoWeb', { 19 | roomid: rid, 20 | }, { 21 | cookie: cookie, 22 | }) 23 | return resp.json() 24 | } 25 | -------------------------------------------------------------------------------- /src/backend/router.ts: -------------------------------------------------------------------------------- 1 | import {fetchRoomListSSE} from './apis/fetchRoomListSSE.ts' 2 | import {sse} from './apis/sse.ts' 3 | import {staticsSSE} from './apis/staticsSSE.ts' 4 | import {getDeployments} from "./apis/info.ts"; 5 | 6 | type APIHandler = (req: Request) => Response | Promise 7 | 8 | const config: Record = { 9 | '/api/fetchRoomList': fetchRoomListSSE, // 获取直播间列表 /api/fetchRoomList?count=30&type=online 10 | '/api/sse': sse, // 测试 SSE 11 | '/api/staticsSSE': staticsSSE, // 获取服务器状态 12 | '/api/deployments': getDeployments, 13 | } 14 | 15 | /** 16 | * 处理前端api请求 17 | * @param api 18 | * @param req 19 | */ 20 | export function routeApi(api: string, req: Request) { 21 | if (api in config) { 22 | return config[api](req) 23 | } else { 24 | return new Response(null, { 25 | status: 502, 26 | }) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /resources/anatomy/modules/wHWR.js: -------------------------------------------------------------------------------- 1 | /** 2 | * id: wHWR 3 | * path: ./toast 4 | */ 5 | 6 | (function(require,module,exports) { 7 | "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.setToastCssText=void 0;var t=null,e=function(e){t=e};function n(e,n){document.querySelectorAll(".web-player-toast").forEach(function(t){return t.remove()});var o=document.createElement("div");o.classList.add("web-player-toast"),o.textContent=e,o.style.cssText=null!=t?t:"\n font-size: 14px;\n color: #fff;\n background-color: rgba(0, 0, 0, 0.6);\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n z-index: 11;\n padding: 6px 12px;\n border-radius: 6px;\n transition: opacity 1s;\n ",n.appendChild(o),setTimeout(function(){o.style.opacity="0"},1500),setTimeout(function(){o.remove()},2500)}exports.setToastCssText=e,exports.default=n; 8 | })() -------------------------------------------------------------------------------- /src/backend/common/message-parser.ts: -------------------------------------------------------------------------------- 1 | export function handleMessage(data: any) { 2 | try { 3 | const msg_type = data.payload.cmd 4 | switch (msg_type) { 5 | case 'DANMU_MSG': 6 | onDanmuMsg(data.rid, data.payload) 7 | break 8 | } 9 | } catch (e) { 10 | console.log(e.message) 11 | } 12 | } 13 | 14 | /** 15 | * 监听【普通弹幕】: DANMU_MSG 16 | * @param rid 房间号 17 | * @param data 弹幕数据 18 | */ 19 | function onDanmuMsg(rid: number, data: any) { 20 | // 示例如下: 21 | // data.info = [ 22 | // [], 23 | // '主播好可爱啊', 24 | // [346319245, "crazywang1", 0, 0, 0, 10000, 1, ""], 25 | // [3, "生态", "籽岷"] 26 | // ] 27 | 28 | const info = { 29 | uid: data.info[2][0], 30 | uname: data.info[2][1], 31 | text: data.info[1], 32 | } 33 | console.log(`【${rid}】${info.uname}=> ${info.text}`) 34 | } 35 | -------------------------------------------------------------------------------- /src/backend/live/user.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 直播间用户实用 API 3 | * 4 | * 需要认证 Cookie(SESSDATA) 5 | */ 6 | 7 | import {get} from '../common/request.ts' 8 | 9 | const cookie = 'SESSDATA=d6a5dc76%2C1708351697%2Cd232c%2A81lwARvZDSBkcR813wNCoP0oqsnKAXCflyofNfrq0P8zRnp4czn2nxaOyvcIYa_MXwAUl4kAAAKwA' 10 | 11 | /** 12 | * 获取用户持有的粉丝勋章信息 13 | * 14 | * @description https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/docs/live/user.md#%E8%8E%B7%E5%8F%96%E7%94%A8%E6%88%B7%E6%8C%81%E6%9C%89%E7%9A%84%E7%B2%89%E4%B8%9D%E5%8B%8B%E7%AB%A0%E4%BF%A1%E6%81%AF 15 | * @param pageSize 每页大小 16 | * @param pageNo 页码 17 | */ 18 | export async function getMyMedals(pageSize = 10, pageNo = 0) { 19 | const resp = await get('https://api.live.bilibili.com/xlive/app-ucenter/v1/user/GetMyMedals', { 20 | page_size: pageSize, 21 | page: pageNo, 22 | }, { 23 | cookie: cookie, 24 | }) 25 | return resp.json() 26 | } 27 | -------------------------------------------------------------------------------- /resources/anatomy/modules/GlyE.js: -------------------------------------------------------------------------------- 1 | /** 2 | * id: GlyE 3 | * path: ./ua 4 | */ 5 | 6 | (function(require,module,exports) { 7 | "use strict";Object.defineProperty(exports,"__esModule",{value:!0});var i=navigator.userAgent,e={chrome:/(?!Chrom.*OPR)Chrom(?:e|ium)\/([0-9.]+)/,edg:/Edg\/([0-9._]+)/,safari:/Version\/([0-9._]+).*Safari/},n=i.includes("Windows NT"),r=i.includes("Mac OS X"),t=["iPad Simulator","iPhone Simulator","iPod Simulator","iPad","iPhone","iPod","iOS"].includes(navigator.platform)||i.includes("Mac")&&"ontouchend"in document,o=e.safari.test(i),u=e.chrome.test(i),a=e.edg.test(i),s=function(){function n(e){var n;return Number(null===(n=e.exec(i))||void 0===n?void 0:n[1].split(".")[0])}return u?n(e.chrome):a?n(e.edg):o?n(e.safari):0}();exports.default={isWin:function(){return n},isMac:function(){return r},isIOS:function(){return t},isSafari:function(){return o},isChrome:function(){return u},isEdg:function(){return a},version:function(){return s}}; 8 | })() -------------------------------------------------------------------------------- /resources/anatomy/modules/wPfs.js: -------------------------------------------------------------------------------- 1 | /** 2 | * id: wPfs 3 | * path: ./formatter 4 | */ 5 | 6 | (function(require,module,exports) { 7 | "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.Formatter=void 0;var t=require("./utils"),e=function(t){var e,n;return["".concat(location.href),"".concat(null==t.spmId?"":t.spmId+".").concat(t.evtId),"",Date.now(),"","","".concat(screen.width,"x").concat(screen.height),"",JSON.stringify(Object.assign(Object.assign({},null!==(e=t.data)&&void 0!==e?e:{}),null!==(n=null==t?void 0:t.tags)&&void 0!==n?n:{}))]},n={"011778":e,"000527":e,"000358":function(e){var n,o;return["".concat(location.href),"".concat(null!==(n=document.referrer)&&void 0!==n?n:""),"".concat(null==e.spmId?"":e.spmId+".").concat(e.evtId),Date.now(),null!==(o=(0,t.getCookie)("fts"))&&void 0!==o?o:"","".concat(screen.width,"x").concat(screen.height),(0,t.isMobile)()?2:1,null==e.data?"":JSON.stringify(e.data),"",0]}};exports.Formatter=n; 8 | })() -------------------------------------------------------------------------------- /src/backend/server.ts: -------------------------------------------------------------------------------- 1 | import {fs} from './deps.ts' 2 | import {initClient, initializeTaskLoop} from './client.ts' 3 | import {routeApi} from "./router.ts"; 4 | 5 | 6 | const pattern = new URLPattern({pathname: '/(api)/:name+'}) 7 | 8 | initializeTaskLoop() 9 | 10 | Deno.serve((req: Request) => { 11 | if ((req.headers.get('upgrade') || '').toLowerCase() === 'websocket') { 12 | // 客户端 websocket 请求 13 | const {socket, response} = Deno.upgradeWebSocket(req) 14 | initClient(socket) 15 | return response 16 | } else { 17 | const matchResult = pattern.exec(req.url) 18 | if (matchResult) { 19 | // api请求 20 | return routeApi(matchResult.pathname.input, req) 21 | } else { 22 | // 静态页面请求 23 | return fs.serveDir(req, { 24 | fsRoot: 'src/frontend/dist', 25 | quiet: true, 26 | }) 27 | } 28 | } 29 | }) 30 | -------------------------------------------------------------------------------- /resources/anatomy/modules/YMnC.js: -------------------------------------------------------------------------------- 1 | /** 2 | * id: YMnC 3 | * path: ./subtitle.less 4 | */ 5 | 6 | (function(require,module,exports) { 7 | var e='.web-player-subtitle{display:none;position:absolute;bottom:15%;left:50%;color:#fff;font-size:24px;width:800px;padding:5px 12px;box-sizing:border-box;border-radius:4px;background:rgba(0,0,0,.4);z-index:11;user-select:none}.web-player-subtitle>div{overflow:hidden;line-height:30px;max-height:60px;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;text-overflow:ellipsis}.web-player-subtitle.draggable{z-index:14;pointer-events:auto;cursor:move}.web-player-subtitle.dragging{background:rgba(0,0,0,.9);border:1px solid hsla(0,0%,100%,.3);box-shadow:0 4px 4px 0 rgba(0,0,0,.25)}.web-player-subtitle:after{content:"AI";position:absolute;top:0;right:4px;font-size:10px;opacity:.75;transform:scale(.8)}',t=document.createElement("style");t.type="text/css",t.appendChild(document.createTextNode(e)),document.head.appendChild(t); 8 | })() -------------------------------------------------------------------------------- /resources/anatomy/modules/Qsz6.js: -------------------------------------------------------------------------------- 1 | /** 2 | * id: Qsz6 3 | * path: ./video-enhance 4 | */ 5 | 6 | (function(require,module,exports) { 7 | "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.startVideoCAS=void 0;var e=require("@bilibili-live/web-player"),t=require("./cas");function i(i){var r=function(){},n=0,o=0,a=function(){var e=i.getVideoEl();if(null!=e){n=e.videoWidth,o=e.videoHeight;var a=(0,t.videoCAS)(e),v=a.canvas,d=a.supported,l=a.stop;d&&(r(),r=l,i.container.appendChild(v))}};a();var v=i.on(e.VideoEventType.FirstFrame,function(){r(),a()}),d=i.on(e.VideoEventType.Ended,r),l=i.on(e.VideoEventType.Error,r),c=i.on(e.LivePlayerEvent.Destroyed,r),s=0,u=i.on(e.VideoEventType.MetaData,function(){var e=0;clearInterval(s),s=window.setInterval(function(){if(e>15)clearInterval(s);else{e+=1;var t=i.getVideoEl();null!=t&&(t.videoWidth===n&&t.videoHeight===o||(clearInterval(s),r(),a()))}},1e3)});return function(){clearInterval(s),r(),v(),c(),u(),d(),l()}}exports.startVideoCAS=i; 8 | })() -------------------------------------------------------------------------------- /resources/anatomy/modules/DfF7.js: -------------------------------------------------------------------------------- 1 | /** 2 | * id: DfF7 3 | * path: @bilibili-live/web-player 4 | */ 5 | 6 | (function(require,module,exports) { 7 | "use strict";var e=this&&this.__createBinding||(Object.create?function(e,r,t,o){void 0===o&&(o=t);var i=Object.getOwnPropertyDescriptor(r,t);i&&("get"in i?r.__esModule:!i.writable&&!i.configurable)||(i={enumerable:!0,get:function(){return r[t]}}),Object.defineProperty(e,o,i)}:function(e,r,t,o){void 0===o&&(o=t),e[o]=r[t]}),r=this&&this.__exportStar||function(r,t){for(var o in r)"default"===o||Object.prototype.hasOwnProperty.call(t,o)||e(t,r,o)};Object.defineProperty(exports,"__esModule",{value:!0}),exports.VideoEventType=exports.ControllerEventType=void 0;var t=require("@bilibili-live/web-player-video");r(require("./live-player/live-player"),exports),r(require("./round-player"),exports);var o=require("./common/controller");Object.defineProperty(exports,"ControllerEventType",{enumerable:!0,get:function(){return o.ControllerEventType}}),exports.VideoEventType=t.EventType; 8 | })() -------------------------------------------------------------------------------- /src/frontend/public/assets/styles/reset.css: -------------------------------------------------------------------------------- 1 | /* reset.css */ 2 | body { 3 | line-height: 1.8; 4 | font-size: 16px; 5 | /*font-family: "SourceHanSerifCN-Bold",PingFang SC,-apple-system,SF UI Text,Lucida Grande,STheiti,Microsoft YaHei,sans-serif;*/ 6 | } 7 | .chapterTitle { 8 | margin-bottom: 18px; 9 | font-size: 24px; 10 | font-family: "SourceHanSerifCN-Bold",PingFang SC,-apple-system,SF UI Text,Lucida Grande,STheiti,Microsoft YaHei,sans-serif; 11 | color: #0d141e; 12 | } 13 | .custom-cover { 14 | display: flex; 15 | justify-content: center; 16 | } 17 | .readerChapterContent pre { 18 | background-color: #e6e6e6; 19 | color: #0d141e; 20 | font-family: Courier,Menlo,Monaco,Consolas,monospace,"PingFang SC",-apple-system,"SF UI Text","Lucida Grande",STheiti,"Microsoft YaHei",sans-serif; 21 | border: 1px solid #000; 22 | border-radius: 4px; 23 | padding: 12px; 24 | word-break: break-all; 25 | word-wrap: break-word; 26 | white-space: pre-wrap; 27 | } 28 | -------------------------------------------------------------------------------- /src/frontend/src/components/Display.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 24 | 25 | 36 | -------------------------------------------------------------------------------- /src/backend/config.ts: -------------------------------------------------------------------------------- 1 | export const UserAgent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36"; 2 | 3 | export const Referer = "https://live.bilibili.com/" 4 | export const Origin = "https://api.live.bilibili.com" 5 | 6 | 7 | const cookies: Record = { 8 | b_lsid: 'BA8E1E6F_18A62F6DACE', 9 | _uuid: '5A9BB1088-13A4-8EE1-E3ED-8D6AA68E728C59666infoc', 10 | buvid_fp: '09f8f73434d1ed43df8f7835ea7a9bf9', 11 | buvid3: '9D1B04CF-633D-4459-B777-20B4B798278436747infoc', 12 | buvid4: '769435BC-2CF7-1E18-AD20-D0E77FCEAC2C67946-023090509-urpZ0VYw0vUtM2tS1QHeEA%3D%3D', 13 | } 14 | const cookie = Object.keys(cookies).reduce((cookie, key) => { 15 | cookie += `${key}=${cookies[key]};` 16 | return cookie 17 | }, '') 18 | 19 | export const UserInfo = { 20 | uid: 549621446, 21 | sessdata: 'a0f51e95%2C1709428786%2C71aa8%2A91MonrXKgSsNSTuTR-eeqk2pbz2IAlurf_4C79iE6BvJ1RNI6KgtK5hlkmy1JuiIsG0vPQsgAALgA', 22 | buvid: cookies['buvid3'], 23 | cookie: cookie, 24 | } 25 | -------------------------------------------------------------------------------- /test/proxy.ts: -------------------------------------------------------------------------------- 1 | const set = new Set([1,2,3,4]) 2 | 3 | function proxySetDelete(originDelete) { 4 | return function (...args) { 5 | const ret = originDelete(...args) 6 | console.log("拦截 delete 操作,参数为: ", ...args, "操作结果: ", ret) 7 | return ret 8 | } 9 | } 10 | 11 | const proxy = new Proxy(set, { 12 | get: function (target, p, receiver) { 13 | const instance = Reflect.get(target, p) 14 | if (p === 'delete') { 15 | return proxySetDelete(instance.bind(target)) 16 | } 17 | return instance 18 | } 19 | }) 20 | 21 | console.log(proxy.size) 22 | proxy.delete(2) 23 | console.log(proxy.size) 24 | 25 | 26 | // let set = new Set(); 27 | // let proxy = new Proxy(set, { 28 | // get(target, prop, receiver) { 29 | // if (prop === 'delete') { 30 | // console.log('代理delete') 31 | // return () => {} 32 | // } 33 | // return target[prop].bind(target) 34 | // } 35 | // }); 36 | // proxy.add('test'); 37 | // proxy.delete('test'); 38 | // console.log(proxy) 39 | -------------------------------------------------------------------------------- /docs/apis/getRoomPlayInfo.md: -------------------------------------------------------------------------------- 1 | # getRoomPlayInfo 2 | 3 | ## 接口地址 4 | 5 | `https://api.live.bilibili.com/xlive/web-room/v2/index/getRoomPlayInfo?room_id=25433884&protocol=0,1&format=0,1,2&codec=0,1&qn=0&platform=web&ptype=8&dolby=5&panorama=1 6 | ` 7 | 8 | ## 方法 9 | 10 | `GET` 11 | 12 | ## 参数 13 | 14 | ### query 15 | 16 | - room_id: 房间id 17 | - protocol: 0,1 18 | - format: 0,1,2 19 | - codec: 0,1 20 | - qn: 0 21 | - platform: web 22 | - ptype: 8 23 | - dolby: 5 24 | - panorama: 1 25 | 26 | ## 返回示例 27 | 28 | ```json 29 | { 30 | "code": 0, 31 | "message": "0", 32 | "ttl": 1, 33 | "data": { 34 | "room_id": 25433884, 35 | "short_id": 0, 36 | "uid": 549621446, 37 | "is_hidden": false, 38 | "is_locked": false, 39 | "is_portrait": false, 40 | "live_status": 0, 41 | "hidden_till": 0, 42 | "lock_till": 0, 43 | "encrypted": false, 44 | "pwd_verified": true, 45 | "live_time": 0, 46 | "room_shield": 0, 47 | "all_special_types": [], 48 | "playurl_info": null 49 | } 50 | } 51 | ``` 52 | 53 | ## 接口目的 54 | 55 | 获取直播间的一些信息 56 | -------------------------------------------------------------------------------- /src/backend/apis/sse.ts: -------------------------------------------------------------------------------- 1 | import {sleep} from "../utils.ts" 2 | 3 | 4 | const encoder = new TextEncoder() 5 | 6 | const events = [ 7 | 'data: YHOO\ndata: +2\ndata: 10\n\n', 8 | ': test stream\n\ndata: first event\nid: 1\n\ndata:second event\nid\n\ndata: third event\n\n', 9 | 'data\n\ndata\ndata\n\ndata:\n\n', 10 | 'data:test\n\ndata: test\n\n', 11 | ] 12 | 13 | export function sse(_: Request): Response { 14 | // console.log(_.headers) 15 | const body = new ReadableStream({ 16 | start: async (controller) => { 17 | for (const event of events) { 18 | console.log(JSON.stringify(event)) 19 | 20 | controller.enqueue(encoder.encode(event)) 21 | 22 | await sleep(1000) 23 | } 24 | }, 25 | cancel(reason) { 26 | console.log(reason) 27 | } 28 | }) 29 | 30 | return new Response(body, { 31 | headers: { 32 | "Content-Type": "text/event-stream", 33 | "Access-Control-Allow-Origin": "*", 34 | } 35 | }) 36 | } 37 | -------------------------------------------------------------------------------- /src/backend/apis/info.ts: -------------------------------------------------------------------------------- 1 | import {dotenv} from "../deps.ts" 2 | import {get} from "../common/request.ts"; 3 | import {jsonResponse} from "../utils.ts"; 4 | 5 | const env = await dotenv.load() 6 | 7 | const projectId = "c995b30b-d571-4fb3-9e9b-8267e537a5d7" 8 | const token = env["ACCESS_TOKEN"] 9 | 10 | interface DeploymentItem { 11 | id: string 12 | projectId: string 13 | description: string 14 | status: "success" 15 | domains: string[] 16 | createdAt: string 17 | updatedAt: string 18 | } 19 | 20 | export async function getDeployments(_: Request) { 21 | const resp = await get(`https://api.deno.com/v1/projects/${projectId}/deployments`, { 22 | order: 'desc', 23 | page: 1, 24 | limit: 5, 25 | }, { 26 | Authorization: `Bearer ${token}`, 27 | }) 28 | const data = await resp.json() 29 | return jsonResponse(data.map((_: DeploymentItem) => ({ 30 | id: _.id, 31 | status: _.status, 32 | description: _.description, 33 | createdAt: _.createdAt, 34 | updatedAt: _.updatedAt, 35 | }))) 36 | } 37 | -------------------------------------------------------------------------------- /src/backend/live/room.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 直播间列表 3 | */ 4 | 5 | 6 | import {get} from '../common/request.ts' 7 | 8 | 9 | /** 10 | * 获取推荐的直播间列表 11 | * @param page 页码 12 | * @param pageSize 每页大小 13 | * @param platform 平台 14 | */ 15 | export async function getUserRecommend(page = 1, pageSize = 30, platform = 'web') { 16 | const resp = await get('https://api.live.bilibili.com/xlive/web-interface/v1/second/getUserRecommend', { 17 | page, 18 | page_size: pageSize, 19 | platform, 20 | }) 21 | return resp.json() 22 | } 23 | 24 | 25 | /** 26 | * 获取直播间列表 27 | * @param sort 排序 online 根据人气排序(人气直播),livetime 根据开播时间排序(最新开播) 28 | * @param page 页码 29 | * @param pageSize 每页大小 30 | * @param platform 平台 31 | */ 32 | export async function getListByArea(sort: 'online' | 'livetime' = 'online', page = 1, pageSize = 30, platform = 'web') { 33 | const resp = await get('https://api.live.bilibili.com/xlive/web-interface/v1/second/getListByArea', { 34 | page, 35 | page_size: pageSize, 36 | platform, 37 | sort, 38 | }) 39 | return resp.json() 40 | } 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 champ 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 | -------------------------------------------------------------------------------- /src/frontend/src/utils/index.ts: -------------------------------------------------------------------------------- 1 | export const proxyHost = import.meta.env.MODE === 'development' ? 'http://localhost:8000' : '' 2 | 3 | export function sleep(duration: number) { 4 | return new Promise(resolve => setTimeout(resolve, duration)) 5 | } 6 | 7 | export function padLeft(data: number | string, len: number = 3, char = '0') { 8 | return String(data).padStart(len, char) 9 | } 10 | 11 | const ridRE = /^(https?:\/\/)?live.bilibili.com\/(?\d+)/ 12 | 13 | export function getRoomId(text: string): number { 14 | text = text.trim() 15 | if (/^\d+$/.test(text)) { 16 | return +text 17 | } else if (text.match(ridRE)) { 18 | return +text.match(ridRE)!.groups!.roomid 19 | } else { 20 | throw new Error('请输入合法的房间号') 21 | } 22 | } 23 | 24 | /** 25 | * 获取 ws 地址 26 | */ 27 | export function getWsEndpoint(): string { 28 | if (window.location.protocol === 'http:') { 29 | return `ws://${window.location.host}` 30 | } else if (window.location.protocol === 'https:') { 31 | return `wss://${window.location.host}` 32 | } else { 33 | throw new Error('获取ws地址失败') 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /docs/apis/send.md: -------------------------------------------------------------------------------- 1 | # send 2 | 3 | ## 接口目的 4 | 5 | 发送弹幕 6 | 7 | ## 接口地址 8 | 9 | `https://api.live.bilibili.com/msg/send` 10 | 11 | ## 方法 12 | 13 | `POST` 14 | 15 | ## 参数 16 | 17 | ### multipart/form-data 18 | 19 | - bubble: 0 20 | - msg: 消息内容 21 | - color: 弹幕颜色 22 | - mode: 1 23 | - fontsize: 25 24 | - rnd: 1657780321 25 | - roomid: 25433884 26 | - csrf: 4aa01086f3173403fdc64623e4dfa86d 27 | - csrf_token: 4aa01086f3173403fdc64623e4dfa86d 28 | 29 | ## 返回示例 30 | 31 | ```json 32 | { 33 | "code": 0, 34 | "data": { 35 | "mode_info": { 36 | "mode": 0, 37 | "show_player_type": 0, 38 | "extra": "{\"send_from_me\":true,\"mode\":0,\"color\":16777215,\"dm_type\":0,\"font_size\":25,\"player_mode\":1,\"show_player_type\":0,\"content\":\"123\",\"user_hash\":\"2826213253\",\"emoticon_unique\":\"\",\"bulge_display\":0,\"recommend_score\":10,\"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\":\"\"}" 39 | } 40 | }, 41 | "message": "", 42 | "msg": "" 43 | } 44 | ``` 45 | -------------------------------------------------------------------------------- /resources/anatomy/modules/V6Hm.js: -------------------------------------------------------------------------------- 1 | /** 2 | * id: V6Hm 3 | * path: ./qvb-p2p 4 | */ 5 | 6 | (function(require,module,exports) { 7 | "use strict";var e=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(exports,"__esModule",{value:!0});var r=require("@bilibili-live/web-player-common"),t=e(require("@bilibili-live/fmp4.js")),i=e(require("./qvb-fetcher")),n=!1;function o(e){var o=e.corePlayer,u=e.videoEl;return o instanceof t.default.fMp4Player?i.default.isSupported()?n?(r.logger.warn("p2p P2P_ERROR_FLAG fallback"),!1):(o.createFetcher=function(e,t,o){var l=new i.default(function(r){e(new a(r))},t,function(){for(var e=[],t=0;t=200&&e<=299,this.headers=new u}}(); 8 | })() -------------------------------------------------------------------------------- /test/message/danmu.ts: -------------------------------------------------------------------------------- 1 | import {ws} from "../ws.ts" 2 | import {parseArrayBuffer, now} from "../../src/deno/utils.ts" 3 | 4 | 5 | function handleMessage(body: any) { 6 | let info = {uname: '', uid: '', text: ''} 7 | 8 | switch (body.cmd) { 9 | case 'DANMU_MSG': 10 | info = { 11 | uid: body.info[2][0], 12 | uname: body.info[2][1], 13 | text: body.info[1], 14 | } 15 | console.log(`[${now()}] ${info.uname}(${info.uid}): ${info.text}`) 16 | break 17 | } 18 | } 19 | 20 | ws.addEventListener('message', (event: MessageEvent) => { 21 | const packets = parseArrayBuffer(event.data) 22 | for (const packet of packets) { 23 | switch (packet.op) { 24 | case 8: 25 | console.log('认证应答包: ', packet.body) 26 | break 27 | case 3: 28 | console.log('心跳应答包: ', packet.body) 29 | break 30 | case 5: 31 | // console.log('普通消息包: ', (packet.body as NormalMessageBody).cmd) 32 | handleMessage(packet.body) 33 | break 34 | } 35 | } 36 | }) 37 | -------------------------------------------------------------------------------- /resources/anatomy/modules/hY1T.js: -------------------------------------------------------------------------------- 1 | /** 2 | * id: hY1T 3 | * path: ./log-storage 4 | */ 5 | 6 | (function(require,module,exports) { 7 | "use strict";var e;Object.defineProperty(exports,"__esModule",{value:!0}),exports.logStorageConfig=exports.goldMiner=exports.logLevelColos=void 0;var o=require("@bilibili-live/web-player-common"),r=require("@bilibili-live/gold-miner"),t=require("./biz-common");exports.logLevelColos=((e={})[o.LogLevel.Error]="red",e[o.LogLevel.Info]="blue",e[o.LogLevel.Warn]="orange",e[o.LogLevel.Debug]="gray",e);var n,i="indexedDB"in window||"webkitIndexedDB"in window||"mozIndexedDB"in window;function l(e){i&&exports.goldMiner.recieveMsg(e,{source:"socket-report",latestCount:20})}function s(e){i&&(exports.goldMiner.init({roomId:e.roomId,uid:(0,t.getUserId)(),ref:window.location.href,createTime:Date.now(),updateTime:Date.now()}),null!=e.socket&&(null!=n&&n(),n=e.socket.on("message",l)))}exports.goldMiner=new r.GoldMiner({appName:"live-web-player"}),exports.goldMiner.formatter(function(e){return{content:e.$content,ts:e.$createTime,color:exports.logLevelColos[e.$type]}}),i&&o.logger.watch(function(e){exports.goldMiner.log({content:e.content,ts:e.ts,type:e.level})}),exports.logStorageConfig=s; 8 | })() -------------------------------------------------------------------------------- /resources/anatomy/modules/KaYZ.js: -------------------------------------------------------------------------------- 1 | /** 2 | * id: KaYZ 3 | * path: ./compatibility-check 4 | */ 5 | 6 | (function(require,module,exports) { 7 | "use strict";var e=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(exports,"__esModule",{value:!0}),exports.isHardSupportHEVC=void 0;var t=e(require("./ua"));function r(e,t){var r;void 0===t&&(t="video/mp4");var o=document.createElement("video");if("function"!=typeof o.canPlayType||"function"!=typeof(null===(r=window.MediaSource)||void 0===r?void 0:r.isTypeSupported))return!1;var n="".concat(t,'; codecs="').concat(e,'"'),a=o.canPlayType(n).toLowerCase();return!("maybe"!==a&&"probably"!==a||!window.MediaSource.isTypeSupported(n))}function o(){var e,t=document.createElement("canvas"),r=!1;try{var o=null!==(e=t.getContext("webgl"))&&void 0!==e?e:t.getContext("experimental-webgl");if(null==o)return r;var n=o.getExtension("WEBGL_debug_renderer_info");if(null==n)return r;-1===o.getParameter(n.UNMASKED_RENDERER_WEBGL).toString().toLowerCase().indexOf("swiftshader")&&(r=!0)}catch(a){}return r}function n(){return t.default.isIOS()||r("hvc1.1.6.L123")&&(t.default.isSafari()||o()&&(t.default.isChrome()||t.default.isEdg()&&t.default.version()>99))}exports.isHardSupportHEVC=n; 8 | })() -------------------------------------------------------------------------------- /resources/anatomy/modules/NwTf.js: -------------------------------------------------------------------------------- 1 | /** 2 | * id: NwTf 3 | * path: ./report 4 | */ 5 | 6 | (function(require,module,exports) { 7 | "use strict";var e=this&&this.__awaiter||function(e,t,o,a){return new(o||(o=Promise))(function(r,i){function n(e){try{u(a.next(e))}catch(t){i(t)}}function c(e){try{u(a.throw(e))}catch(t){i(t)}}function u(e){var t;e.done?r(e.value):(t=e.value,t instanceof o?t:new o(function(e){e(t)})).then(n,c)}u((a=a.apply(e,t||[])).next())})};Object.defineProperty(exports,"__esModule",{value:!0}),exports.reportLog=void 0;const t=require("./ajax"),o=require("./utils");function a(a,r=""){return e(this,void 0,void 0,function*(){const e=new Blob(["--"+JSON.stringify(a.data)]);"prod"===r&&(r=""),r&&(r=`ff-${r}-`);try{const{location:n}=yield(0,t.ajax)(`//${r?"uat-":""}api.bilibili.com/x/upload/web/image`,{method:"POST",data:(0,o.Object2FormData)({bucket:"live-log-ttl",dir:a.appName,file:e})});if(null==n||""===n)return!1;const c={appName:a.appName,userID:(0,o.getUserId)(),source:a.sourceType,url:n.replace(".txt","")};return null!=a.appId&&(c.appID=a.appId),null!=a.msgId&&(c.msgID=a.msgId),yield(0,t.ajax)(`//${r}api.live.bilibili.com/xlive/open-interface/v1/webLogAgent/setWebAppLogRecord`,{method:"POST",data:(0,o.Object2FormData)(c)}),!0}catch(i){return console.warn(i),!1}})}exports.reportLog=a; 8 | })() -------------------------------------------------------------------------------- /resources/anatomy/modules/cQRo.js: -------------------------------------------------------------------------------- 1 | /** 2 | * id: cQRo 3 | * path: ./utils 4 | */ 5 | 6 | (function(require,module,exports) { 7 | "use strict";function t(t){var e,r;const{cookie:o}=document;return null==o?"":null!==(r=null===(e=o.split(";").filter(t=>t.trim().length>0).map(t=>t.split("=")).map(([t,e])=>[t.trim(),e.trim()]).find(([e])=>e===t))||void 0===e?void 0:e[1])&&void 0!==r?r:""}function e(t){return new Promise(e=>{setTimeout(e,t)})}function r(){return Number(t("DedeUserID"))}function o(){return n(8)}function n(t=12){const e=[];for(let r=0;r=1e3){var l=Math.round(1e3*t/f);if(r=c,t=0,i.push(l),10===(i=i.slice(-10)).length)for(var s=Math.max.apply(Math,i),d=0,v=e;d = {}) { 42 | const opts = { 43 | ...DEFAULT_PLAY_URL_OPTIONS, 44 | ...options, 45 | } 46 | const resp = await get('https://api.live.bilibili.com/room/v1/Room/playUrl', { 47 | cid: rid, 48 | ...opts, 49 | }) 50 | return await resp.json() 51 | } 52 | -------------------------------------------------------------------------------- /resources/anatomy/modules/weyG.js: -------------------------------------------------------------------------------- 1 | /** 2 | * id: weyG 3 | * path: ./ui-components/preround-conuter 4 | */ 5 | 6 | (function(require,module,exports) { 7 | "use strict";Object.defineProperty(exports,"__esModule",{value:!0});var n=require("@bilibili-live/web-player-common");function e(e,r,a){void 0===a&&(a=300),n.logger.info("start round count down");var i=e.getElementsByClassName("web-player-video-round-counter");1===i.length&&i[0].remove();var l=document.createElement("div");l.innerHTML='\n

\n ').concat(t(a),'\n 后\n

\n

主播的投稿视频将轮流播放

\n '),l.classList.add("web-player-video-round-counter"),l.style.cssText="\n width: 400px;\n position: absolute;\n z-index: 10;\n pointer-events: none;\n left: 50%;\n top: 50%;\n margin-left: -200px;\n margin-top: -66px;\n color: #aaa;\n ";var c=l.getElementsByTagName("span");c[0].style.fontSize="50px",e.append(l);var s=window.setInterval(function(){a>1?c[0].innerText=t(--a):(clearInterval(s),l.remove(),r().catch(function(e){n.logger.error(e)}))},1e3);return function(){clearInterval(s),l.remove()}}function t(n){var e=Math.floor(n/60),t=Math.floor(n%60);return"0".concat(e,":").concat(t<10?"0"+t.toString():t)}function o(n){return"\n text-align: center;\n line-height: 50px;\n letter-spacing: 3px;\n font-weight: normal;\n font-size: ".concat(n,"px;\n line-height: 1;\n ")}exports.default=e; 8 | })() -------------------------------------------------------------------------------- /resources/anatomy/modules/U3yD.js: -------------------------------------------------------------------------------- 1 | /** 2 | * id: U3yD 3 | * path: ./user-setting 4 | */ 5 | 6 | (function(require,module,exports) { 7 | "use strict";var t=this&&this.__assign||function(){return(t=Object.assign||function(t){for(var e,n=1,a=arguments.length;n { 29 | return `wss://${host.host}:${host.wss_port}/sub` 30 | }) 31 | 32 | console.log('key: ', key) 33 | console.log('hosts: ', hostList) 34 | -------------------------------------------------------------------------------- /src/frontend/public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/anatomy/modules/jYwk.js: -------------------------------------------------------------------------------- 1 | /** 2 | * id: jYwk 3 | * path: ./connects/conn-subtitle 4 | */ 5 | 6 | (function(require,module,exports) { 7 | "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.connSubtitleAndPlayer=void 0;var e=require("@bilibili-live/web-player"),i=require("@bilibili-live/web-player-common"),t=require("@bilibili-live/web-player-subtitle");function r(r){var l=i.userSetting.get("subtitle"),a=new t.Subtitle(r.container,l);function n(){var e=r.getVideoEl();null!=e&&a.setVideoSource(e)}r.ctrl.getCtrlUI().subtitleStatus={visible:l.visible,draggable:l.draggable},a.visible=l.visible,a.draggable=l.draggable,n();var b=r.ctrl.onChange(function(e,t){var r,l;if("subtitle"===e&&null!=t){var n=t;("draggable"in n||"visible"in n)&&(a.draggable=null!==(r=n.draggable)&&void 0!==r?r:a.draggable,a.visible=null!==(l=n.visible)&&void 0!==l?l:a.visible,i.userSetting.set("subtitle",{visible:a.visible,draggable:a.draggable}))}}),o=r.on(i.ExternalEventType.SEIParseData,function(e,t,r){if(e===i.SEIType.BILILIVESUBTITLE)try{for(var l=JSON.parse(r),n=0,b=l;n { 21 | // 进入房间命令 22 | socket.send(JSON.stringify({ 23 | cmd: 'enter', // 命令名,必填 24 | rid, // 房间号,必填 25 | events: ['DANMU_MSG'], // 监听这个房间中的事件列表,必填 26 | })) 27 | 28 | // 退出房间命令 29 | // socket.send(JSON.stringify({ 30 | // cmd: 'leave', // 命令名,必填 31 | // rid, // 房间号,必填 32 | // })) 33 | 34 | // 退出所有房间 35 | // socket.send(JSON.stringify({ 36 | // cmd: 'exit', 37 | // })) 38 | }) 39 | 40 | // 接收进入房间时`events`参数所指定的消息 41 | socket.addEventListener('message', (event) => { 42 | try { 43 | // 接收到的消息,格式为 { rid: 房间号, payload: {} } 44 | const data = JSON.parse(event.data) 45 | if (data.payload.cmd === 'authorized') { 46 | // authorized 是连接成功收到的第一条消息 47 | } else { 48 | // 要监听的弹幕数据 49 | // 在这里写你的业务逻辑 50 | console.log(data) 51 | } 52 | } catch (e) { 53 | // 忽略心跳数据 54 | } 55 | }) 56 | -------------------------------------------------------------------------------- /resources/anatomy/modules/ZYDn.js: -------------------------------------------------------------------------------- 1 | /** 2 | * id: ZYDn 3 | * path: ./drag-element 4 | */ 5 | 6 | (function(require,module,exports) { 7 | "use strict";function e(e,t,n){return Math.max(t,Math.min(n,e))}function t(t,n,o){var i=0,r=0,u=0,s=0,c=100,v=100,d=0,m=0,a=0,l=function(e){var o=e.clientX,l=e.clientY;u=o,s=l;var p=n.getBoundingClientRect(),f=p.x,h=p.y;d=f,m=h;var E=t.getBoundingClientRect(),y=E.x,L=E.y,g=E.width,b=E.height;i=y,r=L,a=b,c=(n.clientWidth-g)/n.clientWidth*100,v=100-b/n.clientHeight*100},p=function(o){var l=o.clientX,p=o.clientY,f=(i+l-u-d)/n.clientWidth*100,h=100-(r+p-s-m+a)/n.clientHeight*100;t.style.transform="",t.style.left="".concat(e(f,0,c),"%"),t.style.bottom="".concat(e(h,15,v),"%")},f=function(e){l(e.touches[0])},h=function(e){e.preventDefault(),p(e.touches[0])},E=function(e){e.preventDefault(),e.stopPropagation(),p(e)},y=function(e){l(e),document.body.addEventListener("mouseup",L),document.body.addEventListener("mousemove",E),o.onMouseDown()},L=function e(t){t.preventDefault(),t.stopPropagation(),document.body.removeEventListener("mouseup",e),document.body.removeEventListener("mousemove",E),o.onMouseUp()};return t.addEventListener("touchstart",f,{passive:!1}),t.addEventListener("touchmove",h,{passive:!1}),t.addEventListener("mousedown",y),function(){t.removeEventListener("touchstart",f),t.removeEventListener("touchmove",h),t.removeEventListener("mousedown",y),document.body.removeEventListener("mouseup",L),document.body.removeEventListener("mousemove",E)}}Object.defineProperty(exports,"__esModule",{value:!0}),exports.dragElement=void 0,exports.dragElement=t; 8 | })() -------------------------------------------------------------------------------- /resources/anatomy/modules/z0Gg.js: -------------------------------------------------------------------------------- 1 | /** 2 | * id: z0Gg 3 | * path: ./retry-strategy 4 | */ 5 | 6 | (function(require,module,exports) { 7 | "use strict";var e,r;Object.defineProperty(exports,"__esModule",{value:!0}),exports.useRetryStrategy=exports.RetryStrategies=void 0;var t,o=require("@bilibili-live/web-player-common"),P=((e={})[o.P2PType.HLS_BILI]=o.P2PType.NONE,e[o.P2PType.FLV_QVB]=o.P2PType.HLS_BILI,e[o.P2PType.NONE]=o.P2PType.HLS_NOT_P2P,e[o.P2PType.HLS_NOT_P2P]=o.P2PType.NONE,e);!function(e){e[e.Retry=0]="Retry",e[e.SwitchLine=1]="SwitchLine",e[e.ChangeProtocol=2]="ChangeProtocol",e[e.Failed=3]="Failed"}(t=exports.RetryStrategies||(exports.RetryStrategies={}));var c=((r={})[o.ProtocolType.HTTP_HLS]=[o.P2PType.HLS_BILI,o.P2PType.HLS_NOT_P2P],r[o.ProtocolType.HTTP_STREAM]=[o.P2PType.FLV_QVB,o.P2PType.NONE],r);function y(e,r,y){if(null==e)return t.Retry;var T=e.getCurP2PType();switch(y-r){case 1:case 2:case 3:return o.logger.error("maxRetryTimes: ".concat(y," retry count: ").concat(r,", auto change line")),e.setCurLine(e.getCurLine()+1),t.SwitchLine;case 0:if(null!=o.wpd.P2PType)return t.Retry;if(o.logger.error("retry count: ".concat(r,", auto change Protocol")),c[o.ProtocolType.HTTP_STREAM].includes(T)&&!e.hasHLSPlayerSupportStream())return t.Failed;var a=P[T];return c[o.ProtocolType.HTTP_HLS].includes(a)?e.useStream({protocol:o.ProtocolType.HTTP_HLS,expectP2PType:a}):e.useStream({protocol:o.ProtocolType.HTTP_STREAM,expectP2PType:a}),t.ChangeProtocol;default:return o.logger.error("maxRetryTimes: ".concat(y," retry count: ").concat(r,", retry")),t.Retry}}exports.useRetryStrategy=y; 8 | })() -------------------------------------------------------------------------------- /docs/refactor.md: -------------------------------------------------------------------------------- 1 | # 重构为 WebSocket 代理服务 2 | 3 | ## 背景 4 | 5 | 首先说下遇到的问题: 6 | 7 | 这个项目由于某些原因目前只支持在浏览器中获取弹幕,如果想要在服务器端用 nodejs 获取,就只能把相关工具做一下兼容。但也只能在 js 语言中使用,换成 python 或者 golang,相关逻辑又需要重写一遍。 8 | 9 | 因此,我有了一个新的想法,就是将获取弹幕的逻辑封装成 websocket 服务的形式提供,这样不管是浏览器还是 nodejs,甚至是 python 和 golang,只需要使用它们各自的 websocket 10 | 客户端去连接这个代理服务,就都可以实时获取弹幕了。 11 | 12 | 重构之后,这个代理服务需要使用服务器进行部署,但好在有很多免费的服务器可以使用(比如 Deno Deploy,虽然有额度限制)。另外,也支持私有部署,即部署到你的私有服务器仅供自己使用。 13 | 14 | ## 重构之后的使用方式 15 | 16 | ### 浏览器 17 | 18 | ```js 19 | const socket = new WebSocket('wss://blive.deno.dev') 20 | 21 | socket.addEventListener('open', () => { 22 | // 进入房间 23 | socket.send(JSON.stringify({ 24 | cmd: 'enter', 25 | rid: 123, 26 | events: ['DANMU_MSG'], 27 | })) 28 | 29 | // 离开房间 30 | socket.send(JSON.stringify({ 31 | cmd: 'leave', 32 | rid: 123, 33 | })) 34 | }) 35 | 36 | socket.addEventListener('message', ({data}) => { 37 | console.log(data) 38 | }) 39 | ``` 40 | 41 | ### NodeJS 42 | 43 | ```js 44 | import WebSocket from 'ws' 45 | 46 | const socket = new WebSocket('wss://blive.deno.dev') 47 | 48 | socket.on('open', () => { 49 | // 进入房间 50 | socket.send(JSON.stringify({ 51 | cmd: 'enter', 52 | rid: 123, 53 | events: ['DANMU_MSG'], 54 | })) 55 | 56 | // 离开房间 57 | socket.send(JSON.stringify({ 58 | cmd: 'leave', 59 | rid: 123, 60 | })) 61 | }); 62 | 63 | socket.on('message', (data) => { 64 | console.log(data) 65 | }); 66 | ``` 67 | 68 | ### golang 69 | 70 | todo 71 | 72 | 73 | ## 测试地址 74 | 75 | `wss://blive.deno.dev` 76 | -------------------------------------------------------------------------------- /src/frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "docx2html", 3 | "private": true, 4 | "version": "0.1.1", 5 | "type": "module", 6 | "scripts": { 7 | "build": "run-p type-check build-only", 8 | "build-only": "vite build", 9 | "type-check": "vue-tsc --noEmit -p tsconfig.json --composite false", 10 | "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore", 11 | "format": "prettier --write src/" 12 | }, 13 | "dependencies": { 14 | "@popperjs/core": "^2.11.8", 15 | "@vueuse/integrations": "^10.2.0", 16 | "bootstrap": "^5.3.0", 17 | "bootstrap-icons": "^1.10.5", 18 | "file-saver": "^2.0.5", 19 | "jszip": "^3.10.1", 20 | "pinia": "^2.1.3", 21 | "uuid": "^9.0.1", 22 | "vue": "^3.3.4", 23 | "vuedraggable": "^4.1.0" 24 | }, 25 | "devDependencies": { 26 | "@rushstack/eslint-patch": "^1.2.0", 27 | "@tsconfig/node18": "^2.0.1", 28 | "@types/bootstrap": "^5.2.6", 29 | "@types/file-saver": "^2.0.7", 30 | "@types/node": "^18.16.17", 31 | "@types/uuid": "^9.0.2", 32 | "@vitejs/plugin-vue": "^4.2.3", 33 | "@vue/eslint-config-prettier": "^7.1.0", 34 | "@vue/eslint-config-typescript": "^11.0.3", 35 | "@vue/tsconfig": "^0.4.0", 36 | "eslint": "^8.39.0", 37 | "eslint-plugin-vue": "^9.11.0", 38 | "npm-run-all": "^4.1.5", 39 | "prettier": "^2.8.8", 40 | "sass": "^1.63.4", 41 | "typescript": "~5.0.4", 42 | "vite": "^4.3.9", 43 | "vue-tsc": "^1.6.5" 44 | }, 45 | "engines": { 46 | "node": ">=16.14" 47 | }, 48 | "packageManager": "pnpm@7.4.0" 49 | } 50 | -------------------------------------------------------------------------------- /resources/anatomy/modules/gT7a.js: -------------------------------------------------------------------------------- 1 | /** 2 | * id: gT7a 3 | * path: ./ajax 4 | */ 5 | 6 | (function(require,module,exports) { 7 | "use strict";var t=this&&this.__awaiter||function(t,e,n,o){return new(n||(n=Promise))(function(a,r){function i(t){try{c(o.next(t))}catch(e){r(e)}}function s(t){try{c(o.throw(t))}catch(e){r(e)}}function c(t){var e;t.done?a(t.value):(e=t.value,e instanceof n?e:new n(function(t){t(e)})).then(i,s)}c((o=o.apply(t,e||[])).next())})};Object.defineProperty(exports,"__esModule",{value:!0}),exports.ajax=void 0;const e=require("./utils"),n="//api.live.bilibili.com",o=new Map;function a(a,r){return t(this,void 0,void 0,function*(){let t=a;/^(https?:)?\/\//.test(a)||(t=`${n}/${a.replace(/^\//,"")}`);const i=new URL(t,location.href),s=o.get(i.host+i.pathname);if(null!=s&&s.expiresTime>performance.now()){const t=Math.random()*s.scope;yield(0,e.sleep)(t)}const c=(0,e.getCookie)("bili_jct");if(""!==c&&"POST"===(null==r?void 0:r.method)&&null!=(null==r?void 0:r.data)){const t={csrf:c,csrf_token:c},e=Object.keys(t);r.data instanceof FormData?(e.forEach(e=>{var n;null===(n=r.data)||void 0===n||n.append(e,t[e])}),r.body=r.data):r.data&&(e.forEach(e=>{r.data[e]=t[e]}),r.body=JSON.stringify(r.data)),delete r.data}let l;try{l=yield fetch(t,Object.assign({credentials:"include"},null!=r?r:{}))}catch(p){const t=String(p);throw new Error(t)}if(!l.ok){const t=`url: ${a}, status: ${l.status}`;throw new Error(t)}const d=yield l.json(),{code:u,data:f,message:h}=d;if(0!==u)throw new Error(h);return f})}exports.ajax=a,a.disperse=((t,e)=>{const a=/^(https?:)?\/\//.test(t)?t:`${n}/${t.replace(/^\//,"")}`;o.set(a.replace(/^(https?:)?\/\//,""),e)}); 8 | })() -------------------------------------------------------------------------------- /docs/apis/get_anchor_in_room.md: -------------------------------------------------------------------------------- 1 | # get_anchor_in_room 2 | 3 | ## 接口地址 4 | 5 | `https://api.live.bilibili.com/live_user/v1/UserInfo/get_anchor_in_room` 6 | 7 | ## 方法 8 | 9 | `GET` 10 | 11 | ## 参数 12 | 13 | ### query 14 | 15 | - roomid: 房间id 16 | 17 | ## 返回示例 18 | 19 | ```json 20 | { 21 | "code": 0, 22 | "msg": "success", 23 | "message": "success", 24 | "data": { 25 | "info": { 26 | "uid": 549621446, 27 | "uname": "bili_28350385046", 28 | "face": "https://i0.hdslb.com/bfs/face/member/noface.jpg", 29 | "rank": "5000", 30 | "platform_user_level": 0, 31 | "mobile_verify": 1, 32 | "identification": 1, 33 | "official_verify": { 34 | "type": -1, 35 | "desc": "", 36 | "role": 0 37 | }, 38 | "vip_type": 0, 39 | "gender": -1 40 | }, 41 | "level": { 42 | "uid": 549621446, 43 | "cost": 0, 44 | "rcost": 80000, 45 | "user_score": "0", 46 | "vip": 0, 47 | "vip_time": "0000-00-00 00:00:00", 48 | "svip": 0, 49 | "svip_time": "0000-00-00 00:00:00", 50 | "update_time": "2022-07-10 09:57:49", 51 | "master_level": { 52 | "level": 4, 53 | "color": 6406234, 54 | "current": [ 55 | 270, 56 | 470 57 | ], 58 | "next": [ 59 | 450, 60 | 920 61 | ], 62 | "anchor_score": 800, 63 | "upgrade_score": 120, 64 | "master_level_color": 6406234, 65 | "sort": ">10000" 66 | }, 67 | "user_level": 0, 68 | "color": 9868950, 69 | "anchor_score": 800 70 | }, 71 | "san": 12 72 | } 73 | } 74 | ``` 75 | 76 | ## 接口说明 77 | -------------------------------------------------------------------------------- /src/frontend/src/components/Header.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 65 | -------------------------------------------------------------------------------- /src/frontend/src/components/DeploymentList.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 45 | 46 | 55 | -------------------------------------------------------------------------------- /src/backend/types.d.ts: -------------------------------------------------------------------------------- 1 | export interface BliveSocketState { 2 | // 重试次数 3 | retryCount: number 4 | 5 | // 6 | listConnectFinishedCount: number 7 | 8 | // 当前 ws 索引 (urlList中的下标) 9 | index: number 10 | 11 | // 连接超时次数 12 | connectTimeoutTimes: number 13 | 14 | // 当前 ws 地址 15 | url: string 16 | 17 | token: string 18 | } 19 | 20 | export interface BliveSocketOptions { 21 | // 调试模式 22 | debug: boolean 23 | 24 | urlList: string[] 25 | 26 | // 直播间真实id 27 | roomid: number 28 | 29 | // 用户id 30 | uid: number 31 | 32 | // 断开后是否自动重连 33 | retry: boolean 34 | 35 | // 最大重试次数,默认为线路数。 36 | retryMaxCount: number 37 | 38 | // 重试阈值次数 39 | retryThreadCount: number 40 | 41 | // 重连遍历间隔 42 | retryRoundInterval: number 43 | 44 | // 重连间隔 45 | retryInterval: number 46 | } 47 | 48 | export interface WSHost { 49 | host: string 50 | port: number 51 | wss_port: number 52 | ws_port: number 53 | } 54 | 55 | export type MessageBody = 56 | | AuthorizeReplyMessageBody 57 | | HeartbeatReplayMessageBody 58 | | NormalMessageBody 59 | 60 | // 认证回复包的消息体(op=8) 61 | export interface AuthorizeReplyMessageBody { 62 | code: number 63 | } 64 | // 心跳回复包的消息体(op=3) 65 | export interface HeartbeatReplayMessageBody { 66 | count: number 67 | } 68 | // 普通消息体(op=5) 69 | export interface NormalMessageBody { 70 | cmd: string 71 | } 72 | 73 | interface ParsedPacket { 74 | packetSize: number 75 | headerSize: number 76 | protoVer: number 77 | op: number 78 | seq: number 79 | body: MessageBody 80 | } 81 | 82 | // export type EventType = 'open' | 'close' | 'error' | 'authorized' | 'message' | 'heart_beat_reply' 83 | -------------------------------------------------------------------------------- /resources/anatomy/modules/lmPI.js: -------------------------------------------------------------------------------- 1 | /** 2 | * id: lmPI 3 | * path: ./index.less 4 | */ 5 | 6 | (function(require,module,exports) { 7 | var e='.web-player-report-msg{color:#4fc1e9;font-size:12px;line-height:2}.web-player-report-msg img{height:25px}.web-player-report-uname{font-size:12px;color:#666;line-height:2}.web-player-report-select-input{height:32px;position:relative;border:1px solid #e3e8ec;font-size:12px;background:#fff;cursor:pointer;border-radius:4px;margin:10px 0 25px}.web-player-report-select-wrap{position:absolute;height:34px;width:360px}.web-player-report-select-wrap:hover .bilibili-live-player-report-icon:after{transform-origin:center;transform:rotate(-135deg);transition:transform .3s}.web-player-report-icon{display:inline-block;width:40px;height:30px;margin-right:20px}.web-player-report-icon:after{display:inline-block;content:" ";height:6px;width:6px;border-color:#999;border-style:solid;border-width:0 2px 2px 0;transform:matrix(.71,.71,-.71,.71,0,0);transform-origin:center;transition:transform .3s;position:absolute;top:50%;right:10px;margin-top:-4px}.web-player-report-curt{padding-left:8px;height:32px;line-height:32px;color:#646c7a}.web-player-report-ul{display:none;position:relative;top:-27px;width:358px;height:160px;padding:0;margin:0;font-size:inherit;border:1px solid #ddd;border-top:0;background:#fff;overflow-y:auto;-webkit-box-shadow:0 1px 2px 0 rgba(105,115,133,.22);box-shadow:0 1px 2px 0 rgba(105,115,133,.22);border-radius:0 0 4px 4px;z-index:1;cursor:pointer}.web-player-report-ul li{padding:10px 0 10px 8px;color:#999;font-size:12px;height:24px;line-height:24px}.web-player-report-ul li:hover{background:#eef9fd}',r=document.createElement("style");r.type="text/css",r.appendChild(document.createTextNode(e)),document.head.appendChild(r); 8 | })() -------------------------------------------------------------------------------- /resources/anatomy/modules/FT5O.js: -------------------------------------------------------------------------------- 1 | /** 2 | * id: FT5O 3 | * path: process 4 | */ 5 | 6 | (function(require,module,exports) { 7 | 8 | var t,e,n=module.exports={};function r(){throw new Error("setTimeout has not been defined")}function o(){throw new Error("clearTimeout has not been defined")}function i(e){if(t===setTimeout)return setTimeout(e,0);if((t===r||!t)&&setTimeout)return t=setTimeout,setTimeout(e,0);try{return t(e,0)}catch(n){try{return t.call(null,e,0)}catch(n){return t.call(this,e,0)}}}function u(t){if(e===clearTimeout)return clearTimeout(t);if((e===o||!e)&&clearTimeout)return e=clearTimeout,clearTimeout(t);try{return e(t)}catch(n){try{return e.call(null,t)}catch(n){return e.call(this,t)}}}!function(){try{t="function"==typeof setTimeout?setTimeout:r}catch(n){t=r}try{e="function"==typeof clearTimeout?clearTimeout:o}catch(n){e=o}}();var c,s=[],l=!1,a=-1;function f(){l&&c&&(l=!1,c.length?s=c.concat(s):a=-1,s.length&&h())}function h(){if(!l){var t=i(f);l=!0;for(var e=s.length;e;){for(c=s,s=[];++a1)for(var n=1;n ").concat(n.content)}exports.default=r; 8 | })() -------------------------------------------------------------------------------- /src/frontend/public/assets/styles/footer_note.css: -------------------------------------------------------------------------------- 1 | /* footer_note.css */ 2 | span.reader_footer_note { 3 | position: relative; 4 | display: inline-block; 5 | width: 19px; 6 | height: 19px; 7 | background-color: black; 8 | vertical-align: sub; 9 | border-radius: 50%; 10 | cursor: pointer; 11 | margin: 0 3px; 12 | background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACYAAAAmCAQAAAACNCElAAACuUlEQVR42q2WTWgTQRTHX4v1UNFiDgVFPEmRXETbS0V6yKESpMGLl3rw7MGLBizoTTAXmx78wAUpIvHSFAq7JqbGpjWm1kA/Y2I/qOgam1qbLNbEJDXNPpmuS5PMbJqtu//Lzhveb3fmvZn3ALQfE1jBDg7gwA1u4MABdrCCCXQ+DWCDXhA01As2aKgNVA8W6NcEqeoHC9TvhmqGvl1BqvqguRrKDK6aUUQuMGuhOmBIF4poCDrYf6UfpeDM9F659oRSFttcHsGSbW/xD355sNAVUscnXi3/nFy/P3/UVyUUJZG1lE8+XUZEnPxxyKOML45vFRGzBVtIE2fZSdGKvNr/IpEhuJszqoVbJOOVtDrPyLt/aWyjv9Qzi1iUu0I9s8q4yfv7D2Iyq8xFUsfoJdsUmJOG1QmdwbZAW8ARbfQoujbli1961+hpH81vISYyZwLUIds+zmXGe7E7H86/Vd55kVsa/Ew0/p0sE2VlRHRrlvoFE4C13HQljDLi1Pphb4t/XlKt3ROEVSxWTRErgL3S+GaVODqizz89XtQFswM4Ko2X35MFXZ/OFzqDumAOAI42nxtrH+UWpVydwIJ1T2ikLwfgZk2YvJlNXgQGrDWQKyQyrQGGlxtggAVzfkS8OgnC7TleJAqvKdHkxa9pWZblVO74MOU1wFxmk3cjj/IRHwi69oxjBACEJ0uI4q9SS60BqEiNs2PBVZJpwVXdMDuVtOQYSznESEo3zEodJ6KZJKIsnx7RCduuqFSFjCSJ66MFNuykP5o6NcKopuwrKJ4mri/jLNg+IZJC3Ni8EGJfQdTlmC0Q14cLd6NKlvHi65WYFJOiEi8OfyNvMSm8dtDDuhypa/vG9Fzy2fIBj46iYtEoKHtQX3mrYGCpM7gIG9weGNy4GNxSGdzs7bShTk2Qs/Y29D8a5L99J97IcV5pLgAAAABJRU5ErkJggg=='); 13 | background-size: 100%; 14 | background-repeat: no-repeat; 15 | } 16 | 17 | span.reader_footer_note:hover:after { 18 | position: fixed; 19 | content: attr(data-wr-footernote); 20 | left: 0; 21 | bottom: 0; 22 | margin: 1em; 23 | background: black; 24 | border-radius: 0.25em; 25 | color: white; 26 | padding: 0.5em; 27 | font-size: 1em; 28 | font-family: "汉仪楷体", sans-serif; 29 | z-index: 10; 30 | } 31 | -------------------------------------------------------------------------------- /resources/anatomy/modules/QivA.js: -------------------------------------------------------------------------------- 1 | /** 2 | * id: QivA 3 | * path: ./ui-components/not-auto-play 4 | */ 5 | 6 | (function(require,module,exports) { 7 | "use strict";var e=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(exports,"__esModule",{value:!0}),exports.createMutePlayTip=exports.createNotAutoPlayTip=void 0;var t=require("@bilibili-live/web-player"),a=e(require("./not-auto-play.less"));function n(e,t){var n=document.createElement("div"),i=document.createElement("div"),c=document.createElement("p"),l=document.createElement("img");return n.classList.add(a.default.notAutoPlayTipContainer),i.classList.add(a.default.tipBtn),i.innerHTML="点击播放",c.classList.add(a.default.tipText),c.innerHTML='\n 你的浏览器不支持自动播放\n '),l.classList.add(a.default.tipImg),l.src="//i0.hdslb.com/bfs/activity-plat/static/20211217/d0411babbbf77c49ca42a3320eb804ae/OqYjEF1uIM.png",i.onclick=function(){n.remove(),t()},n.appendChild(l),n.appendChild(c),n.appendChild(i),e.appendChild(n),function(){n.remove()}}function i(e,n,i){var c=document.createElement("div"),l=document.createElement("img"),o=document.createElement("p");return l.src="//i0.hdslb.com/bfs/activity-plat/static/20211220/d0411babbbf77c49ca42a3320eb804ae/hDsUctDb2f.png",o.innerText="点击取消静音",c.classList.add(a.default.mutePlayTipContainer),o.classList.add(a.default.tipText),l.classList.add(a.default.tipImg),c.appendChild(l),c.appendChild(o),e.appendChild(c),c.onclick=function(){c.remove(),i()},n.on(t.ControllerEventType.BottomDistanceChange,function(e){var t=n.getCtrlUI().show;c.style.bottom="".concat(e+(t?50:10),"px")}),{destroy:function(){c.remove()},change:function(e){c.style.bottom="".concat(parseInt(getComputedStyle(n.getCtrlEl()).bottom)+(e?50:10),"px")}}}exports.createNotAutoPlayTip=n,exports.createMutePlayTip=i; 8 | })() -------------------------------------------------------------------------------- /resources/anatomy/modules/z9tw.js: -------------------------------------------------------------------------------- 1 | /** 2 | * id: z9tw 3 | * path: ./not-auto-play.less 4 | */ 5 | 6 | (function(require,module,exports) { 7 | module.exports={notAutoPlayTipContainer:"_not-auto-play-tip-container_6f52f",tipImg:"_tip-img_6f52f",tipText:"_tip-text_6f52f",textTipEmphasize:"_text-tip-emphasize_6f52f",tipBtn:"_tip-btn_6f52f",mutePlayTipContainer:"_mute-play-tip-container_6f52f"};var t="._not-auto-play-tip-container_6f52f{width:100%;height:100%;display:flex;align-items:center;flex-direction:column;justify-content:center;position:absolute;top:0;left:0;background:rgba(0,0,0,.5);z-index:40}._not-auto-play-tip-container_6f52f ._tip-img_6f52f{margin-top:-10%;width:230px}._not-auto-play-tip-container_6f52f ._tip-text_6f52f{font-family:PingFang SC;font-style:normal;font-weight:400;font-size:18px;line-height:34px;color:#fff;margin-bottom:20px;margin-top:0}._not-auto-play-tip-container_6f52f ._tip-text_6f52f ._text-tip-emphasize_6f52f{color:#23ade5}._not-auto-play-tip-container_6f52f ._tip-btn_6f52f{width:135px;height:45px;background:#23ade5;border-radius:4px;color:#fff;font-family:PingFang SC;font-style:normal;font-weight:500;font-size:18px;text-align:center;vertical-align:middle;line-height:45px;cursor:pointer}._mute-play-tip-container_6f52f{position:absolute;width:150px;height:32px;background:#23ade5;border-radius:4px;z-index:40;bottom:40;left:30px;bottom:10px;cursor:pointer}._mute-play-tip-container_6f52f ._tip-img_6f52f{width:18px;height:18px;line-height:28px;position:absolute;top:6px;left:11px;margin-right:9px}._mute-play-tip-container_6f52f ._tip-text_6f52f{position:absolute;font-family:PingFang SC;font-style:normal;font-weight:500;font-size:16px;line-height:32px;color:#fff;top:0;left:41px;white-space:nowrap;margin:0}",i=document.createElement("style");i.type="text/css",i.appendChild(document.createTextNode(t)),document.head.appendChild(i); 8 | })() -------------------------------------------------------------------------------- /src/frontend/src/stores/room.ts: -------------------------------------------------------------------------------- 1 | import {defineStore} from 'pinia' 2 | import {computed, reactive, ref} from 'vue' 3 | import type {Room} from '@/types' 4 | import {v4 as uuid} from 'uuid' 5 | import {useWebSocketStore} from "@/stores/ws" 6 | 7 | 8 | export const useRoomStore = defineStore('RoomStore', () => { 9 | const wsStore = useWebSocketStore() 10 | 11 | const rooms = reactive([]) 12 | 13 | const activatedRoom = computed(() => rooms.find(room => room.active)) 14 | 15 | async function connectRoom(roomId: number) { 16 | if (rooms.some(room => room.rid === roomId || room.roomId === roomId)) { 17 | // 房间已存在 18 | return 19 | } 20 | 21 | if (!wsStore.connected) { 22 | await wsStore.init() 23 | } 24 | 25 | wsStore.ws!.send(JSON.stringify({ 26 | cmd: 'enter', 27 | rid: roomId, 28 | events: ['DANMU_MSG', 'INTERACT_WORD', 'SEND_GIFT'], 29 | })) 30 | 31 | rooms.push({ 32 | id: uuid(), 33 | events: ['DANMU_MSG', 'INTERACT_WORD', 'SEND_GIFT'], 34 | rid: roomId, 35 | roomId: roomId, 36 | status: 'offline', 37 | active: false, 38 | logs: [], 39 | }) 40 | } 41 | function disconnectRoom(room: Room) { 42 | const idx = rooms.findIndex(_ => _.id === room.id) 43 | if (idx !== -1) { 44 | rooms.splice(idx, 1) 45 | } 46 | wsStore.ws?.send(JSON.stringify({cmd: 'leave', rid: room.rid})) 47 | } 48 | 49 | function activeRoom(room: Room) { 50 | rooms.forEach(room => { 51 | room.active = false 52 | }) 53 | room.active = true 54 | } 55 | 56 | return { 57 | rooms, 58 | activatedRoom, 59 | activeRoom, 60 | connectRoom, 61 | disconnectRoom, 62 | } 63 | }) 64 | -------------------------------------------------------------------------------- /resources/anatomy/modules/EORf.js: -------------------------------------------------------------------------------- 1 | /** 2 | * id: EORf 3 | * path: ./ui-components/feedback 4 | */ 5 | 6 | (function(require,module,exports) { 7 | "use strict";Object.defineProperty(exports,"__esModule",{value:!0});var e='\n \n \n \n \n \n \n \n \n \n \n \n';function n(n,t){var o=document.createElement("div"),i=0;function s(e){e.stopPropagation(),clearTimeout(i),i=0,o.style.opacity="1.0"}function a(){clearTimeout(i),i=window.setTimeout(function(){o.style.opacity="0"},1e3),o.style.opacity="0.8"}function r(){i=window.setTimeout(function(){o.style.opacity="0"},1e3)}return o.className="web-player-icon-feedback",o.innerHTML=e,o.style.cssText="\n position: absolute;\n top: 0px;\n right: 12px;\n z-index: 100;\n width: 36px;\n height: 36px;\n cursor: pointer;\n transition: opacity 0.5s ease 0s;\n opacity: 0;\n ",n.appendChild(o),n.addEventListener("mousemove",a),n.addEventListener("mouseleave",r),o.addEventListener("click",t),o.addEventListener("mousemove",s),function(){o.remove(),n.removeEventListener("mousemove",a),n.removeEventListener("mouseleave",r),o.removeEventListener("click",t),o.removeEventListener("mousemove",s),clearTimeout(i)}}exports.default=n; 8 | })() -------------------------------------------------------------------------------- /test/ws.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 拉取直播间 websocket 数据作为测试数据 3 | */ 4 | import {convertToArrayBuffer, parseArrayBuffer} from "../src/deno/utils.ts" 5 | import {config} from "../src/deno/const.ts" 6 | import type {AuthorizeReplyMessageBody} from "../src/deno/types.d.ts" 7 | 8 | const roomid = 7734200 9 | const url = 'wss://hw-sh-live-comet-02.chat.bilibili.com:443/sub' 10 | const key = '6Vx3AnrvOo6DU5u9SOCuQqLgrPofCLfTBoTTTrO50uB3QCba864-HHWsiBbwWSSGIrpFSLaLyhXLSrSJNR2TjCo-EU3IYzn3XWG3Tx4DeoubvHL-PUERCZ1wQJJwzuJG5GspYiC7EjKRxKK9' 11 | 12 | 13 | export const ws = new WebSocket(url) 14 | ws.binaryType = "arraybuffer" 15 | 16 | // 心跳定时器 17 | let HEART_BEAT_INTERVAL: number | undefined = undefined 18 | 19 | ws.addEventListener('open', () => { 20 | console.log('🚀open') 21 | 22 | const auth = { 23 | uid: 0, 24 | roomid: roomid, 25 | protover: 3, 26 | buvid: '', 27 | platform: 'web', 28 | type: 2, 29 | key: key, 30 | } 31 | // 发送认证包 32 | console.log('发送认证包: ', auth) 33 | ws.send(convertToArrayBuffer(JSON.stringify(auth), 7)) 34 | 35 | }) 36 | ws.addEventListener('close', (event: CloseEvent) => { 37 | console.log('🚫close: ', event.reason) 38 | if (HEART_BEAT_INTERVAL !== undefined) { 39 | clearInterval(HEART_BEAT_INTERVAL) 40 | } 41 | }) 42 | ws.addEventListener('error', (event: Event | ErrorEvent) => { 43 | console.log('💢error: ', (event as ErrorEvent).message) 44 | }) 45 | ws.addEventListener('message', (event: MessageEvent) => { 46 | const packets = parseArrayBuffer(event.data) 47 | packets.forEach(packet => { 48 | if (packet.op === 8 && (packet.body as AuthorizeReplyMessageBody).code === 0) { 49 | // 认证成功 50 | console.log('🚀authorized') 51 | ws.send(convertToArrayBuffer('', 2)) 52 | HEART_BEAT_INTERVAL = setInterval(() => { 53 | // 发送心跳包 54 | ws.send(convertToArrayBuffer('', 2)) 55 | }, 30 * 1000) 56 | } 57 | }) 58 | }) 59 | -------------------------------------------------------------------------------- /resources/anatomy/modules/E90K.js: -------------------------------------------------------------------------------- 1 | /** 2 | * id: E90K 3 | * path: ./ui-components/error-panel 4 | */ 5 | 6 | (function(require,module,exports) { 7 | "use strict";function e(e,n,t){var r="直播已断开",l=null!=t?t:"直播时空次元异常,正在努力恢复中",a=document.createElement("div"),i=document.createElement("div"),o=document.createElement("div"),s=document.createElement("div"),c=document.createElement("div");a.className="web-player-error-panel",o.className="web-player-error-code",s.className="web-player-error-image",c.className="web-player-error-tip";r+='\n
\n ( 请尝试\n 刷新\n )\n
\n '),i.style.cssText="\n display: block;\n max-width: 80%;\n display: inline-block;\n font-size: 16px;\n color: #eee;\n line-height: 24px;\n padding: 10px;\n cursor: default;\n ",s.style.cssText="\n display: block;\n width: 30%;\n height: 30%;\n background: url(//s1.hdslb.com/bfs/static/player/live/html5/images/error.png) no-repeat 50% 0;\n background-size: contain;\n ",a.style.cssText="\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n box-sizing: border-box;\n z-index: 18;\n background: #000;\n cursor: default;\n display: flex;\n flex-flow: column;\n justify-content: center;\n align-items: center;\n ";var d="\n font-size: 24px;\n margin-top: 12px;\n color: #999999;\n height: 33px;\n line-height: 33px;\n text-align: center;\n ";return o.style.cssText=d,c.style.cssText=d,i.innerHTML=r,a.appendChild(i),a.appendChild(s),c.innerHTML=l,o.innerText="status: ".concat(n),i.style.display="none",a.appendChild(o),a.appendChild(c),e.appendChild(a),function(){a.remove()}}Object.defineProperty(exports,"__esModule",{value:!0}),exports.createErrorPanel=void 0,exports.createErrorPanel=e; 8 | })() -------------------------------------------------------------------------------- /resources/anatomy/modules/oQm6.js: -------------------------------------------------------------------------------- 1 | /** 2 | * id: oQm6 3 | * path: ./directives/directives 4 | */ 5 | 6 | (function(require,module,exports) { 7 | "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.genDirectives=u;var e=require("./exp");function t(e,t){return a(e)||o(e,t)||r(e,t)||n()}function n(){throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function r(e,t){if(e){if("string"==typeof e)return i(e,t);var n=Object.prototype.toString.call(e).slice(8,-1);return"Object"===n&&e.constructor&&(n=e.constructor.name),"Map"===n||"Set"===n?Array.from(e):"Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)?i(e,t):void 0}}function i(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);n { 22 | (t.hrefs || []).forEach((href, idx) => { 23 | fontStyles.push({ 24 | id: `${t.id}_${idx}`, 25 | href: href, 26 | }) 27 | }) 28 | })) 29 | fontStyles.forEach(fontStyle => { 30 | const link = document.createElement("link") 31 | link.id = fontStyle.id 32 | link.href = fontStyle.href 33 | link.type = "text/css" 34 | link.rel = "stylesheet" 35 | document.querySelector("head").appendChild(link) 36 | }) 37 | addListener("system/fontsInfoChange", () => { 38 | callNativeSync("system/getFontsInfo").forEach((t => { 39 | (t.hrefs || []).forEach((href, idx) => { 40 | const link = document.querySelector(`#${t.id}_${idx}`) 41 | link && link.setAttribute("href", href) 42 | }) 43 | })) 44 | }) 45 | 46 | const initHtmlDataFont = function () { 47 | const t = callNativeSync("config/getSystemFont") 48 | html.setAttribute("data-font", t.dataFont || "") 49 | } 50 | initHtmlDataFont() 51 | addListener("config/systemFontChange", () => initHtmlDataFont()) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /resources/anatomy/modules/KGHo.js: -------------------------------------------------------------------------------- 1 | /** 2 | * id: KGHo 3 | * path: @bilibili-live/web-player-common 4 | */ 5 | 6 | (function(require,module,exports) { 7 | "use strict";var e=this&&this.__createBinding||(Object.create?function(e,t,r,o){void 0===o&&(o=r);var n=Object.getOwnPropertyDescriptor(t,r);n&&("get"in n?t.__esModule:!n.writable&&!n.configurable)||(n={enumerable:!0,get:function(){return t[r]}}),Object.defineProperty(e,o,n)}:function(e,t,r,o){void 0===o&&(o=r),e[o]=t[r]}),t=this&&this.__exportStar||function(t,r){for(var o in t)"default"===o||Object.prototype.hasOwnProperty.call(r,o)||e(r,t,o)},r=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(exports,"__esModule",{value:!0}),exports.mediaAction=exports.userSetting=exports.setToastCssText=exports.toast=exports.wpd=exports.LogLevel=exports.LogItem=exports.logger=exports.ua=void 0,t(require("./interface"),exports),t(require("./common-utils"),exports);var o=require("./ua");Object.defineProperty(exports,"ua",{enumerable:!0,get:function(){return r(o).default}}),t(require("./create-element"),exports),t(require("./loading"),exports),t(require("./ajax"),exports),t(require("./fps-monitor"),exports),t(require("./compatibility-check"),exports);var n=require("./logger");Object.defineProperty(exports,"logger",{enumerable:!0,get:function(){return r(n).default}}),Object.defineProperty(exports,"LogItem",{enumerable:!0,get:function(){return n.LogItem}}),Object.defineProperty(exports,"LogLevel",{enumerable:!0,get:function(){return n.Level}}),t(require("./event-bus"),exports);var i=require("./player-debug");Object.defineProperty(exports,"wpd",{enumerable:!0,get:function(){return r(i).default}});var u=require("./toast");Object.defineProperty(exports,"toast",{enumerable:!0,get:function(){return r(u).default}}),Object.defineProperty(exports,"setToastCssText",{enumerable:!0,get:function(){return u.setToastCssText}});var s=require("./user-setting");Object.defineProperty(exports,"userSetting",{enumerable:!0,get:function(){return r(s).default}});var a=require("./media-action");Object.defineProperty(exports,"mediaAction",{enumerable:!0,get:function(){return r(a).default}}); 8 | })() -------------------------------------------------------------------------------- /src/frontend/public/lib/base64js.min.js: -------------------------------------------------------------------------------- 1 | (function(a){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=a();else if("function"==typeof define&&define.amd)define([],a);else{var b;b="undefined"==typeof window?"undefined"==typeof global?"undefined"==typeof self?this:self:global:window,b.base64js=a()}})(function(){return function(){function b(d,e,g){function a(j,i){if(!e[j]){if(!d[j]){var f="function"==typeof require&&require;if(!i&&f)return f(j,!0);if(h)return h(j,!0);var c=new Error("Cannot find module '"+j+"'");throw c.code="MODULE_NOT_FOUND",c}var k=e[j]={exports:{}};d[j][0].call(k.exports,function(b){var c=d[j][1][b];return a(c||b)},k,k.exports,b,d,e,g)}return e[j].exports}for(var h="function"==typeof require&&require,c=0;c>16,j[k++]=255&b>>8,j[k++]=255&b;return 2===h&&(b=l[a.charCodeAt(c)]<<2|l[a.charCodeAt(c+1)]>>4,j[k++]=255&b),1===h&&(b=l[a.charCodeAt(c)]<<10|l[a.charCodeAt(c+1)]<<4|l[a.charCodeAt(c+2)]>>2,j[k++]=255&b>>8,j[k++]=255&b),j}function g(a){return k[63&a>>18]+k[63&a>>12]+k[63&a>>6]+k[63&a]}function h(a,b,c){for(var d,e=[],f=b;fj?j:g+f));return 1===d?(b=a[c-1],e.push(k[b>>2]+k[63&b<<4]+"==")):2===d&&(b=(a[c-2]<<8)+a[c-1],e.push(k[b>>10]+k[63&b>>4]+k[63&b<<2]+"=")),e.join("")}c.byteLength=function(a){var b=d(a),c=b[0],e=b[1];return 3*(c+e)/4-e},c.toByteArray=f,c.fromByteArray=j;for(var k=[],l=[],m="undefined"==typeof Uint8Array?Array:Uint8Array,n="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",o=0,p=n.length;o { 13 | const ws: Ref = ref(null) 14 | const connected = computed(() => !!ws.value && ws.value.readyState === WebSocket.OPEN) 15 | 16 | const roomStore = useRoomStore() 17 | 18 | function init() { 19 | return new Promise((resolve, reject) => { 20 | if (!ws.value || ws.value.readyState !== WebSocket.OPEN) { 21 | ws.value = new WebSocket(bliveEndpoint) 22 | ws.value.onopen = () => { 23 | resolve(true) 24 | } 25 | ws.value.onerror = (ev: Event) => { 26 | reject(new Error((ev as CloseEvent).reason)) 27 | ws.value = null 28 | } 29 | ws.value.onclose = () => { 30 | ws.value = null 31 | } 32 | ws.value.onmessage = (evt: Event) => { 33 | const rawData = (evt as MessageEvent).data 34 | try { 35 | const data: MsgLog = JSON.parse(rawData) 36 | console.log(data) 37 | const targetRoom = roomStore.rooms.find(room => room.rid === data.rid) 38 | if (targetRoom) { 39 | targetRoom.logs.push(data) 40 | if (data.payload.cmd === 'authorized') { 41 | targetRoom.status = 'online' 42 | } 43 | } 44 | } catch (e) {} 45 | } 46 | } else { 47 | resolve(true) 48 | } 49 | }) 50 | } 51 | 52 | return { 53 | ws, 54 | connected, 55 | init, 56 | } 57 | }) 58 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | 78 | # Next.js build output 79 | .next 80 | 81 | # Nuxt.js build / generate output 82 | .nuxt 83 | dist 84 | 85 | # Gatsby files 86 | .cache/ 87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 88 | # https://nextjs.org/blog/next-9-1#public-directory-support 89 | # public 90 | 91 | # vuepress build output 92 | .vuepress/dist 93 | 94 | # Serverless directories 95 | .serverless/ 96 | 97 | # FuseBox cache 98 | .fusebox/ 99 | 100 | # DynamoDB Local files 101 | .dynamodb/ 102 | 103 | # TernJS port file 104 | .tern-port 105 | 106 | # editor directory 107 | .idea 108 | 109 | log.txt 110 | .env.local 111 | -------------------------------------------------------------------------------- /resources/anatomy/modules/H7b7.js: -------------------------------------------------------------------------------- 1 | /** 2 | * id: H7b7 3 | * path: ./switch-better-line 4 | */ 5 | 6 | (function(require,module,exports) { 7 | "use strict";var e=this&&this.__classPrivateFieldGet||function(e,t,n,i){if("a"===n&&!i)throw new TypeError("Private accessor was defined without a getter");if("function"==typeof t?e!==t||!i:!t.has(e))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===n?i:"a"===n?i.call(e):i?i.value:t.get(e)},t=this&&this.__classPrivateFieldSet||function(e,t,n,i,r){if("m"===i)throw new TypeError("Private method is not writable");if("a"===i&&!r)throw new TypeError("Private accessor was defined without a setter");if("function"==typeof t?e!==t||!r:!t.has(e))throw new TypeError("Cannot write private member to an object whose class did not declare it");return"a"===i?r.call(e,n):r?r.value=n:t.set(e,n),n};Object.defineProperty(exports,"__esModule",{value:!0}),exports.SwitchBetterLine=void 0;var n=5,i=8e3,r=6e4,a=1e4,o=5e3,s=0,f=function(){function f(v,m){var l=this;this.video=v,this.opts=m,c.set(this,!1),w.set(this,[]),h.set(this,0),u.set(this,0),p.set(this,[]),this.getNeedSwitchLine=function(){return e(l,c,"f")},this.destroy=function(){e(l,p,"f").forEach(function(e){return e()}),t(l,p,[],"f")},d.set(this,function(d){var v=0;s=performance.now();var m=function(){e(l,c,"f")?f.switchNum+=1:v=performance.now()},g=function(){if(!(s+a>performance.now()||0===v||e(l,u,"f")+o>=performance.now())){var f={duration:performance.now()-v,waitingStartTime:v};e(l,w,"f").push(f),t(l,w,e(l,w,"f").filter(function(n){return n.waitingStartTime+r=performance.now()}),"f"),t(l,h,e(l,w,"f").reduce(function(e,t){return e+t.duration},0),"f"),(e(l,h,"f")>i||e(l,w,"f").length>=n)&&t(l,c,!0,"f")}},E=function(){t(l,u,performance.now(),"f")};d.addEventListener("playing",g),d.addEventListener("seeking",E),d.addEventListener("waiting",m),e(l,p,"f").push(function(){d.removeEventListener("seeking",E),d.removeEventListener("playing",g),d.removeEventListener("waiting",m)})}),f.switchNum+1!==this.opts.lineNum&&e(this,d,"f").call(this,v)}var c,w,h,u,p,d;return c=new WeakMap,w=new WeakMap,h=new WeakMap,u=new WeakMap,p=new WeakMap,d=new WeakMap,f.switchNum=0,f.lastSwitchTime=0,f}();exports.SwitchBetterLine=f; 8 | })() -------------------------------------------------------------------------------- /resources/anatomy/modules/MwY8.js: -------------------------------------------------------------------------------- 1 | /** 2 | * id: MwY8 3 | * path: ./video-utils 4 | */ 5 | 6 | (function(require,module,exports) { 7 | "use strict";var e=this&&this.__awaiter||function(e,t,n,r){return new(n||(n=Promise))(function(o,a){function i(e){try{c(r.next(e))}catch(t){a(t)}}function u(e){try{c(r.throw(e))}catch(t){a(t)}}function c(e){var t;e.done?o(e.value):(t=e.value,t instanceof n?t:new n(function(e){e(t)})).then(i,u)}c((r=r.apply(e,t||[])).next())})},t=this&&this.__generator||function(e,t){var n,r,o,a,i={label:0,sent:function(){if(1&o[0])throw o[1];return o[1]},trys:[],ops:[]};return a={next:u(0),throw:u(1),return:u(2)},"function"==typeof Symbol&&(a[Symbol.iterator]=function(){return this}),a;function u(u){return function(c){return function(u){if(n)throw new TypeError("Generator is already executing.");for(;a&&(a=0,u[0]&&(i=0)),i;)try{if(n=1,r&&(o=2&u[0]?r.return:u[0]?r.throw||((o=r.return)&&o.call(r),0):r.next)&&!(o=o.call(r,u[1])).done)return o;switch(r=0,o&&(u=[2&u[0],o.value]),u[0]){case 0:case 1:o=u;break;case 4:return i.label++,{value:u[1],done:!1};case 5:i.label++,r=u[1],u=[0];continue;case 7:u=i.ops.pop(),i.trys.pop();continue;default:if(!(o=(o=i.trys).length>0&&o[o.length-1])&&(6===u[0]||2===u[0])){i=0;continue}if(3===u[0]&&(!o||u[1]>o[0]&&u[1]=200&&e.status<=299?[4,e.text()]:[3,3];case 2:if(o=t.sent(),(a=o.split("\n")).findIndex(function(e){return"#EXTM3U"===e})>-1&&a.findIndex(function(e){return"#EXT-X-VERSION:3"===e})>-1&&-1!==(i=a.findIndex(function(e){return e.startsWith("#EXT-X-STREAM-INF")})))return[2,null!==(r=a[i+1])&&void 0!==r?r:n];t.label=3;case 3:return[2,n]}})})}Object.defineProperty(exports,"__esModule",{value:!0}),exports.redirectHLSV3=exports.parseAudioChannel=void 0,exports.parseAudioChannel=n,exports.redirectHLSV3=r; 8 | })() -------------------------------------------------------------------------------- /resources/anatomy/modules/jGuH.js: -------------------------------------------------------------------------------- 1 | /** 2 | * id: jGuH 3 | * path: ./exp 4 | */ 5 | 6 | (function(require,module,exports) { 7 | "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.ExpUtils=void 0;var e=require("../utils");function t(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function r(e,t){for(var r=0;r0?Math.max.apply(this,o(this,i,"f")):1,"f");try{n(this,a,o(this,e,"f")/(o(this,r,"f")-o(this,h,"f")),"f")}catch(w){f.logger.error(w)}for(var p=0,c=o(this,i,"f").length;p\n \n \n \n \n ')},p.prototype.destroy=function(){n(this,i,[],"f"),n(this,s,[],"f"),n(this,h,0,"f"),n(this,r,0,"f"),n(this,a,0,"f"),this.initGraph()},p.prototype.initGraph=function(){for(var h=0,r=o(this,t,"f")/2;h0&&o[o.length-1])&&(6===a[0]||2===a[0])){u=0;continue}if(3===a[0]&&(!o||a[1]>o[0]&&a[1] 2 | 3 | 4 | 5 | 6 | 稳定性测试 7 | 8 | 9 | 10 |

稳定性测试

11 |
12 | 21 | 22 |
23 | 24 | 25 | 26 |
27 |
28 |
29 |
41 | {{room.danmuCount}} 42 |
43 |
44 | 48 | 49 |
50 | 内存 51 |

52 | rss: 53 | {{formatByte(statics.mem.rss)}} 54 |

55 |

56 | heapTotal: 57 | {{formatByte(statics.mem.heapTotal)}} 58 |

59 |

60 | heapUsed: 61 | {{formatByte(statics.mem.heapUsed)}} 62 |

63 |

64 | external: 65 | {{formatByte(statics.mem.external)}} 66 |

67 |
68 | 69 | 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /resources/anatomy/modules/SCHR.js: -------------------------------------------------------------------------------- 1 | /** 2 | * id: SCHR 3 | * path: ./dimension 4 | */ 5 | 6 | (function(require,module,exports) { 7 | "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.getDimension=u,exports.IDimensionOS=exports.IDimensionNavigationType=exports.IDimensionNetworkType=void 0;var n,o,e,i=require("./timing");function r(){return"hidden"===document.visibilityState||document.hidden?0:1}function t(){var o,e,i;return"4g"===(null===(o=null===navigator||void 0===navigator?void 0:navigator.connection)||void 0===o?void 0:o.effectiveType)?n["4g"]:"3g"===(null===(e=null===navigator||void 0===navigator?void 0:navigator.connection)||void 0===e?void 0:e.effectiveType)?n["3g"]:"2g"===(null===(i=null===navigator||void 0===navigator?void 0:navigator.connection)||void 0===i?void 0:i.effectiveType)?n["2g"]:n.other}function a(){var n;try{var e=(null!==(n=null===performance||void 0===performance?void 0:performance.getEntriesByType("navigation")[0])&&void 0!==n?n:(0,i.getNavigationEntryFromPerformanceTiming)()).type;return"navigate"===e?o.navigate:"reload"===e?o.reload:"back_forward"===e?o.backForward:o.other}catch(r){return o.other}}function v(){var n;return null!=(null===(n=null===navigator||void 0===navigator?void 0:navigator.serviceWorker)||void 0===n?void 0:n.controller)?1:0}function c(){var n="Mac68K"===navigator.platform||"MacPPC"===navigator.platform||"Macintosh"===navigator.platform||"MacIntel"===navigator.platform,o="Win32"===navigator.platform||"Windows"===navigator.platform,i="X11"===navigator.platform&&!o&&!n,r=String(navigator.platform).includes("Linux");return n?e.Mac:o?e.Windows:i?e.Unix:r?e.Linux:e.Other}function d(){try{return"h2"===performance.getEntriesByType("navigation")[0].nextHopProtocol?1:0}catch(n){return 0}}function l(){var n;return null!=(null===(n=null===window||void 0===window?void 0:window.PerformanceObserver)||void 0===n?void 0:n.supportedEntryTypes)&&["largest-contentful-paint","paint"].every(function(n){return window.PerformanceObserver.supportedEntryTypes.includes(n)})?1:0}function u(){return{OS:c(),VisibilityState:r(),NavigationType:a(),NetworkType:t(),ServiceWorkerState:v(),NextHopProtocol:d(),SupportedEntryTypes:l()}}exports.IDimensionNetworkType=n,function(n){n[n.other=0]="other",n[n["2g"]=2]="2g",n[n["3g"]=3]="3g",n[n["4g"]=4]="4g"}(n||(exports.IDimensionNetworkType=n={})),exports.IDimensionNavigationType=o,function(n){n[n.navigate=0]="navigate",n[n.reload=1]="reload",n[n.backForward=2]="backForward",n[n.other=255]="other"}(o||(exports.IDimensionNavigationType=o={})),exports.IDimensionOS=e,function(n){n[n.Other=0]="Other",n[n.Mac=1]="Mac",n[n.Windows=2]="Windows",n[n.Unix=3]="Unix",n[n.Linux=4]="Linux"}(e||(exports.IDimensionOS=e={})); 8 | })() -------------------------------------------------------------------------------- /src/frontend/public/weread/8.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | // https://weread-1258476243.file.myqcloud.com/web/wrwebnjlogic/js/8.a6ca3e96.js 3 | 4 | (() => { 5 | function processStyles(styles, bookId) { 6 | return function (styles, bookId) { 7 | if (!styles || styles.length <= 0) { 8 | return '' 9 | } 10 | 11 | // 去掉 /* */ 注释 12 | styles = styles.trim().replace(/\/\*.*?\*\//gi, ''); 13 | let re = new RegExp('[^{}]*?{[\x5cs\x5cS]+?}', 'gi'), 14 | matchArray = styles.match(re); 15 | if (!matchArray || 0 === matchArray.length) { 16 | return '' 17 | } 18 | 19 | // 把 epub 内联图片地址替换成绝对地址,方便 html 进行访问 20 | return matchArray.map((_) => { 21 | return '.readerChapterContent ' + 22 | (_ = _.trim()).split('\n').map((__) => { 23 | return -1 === (__ = __.trim()).indexOf('{') && 24 | -1 === __.indexOf('}') && -1 === __.indexOf(';') 25 | ? __ + ';' 26 | : __; 27 | }).join(''); 28 | }).join('').trim().replace( 29 | /\.\.\/images\/(.*?\.(png|jpg|jpeg|gif))/gi, 30 | 'https://res.weread.qq.com/wrepub/web/' + bookId + '/$1' 31 | ); 32 | }(styles || '', bookId); 33 | } 34 | function processHtmls(sections, bookId) { 35 | return sections.map((section) => { 36 | return function (section, bookId) { 37 | if (!section || section.length <= 0) { 38 | return '' 39 | } 40 | 41 | let re1 = new RegExp(']+?data-wr-co="([^"]+?)"[^>]+?alt="([^"]+?)"[^>]+?qqreader-footnote[^>]+?>', 'gi'); 42 | section = section.replace(re1, ''); 43 | 44 | let re2 = new RegExp(']+?data-wr-co="([^"]+?)"[^>]+?qqreader-footnote[^>]+?alt="([^"]+?)"[^>]*?>', 'gi'); 45 | 46 | section = section.replace(re2, '') 47 | section = section.replace(/\.\.\/video\/(.*?\.(mp4|wmv|3gp|rm|rmvb|mov|m4v|avi))/gi, 'https://res.weread.qq.com/wrepub/web/' + bookId + '/$1') 48 | section = section.replace(/\.\.\/images\/(.*?\.(png|jpg|jpeg|gif))/gi, 'https://res.weread.qq.com/wrepub/web/' + bookId + '/$1') 49 | return section.trim(); 50 | }(section || '', bookId) 51 | }); 52 | } 53 | 54 | window.weread.store = { 55 | processStyles: processStyles, 56 | processHtmls: processHtmls, 57 | } 58 | })() 59 | -------------------------------------------------------------------------------- /src/backend/common/request.ts: -------------------------------------------------------------------------------- 1 | // deno-lint-ignore-file no-explicit-any 2 | 3 | import { UserAgent, Referer, Origin } from "../config.ts"; 4 | 5 | function stringifyQuery( 6 | query: Record = {}, 7 | ): Record { 8 | const data: Record = {}; 9 | Object.keys(query).reduce((obj, key) => { 10 | obj[key] = query[key].toString(); 11 | return obj; 12 | }, data); 13 | return data; 14 | } 15 | 16 | export function get( 17 | url: string, 18 | query: Record = {}, 19 | header: Record = {}, 20 | ) { 21 | if (Object.keys(query).length) { 22 | url += "?" + new URLSearchParams(stringifyQuery(query)).toString(); 23 | } 24 | const headers: Record = { 25 | "User-Agent": UserAgent, 26 | "Referer": Referer, 27 | "Origin": Origin, 28 | ...header, 29 | }; 30 | return fetch(url, { 31 | method: "GET", 32 | cache: "default", 33 | headers, 34 | }); 35 | } 36 | 37 | function post( 38 | url: string, 39 | data: Record = {}, 40 | format = "json", 41 | header: Record = {}, 42 | ) { 43 | let body; 44 | const headers: Record | undefined = { 45 | "User-Agent": UserAgent, 46 | "Referer": Referer, 47 | "Origin": Origin, 48 | ...header, 49 | }; 50 | 51 | if (format === "query" && Object.keys(data).length) { 52 | url += "?" + new URLSearchParams(stringifyQuery(data)).toString(); 53 | body = undefined; 54 | } else if (format === "json") { 55 | body = JSON.stringify(data); 56 | headers["Content-Type"] = "application/json"; 57 | } else if (format === "form-data") { 58 | const formData = new FormData(); 59 | Object.keys(data).forEach((key) => { 60 | formData.append(key, data[key]); 61 | }); 62 | body = formData; 63 | } 64 | return fetch(url, { 65 | method: "POST", 66 | cache: "no-store", 67 | body, 68 | headers, 69 | }); 70 | } 71 | 72 | export function postJSON( 73 | url: string, 74 | data: Record = {}, 75 | headers: Record = {}, 76 | ) { 77 | return post(url, data, "json", headers); 78 | } 79 | 80 | export function postQuery( 81 | url: string, 82 | data: Record = {}, 83 | headers: Record = {}, 84 | ) { 85 | return post(url, data, "query", headers); 86 | } 87 | 88 | export function postFormData( 89 | url: string, 90 | data: Record = {}, 91 | headers: Record = {}, 92 | ) { 93 | return post(url, data, "form-data", headers); 94 | } 95 | -------------------------------------------------------------------------------- /src/frontend/backup/css/main.css: -------------------------------------------------------------------------------- 1 | * { 2 | box-sizing: border-box; 3 | } 4 | 5 | body, p { 6 | margin: 0; 7 | } 8 | label { 9 | /*display: inline-block;*/ 10 | /*width: 100%;*/ 11 | } 12 | 13 | .section-app { 14 | position: relative; 15 | max-width: 800px; 16 | min-width: 580px; 17 | background-color: antiquewhite; 18 | margin: 10px auto; 19 | border-radius: 8px; 20 | } 21 | 22 | .section-app > header { 23 | border-bottom: 1px solid #dcdcdc; 24 | padding: 10px 20px; 25 | } 26 | 27 | .section-app > header > h1 { 28 | text-align: center; 29 | margin-bottom: 20px; 30 | } 31 | 32 | .section-app > footer { 33 | display: flex; 34 | justify-content: space-between; 35 | border-top: 1px solid #dcdcdc; 36 | padding: 10px 20px; 37 | } 38 | 39 | .section-app > footer { 40 | & > .notice { 41 | font-size: 14px; 42 | color: red; 43 | } 44 | & > a { 45 | font-size: 14px; 46 | } 47 | } 48 | 49 | .section-app > main { 50 | padding: 10px 20px; 51 | } 52 | 53 | .section-app > main > form { 54 | margin: 30px auto; 55 | display: flex; 56 | gap: 10px; 57 | } 58 | 59 | .section-app > main label, 60 | .section-app > main input { 61 | flex: 1; 62 | width: 100%; 63 | height: 36px; 64 | font-size: 16px; 65 | padding-left: .75em; 66 | } 67 | 68 | .section-app form > label { 69 | display: inline-flex; 70 | align-items: center; 71 | } 72 | 73 | label.required > span::before { 74 | content: "*"; 75 | color: red; 76 | } 77 | 78 | .section-app > main > form button { 79 | height: 36px; 80 | width: 160px; 81 | } 82 | 83 | .rooms li { 84 | margin-bottom: 10px; 85 | } 86 | 87 | .rooms li > span { 88 | margin-right: 20px; 89 | } 90 | 91 | .rooms li > a { 92 | font-size: 14px; 93 | } 94 | .section-command { 95 | position: relative; 96 | max-width: 800px; 97 | min-width: 580px; 98 | margin: 10px auto; 99 | border-radius: 8px; 100 | } 101 | .section-command select { 102 | padding: 2px 5px; 103 | margin-right: 10px; 104 | } 105 | .section-command input { 106 | padding: 2px 5px; 107 | margin-right: 10px; 108 | } 109 | .section-command .result { 110 | margin-top: 20px; 111 | display: flex; 112 | flex-direction: column; 113 | } 114 | .section-command > .result textarea { 115 | resize: none; 116 | width: 100%; 117 | } 118 | .section-command > .result button { 119 | font-size: 18px; 120 | } 121 | 122 | .notify { 123 | position: fixed; 124 | top: 0; 125 | width: 100%; 126 | height: 40px; 127 | line-height: 40px; 128 | background-color: gainsboro; 129 | color: red; 130 | text-align: center; 131 | } 132 | [v-cloak] { 133 | display: none; 134 | } 135 | -------------------------------------------------------------------------------- /resources/anatomy/modules/E7iI.js: -------------------------------------------------------------------------------- 1 | /** 2 | * id: E7iI 3 | * path: ./panel 4 | */ 5 | 6 | (function(require,module,exports) { 7 | "use strict";var t,e,i,n,r=this&&this.__assign||function(){return(r=Object.assign||function(t){for(var e,i=1,n=arguments.length;i label { 20 | display: flex; 21 | justify-content: center; 22 | align-items: center; 23 | margin: 0 auto; 24 | 25 | & > span { 26 | font-size: 18px; 27 | } 28 | } 29 | 30 | & input { 31 | min-width: 15em; 32 | padding: .5em; 33 | font-size: 16px; 34 | } 35 | 36 | & select { 37 | padding: .5em; 38 | font-size: 16px; 39 | margin-left: .5em; 40 | } 41 | 42 | & .actions { 43 | display: flex; 44 | justify-content: center; 45 | gap: 20px; 46 | margin-top: 20px; 47 | 48 | & > button { 49 | position: relative; 50 | font-size: 16px; 51 | } 52 | } 53 | } 54 | 55 | .container { 56 | margin: 40px auto; 57 | display: flex; 58 | flex-wrap: wrap; 59 | gap: 5px; 60 | width: 820px; 61 | } 62 | 63 | .room { 64 | display: flex; 65 | justify-content: center; 66 | align-items: center; 67 | width: 50px; 68 | height: 50px; 69 | fill: var(--color-calendar-graph-day-bg); 70 | shape-rendering: geometricPrecision; 71 | background-color: var(--color-calendar-graph-day-bg); 72 | border-radius: 2px; 73 | outline: 1px solid var(--color-calendar-graph-day-border); 74 | outline-offset: -1px; 75 | 76 | & > span { 77 | font-size: 16px; 78 | } 79 | 80 | &.dead { 81 | --color-calendar-graph-day-bg: rgba(252, 0, 0, 0.8); 82 | } 83 | &.low { 84 | --color-calendar-graph-day-bg: rgba(232, 238, 72, 0.8); 85 | } 86 | &.mid { 87 | --color-calendar-graph-day-bg: rgba(164, 199, 80, 0.8); 88 | } 89 | &.high { 90 | --color-calendar-graph-day-bg: rgb(64, 184, 32, 0.8); 91 | } 92 | } 93 | 94 | .statics { 95 | position: fixed; 96 | right: 0; 97 | top: 0; 98 | padding: 20px; 99 | background: cornsilk; 100 | 101 | & > p { 102 | width: 6em; 103 | } 104 | } 105 | 106 | .memory-usage { 107 | position: fixed; 108 | bottom: 30px; 109 | right: 10px; 110 | padding: 0; 111 | 112 | & > p { 113 | display: flex; 114 | justify-content: space-between; 115 | align-items: center; 116 | min-width: 140px; 117 | border-bottom: 1px solid #d5d5d5; 118 | height: 30px; 119 | padding: 0 1em; 120 | 121 | &:last-child { 122 | border-bottom: none; 123 | } 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /resources/anatomy/modules/blC7.js: -------------------------------------------------------------------------------- 1 | /** 2 | * id: blC7 3 | * path: ../common/connects 4 | */ 5 | 6 | (function(require,module,exports) { 7 | "use strict";var e=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(exports,"__esModule",{value:!0}),exports.connectMirror=exports.connectVideoPip=exports.connectVideoLoading=exports.connectUIandVideo=void 0;var t=require("@bilibili-live/web-player-common"),n=require("@bilibili-live/web-player-video"),r=e(require("@bilibili-live/web-player-track"));function i(e,n){var r=e.getCtrlUI(),i=n.getVideoEl();if(null==i||null==r)return function(){};var o=t.userSetting.getVolume();null!=o&&(i.volume=o.value/100,i.muted=o.disabled,r.volume={disabled:i.muted,value:100*i.volume});var u=function(){r.playStatus=!0},a=function(){r.playStatus=!1},l=function(){n.getVideoEl().paused&&e.show()},c=function(e){null!=r.volume&&(r.volume={disabled:i.muted,value:100*i.volume})};i.addEventListener("playing",u),i.addEventListener("pause",a),i.addEventListener("loadeddata",l),i.addEventListener("volumechange",c);var d=e.onChange(function(e,n){if("playStatus"===e)n?i.play().catch(function(e){t.logger.error(e)}):i.pause();else if("volume"===e){var r=n,o=r.disabled,u=r.value;i.muted=o,i.volume=u/100}});return function(){d(),i.removeEventListener("playing",u),i.removeEventListener("pause",a),i.removeEventListener("loadeddata",l),i.removeEventListener("volumechange",c)}}function o(e,r,i){var o,u=function(){o=(0,t.loading)(r)},a=function(){null==o||o()},l=e.on(n.EventType.WaitStart,function(){u()}),c=e.on(n.EventType.WaitEnd,a);return e.once(n.EventType.FirstFrame,function(){a(),(0,t.coverImg)(null,r)}),null==i||""===i?(u(),(0,t.coverImg)(null,r)):(0,t.coverImg)(i,r),function(){l(),c(),a()}}function u(e,i){var o=function(){null!=document.pictureInPictureElement&&document.exitPictureInPicture().catch(function(e){t.logger.error(e)})},u=e.on(n.EventType.Destroyed,function(){o(),c.pipStatus=!1,u()}),a=i.onChange(function(n,r){if("pipStatus"===n&&document.pictureInPictureEnabled){if(r){var i=e.getVideoEl();return o(),void i.requestPictureInPicture().catch(t.logger.error)}o()}}),l=e.getVideoEl(),c=i.getCtrlUI(),d=0,v=function(){r.default.operation(r.default.OperationCode.PictureInPicture,Math.floor((performance.now()-d)/1e3),{sampleRate:1})},s=function(){c.pipStatus=!1,v()},p=function(){d=performance.now()},f=function(){null!=document.pictureInPictureElement&&v()};return l.addEventListener("leavepictureinpicture",s),l.addEventListener("enterpictureinpicture",p),window.addEventListener("beforeunload",f),function(){a(),l.removeEventListener("leavepictureinpicture",s),l.removeEventListener("enterpictureinpicture",p),window.removeEventListener("beforeunload",f),o()}}function a(e,t){!0===t.getCtrlUI().mirrorStatus&&(e.getVideoEl().style.transform="rotateY(180deg)");var n=t.onChange(function(t,n){if("mirrorStatus"===t){var r=e.getVideoEl();r.style.transform=!0!==n?"":"rotateY(180deg)"}});return function(){n()}}exports.connectUIandVideo=i,exports.connectVideoLoading=o,exports.connectVideoPip=u,exports.connectMirror=a; 8 | })() -------------------------------------------------------------------------------- /resources/anatomy/parser.js: -------------------------------------------------------------------------------- 1 | const acorn = require('acorn') 2 | const path = require('path') 3 | const fs = require('fs') 4 | const fse = require('fs-extra') 5 | 6 | const source = fs.readFileSync(path.resolve(__dirname, '../raw/room-player-2023-09-21.min.js'), { 7 | encoding: 'utf-8' 8 | }) 9 | 10 | const programAst = acorn.parse(source, { 11 | ecmaVersion: 2017, 12 | }) 13 | 14 | 15 | /** 16 | * 获取节点的源码内容 17 | * @param node 节点 18 | * @param hint 类型提示 19 | * @return {string} 源码 20 | */ 21 | function getNodeSource(node, hint = null) { 22 | let {start, end} = node 23 | switch (hint) { 24 | // 字符串字面量,去除前后引号 25 | case 'string-literal': 26 | start += 1 27 | end -= 1 28 | break 29 | } 30 | 31 | return source.substring(start, end) 32 | } 33 | 34 | const assignmentExpNode = programAst.body[0].expression 35 | const firstArgNode = assignmentExpNode.right.arguments[0] 36 | const thirdArgNode = assignmentExpNode.right.arguments[2] 37 | 38 | // const globalName = getNodeSource(assignmentExpNode.left) 39 | // const entry = getNodeSource(thirdArgNode) 40 | 41 | const graph = {} 42 | const map = {} 43 | 44 | for (const {key, value} of firstArgNode.properties) { 45 | const deps = value.elements[1].properties.reduce((obj, prop) => { 46 | const {key, value} = prop 47 | obj[getNodeSource(value, 'string-literal')] = getNodeSource(key, 'string-literal') 48 | map[getNodeSource(value, 'string-literal')] = getNodeSource(key, 'string-literal') 49 | return obj 50 | }, {}) 51 | 52 | const impl = value.elements[0] 53 | 54 | graph[getNodeSource(key, 'string-literal')] = { 55 | id: getNodeSource(key, 'string-literal'), 56 | deps, 57 | impl: { 58 | start: impl.start, 59 | end: impl.end, 60 | }, 61 | } 62 | } 63 | for (const module of Object.values(graph)) { 64 | if (module.id in map) { 65 | module.path = map[module.id] 66 | } 67 | } 68 | 69 | console.log(graph) 70 | console.log(map) 71 | 72 | // 拆分模块文件 73 | function writeGraph(graph) { 74 | fse.emptyDirSync(path.resolve(__dirname, 'modules')) 75 | 76 | for (const module of Object.values(graph)) { 77 | writeModule(module) 78 | } 79 | } 80 | function writeModule(module) { 81 | const fileName = `modules/${module.id}${module.path ? '' : '-entrypoint'}.js` 82 | const fileContent = `${generateFileComment(module)}\n(${getNodeSource(module.impl)})()` 83 | 84 | fs.writeFileSync(path.resolve(__dirname, fileName), fileContent, { 85 | encoding: 'utf-8', 86 | }) 87 | } 88 | 89 | function generateFileComment(module) { 90 | let comment = '/**\n' 91 | if (module.id) { 92 | comment += ` * id: ${module.id}\n` 93 | } 94 | if (module.path) { 95 | comment += ` * path: ${module.path}\n` 96 | } 97 | comment += ' */\n' 98 | return comment 99 | } 100 | 101 | writeGraph(graph) 102 | -------------------------------------------------------------------------------- /src/frontend/backup/style.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | style 6 | 57 | 58 | 59 |
60 |
1
61 |
2
62 |
130
63 |
0
64 |
0
65 |
2300
66 |
0
67 |
20
68 |
20
69 |
300
70 |
1300
71 |
0
72 |
20
73 |
20
74 |
20
75 |
20
76 |
200
77 |
20
78 |
3
79 |
80 |
81 | 标题 82 |
83 | 86 |
87 |
88 | 91 |
92 |
93 | 94 | 95 | -------------------------------------------------------------------------------- /resources/anatomy/modules/ryqy.js: -------------------------------------------------------------------------------- 1 | /** 2 | * id: ryqy 3 | * path: ./bili-p2p 4 | */ 5 | 6 | (function(require,module,exports) { 7 | "use strict";var e=this&&this.__assign||function(){return(e=Object.assign||function(e){for(var t,r=1,i=arguments.length;r 2 | import {useRoomStore} from "@/stores/room"; 3 | import type {Room} from '@/types' 4 | 5 | const roomStore = useRoomStore() 6 | 7 | 8 | 9 | 32 | 33 | 126 | -------------------------------------------------------------------------------- /src/frontend/backup/css/loading.css: -------------------------------------------------------------------------------- 1 | #init__loading__placeholder { 2 | pointer-events: none; 3 | position: fixed; 4 | top: 0; 5 | left: 0; 6 | opacity: 1; 7 | z-index: 999; 8 | width: 100%; 9 | height: 100%; 10 | background: rgba(255, 255, 255, 0.9); 11 | transition: opacity .5s linear; 12 | } 13 | .hidden { 14 | opacity: 0 !important; 15 | } 16 | 17 | #init__loading__placeholder .loading-title { 18 | position: absolute; 19 | top: 40%; 20 | left: 50%; 21 | margin-top: 40px; 22 | margin-left: -70px; 23 | color: #0299be; 24 | font-size: 14px; 25 | font-weight: 600; 26 | letter-spacing: 2px; 27 | } 28 | 29 | #init__loading__placeholder .loading-grid { 30 | position: absolute; 31 | top: 40%; 32 | left: 50%; 33 | width: 30px; 34 | height: 30px; 35 | margin: -15px 0 0 -15px; 36 | } 37 | 38 | #init__loading__placeholder .loading { 39 | width: 10px; 40 | height: 10px; 41 | background-color: #0299be; 42 | float: left; 43 | -webkit-animation: loading-grid-scale-delay 1.3s infinite ease-in-out; 44 | animation: loading-grid-scale-delay 1.3s infinite ease-in-out; 45 | } 46 | 47 | #init__loading__placeholder .loading1 { 48 | border-top-left-radius: 4px; 49 | -webkit-animation-delay: 0.2s; 50 | animation-delay: 0.2s; 51 | } 52 | 53 | #init__loading__placeholder .loading2 { 54 | -webkit-animation-delay: 0.3s; 55 | animation-delay: 0.3s; 56 | } 57 | 58 | #init__loading__placeholder .loading3 { 59 | border-top-right-radius: 4px; 60 | -webkit-animation-delay: 0.4s; 61 | animation-delay: 0.4s; 62 | } 63 | 64 | #init__loading__placeholder .loading4 { 65 | -webkit-animation-delay: 0.1s; 66 | animation-delay: 0.1s; 67 | } 68 | 69 | #init__loading__placeholder .loading5 { 70 | -webkit-animation-delay: 0.2s; 71 | animation-delay: 0.2s; 72 | } 73 | 74 | #init__loading__placeholder .loading6 { 75 | -webkit-animation-delay: 0.3s; 76 | animation-delay: 0.3s; 77 | } 78 | 79 | #init__loading__placeholder .loading7 { 80 | border-bottom-left-radius: 4px; 81 | -webkit-animation-delay: 0s; 82 | animation-delay: 0s; 83 | } 84 | 85 | #init__loading__placeholder .loading8 { 86 | -webkit-animation-delay: 0.1s; 87 | animation-delay: 0.1s; 88 | } 89 | 90 | #init__loading__placeholder .loading9 { 91 | border-bottom-right-radius: 4px; 92 | -webkit-animation-delay: 0.2s; 93 | animation-delay: 0.2s; 94 | } 95 | 96 | @-webkit-keyframes loading-grid-scale-delay { 97 | 0%, 98 | 70%, 99 | 100% { 100 | -webkit-transform: scale3D(1, 1, 1); 101 | transform: scale3D(1, 1, 1); 102 | } 103 | 104 | 35% { 105 | -webkit-transform: scale3D(0, 0, 1); 106 | transform: scale3D(0, 0, 1); 107 | } 108 | } 109 | 110 | @keyframes loading-grid-scale-delay { 111 | 0%, 112 | 70%, 113 | 100% { 114 | -webkit-transform: scale3D(1, 1, 1); 115 | transform: scale3D(1, 1, 1); 116 | } 117 | 118 | 35% { 119 | -webkit-transform: scale3D(0, 0, 1); 120 | transform: scale3D(0, 0, 1); 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /resources/anatomy/modules/NApx.js: -------------------------------------------------------------------------------- 1 | /** 2 | * id: NApx 3 | * path: @bilibili-live/live-danmaku-engine-v2 4 | */ 5 | 6 | (function(require,module,exports) { 7 | var define; 8 | var t;!function(e,n){"object"==typeof exports&&"object"==typeof module?module.exports=n():"function"==typeof t&&t.amd?t([],n):"object"==typeof exports?exports.DanmakuBridge=n():e.DanmakuBridge=n()}(self,function(){return function(){"use strict";var t={};!function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})}(t);var e=function(t,e,n){if(n||2===arguments.length)for(var o,a=0,r=e.length;a0&&(this.tasks.forEach(function(t){t&&t()}),this.tasks.length=0)},t.prototype.play=function(){this.asyncCallMethod("play")},t.prototype.add=function(t,e){this.callMethod("add",t,e)},t.prototype.searchAreaDanmaku=function(t,e){return this.asyncCallMethod("searchAreaDanmaku",t,e)},t.prototype.option=function(t,e){return this.asyncCallMethod("option",t,e)},t.prototype.visible=function(t){return this.asyncCallMethod("visible",t)},t.prototype.resize=function(){this.asyncCallMethod("resize")},t.prototype.clear=function(){this.asyncCallMethod("clear")},t.prototype.destroy=function(){this.asyncCallMethod("destroy")},t.prototype.loadDanmaku=function(t){var e=this,n=document.createElement("script");n.type="text/javascript",n.onload=function(){e.Danmaku=new window.LiveDanmakuEngine.default(e.config),e.callTasks()},n.onerror=function(){e.errorTimes>=2||(e.errorTimes++,console.error("[Danmaku Core Error]","Times: ".concat(e.errorTimes),"Url: ".concat(t)),e.loadDanmaku(t))},n.src=t,document.getElementsByTagName("body")[0].appendChild(n)},t}();return t.default=a,t}()}); 9 | })() -------------------------------------------------------------------------------- /docs/apis/nav.md: -------------------------------------------------------------------------------- 1 | # nav 2 | 3 | ## 接口地址 4 | 5 | `https://api.bilibili.com/x/web-interface/nav` 6 | 7 | ## 方法 8 | 9 | `GET` 10 | 11 | ## 参数 12 | 13 | 无 14 | 15 | ## 返回示例 16 | 17 | ```json 18 | { 19 | "code": 0, 20 | "message": "0", 21 | "ttl": 1, 22 | "data": { 23 | "isLogin": true, 24 | "email_verified": 0, 25 | "face": "http://i0.hdslb.com/bfs/face/member/noface.jpg", 26 | "face_nft": 0, 27 | "face_nft_type": 0, 28 | "level_info": { 29 | "current_level": 0, 30 | "current_min": 0, 31 | "current_exp": 0, 32 | "next_exp": 1 33 | }, 34 | "mid": 549621446, 35 | "mobile_verified": 1, 36 | "money": 0, 37 | "moral": 70, 38 | "official": { 39 | "role": 0, 40 | "title": "", 41 | "desc": "", 42 | "type": -1 43 | }, 44 | "officialVerify": { 45 | "type": -1, 46 | "desc": "" 47 | }, 48 | "pendant": { 49 | "pid": 0, 50 | "name": "", 51 | "image": "", 52 | "expire": 0, 53 | "image_enhance": "", 54 | "image_enhance_frame": "" 55 | }, 56 | "scores": 0, 57 | "uname": "bili_28350385046", 58 | "vipDueDate": 0, 59 | "vipStatus": 0, 60 | "vipType": 0, 61 | "vip_pay_type": 0, 62 | "vip_theme_type": 0, 63 | "vip_label": { 64 | "path": "", 65 | "text": "", 66 | "label_theme": "", 67 | "text_color": "", 68 | "bg_style": 0, 69 | "bg_color": "", 70 | "border_color": "", 71 | "use_img_label": true, 72 | "img_label_uri_hans": "", 73 | "img_label_uri_hant": "", 74 | "img_label_uri_hans_static": "https://i0.hdslb.com/bfs/vip/d7b702ef65a976b20ed854cbd04cb9e27341bb79.png", 75 | "img_label_uri_hant_static": "https://i0.hdslb.com/bfs/activity-plat/static/20220614/e369244d0b14644f5e1a06431e22a4d5/KJunwh19T5.png" 76 | }, 77 | "vip_avatar_subscript": 0, 78 | "vip_nickname_color": "", 79 | "vip": { 80 | "type": 0, 81 | "status": 0, 82 | "due_date": 0, 83 | "vip_pay_type": 0, 84 | "theme_type": 0, 85 | "label": { 86 | "path": "", 87 | "text": "", 88 | "label_theme": "", 89 | "text_color": "", 90 | "bg_style": 0, 91 | "bg_color": "", 92 | "border_color": "", 93 | "use_img_label": true, 94 | "img_label_uri_hans": "", 95 | "img_label_uri_hant": "", 96 | "img_label_uri_hans_static": "https://i0.hdslb.com/bfs/vip/d7b702ef65a976b20ed854cbd04cb9e27341bb79.png", 97 | "img_label_uri_hant_static": "https://i0.hdslb.com/bfs/activity-plat/static/20220614/e369244d0b14644f5e1a06431e22a4d5/KJunwh19T5.png" 98 | }, 99 | "avatar_subscript": 0, 100 | "nickname_color": "", 101 | "role": 0, 102 | "avatar_subscript_url": "", 103 | "tv_vip_status": 0, 104 | "tv_vip_pay_type": 0 105 | }, 106 | "wallet": { 107 | "mid": 549621446, 108 | "bcoin_balance": 0, 109 | "coupon_balance": 0, 110 | "coupon_due_time": 0 111 | }, 112 | "has_shop": false, 113 | "shop_url": "", 114 | "allowance_count": 0, 115 | "answer_status": 1, 116 | "is_senior_member": 0 117 | } 118 | } 119 | ``` 120 | 121 | ## 接口目的 122 | -------------------------------------------------------------------------------- /resources/anatomy/modules/cPXi.js: -------------------------------------------------------------------------------- 1 | /** 2 | * id: cPXi 3 | * path: ./interface 4 | */ 5 | 6 | (function(require,module,exports) { 7 | "use strict";var e,a,t,r,o,i,n,l,s;Object.defineProperty(exports,"__esModule",{value:!0}),exports.SEIType=exports.CorePlayerType=exports.ExternalEventType=exports.ApiErrorCode=exports.P2PType=exports.EQuality=exports.CodecType=exports.FormatType=exports.ProtocolType=void 0,function(e){e.HTTP_HLS="http_hls",e.HTTP_STREAM="http_stream"}(e=exports.ProtocolType||(exports.ProtocolType={})),function(e){e.FLV="flv",e.TS="ts",e.FMP4="fmp4"}(a=exports.FormatType||(exports.FormatType={})),function(e){e.AVC="avc",e.HEVC="hevc"}(t=exports.CodecType||(exports.CodecType={})),function(e){e[e.Origin=1e4]="Origin",e[e.K4=2e4]="K4",e[e.Dolby=3e4]="Dolby"}(r=exports.EQuality||(exports.EQuality={})),function(e){e[e.HLS_NOT_P2P=-1]="HLS_NOT_P2P",e[e.NONE=0]="NONE",e[e.HLS_BILI=1]="HLS_BILI",e[e.FLV_QVB=8]="FLV_QVB"}(o=exports.P2PType||(exports.P2PType={})),function(e){e[e.AreaBlock=6005]="AreaBlock"}(i=exports.ApiErrorCode||(exports.ApiErrorCode={})),function(e){e.Initialized="initialized",e.LiveStateChange="liveStateChange",e.StartPlayRound="startPlayRound",e.VideoStateChange="videoStateChange",e.FullscreenChange="fullscreenChange",e.Playing="playing",e.Paused="paused",e.SwitchLine="switchLine",e.SwitchQuality="switchQuality",e.WebFullscreen="webFullscreen",e.FeedBackClick="feedBackClick",e.BlockSettingClick="blockSettingClick",e.Set="set",e.Reload="reload",e.GuidChange="guidChange",e.InitDanmaku="initDanmaku",e.AddDanmaku="addDanmaku",e.SendDanmaku="sendDanmaku",e.ReceiveOnlineCount="receiveOnlineCount",e.ReceiveMessage="receiveMessage",e.UserLogin="userLogin",e.SendGift="sendGift",e.FirstLoadedAPIPlayer="firstLoadedAPIPlayer",e.FirstLoadedAPIPlayurl="firstLoadedAPIPlayurl",e.FirstLoadStart="firstLoadStart",e.FirstLoadedMetaData="firstLoadedMetaData",e.FirstPlaying="firstPlaying",e.EnterTheRoom="enterTheRoom",e.OperableElementsChange="operableElementsChange",e.AutoPlay="autoPlay",e.SwitchQualityNotLogin="switchQualityNotLogin",e.Recommend="recommend",e.DanmakuMaskChange="danmakuMaskChange",e.DanmakuMaskStatusChange="danmakuMaskStatusChange",e.CtrlVisibleChange="ctrlVisibleChange",e.WebPlayerCreated="webPlayerCreated",e.FirstFrame="FirstFrame",e.VideoDestroyed="VideoDestroyed",e.SEIData="SEIData",e.VolumeChange="VolumeChange",e.VideoDirectionChange="VideoDirectionChange",e.UserClickPlayIcon="UserClickPlayIcon",e.MergeStream="MergeStream",e.SEIParseData="SEIParseData",e.OrientationChange="OrientationChange",e.UserRedirect="UserRedirect",e.MutePlay="MutePlay",e.NotAutoPlay="NotAutoPlay",e.WaitStart="WaitStart",e.WaitEnd="WaitEnd",e.VideoError="VideoError",e.MetaDataChange="MetaDataChange",e.PlayFaild="PlayFailed",e.ScreenStateChange="ScreenStateChange",e.UserOperation="UserOperation"}(n=exports.ExternalEventType||(exports.ExternalEventType={})),function(e){e.fMp4Player="fMp4Player",e.Hls7Player="Hls7Player",e.NativePlayer="NativePlayer"}(l=exports.CorePlayerType||(exports.CorePlayerType={})),function(e){e.LIVE_SEI_CHANNEL="LIVE_SEI_CHANNEL",e.BILIMASK__SVGBIN="BILIMASK__SVGBIN",e.B_LIVE_VIBRATION="B_LIVE_VIBRATION",e.LIVE_SEI_PC_LINK="LIVE_SEI_PC_LINK",e.BVC_KUAWAN____TS="BVC_KUAWAN____TS",e.BILILIVESUBTITLE="BILILIVESUBTITLE",e.BVCLIVESTREAMHOP="BVCLIVESTREAMHOP",e.BVC__CANVAS_DATA="BVC__CANVAS_DATA"}(s=exports.SEIType||(exports.SEIType={})); 8 | })() -------------------------------------------------------------------------------- /resources/anatomy/modules/ywxt.js: -------------------------------------------------------------------------------- 1 | /** 2 | * id: ywxt 3 | * path: ./modal 4 | */ 5 | 6 | (function(require,module,exports) { 7 | "use strict";Object.defineProperty(exports,"__esModule",{value:!0});var t=require("@bilibili-live/web-player-common");require("./modal.less");var e=function(){function e(t,i){this.container=t,this._snippet=['
','
','','
'],this._opts=Object.assign({},e._DEFAULT_OPTIONS,i),this._initialize()}return e.prototype.show=function(){var t=this._opts,e=this._template;this._freshPos(),e._container.classList.add("active"),"function"==typeof t.onShow&&t.onShow()},e.prototype.hide=function(){var t=this._opts;this._template._container.classList.remove("active"),"function"==typeof t.onHide&&t.onHide(),t.autoRemove&&this.destroy()},e.prototype.destroy=function(){this._template._container.remove()},e.prototype._initialize=function(){this._create()._bindEvents(),this._opts.autoShow&&this.show()},e.prototype._create=function(){var e=(0,t.createElement)("div",{classname:"blp-plugin-modal"});e.innerHTML=this._snippet.join(""),this._template={_container:e,_header:e.querySelector(".blp-plugin-modal-header"),_body:e.querySelector(".blp-plugin-modal-body"),_footer:e.querySelector(".blp-plugin-modal-footer"),_close:e.querySelector(".blp-plugin-modal-close")};var i=this._template,n=this._opts;return i._header.innerText=n.title,i._body.innerHTML=""!==n.html?n.html:n.text,this._createBtn(i._footer,n.btns,n),n.hideBtn&&i._close.classList.add("active"),this.container.appendChild(e),null!=n.width&&0!==n.width&&(e.style.width="".concat(n.width,"px")),this},e.prototype._createBtn=function(e,i,n){var o=this;if(i instanceof Array)i.forEach(function(t){o._createBtn(e,t,n)});else{var l=null;switch(i.type){case"submit":(l=(0,t.createElement)("button",{classname:"blp-plugin-modal-btn submit-btn"})).innerHTML=i.text,"function"==typeof i.click&&l.addEventListener("click",function(t){i.click(t),o.hide()});break;case"cancel":(l=(0,t.createElement)("button",{classname:"blp-plugin-modal-btn cancel-btn"})).innerHTML=i.text,"flex-report"===n.btnPostion&&l.classList.add("live-bilibili-player-report-cancel"),l.addEventListener("click",function(){var t;null===(t=i.click)||void 0===t||t.call(i),o.hide()})}null!=l&&e.append(l)}},e.prototype._bindEvents=function(){var t=this;return this._opts.hideBtn&&this._template._close.addEventListener("click",function(){t.hide()}),this},e.prototype._freshPos=function(){var t=this._opts,e=this._template._container,i=this._template._footer,n="",o="",l=0,s=0,a=e.style;switch(t.btnPostion){case"flex-report":i.style.cssText="\n display: flex;\n justify-content: space-around;\n margin-top: 69px;\n ",a.height="190px",a.padding="20px"}switch(t.position){case"center-center":n="50%",o="50%",l=-1*parseInt(a.width)/2,s=-1*parseInt(a.height)/2}a.left=n,a.top=o,a.marginLeft="".concat(l,"px"),a.marginTop="".concat(s,"px")},e._DEFAULT_OPTIONS={title:"Bilibili HTML5 Live Player Modal",theme:"white",text:"Are you confirm the operation?",html:"",btns:[{type:"submit",text:"Confirm",click:function(){}},{type:"cancel",text:"Cancel",click:function(){}}],mask:!0,position:"center-center",hideBtn:!0,autoShow:!1,autoRemove:!1},e}();exports.default=e; 8 | })() -------------------------------------------------------------------------------- /resources/anatomy/js/vendors.js.LICENSE.txt: -------------------------------------------------------------------------------- 1 | /*! 2 | * Determine if an object is a Buffer 3 | * 4 | * @author Feross Aboukhadijeh 5 | * @license MIT 6 | */ 7 | 8 | /*! 9 | * Draggabilly v3.0.0 10 | * Make that shiz draggable 11 | * https://draggabilly.desandro.com 12 | * MIT license 13 | */ 14 | 15 | /*! 16 | * Infinite Scroll v2.0.4 17 | * measure size of elements 18 | * MIT license 19 | */ 20 | 21 | /*! 22 | * Unidragger v3.0.1 23 | * Draggable base class 24 | * MIT license 25 | */ 26 | 27 | /*! 28 | * Vue.js v2.6.14 29 | * (c) 2014-2021 Evan You 30 | * Released under the MIT License. 31 | */ 32 | 33 | /*! 34 | * pinia v2.1.6 35 | * (c) 2023 Eduardo San Martin Morote 36 | * @license MIT 37 | */ 38 | 39 | /*! 40 | * vuex v3.6.2 41 | * (c) 2021 Evan You 42 | * @license MIT 43 | */ 44 | 45 | /*! ***************************************************************************** 46 | Copyright (C) Microsoft. All rights reserved. 47 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use 48 | this file except in compliance with the License. You may obtain a copy of the 49 | License at http://www.apache.org/licenses/LICENSE-2.0 50 | 51 | THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 52 | KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED 53 | WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, 54 | MERCHANTABLITY OR NON-INFRINGEMENT. 55 | 56 | See the Apache Version 2.0 License for specific language governing permissions 57 | and limitations under the License. 58 | ***************************************************************************** */ 59 | 60 | /*! ***************************************************************************** 61 | Copyright (c) Microsoft Corporation. 62 | 63 | Permission to use, copy, modify, and/or distribute this software for any 64 | purpose with or without fee is hereby granted. 65 | 66 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 67 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 68 | AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 69 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 70 | LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 71 | OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 72 | PERFORMANCE OF THIS SOFTWARE. 73 | ***************************************************************************** */ 74 | 75 | /** 76 | * @license 77 | * Lodash 78 | * Copyright OpenJS Foundation and other contributors 79 | * Released under MIT license 80 | * Based on Underscore.js 1.8.3 81 | * Copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors 82 | */ 83 | 84 | /** 85 | * Prefect script loader is designed to load script dynamically. 86 | * Based on (eldargab/load-script)[https://github.com/eldargab/load-script] 87 | * 88 | * @author: Eldar Gabdullin (Original), LancerComet (Revision) 89 | * @license: MIT 90 | */ 91 | 92 | /** @preserve 93 | * Counter block mode compatible with Dr Brian Gladman fileenc.c 94 | * derived from CryptoJS.mode.CTR 95 | * Jan Hruby jhruby.web@gmail.com 96 | */ 97 | -------------------------------------------------------------------------------- /resources/anatomy/modules/Klvu.js: -------------------------------------------------------------------------------- 1 | /** 2 | * id: Klvu 3 | * path: ./loading 4 | */ 5 | 6 | (function(require,module,exports) { 7 | "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.unloading=exports.loading=void 0;var e='\n\n\n \n \n \n\n \n \n \n\n \n \n \n\n \n \n \n\n \n \n \n\n \n \n \n\n \n \n \n\n \n \n \n\n \n \n \n\n\n';function t(t){t.querySelectorAll(".web-player-loading").forEach(function(e){return e.remove()});var n=document.createElement("div");n.classList.add("web-player-loading"),n.innerHTML=e,n.style.cssText="\n position: absolute;\n top: 0;\n bottom: 0;\n left: 0;\n right: 0;\n z-index: 10;\n display: none;\n pointer-events: none;\n ",t.appendChild(n);var i=setTimeout(function(){n.style.display="flex"},500);return function(){clearTimeout(i),n.remove()}}function n(){document.querySelectorAll(".web-player-loading").forEach(function(e){return e.remove()})}exports.loading=t,exports.unloading=n; 8 | })() -------------------------------------------------------------------------------- /resources/anatomy/modules/PTXm.js: -------------------------------------------------------------------------------- 1 | /** 2 | * id: PTXm 3 | * path: ./browser-fullscreen 4 | */ 5 | 6 | (function(require,module,exports) { 7 | "use strict";var e=this&&this.__awaiter||function(e,n,t,r){return new(t||(t=Promise))(function(l,u){function i(e){try{c(r.next(e))}catch(n){u(n)}}function o(e){try{c(r.throw(e))}catch(n){u(n)}}function c(e){var n;e.done?l(e.value):(n=e.value,n instanceof t?n:new t(function(e){e(n)})).then(i,o)}c((r=r.apply(e,n||[])).next())})},n=this&&this.__generator||function(e,n){var t,r,l,u,i={label:0,sent:function(){if(1&l[0])throw l[1];return l[1]},trys:[],ops:[]};return u={next:o(0),throw:o(1),return:o(2)},"function"==typeof Symbol&&(u[Symbol.iterator]=function(){return this}),u;function o(o){return function(c){return function(o){if(t)throw new TypeError("Generator is already executing.");for(;u&&(u=0,o[0]&&(i=0)),i;)try{if(t=1,r&&(l=2&o[0]?r.return:o[0]?r.throw||((l=r.return)&&l.call(r),0):r.next)&&!(l=l.call(r,o[1])).done)return l;switch(r=0,l&&(o=[2&o[0],l.value]),o[0]){case 0:case 1:l=o;break;case 4:return i.label++,{value:o[1],done:!1};case 5:i.label++,r=o[1],o=[0];continue;case 7:o=i.ops.pop(),i.trys.pop();continue;default:if(!(l=(l=i.trys).length>0&&l[l.length-1])&&(6===o[0]||2===o[0])){i=0;continue}if(3===o[0]&&(!l||o[1]>l[0]&&o[1]=0;--i){if(h(b,t,"f")[i].timestamp1&&l&&b.webXInputSetState(0,0),null!=e&&null!=e.vibrate&&e.vibrate!==h(b,r,"f")){var n=e.timestamp-t;if(Math.abs(n)<=1){if(l){var o=Math.max(0,n);setTimeout(function(){b.webXInputSetState(e.vibrate.l,e.vibrate.r)},1e3*o)}c(b,r,e.vibrate,"f"),c(b,a,e.timestamp,"f")}}},this.onFrameCallback=function(t){t>performance.now()-5&&b.onUpdateVibrate(),c(b,n,window.requestAnimationFrame(b.onFrameCallback),"f")},this.destroy=function(){cancelAnimationFrame(h(b,n,"f")),c(b,t,[],"f"),b.webXInputSetState(0,0),h(b,e,"f").dispose()},s.set(this,function(t){0===t?(h(b,o,"f").shiftLimit=0,h(b,o,"f").curLocation=0):t<.075?h(b,o,"f").shiftLimit=2:t<=.88?h(b,o,"f").shiftLimit=5:t>.88&&(h(b,o,"f").shiftLimit=7),h(b,o,"f").speed=4*t,h(b,f,"f").call(b)}),f.set(this,function(){"top"===h(b,o,"f").direction?(h(b,o,"f").curLocation+=h(b,o,"f").speed,h(b,o,"f").curLocation+=h(b,o,"f").speed):(h(b,o,"f").curLocation-=h(b,o,"f").speed,h(b,o,"f").curLocation-=h(b,o,"f").speed),Math.abs(h(b,o,"f").curLocation)>h(b,o,"f").shiftLimit&&("top"===h(b,o,"f").direction?(h(b,o,"f").direction="bottom",h(b,o,"f").curLocation=h(b,o,"f").shiftLimit):(h(b,o,"f").direction="top",h(b,o,"f").curLocation=h(b,o,"f").shiftLimit)),null!=b.dmBiz&&b.dmBiz.offsetDM(h(b,o,"f").curLocation)}),c(this,t,[],"f"),c(this,i,m,"f"),this.onFrameCallback(0)}return m.prototype.webXInputSetState=function(t,i){h(this,e,"f").setState(t,i),h(this,s,"f").call(this,h(this,e,"f").getMotorPercent())},m}();exports.VibrateTask=m,t=new WeakMap,e=new WeakMap,i=new WeakMap,a=new WeakMap,r=new WeakMap,n=new WeakMap,o=new WeakMap,s=new WeakMap,f=new WeakMap; 8 | })() -------------------------------------------------------------------------------- /resources/anatomy/modules/PFwM.js: -------------------------------------------------------------------------------- 1 | /** 2 | * id: PFwM 3 | * path: ./ajax 4 | */ 5 | 6 | (function(require,module,exports) { 7 | "use strict";var e=this&&this.__assign||function(){return(e=Object.assign||function(e){for(var t,n=1,r=arguments.length;n0&&a[a.length-1])&&(6===i[0]||2===i[0])){c=0;continue}if(3===i[0]&&(!a||i[1]>a[0]&&i[1]performance.now()?(h=Math.random()*p.scope,[4,(0,r.sleep)(h)]):[3,2];case 1:n.sent(),n.label=2;case 2:""!==(d=(0,r.getCookie)("bili_jct"))&&"POST"===(null==s?void 0:s.method)&&null!=(null==s?void 0:s.data)&&(w=new FormData,b=e(e({},null!==(u=s.data)&&void 0!==u?u:{}),{csrf:d,csrf_token:d}),Object.keys(b).forEach(function(e){w.append(e,b[e])}),s.body=w),n.label=3;case 3:return n.trys.push([3,5,,6]),[4,fetch(l,e({credentials:"include"},null!=s?s:{}))];case 4:return v=n.sent(),[3,6];case 5:throw y=n.sent(),m=String(y),c.forEach(function(e){return e("requestErr",{errMsg:m,apiUrl:f.pathname})}),new Error(m);case 6:if(!v.ok)throw g="url: ".concat(i,", status: ").concat(v.status),c.forEach(function(e){return e("responseErr",{errMsg:"status: ".concat(v.status),apiUrl:f.pathname})}),new Error(g);return[4,v.json()];case 7:if(E=n.sent(),c.forEach(function(e){return e("perf",{duration:performance.now()-t,apiUrl:f.pathname})}),"function"==typeof(null==s?void 0:s.handleRes))return s.handleRes(E),[2,E.data];if(x=E.code,_=E.data,j=E.message,0!==x)throw c.forEach(function(e){return e("apiBizErr",{errMsg:j,apiUrl:f.pathname})}),new Error(j);return[2,_]}})})}exports.ajax=i,i.watch=function(e){return c.add(e),function(){c.delete(e)}},i.disperse=function(e,t){var n=/^(https?:)?\/\//.test(e)?e:"".concat(a,"/").concat(e.replace(/^\//,""));o.set(n.replace(/^(https?:)?\/\//,""),t)}; 8 | })() -------------------------------------------------------------------------------- /src/frontend/backup/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | B站直播间弹幕采集演示 7 | 8 | 9 | 10 | 11 | 12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
加载中, 请耐心等待
25 |
26 |
27 |
28 |
29 |

B站直播间弹幕采集演示

30 |
31 |
32 |
33 | 37 | 38 |
39 |
40 |

当前连接的房间列表:

41 |
    42 |
  • 43 | {{room}} 44 | 断开 45 |
  • 46 |
47 |
48 |
49 |
50 |

注意: 请打开控制台查看弹幕数据

51 | 稳定性测试 52 |
53 |
54 |
55 |

56 | 60 |

61 | 62 | 63 | 86 |
87 |
WebSocket 中断,可能是因为服务在重新部署,请稍后重试。
88 |
89 | 90 | 91 | 92 | 93 | --------------------------------------------------------------------------------