// ==UserScript==
// @name 游戏管理助手
// @namespace http://ecmk48.free.mbbs.cc/
// @version 3.0.0
// @description 提供侧拉面板可踢出玩家
// @author EC
// @match *://wtf.haven.chat/
// @match *://154.211.15.42:9191/wtfgame/
// @match *://124.221.145.197/WTFGame/
// @grant none
// @license GPL-3.0
// @run-at document-start
// ==/UserScript==
(function() {
'use strict';
// ===== 配置 =====
const KICK_DELAY = 600; // 毫秒
// ===== 状态 =====
let players = [];
let ws, hooksend;
let rememberedPlayers = []; // 存储玩家名字
let isPanelOpen = false;
let kickTimeouts = new Map(); // 存储每个玩家的踢出定时器 (key: 玩家名字)
// ===== 创建侧拉栏UI =====
function createSidePanel() {
// 主容器
const panel = document.createElement('div');
panel.id = 'violation-side-panel';
panel.style.cssText = `
position: fixed;
top: 0;
right: -340px;
width: 320px;
height: 100vh;
background: #ffffff;
box-shadow: -2px 0 12px rgba(0,0,0,0.15);
z-index: 99999;
transition: right 0.3s ease;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Arial, sans-serif;
display: flex;
flex-direction: column;
overflow: hidden;
`;
panel.dataset.open = 'false';
// ===== 触发器按钮(半透明黑色圆) =====
const toggleBtn = document.createElement('div');
toggleBtn.id = 'panel-toggle-btn';
toggleBtn.style.cssText = `
position: fixed;
top: 50%;
right: 0;
transform: translateY(-50%);
width: 48px;
height: 48px;
background: rgba(0, 0, 0, 0.5);
border: none;
border-radius: 50%;
cursor: pointer;
z-index: 100000;
display: flex;
align-items: center;
justify-content: center;
font-size: 18px;
color: #ffffff;
box-shadow: 0 2px 8px rgba(0,0,0,0.2);
transition: right 0.3s ease, background 0.2s ease;
user-select: none;
`;
toggleBtn.textContent = '◀';
// 悬停效果
toggleBtn.addEventListener('mouseenter', function() {
this.style.background = 'rgba(0, 0, 0, 0.7)';
});
toggleBtn.addEventListener('mouseleave', function() {
this.style.background = 'rgba(0, 0, 0, 0.5)';
});
document.body.appendChild(toggleBtn);
// 标题栏
const header = document.createElement('div');
header.style.cssText = `
padding: 16px 20px;
background: #ffffff;
border-bottom: 2px solid #f0f0f0;
flex-shrink: 0;
display: flex;
justify-content: space-between;
align-items: center;
`;
header.innerHTML = `
玩家列表
0 人在线
`;
// 玩家列表容器
const listContainer = document.createElement('div');
listContainer.id = 'player-list-container';
listContainer.style.cssText = `
flex: 1;
overflow-y: auto;
padding: 8px 0;
background: #ffffff;
`;
// 组装面板
panel.appendChild(header);
panel.appendChild(listContainer);
document.body.appendChild(panel);
// ===== 切换面板 =====
function togglePanel() {
isPanelOpen = !isPanelOpen;
if (isPanelOpen) {
panel.style.right = '0';
toggleBtn.style.right = '320px';
toggleBtn.textContent = '▶';
panel.dataset.open = 'true';
renderPlayerList();
} else {
panel.style.right = '-340px';
toggleBtn.style.right = '0';
toggleBtn.textContent = '◀';
panel.dataset.open = 'false';
}
}
toggleBtn.addEventListener('click', togglePanel);
// 点击外部关闭(可选)
document.addEventListener('click', function(e) {
if (isPanelOpen && !panel.contains(e.target) && e.target !== toggleBtn) {
togglePanel();
}
});
return { panel, listContainer, toggleBtn, togglePanel };
}
// ===== 渲染玩家列表 =====
function renderPlayerList() {
const container = document.getElementById('player-list-container');
const countEl = document.getElementById('panel-player-count');
if (!container) return;
if (countEl) {
countEl.textContent = `${players.length} 人在线`;
}
if (!players || players.length === 0) {
container.innerHTML = `
暂无玩家在线
`;
return;
}
// 排序:记住的玩家优先
const sortedPlayers = [...players].sort((a, b) => {
const aRemembered = rememberedPlayers.includes(a.name);
const bRemembered = rememberedPlayers.includes(b.name);
if (aRemembered && !bRemembered) return -1;
if (!aRemembered && bRemembered) return 1;
return (a.name || '').localeCompare(b.name || '');
});
let html = '';
sortedPlayers.forEach((player, index) => {
const isRemembered = rememberedPlayers.includes(player.name);
const name = player.name || player.nickname || 'Unknown';
html += `
${isRemembered ? '📌 ' : ''}${escapeHtml(name)}
`;
});
container.innerHTML = html;
// ===== 事件绑定 =====
// 记住/取消记住
container.querySelectorAll('.remember-btn').forEach(btn => {
btn.addEventListener('click', function(e) {
e.stopPropagation();
const name = this.dataset.name;
toggleRememberPlayer(name);
});
});
// 踢出
container.querySelectorAll('.kick-btn').forEach(btn => {
btn.addEventListener('click', function(e) {
e.stopPropagation();
const name = this.dataset.name;
kickPlayerByName(name);
});
});
}
// ===== 记住玩家逻辑 =====
function toggleRememberPlayer(name) {
const index = rememberedPlayers.indexOf(name);
if (index > -1) {
// 取消记住
rememberedPlayers.splice(index, 1);
// 清除该玩家的踢出定时器
if (kickTimeouts.has(name)) {
clearTimeout(kickTimeouts.get(name));
kickTimeouts.delete(name);
}
} else {
// 记住玩家 - 只存储,不踢出
rememberedPlayers.push(name);
}
renderPlayerList();
}
// ===== 安排踢出(延迟执行) =====
function scheduleKick(name) {
// 清除旧的定时器
if (kickTimeouts.has(name)) {
clearTimeout(kickTimeouts.get(name));
kickTimeouts.delete(name);
}
// 设置新的定时器
const timeout = setTimeout(() => {
// 再次确认该玩家仍在记住列表中
if (rememberedPlayers.includes(name)) {
kickPlayerByName(name);
}
kickTimeouts.delete(name);
}, KICK_DELAY);
kickTimeouts.set(name, timeout);
console.log(`[违规检测] 已安排踢出玩家 ${name},${KICK_DELAY}ms后执行`);
}
// ===== 踢出玩家(通过名字查找UUID) =====
function kickPlayerByName(name) {
if (!name) return;
// 从当前在线玩家中查找该名字对应的UUID
const player = players.find(p => p.name === name);
if (!player) {
console.log(`[违规检测] 玩家 ${name} 不在线,无法踢出`);
return;
}
const uuid = player.uuid;
if (!uuid) {
console.log(`[违规检测] 玩家 ${name} 没有UUID,无法踢出`);
return;
}
try {
send(`{"k":"kick","v":{"uuid":${uuid}}}`);
console.log(`[违规检测] 踢出玩家: ${name} (UUID: ${uuid})`);
// 清除该玩家的定时器
if (kickTimeouts.has(name)) {
clearTimeout(kickTimeouts.get(name));
kickTimeouts.delete(name);
}
} catch (e) {
console.warn('[违规检测] 踢出失败:', e);
}
}
// ===== 工具函数 =====
function escapeHtml(text) {
if (!text) return '';
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
// ===== WebSocket劫持 =====
const hook = window.WebSocket;
window.WebSocket = function(protocols) {
ws = new hook(protocols);
hooksend = ws.send;
ws.send = function(data) {
hooksend.call(ws, data);
};
ws.addEventListener('message', function(event) {
try {
const datas = jie(event.data);
processMessage(datas);
} catch (e) {
// 静默处理
}
});
return ws;
};
// ===== 加密/解密 =====
function jia(str) {
return btoa(decodeURI(encodeURIComponent(str)));
}
function jie(encodedStr) {
try {
return decodeURIComponent(encodeURI(atob(encodedStr)));
} catch {
return '';
}
}
function send(message) {
let data = jia(message); // 加密消息
hooksend.call(ws, data); // 调用原生的send方法发送加密后的消息
}
// ===== 消息处理 =====
function processMessage(e) {
if (!e) return;
try {
// 玩家加入
if (e.includes('$newplayer')) {
const arr = parsePlayerData(e);
if (arr && arr.length > 0) {
const player = arr[0];
const existing = players.find(p => p.uuid === player.uuid);
if (!existing) {
players.push(player);
// 检查是否在记住列表中(通过名字)
if (rememberedPlayers.includes(player.name)) {
console.log(`[违规检测] 记住的玩家 ${player.name} 上线,安排踢出`);
scheduleKick(player.name);
}
}
renderPlayerList();
}
}
// 玩家离开
else if (e.includes('$leave')) {
const arr = parsePlayerData(e);
if (arr && arr.length > 0) {
const uuid = arr[0].uuid;
const player = players.find(p => p.uuid === uuid);
players = players.filter(obj => obj.uuid !== uuid);
// 清除该玩家的踢出定时器(通过名字)
if (player && kickTimeouts.has(player.name)) {
clearTimeout(kickTimeouts.get(player.name));
kickTimeouts.delete(player.name);
}
renderPlayerList();
}
}
// 玩家数据更新
else if (e.includes('$playerdata')) {
const arr = parseMultiplePlayers(e);
if (arr && arr.length > 0) {
players = arr;
// 检查记住的玩家是否在线
rememberedPlayers.forEach(name => {
const player = players.find(p => p.name === name);
if (player && !kickTimeouts.has(name)) {
// 记住的玩家在线但还未安排踢出
scheduleKick(name);
}
});
renderPlayerList();
}
} else if (e.includes('$room')) {
players = [];
renderPlayerList();
}
} catch (error) {
console.debug('[违规检测] 消息处理错误:', error);
}
}
// ===== 数据解析 =====
function parsePlayerData(data) {
try {
const Data = JSON.parse(data);
return [{
name: Data.v.name || Data.v.nickname || 'Unknown',
uuid: Data.v.uuid || '',
x: Data.v.x || 0,
y: Data.v.y || 0
}];
} catch {
return [];
}
}
function parseMultiplePlayers(data) {
try {
const Data = JSON.parse(data);
return Data.v.map(item => ({
name: item.name || item.nickname || 'Unknown',
uuid: item.uuid || '',
x: item.x || 0,
y: item.y || 0
}));
} catch {
return [];
}
}
// ===== 初始化 =====
function init() {
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => {
createSidePanel();
renderPlayerList();
console.log('[违规检测] 侧拉栏系统已启动!');
});
} else {
createSidePanel();
renderPlayerList();
console.log('[违规检测] 侧拉栏系统已启动!');
}
// 定期刷新列表(保持数据同步)
setInterval(() => {
if (isPanelOpen) {
renderPlayerList();
}
}, 3000);
}
init();
})();