xfgryujk's bliveproxy

从xfgryujk哪里抄来的,忽略了一个报错的地方

This script should not be not be installed directly. It is a library for other scripts to include with the meta directive // @require https://update.greasyfork.org/scripts/434638/984839/xfgryujk%27s%20bliveproxy.js

// ==UserScript==
// @license             MIT
// @original-script     https://github.com/xfgryujk/bliveproxy
// @original-author     xfgryujk
// @original-license    MIT
// ==/UserScript==

// MIT License

// Copyright (c) 2020 xfgryujk

// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:

// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.

// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

(function () {
    const HEADER_SIZE = 16;

    const WS_BODY_PROTOCOL_VERSION_NORMAL = 0;
    const WS_BODY_PROTOCOL_VERSION_HEARTBEAT = 1;
    const WS_BODY_PROTOCOL_VERSION_DEFLATE = 2;
    const WS_BODY_PROTOCOL_VERSION_BROTLI = 3;

    const OP_HEARTBEAT_REPLY = 3;
    const OP_SEND_MSG_REPLY = 5;

    let textEncoder = new TextEncoder();
    let textDecoder = new TextDecoder();

    function main() {
        if (unsafeWindow.bliveproxy) {
            // 防止多次加载
            return;
        }
        initApi();
        hook();
    }

    function initApi() {
        unsafeWindow.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() {
        unsafeWindow.WebSocket = new Proxy(unsafeWindow.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 offset = 0;
        while (offset < data.byteLength) {
            let dataView = new DataView(data.buffer, offset);
            let packLen = dataView.getUint32(0);
            if (packLen > 1534026300) { return; }
            // 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 + HEADER_SIZE, packLen - HEADER_SIZE);
            if (operation === OP_SEND_MSG_REPLY) {
                switch (ver) {
                    case WS_BODY_PROTOCOL_VERSION_NORMAL:
                        body = textDecoder.decode(body);
                        body = JSON.parse(body);
                        handleCommand(body, callRealOnMessageByPacket);
                        break;
                    case WS_BODY_PROTOCOL_VERSION_DEFLATE:
                        body = pako.inflate(body);
                        handleMessage(body, callRealOnMessageByPacket);
                        break;
                    case WS_BODY_PROTOCOL_VERSION_BROTLI:
                        body = BrotliDecode(body);
                        handleMessage(body, callRealOnMessageByPacket);
                        break;
                    default: {
                        let packet = makePacketFromUint8Array(body, operation);
                        callRealOnMessageByPacket(packet);
                        break;
                    }
                }
            } else {
                let packet = makePacketFromUint8Array(body, operation);
                callRealOnMessageByPacket(packet);
            }

            offset += packLen;
        }
    }

    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);
            }
        }

        let packet = makePacketFromCommand(command);
        callRealOnMessageByPacket(packet);
    }

    main();
})();