[MWI] WebSocket 双向日志查看器

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

当前为 2025-06-09 提交的版本,查看 最新版本

// ==UserScript==
// @name         [MWI] WebSocket 双向日志查看器
// @namespace    http://tampermonkey.net/
// @version      1.0.0
// @description  显示 WebSocket 收发的 JSON 数据,并支持手动发包和屏蔽指定类型
// @author       XIxixi297
// @license      MIT
// @match        https://www.milkywayidle.com/*
// @match        https://test.milkywayidle.com/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=milkywayidle.com
// @grant        none
// @run-at       document-start
// ==/UserScript==

(function () {
    'use strict';

    // 全局 WebSocket 实例存储
    window.wsInstances = [];
    window.currentWS = null;

    // 屏蔽列表 - 存储需要屏蔽的 type,支持持久化
    const STORAGE_KEY = 'ws_blocked_types';

    // 从localStorage加载屏蔽列表
    function loadBlockedTypes() {
        try {
            const stored = localStorage.getItem(STORAGE_KEY);
            return stored ? new Set(JSON.parse(stored)) : new Set();
        } catch (e) {
            console.warn('加载屏蔽列表失败,使用空列表:', e);
            return new Set();
        }
    }

    // 保存屏蔽列表到localStorage
    function saveBlockedTypes() {
        try {
            localStorage.setItem(STORAGE_KEY, JSON.stringify(Array.from(window.wsBlockedTypes)));
        } catch (e) {
            console.error('保存屏蔽列表失败:', e);
        }
    }

    window.wsBlockedTypes = loadBlockedTypes();

    const OriginalWebSocket = window.WebSocket;
    window.WebSocket = new Proxy(OriginalWebSocket, {
        construct(target, args) {
            const ws = new target(...args);
            const url = args[0];

            // 保存到全局变量
            window.wsInstances.push(ws);
            window.currentWS = ws; // 最新的连接

            // 拦截 .send()
            const originalSend = ws.send;
            ws.send = function (data) {
                try {
                    const parsed = JSON.parse(data);
                    const msgType = parsed.type || '未知类型';

                    // 检查是否需要屏蔽
                    if (!window.wsBlockedTypes.has(msgType)) {
                        console.groupCollapsed(`%c→ 发送: ${msgType}`, 'color: #03A9F4; font-weight: bold;');
                        console.log(parsed);
                        console.groupEnd();
                    }
                } catch (e) {
                    console.log('%c→ 发送非JSON:', 'color: #03A9F4;', data);
                }
                return originalSend.call(this, data);
            };

            // 拦截 .onmessage
            ws.addEventListener("message", function (event) {
                try {
                    const parsed = JSON.parse(event.data);
                    const msgType = parsed.type || '未知类型';

                    // 检查是否需要屏蔽
                    if (!window.wsBlockedTypes.has(msgType)) {
                        console.groupCollapsed(`%c← 接收: ${msgType}`, 'color: #4CAF50; font-weight: bold;');
                        console.log(parsed);
                        console.groupEnd();
                    }
                } catch (e) {
                    console.log('%c← 接收非JSON:', 'color: #4CAF50;', event.data);
                }
            });

            ws.addEventListener("open", function () {
                console.info('%cWebSocket 连接已建立: ' + url, 'color: gray;');
            });

            ws.addEventListener("close", function (e) {
                console.warn('%cWebSocket 连接关闭', 'color: orange;', e);
                // 从数组中移除关闭的连接
                const index = window.wsInstances.indexOf(ws);
                if (index > -1) {
                    window.wsInstances.splice(index, 1);
                }
                if (window.currentWS === ws) {
                    window.currentWS = window.wsInstances[window.wsInstances.length - 1] || null;
                }
            });

            ws.addEventListener("error", function (e) {
                console.error('%cWebSocket 错误', 'color: red;', e);
            });

            return ws;
        }
    });

    // 便捷发包函数
    window.sendWS = function(data) {
        if (!window.currentWS) {
            console.error('没有可用的 WebSocket 连接');
            return false;
        }

        if (window.currentWS.readyState !== WebSocket.OPEN) {
            console.error('WebSocket 连接未打开');
            return false;
        }

        const jsonData = typeof data === 'string' ? data : JSON.stringify(data);
        window.currentWS.send(jsonData);
        console.log('%c✅ 手动发送成功:', 'color: #FF9800; font-weight: bold;', data);
        return true;
    };

    // 显示所有 WebSocket 连接
    window.listWS = function() {
        console.log('%cWebSocket 连接列表:', 'color: #9C27B0; font-weight: bold;');
        window.wsInstances.forEach((ws, index) => {
            const status = ws.readyState === WebSocket.OPEN ? '✅打开' :
                          ws.readyState === WebSocket.CONNECTING ? '🔄连接中' :
                          ws.readyState === WebSocket.CLOSING ? '🔄关闭中' : '❌已关闭';
            console.log(`[${index}] ${ws.url} - ${status}`);
        });
        console.log('当前活跃连接:', window.currentWS?.url || '无');
    };

    // 切换当前 WebSocket
    window.switchWS = function(index) {
        if (index >= 0 && index < window.wsInstances.length) {
            window.currentWS = window.wsInstances[index];
            console.log('%c切换到 WebSocket:', 'color: #9C27B0;', window.currentWS.url);
        } else {
            console.error('无效的索引');
        }
    };

    // 添加屏蔽类型
    window.blockType = function(type) {
        if (typeof type === 'string') {
            window.wsBlockedTypes.add(type);
            saveBlockedTypes();
            console.log('%c🚫 已屏蔽类型:', 'color: #F44336; font-weight: bold;', type);
        } else if (Array.isArray(type)) {
            type.forEach(t => window.wsBlockedTypes.add(t));
            saveBlockedTypes();
            console.log('%c🚫 已屏蔽类型:', 'color: #F44336; font-weight: bold;', type);
        } else {
            console.error('类型必须是字符串或字符串数组');
        }
    };

    // 移除屏蔽类型
    window.unblockType = function(type) {
        if (typeof type === 'string') {
            window.wsBlockedTypes.delete(type);
            saveBlockedTypes();
            console.log('%c✅ 已取消屏蔽类型:', 'color: #4CAF50; font-weight: bold;', type);
        } else if (Array.isArray(type)) {
            type.forEach(t => window.wsBlockedTypes.delete(t));
            saveBlockedTypes();
            console.log('%c✅ 已取消屏蔽类型:', 'color: #4CAF50; font-weight: bold;', type);
        } else {
            console.error('类型必须是字符串或字符串数组');
        }
    };

    // 显示当前屏蔽列表
    window.listBlockedTypes = function() {
        if (window.wsBlockedTypes.size === 0) {
            console.log('%c当前没有屏蔽任何类型', 'color: #607D8B;');
        } else {
            console.log('%c当前屏蔽的类型:', 'color: #F44336; font-weight: bold;');
            Array.from(window.wsBlockedTypes).forEach((type, index) => {
                console.log(`  [${index + 1}] ${type}`);
            });
        }
    };

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

    console.info('%c[MWI WS Logger] WebSocket 拦截器已启用 - 增强版', 'color: purple; font-weight: bold;');

    // 显示加载的屏蔽列表
    if (window.wsBlockedTypes.size > 0) {
        console.info(`%c已加载 ${window.wsBlockedTypes.size} 个屏蔽类型:`, 'color: #FF9800; font-weight: bold;', Array.from(window.wsBlockedTypes));
    }

    console.info('%c使用方法:', 'color: #2196F3; font-weight: bold;');
    console.info('%c  sendWS({type: "messageType", data: "your data"}) - 发送消息', 'color: #2196F3;');
    console.info('%c  listWS() - 查看所有 WebSocket 连接', 'color: #2196F3;');
    console.info('%c  switchWS(index) - 切换当前连接', 'color: #2196F3;');
    console.info('%c  blockType("typeName") - 屏蔽指定类型的消息', 'color: #F44336;');
    console.info('%c  blockType(["type1", "type2"]) - 屏蔽多个类型', 'color: #F44336;');
    console.info('%c  unblockType("typeName") - 取消屏蔽指定类型', 'color: #4CAF50;');
    console.info('%c  listBlockedTypes() - 查看当前屏蔽列表', 'color: #607D8B;');
    console.info('%c  clearBlockedTypes() - 清空所有屏蔽', 'color: #607D8B;');
})();