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