Bilibili-Evolved/min/live-socket.min.js
2020-03-12 22:04:48 +08:00

1 line
5.0 KiB
JavaScript

(()=>{return(e,t)=>{const s={json:0,heartBeatResponse:1,buffer:2};const r={heartBeat:2,heartBeatResponse:3,message:5,enterRoom:7,enterRoomResponse:8};class i{constructor(){this.textEncoder=new TextEncoder;this.textDecoder=new TextDecoder}readInt(e,t,s){let r=0;for(let i=s-1;i>=0;i--){r+=Math.pow(256,s-i-1)*e[t+i]}return r}writeInt(e,t,s,r){let i=0;while(i<s){e[t+i]=r/Math.pow(256,s-i-1);i++}}encode(e,t){const s=this.textEncoder.encode(e);const i=16+s.byteLength;const o=[0,0,0,0,0,16,0,1,0,0,0,r[t],0,0,0,1];this.writeInt(o,0,4,i);return new Uint8Array(o.concat(...s)).buffer}decode(e){const i=async e=>{const o={packetLength:this.readInt(e,0,4),headerLength:this.readInt(e,4,2),protocolVersion:this.readInt(e,6,2),operation:this.readInt(e,8,4),sequenceID:this.readInt(e,12,4)};const n=[o];if(o.packetLength<e.length){n.push(...await i(e.slice(o.packetLength)))}if(o.operation===r.message){const r=e.slice(o.headerLength,o.packetLength);if(o.protocolVersion===s.buffer){const{pako:e}=await t.importAsync("pako-inflate");o.messages=(await i(e.inflate(r))).map(e=>e.messages[0])}else if(o.protocolVersion===s.json){o.messages=[JSON.parse(this.textDecoder.decode(r))]}}else if(o.operation===r.heartBeatResponse){o.heartBeatResponse={count:this.readInt(e,16,4)}}return n};return new Promise(t=>{const s=new FileReader;s.onload=(async e=>{const s=new Uint8Array(e.target.result);t(await i(s))});s.readAsArrayBuffer(e)})}}class o{constructor(){this.startTime=0}getLiveTime(){return new Promise(e=>{if(this.startTime){e(this.startTime);return}const t=dq(".bilibili-live-player-video-controller-duration-btn span");const s=Observer.childList(t,t=>{const r=t.length>0&&t.some(e=>{return e.addedNodes.length>0&&[...e.addedNodes].every(e=>e.nodeType===Node.TEXT_NODE)});if(r){s.stop();const r=t[0].addedNodes[0].textContent;const[i,o,n=0]=r.split(":").reverse().map(_.unary(parseInt));const a=Number(new Date);this.startTime=a-n*1e3*3600-o*60*1e3-i*1e3;e(this.startTime)}})})}}class n extends EventTarget{constructor(e){super();this.roomID=e;this.retryInterval=200;this.autoRetry=true;this.servers=["broadcastlv.chat.bilibili.com"];this.selectedServer="";this.liveTime=new o;this.bufferHelper=new i;this.stopRequested=false;window.addEventListener("unload",()=>this.stop())}heartBeat(){if(this.webSocket.readyState===WebSocket.OPEN){this.webSocket.send(this.bufferHelper.encode("","heartBeat"))}else{this.stop();this.restart()}}restart(){this.dispatchEvent(new CustomEvent("restart"));if(!this.stopRequested&&this.autoRetry){console.log(`Live Socket: unexpected disconnect, retry in ${this.retryInterval}ms`);const e=this.servers.indexOf(this.selectedServer);if(e<this.servers.length-1){this.selectedServer=this.servers[e+1]}else{[this.selectedServer]=this.servers}console.log(`Live Socket: server changed to`,this.selectedServer);setTimeout(()=>this.start(),this.retryInterval)}}async start(){const e=await Ajax.getJson(`https://api.live.bilibili.com/room/v1/Danmu/getConf?room_id=${this.roomID}&platform=pc&player=web`);const t=_.get(e,"data.host_server_list",[]);this.servers=[...new Set([...this.servers,...t.map(e=>e.host)])];if(this.selectedServer===""){[this.selectedServer]=this.servers;console.log("Initial server:",this.selectedServer)}if(this.webSocket&&(this.webSocket.readyState===WebSocket.CONNECTING||this.webSocket.readyState===WebSocket.OPEN)){this.stop()}this.webSocket=new WebSocket(`wss://${this.selectedServer}/sub`);this.stopRequested=false;this.dispatchEvent(new CustomEvent("start",{detail:this.webSocket}));this.webSocket.addEventListener("open",()=>{const t={roomid:this.roomID,uid:parseInt(getUID()),protover:2,platform:"web",clientVer:"1.10.1",type:"2",key:_.get(e,"data.token")};this.webSocket.send(this.bufferHelper.encode(JSON.stringify(t),"enterRoom"));this.dispatchEvent(new CustomEvent("open",{detail:t}))});this.webSocket.addEventListener("message",async e=>{const[t]=await this.bufferHelper.decode(e.data);this.dispatchEvent(new CustomEvent("message",{detail:t}));switch(t.operation){case r.enterRoomResponse:{if(this.heartBeatTimer){clearInterval(this.heartBeatTimer)}this.heartBeatTimer=setInterval(()=>{this.heartBeat()},30*1e3);break}case r.heartBeatResponse:{this.dispatchEvent(new CustomEvent("heartBeatResponse",{detail:t.heartBeatResponse.count}));break}case r.message:{const e=await this.liveTime.getLiveTime();t.messages.forEach(t=>{if(t.cmd==="DANMU_MSG"){const s=t.info;const r={content:s[1],type:s[0][1],fontSize:s[0][2],color:s[0][3],sendTime:s[0][4],userHash:s[0][7],userID:s[2][0],userName:s[2][1],startTime:e,get time(){return this.sendTime-this.startTime}};this.dispatchEvent(new CustomEvent("danmaku",{detail:r}))}});break}default:break}});this.webSocket.addEventListener("close",e=>{if(!this.stopRequested){console.error("Live Socket: close",e);this.restart()}});this.webSocket.addEventListener("error",e=>{console.error("Live Socket: error",e);this.restart()})}stop(){this.stopRequested=true;if(this.heartBeatTimer){clearInterval(this.heartBeatTimer)}if(this.webSocket&&this.webSocket.readyState===WebSocket.OPEN){this.webSocket.close()}}}return{export:{LiveSocket:n}}}})();