Intercept WebSocket messages on StumbleChat and display them in a resizable and collapsible table overlay
当前为
// ==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;
};
})();