🏠 

Greasy Fork is available in English.

bliveproxy111

B站直播websocket hook框架

此脚本不应直接安装,它是一个供其他脚本使用的外部库。如果您需要使用该库,请在脚本元属性加入:// @require https://update.greasyfork.org/scripts/443893/1043295/bliveproxy111.js

// ==UserScript==
// @name         bliveproxy111
// @namespace    http://tampermonkey.net/
// @version      0.4
// @description  B站直播websocket hook框架
// @author       xfgryujk
// @include      /https?:\/\/live\.bilibili\.com\/?\??.*/
// @include      /https?:\/\/live\.bilibili\.com\/\d+\??.*/
// @include      /https?:\/\/live\.bilibili\.com\/(blanc\/)?\d+\??.*/
// @run-at       document-start
// @require      https://cdn.jsdelivr.net/gh/google/brotli@5692e422da6af1e991f9182345d58df87866bc5e/js/decode.js
// @grant        unsafeWindow
// ==/UserScript==
// 使用方法:
// bliveproxy.addCommandHandler('DANMU_MSG', command => {
//   console.log(command)
//   let info = command.info
//   info[1] = '测试'
// })
(function() {
const HEADER_SIZE = 16
const WS_BODY_PROTOCOL_VERSION_NORMAL = 0
const WS_BODY_PROTOCOL_VERSION_HEARTBEAT = 1
const WS_BODY_PROTOCOL_VERSION_BROTLI = 3
const OP_HEARTBEAT_REPLY = 3 // WS_OP_HEARTBEAT_REPLY
const OP_SEND_MSG_REPLY = 5 // WS_OP_MESSAGE
const OP_AUTH_REPLY = 8 // WS_OP_CONNECT_SUCCESS
let textEncoder = new TextEncoder()
let textDecoder = new TextDecoder()
function main() {
if (window.bliveproxy) {
// 防止多次加载
return
}
initApi()
hook()
}
function initApi() {
window.bliveproxy = api
}
let api = {
addCommandHandler(cmd, handler) {
let handlers = this._commandHandlers[cmd]
if (!handlers) {
handlers = this._commandHandlers[cmd] = []
}
handlers.push(handler)
},
removeCommandHandler(cmd, handler) {
let handlers = this._commandHandlers[cmd]
if (!handlers) {
return
}
this._commandHandlers[cmd] = handlers.filter(item => item !== handler)
},
// 私有API
_commandHandlers: {},
_getCommandHandlers(cmd) {
return this._commandHandlers[cmd] || null
}
}
function hook() {
window.WebSocket = new Proxy(window.WebSocket, {
construct(target, args) {
let obj = new target(...args)
return new Proxy(obj, proxyHandler)
}
})
}
let proxyHandler = {
get(target, property) {
let value = target[property]
if ((typeof value) === 'function') {
value = value.bind(target)
}
return value
},
set(target, property, value) {
if (property === 'onmessage') {
let realOnMessage = value
value = function(event) {
myOnMessage(event, realOnMessage)
}
}
target[property] = value
return value
}
}
function myOnMessage(event, realOnMessage) {
if (!(event.data instanceof ArrayBuffer)) {
realOnMessage(event)
return
}
let data = new Uint8Array(event.data)
function callRealOnMessageByPacket(packet) {
realOnMessage({...event, data: packet})
}
handleMessage(data, callRealOnMessageByPacket)
}
function makePacketFromCommand(command) {
let body = textEncoder.encode(JSON.stringify(command))
return makePacketFromUint8Array(body, OP_SEND_MSG_REPLY)
}
function makePacketFromUint8Array(body, operation) {
let packLen = HEADER_SIZE + body.byteLength
let packet = new ArrayBuffer(packLen)
// 不需要压缩
let ver = operation === OP_HEARTBEAT_REPLY ? WS_BODY_PROTOCOL_VERSION_HEARTBEAT : WS_BODY_PROTOCOL_VERSION_NORMAL
let packetView = new DataView(packet)
packetView.setUint32(0, packLen)        // pack_len
packetView.setUint16(4, HEADER_SIZE)    // raw_header_size
packetView.setUint16(6, ver)            // ver
packetView.setUint32(8, operation)      // operation
packetView.setUint32(12, 1)             // seq_id
let packetBody = new Uint8Array(packet, HEADER_SIZE, body.byteLength)
for (let i = 0; i < body.byteLength; i++) {
packetBody[i] = body[i]
}
return packet
}
function handleMessage(data, callRealOnMessageByPacket) {
let dataView = new DataView(data.buffer)
let operation = dataView.getUint32(8)
switch (operation) {
case OP_AUTH_REPLY:
case OP_SEND_MSG_REPLY: {
// 可能有多个包一起发,需要分包
let offset = 0
while (offset < data.byteLength) {
let dataView = new DataView(data.buffer, offset)
let packLen = dataView.getUint32(0)
let rawHeaderSize = dataView.getUint16(4)
let ver = dataView.getUint16(6)
let operation = dataView.getUint32(8)
// let seqId = dataView.getUint32(12)
let body = new Uint8Array(data.buffer, offset + rawHeaderSize, packLen - rawHeaderSize)
if (operation === OP_SEND_MSG_REPLY) {
// 业务消息
switch (ver) {
case WS_BODY_PROTOCOL_VERSION_NORMAL: {
// body是单个JSON消息
body = textDecoder.decode(body)
body = JSON.parse(body)
handleCommand(body, callRealOnMessageByPacket)
break
}
case WS_BODY_PROTOCOL_VERSION_BROTLI: {
// body是压缩过的多个消息
body = BrotliDecode(body)
handleMessage(body, callRealOnMessageByPacket)
break
}
default: {
// 未知的body格式
let packet = makePacketFromUint8Array(body, operation)
callRealOnMessageByPacket(packet)
break
}
}
} else {
// 非业务消息
let packet = makePacketFromUint8Array(body, operation)
callRealOnMessageByPacket(packet)
}
offset += packLen
}
break
}
// 服务器心跳包,前4字节是人气值,后面是客户端发的心跳包内容
// packLen不包括客户端发的心跳包内容,不知道是不是服务器BUG
// 这里没用到心跳包就不处理了
// case OP_HEARTBEAT_REPLY:
default: {
// 只有一个包
let packLen = dataView.getUint32(0)
let rawHeaderSize = dataView.getUint16(4)
let body = new Uint8Array(data.buffer, rawHeaderSize, packLen - rawHeaderSize)
let packet = makePacketFromUint8Array(body, operation)
callRealOnMessageByPacket(packet)
break
}
}
}
function handleCommand(command, callRealOnMessageByPacket) {
if (command instanceof Array) {
for (let oneCommand of command) {
this.handleCommand(oneCommand)
}
return
}
let cmd = command.cmd || ''
let pos = cmd.indexOf(':')
if (pos != -1) {
cmd = cmd.substr(0, pos)
}
let handlers = api._getCommandHandlers(cmd)
if (handlers) {
for (let handler of handlers) {
handler(command)
}
}
// console.log(command)
let packet = makePacketFromCommand(command)
callRealOnMessageByPacket(packet)
}
main()
})();