Stumblechat Stalker

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

目前为 2024-11-10 提交的版本。查看 最新版本

// ==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;
    };
})();