Stumblechat Stalker

Intercept WebSocket messages on StumbleChat and display them in a resizable and collapsible table overlay

当前为 2024-11-10 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Stumblechat Stalker
// @namespace    http://tampermonkey.net/
// @version      1.03
// @description  Intercept WebSocket messages on StumbleChat and display them in a resizable and collapsible table overlay
// @author       MeKLiN
// @match        https://stumblechat.com/room/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=stumblechat.com
// @grant        none
// @license      MIT
// ==/UserScript==
//revert
// ==UserScript==
// @name         Stumblechat Stalker
// @namespace    http://tampermonkey.net/
// @version      1.02
// @description  Intercept WebSocket messages on StumbleChat and display them in a resizable and collapsible table overlay
// @author       MeKLiN
// @match        https://stumblechat.com/room/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=stumblechat.com
// @grant        none
// @license      MIT
(function() {
    'use strict';

    let pingCount = 0;
    let messageSections = {}; // Store sections by handle
    let handleMessages = {}; // Track handles and associated messages

    const reopenButton = document.createElement("button");
    reopenButton.textContent = "Open WebSocket Log";
    reopenButton.style.position = "fixed";
    reopenButton.style.bottom = "10px";
    reopenButton.style.left = "10px";
    reopenButton.style.padding = "10px 15px";
    reopenButton.style.backgroundColor = "#4CAF50";
    reopenButton.style.color = "white";
    reopenButton.style.border = "none";
    reopenButton.style.cursor = "pointer";
    reopenButton.style.display = "inline-block"; // Ensure the button is visible by default
    reopenButton.style.zIndex = "10000"; // High z-index to ensure visibility on top
    document.body.appendChild(reopenButton);

    function createWebSocketOverlay() {
        const overlay = document.createElement("div");
        overlay.id = "webSocketOverlay";
        overlay.style.position = "fixed";
        overlay.style.top = "10%";
        overlay.style.left = "10%";
        overlay.style.fontSize = "12px";
        overlay.style.width = "80%"; // Adjust to 80% width
        overlay.style.maxHeight = "80%"; // Adjust max height to 80% of the window
        overlay.style.backgroundColor = "rgba(0, 0, 0, 0.7)";
        overlay.style.zIndex = "9999"; // Ensure overlay is just below the button
        overlay.style.display = "none"; // Hidden by default
        overlay.style.alignItems = "center";
        overlay.style.justifyContent = "center";
        overlay.style.padding = "10px";
        overlay.style.resize = "both";
        overlay.style.overflowY = "auto"; // Allow vertical scrolling within the overlay
        overlay.style.boxSizing = "border-box";
        overlay.style.cursor = "move";

        let offsetX, offsetY, isDragging = false;

        overlay.addEventListener('mousedown', (e) => {
            isDragging = true;
            offsetX = e.clientX - overlay.offsetLeft;
            offsetY = e.clientY - overlay.offsetTop;
            document.addEventListener('mousemove', dragOverlay);
            document.addEventListener('mouseup', () => {
                isDragging = false;
                document.removeEventListener('mousemove', dragOverlay);
            });
        });

        function dragOverlay(e) {
            if (isDragging) {
                overlay.style.left = (e.clientX - offsetX) + 'px';
                overlay.style.top = (e.clientY - offsetY) + 'px';
            }
        }

        const closeButton = document.createElement("button");
        closeButton.textContent = "Close";
        closeButton.style.position = "absolute";
        closeButton.style.top = "10px";
        closeButton.style.right = "10px";
        closeButton.style.padding = "5px 10px";
        closeButton.style.backgroundColor = "#ff4b5c";
        closeButton.style.color = "white";
        closeButton.style.border = "none";
        closeButton.style.cursor = "pointer";
        closeButton.addEventListener('click', () => {
            overlay.style.display = "none";
            reopenButton.style.display = "inline-block"; // Show the reopen button again
        });
        overlay.appendChild(closeButton);

        const table = document.createElement("table");
        table.style.width = "100%";
        table.style.backgroundColor = "white";
        table.style.borderCollapse = "collapse";
        overlay.appendChild(table);

        document.body.appendChild(overlay);

        return { overlay, table };
    }

    function createCollapsibleSection(handle, username, symbol) {
        const section = document.createElement("tr");
        const header = document.createElement("td");
        header.colSpan = 2;
        header.style.padding = "10px";
        header.style.cursor = "pointer";
        header.style.backgroundColor = "#f2f2f2";
        header.innerHTML = `<strong style="color: red;">${symbol}</strong> ${handle} (${username})`;

        header.addEventListener("click", () => {
            const contentRow = section.querySelector(".contentRow");
            const contentCell = contentRow.querySelector("td");

            if (contentRow.style.display === "none") {
                contentRow.style.display = "table-row";
                // Scroll to the top of the overlay when opening a section
                const overlay = document.getElementById('webSocketOverlay');
                overlay.scrollTop = 0; // Scroll to the top
            } else {
                contentRow.style.display = "none";
            }
        });

        section.appendChild(header);

        const contentRow = document.createElement("tr");
        contentRow.classList.add("contentRow");
        contentRow.style.display = "none";
        const contentCell = document.createElement("td");
        contentCell.colSpan = 2;
        contentCell.style.padding = "10px";
        contentCell.style.whiteSpace = "normal";
        contentCell.style.wordWrap = "break-word";
        contentCell.style.overflowWrap = "break-word";
        contentCell.style.maxWidth = "100%";
        contentCell.style.overflowX = "auto"; // Allow horizontal scrolling when needed
        contentCell.style.border = "1px solid #ccc";
        contentCell.style.backgroundColor = "#f9f9f9";

        contentRow.appendChild(contentCell);
        section.appendChild(contentRow);

        return section;
    }

    function breakLongText(content) {
        // Split long strings into chunks and insert <br> after every 200 characters
        const maxLength = 200;
        let formattedContent = content;
        const regex = /.{1,200}(?:\s|$)/g;
        formattedContent = formattedContent.replace(regex, '$&<br>');

        return formattedContent;
    }

    function getUserNicknameByHandle(handle) {
        const userList = document.querySelector("#userlist .list");
        if (userList) {
            const userElement = userList.querySelector(`[user-id='${handle}']`);
            if (userElement) {
                const nickname = userElement.querySelector(".nickname");
                return nickname ? nickname.textContent : "Unknown";
            }
        }
        return "Unknown";
    }

    function displayWebSocketMessage(message, table) {
        let data;
        try {
            data = JSON.parse(message);
        } catch (e) {
            // Handle non-JSON messages
            console.log('Non-JSON message received:', message);
            data = { handle: "unknown", stumble: "msg", content: message }; // Placeholder for non-JSON message
        }

        const handle = data.handle;
        const username = getUserNicknameByHandle(handle);
        let symbol = "🔵";

        switch (data.stumble) {
            case "sysmsg":
                symbol = "🔴";
                break;
            case "consume":
                symbol = "🟠";
                break;
            case "connect":
                symbol = "🟣";
                break;
            case "msg":
                symbol = "🔵"; // For general messages
                break;
            default:
                break;
        }

        // Create a section for each handle if it doesn't exist already
        let section;
        if (!handleMessages[handle]) {
            section = createCollapsibleSection(handle, username, symbol);
            table.appendChild(section);
            handleMessages[handle] = section;
        } else {
            section = handleMessages[handle];
        }

        // Display the message content in the section
        const contentRow = section.querySelector(".contentRow td");
        const contentText = breakLongText(message);
        contentRow.innerHTML += `<p>${contentText}</p>`;
    }

    const originalWebSocket = window.WebSocket;
    window.WebSocket = function(url, protocols) {
        console.log('WebSocket URL:', url);

        const ws = new originalWebSocket(url, protocols);

        const { overlay, table } = createWebSocketOverlay();

        ws.addEventListener('message', event => {
            displayWebSocketMessage(event.data, table);
        });

        reopenButton.addEventListener("click", () => {
            overlay.style.display = "flex";
            reopenButton.style.display = "none"; // Hide the reopen button once overlay is open
        });

        return ws;
    };
})();