Intercept WebSocket messages on StumbleChat and display them in tabs, sorted by sender's handle, with minimize and resize functionality
当前为
// ==UserScript==
// @name Stumblechat Stalker
// @namespace http://tampermonkey.net/
// @version 1.04
// @description Intercept WebSocket messages on StumbleChat and display them in tabs, sorted by sender's handle, with minimize and resize functionality
// @author MeKLiN
// @match https://stumblechat.com/room/*
// @icon https://www.google.com/s2/favicons?sz=64&domain=stumblechat.com
// @grant none
// @license MIT
// ==/UserScript==
(function() {
'use strict';
let userMap = {}; // Store user data with handle as key and { username, nick } as value
let overlayVisible = true; // Flag to track overlay visibility
// Create a function to update the userMap based on join/quit events
function updateUserMap(message) {
if (message.stumble === "joined" || message.stumble === "join") {
message.userlist.forEach(user => {
userMap[user.handle] = {
username: user.username || "Unknown",
nick: user.nick || "Unknown"
};
});
} else if (message.stumble === "quit") {
// Remove the user from the map when they quit
delete userMap[message.handle];
}
}
// Function to display WebSocket message under the appropriate tab
function displayWebSocketMessage(message, tab) {
const tabDiv = document.getElementById(tab);
if (tabDiv) {
// Group by sender's handle
let senderDiv = document.getElementById(message.handle);
if (!senderDiv) {
senderDiv = document.createElement("div");
senderDiv.id = message.handle;
// Retrieve the username from the user map
const user = userMap[message.handle] || { username: "Unknown", nick: "Unknown" };
// Create the user's tab button
const userTabButton = document.createElement("button");
userTabButton.innerText = `${user.username} (${message.handle} / ${user.nick})`; // Username, handle, and nick
userTabButton.onclick = () => {
// Toggle visibility of the user's message div
if (senderDiv.style.display === "none") {
senderDiv.style.display = "block";
} else {
senderDiv.style.display = "none";
}
};
// Add the button to the tab
tabDiv.appendChild(userTabButton);
// Create the user's message container
const senderHeader = document.createElement("h4");
senderHeader.innerText = `${user.username} (${message.handle} / ${user.nick})`;
senderDiv.appendChild(senderHeader);
senderDiv.style.display = "none"; // Initially hidden
// Add the message container to the tab
tabDiv.appendChild(senderDiv);
}
const messageDiv = document.createElement("div");
messageDiv.innerHTML = message.text; // Show the message content
senderDiv.appendChild(messageDiv);
tabDiv.scrollTop = tabDiv.scrollHeight; // Auto-scroll
}
}
// Override WebSocket constructor to intercept WebSocket creation
const originalWebSocket = window.WebSocket;
window.WebSocket = function(url, protocols) {
console.log('WebSocket URL:', url);
// Call original WebSocket constructor
const ws = new originalWebSocket(url, protocols);
// Event listener for receiving messages
ws.addEventListener('message', event => {
try {
const message = JSON.parse(event.data);
// Update userMap when users join or quit
if (message.stumble === "join" || message.stumble === "joined" || message.stumble === "quit") {
updateUserMap(message);
}
// Filter messages by the "stumble" type
switch (message.stumble) {
case "join":
case "joined":
displayWebSocketMessage(message, "joinTab");
break;
case "msg":
displayWebSocketMessage(message, "msgTab");
break;
default:
console.log("Unhandled message type:", message.stumble);
break;
}
} catch (error) {
console.error("Error processing WebSocket message:", error);
}
});
return ws;
};
// Method to create the overlay container with tabs
function createWebSocketTabs() {
const overlayDiv = document.createElement("div");
overlayDiv.id = "overlayContainer";
overlayDiv.style.position = "fixed";
overlayDiv.style.top = "0";
overlayDiv.style.left = "0";
overlayDiv.style.zIndex = "9999";
overlayDiv.style.backgroundColor = "#222";
overlayDiv.style.color = "#fff";
overlayDiv.style.padding = "10px";
overlayDiv.style.borderRadius = "8px";
overlayDiv.style.maxWidth = "400px";
overlayDiv.style.maxHeight = "80vh";
overlayDiv.style.overflowY = "auto";
overlayDiv.style.display = "flex";
overlayDiv.style.flexDirection = "column";
overlayDiv.style.justifyContent = "flex-start";
overlayDiv.style.resize = "both";
overlayDiv.style.overflow = "hidden";
overlayDiv.style.visibility = "visible"; // Ensures the overlay is visible initially
// Create a collapse/restore button
const toggleButton = document.createElement("button");
toggleButton.innerHTML = "Collapse/Expand";
toggleButton.style.marginBottom = "10px";
toggleButton.style.padding = "8px";
toggleButton.style.cursor = "pointer";
toggleButton.style.zIndex = "10000"; // Set z-index to 10000
toggleButton.onclick = () => {
// Toggle the overlay visibility based on the flag
if (overlayVisible) {
overlayDiv.style.display = "none"; // Hide overlay
overlayVisible = false;
} else {
overlayDiv.style.display = "flex"; // Show overlay
overlayVisible = true;
}
};
overlayDiv.appendChild(toggleButton);
// Create Tabs
const tabNames = ["join", "msg"];
tabNames.forEach(tab => {
const tabButton = document.createElement("button");
tabButton.innerHTML = tab.charAt(0).toUpperCase() + tab.slice(1);
tabButton.style.backgroundColor = "#444";
tabButton.style.border = "none";
tabButton.style.padding = "10px";
tabButton.style.margin = "2px";
tabButton.style.cursor = "pointer";
tabButton.onclick = () => {
showTab(tab); // Show the clicked tab
};
overlayDiv.appendChild(tabButton);
});
// Create divs for each tab
const tabContentDivs = ["join", "msg"];
tabContentDivs.forEach(tab => {
const tabDiv = document.createElement("div");
tabDiv.id = tab + "Tab";
tabDiv.style.display = "none";
tabDiv.style.padding = "10px";
tabDiv.style.border = "1px solid #555";
tabDiv.style.borderRadius = "5px";
tabDiv.style.marginTop = "5px";
tabDiv.style.maxHeight = "60vh";
tabDiv.style.overflowY = "auto";
overlayDiv.appendChild(tabDiv);
});
// Append the overlay to the body
document.body.appendChild(overlayDiv);
// Function to show the selected tab
function showTab(tab) {
tabContentDivs.forEach(tabName => {
const tabDiv = document.getElementById(tabName + "Tab");
tabDiv.style.display = tabName === tab ? "block" : "none";
});
}
// Default to showing the "join" tab
showTab("join");
}
// Call the functions to create the WebSocket tabs
createWebSocketTabs();
})();