[MWI] WebSocket 调试工具

显示 WebSocket 收发的 JSON 数据,并支持手动发包和屏蔽指定类型

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         [MWI] WebSocket 调试工具
// @namespace    http://tampermonkey.net/
// @version      1.0.1
// @description  显示 WebSocket 收发的 JSON 数据,并支持手动发包和屏蔽指定类型
// @author       XIxixi297
// @license      MIT
// @match        https://www.milkywayidle.com/*
// @match        https://test.milkywayidle.com/*
// @grant        none
// @run-at       document-start
// ==/UserScript==

(function () {
    'use strict';

    if (window.__wsLoggerInstalled) return;
    window.__wsLoggerInstalled = true;

    // 全局状态
    const wsLogger = {
        instances: new Set(),
        currentWS: null,
        blockedTypes: new Set(),
        STORAGE_KEY: 'ws_blocked_types'
    };

    // 初始化屏蔽列表
    try {
        const stored = localStorage.getItem(wsLogger.STORAGE_KEY);
        if (stored) wsLogger.blockedTypes = new Set(JSON.parse(stored));
    } catch (e) {}

    function saveBlockedTypes() {
        try {
            localStorage.setItem(wsLogger.STORAGE_KEY, JSON.stringify([...wsLogger.blockedTypes]));
        } catch (e) {}
    }

    // 消息日志记录
    function logMessage(data, direction) {
        try {
            const parsed = typeof data === 'string' ? JSON.parse(data) : data;
            const msgType = parsed.type || '未知类型';

            if (!wsLogger.blockedTypes.has(msgType)) {
                const color = direction === 'send' ? '#03A9F4' : '#4CAF50';
                const arrow = direction === 'send' ? '→' : '←';
                console.groupCollapsed(`%c${arrow} ${msgType}`, `color: ${color}; font-weight: bold;`);
                console.log(parsed);
                console.groupEnd();
            }
        } catch (e) {
            const color = direction === 'send' ? '#03A9F4' : '#4CAF50';
            const arrow = direction === 'send' ? '→' : '←';
            console.log(`%c${arrow} 非JSON:`, `color: ${color};`, data);
        }
    }

    // WebSocket劫持
    const OriginalWebSocket = window.WebSocket;
    window.WebSocket = function(url, protocols) {
        const ws = new OriginalWebSocket(url, protocols);

        // 添加到实例集合
        wsLogger.instances.add(ws);
        wsLogger.currentWS = ws;

        // Hook send 方法
        const originalSend = ws.send;
        ws.send = function(data) {
            logMessage(data, 'send');
            return originalSend.apply(this, arguments);
        };

        // 监听消息
        ws.addEventListener('message', (event) => {
            logMessage(event.data, 'receive');
        });

        ws.addEventListener('open', () => {
            console.info('%cWebSocket 已连接: ' + url, 'color: gray;');
        });

        ws.addEventListener('close', () => {
            console.warn('%cWebSocket 已断开', 'color: orange;');
            wsLogger.instances.delete(ws);
            if (wsLogger.currentWS === ws) {
                const remaining = [...wsLogger.instances];
                wsLogger.currentWS = remaining[remaining.length - 1] || null;
            }
        });

        return ws;
    };

    // 保持原型链
    Object.setPrototypeOf(window.WebSocket, OriginalWebSocket);
    window.WebSocket.prototype = OriginalWebSocket.prototype;

    // 工具函数
    window.sendWS = function(data) {
        if (!wsLogger.currentWS || wsLogger.currentWS.readyState !== WebSocket.OPEN) {
            console.error('没有可用的WebSocket连接');
            return false;
        }
        const jsonData = typeof data === 'string' ? data : JSON.stringify(data);
        wsLogger.currentWS.send(jsonData);
        console.log('%c✅ 手动发送:', 'color: #FF9800; font-weight: bold;', data);
        return true;
    };

    window.listWS = function() {
        console.log('%cWebSocket 连接列表:', 'color: #9C27B0; font-weight: bold;');
        [...wsLogger.instances].forEach((ws, index) => {
            const status = ws.readyState === WebSocket.OPEN ? '✅' : '❌';
            console.log(`[${index}] ${ws.url} ${status}`);
        });
    };

    window.blockType = function(type) {
        if (typeof type === 'string') {
            wsLogger.blockedTypes.add(type);
        } else if (Array.isArray(type)) {
            type.forEach(t => wsLogger.blockedTypes.add(t));
        }
        saveBlockedTypes();
        console.log('%c🚫 已屏蔽:', 'color: #F44336; font-weight: bold;', type);
    };

    window.unblockType = function(type) {
        if (typeof type === 'string') {
            wsLogger.blockedTypes.delete(type);
        } else if (Array.isArray(type)) {
            type.forEach(t => wsLogger.blockedTypes.delete(t));
        }
        saveBlockedTypes();
        console.log('%c✅ 已取消屏蔽:', 'color: #4CAF50; font-weight: bold;', type);
    };

    window.listBlocked = function() {
        if (wsLogger.blockedTypes.size === 0) {
            console.log('%c无屏蔽类型', 'color: #607D8B;');
        } else {
            console.log('%c屏蔽列表:', 'color: #F44336; font-weight: bold;', [...wsLogger.blockedTypes]);
        }
    };

    window.clearBlocked = function() {
        const count = wsLogger.blockedTypes.size;
        wsLogger.blockedTypes.clear();
        saveBlockedTypes();
        console.log(`%c✅ 已清空 ${count} 个屏蔽类型`, 'color: #4CAF50; font-weight: bold;');
    };

    // 启动提示
    console.info('%c[MWI] WebSocket监听器已启用', 'color: purple; font-weight: bold;');

    if (wsLogger.blockedTypes.size > 0) {
        console.info(`%c已加载 ${wsLogger.blockedTypes.size} 个屏蔽类型:`, 'color: #FF9800; font-weight: bold;', [...wsLogger.blockedTypes]);
    }

    console.info('%c使用方法:', 'color: #2196F3; font-weight: bold;');
    console.info('%c  sendWS(data) - 发送消息到当前WebSocket', 'color: #2196F3;');
    console.info('%c  blockType(type) - 屏蔽指定类型消息 (支持字符串或数组)', 'color: #F44336;');
    console.info('%c  unblockType(type) - 取消屏蔽指定类型', 'color: #4CAF50;');
    console.info('%c  listBlocked() - 查看当前屏蔽的消息类型', 'color: #607D8B;');
    console.info('%c  clearBlocked() - 清空所有屏蔽类型', 'color: #607D8B;');
    console.info('%c示例: blockType(["chat_message_received", "ping"]) - 屏蔽多个类型', 'color: #9E9E9E;');

})();