您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Kick tools for Drawaria.online with a draggable menu. Includes manual and automatic kick/prohibit drawing vote.
// ==UserScript== // @name Drawaria Kick Tools (Standalone Script) // @namespace drawaria.modded.kick // @version 1.0.0 // @description Kick tools for Drawaria.online with a draggable menu. Includes manual and automatic kick/prohibit drawing vote. // @author ≺ᴄᴜʙᴇ³≻ and YouTubeDrawaria // @match https://drawaria.online/* // @grant GM_addStyle // @run-at document-end // @license MIT // @icon https://www.google.com/s2/favicons?sz=64&domain=drawaria.online // ==/UserScript== (function() { 'use strict'; // --- Utilities and QBit Replacements --- // === 1. UUID Generator (replaces generate.uuidv4()) === function uuidv4() { return crypto.randomUUID(); } // === 2. Notification System (replaces ModBase.notify) === function notify(level, message, moduleName = "Kick Tools") { let color = "#6c757d"; // log if (level === "error") color = "#dc3545"; else if (level === "warning") color = "#ffc107"; else if (level === "info") color = "#17a2b8"; else if (level === "success") color = "#28a745"; console.log(`%c${moduleName}: ${message}`, `color: ${color}`); // Optional: Display message in game chat if available const loggingContainer = document.getElementById("chatbox_messages"); if (loggingContainer) { const chatmessage = document.createElement("div"); chatmessage.className = `chatmessage systemchatmessage5`; chatmessage.dataset.ts = Date.now().toString(); chatmessage.style.color = color; chatmessage.textContent = `${moduleName}: ${message}`; loggingContainer.appendChild(chatmessage); loggingContainer.scrollTop = loggingContainer.scrollHeight; } } // === 3. domMake Replacements (for building UI) === function createElement(tag, attrs = {}, children = []) { const el = document.createElement(tag); for (const attr in attrs) { if (attr === "className") { el.className = attrs[attr]; } else { el.setAttribute(attr, attrs[attr]); } } children.forEach(child => { if (typeof child === "string") { el.appendChild(document.createTextNode(child)); } else if (child) { el.appendChild(child); } }); return el; } function createButton(content, className = "btn btn-outline-secondary", attrs = {}) { const btn = createElement("button", { className: className, ...attrs }); if (typeof content === "string") { btn.innerHTML = content; } else { btn.appendChild(content); } return btn; } function createRow(children = [], className = "_row", attrs = {}) { const row = createElement("div", { className: className, ...attrs }); children.forEach(child => { if (child) row.appendChild(child); }); return row; } function createIconList(children = [], className = "icon-list", attrs = {}) { const list = createElement("div", { className: className, ...attrs }); children.forEach(child => { if (child) list.appendChild(child); }); return list; } // === 4. _io.emits Replacement (for sending WebSocket commands) === const emits = { sendvotekick: function (playerid) { let data = ["sendvotekick", playerid]; return `${42}${JSON.stringify(data)}`; }, pgdrawvote: function (playerid, value) { let data = ["pgdrawvote", playerid, value]; return `${42}${JSON.stringify(data)}`; } }; // === 5. Socket Management (replaces globalThis.sockets and BotClientManager._getBot) === const activeSockets = []; const originalWebSocketSend = WebSocket.prototype.send; WebSocket.prototype.send = function(...args) { if (activeSockets.indexOf(this) === -1) { activeSockets.push(this); this.addEventListener('close', () => { const pos = activeSockets.indexOf(this); if (~pos) activeSockets.splice(pos, 1); }); } return originalWebSocketSend.apply(this, args); }; function getPrimarySocket(skipNotification = false) { if (activeSockets.length === 0) { if (!skipNotification) notify("warning", "No active WebSocket connections to send commands.", "Kick Tools"); return null; } // Returns the first active socket, assuming it's the primary or most relevant one return activeSockets[0]; } // === 6. Helper functions to drag the menu === function makeDraggable(element, handle = element) { let pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0; const dragMouseDown = (e) => { e = e || window.event; e.preventDefault(); pos3 = e.clientX; pos4 = e.clientY; document.onmouseup = closeDragElement; document.onmousemove = elementDrag; }; const elementDrag = (e) => { e = e || window.event; e.preventDefault(); pos1 = pos3 - e.clientX; pos2 = pos4 - e.clientY; pos3 = e.clientX; pos4 = e.clientY; element.style.top = (element.offsetTop - pos2) + "px"; element.style.left = (element.offsetLeft - pos1) + "px"; }; const closeDragElement = () => { document.onmouseup = null; document.onmousemove = null; }; handle.onmousedown = dragMouseDown; } // --- Adapted PlayerKickTools Class --- class PlayerKickTools { _containerId = "kick-tools-menu-container"; _humanKickPlayerListContainer; _botKickPlayerListContainer; _cachedPlayers = []; _isAutoKickActive = false; _autoKickInterval = null; _kickedPlayersThisSession = new Set(); _isAutoProhibitDrawingActive = false; _autoProhibitDrawingInterval = null; _prohibitedPlayersThisSession = new Set(); constructor() { this._createMenu(); this._onStartup(); } _createMenu() { const menuContainer = createElement("div", { id: this._containerId, className: "cubic-engine-mod-menu", style: ` position: fixed; top: 10%; left: 10%; width: 300px; background-color: var(--CE-bg_color, #f8f9fa); border: 1px solid var(--CE-color, #ced4da); border-radius: .25rem; box-shadow: 0 4px 8px rgba(0,0,0,0.2); z-index: 9999; font-family: Arial, sans-serif; color: var(--CE-color, #212529); max-height: 80vh; overflow-y: auto; ` }); const header = createElement("div", { className: "cubic-engine-mod-menu-header", style: ` padding: 8px; background-color: var(--primary, #007bff); color: white; cursor: grab; border-top-left-radius: .25rem; border-top-right-radius: .25rem; display: flex; justify-content: space-between; align-items: center; ` }, [createElement("span", {}, ["Kick Tools"]), createButton("X", "btn-close-menu")]); header.querySelector(".btn-close-menu").addEventListener("click", () => { menuContainer.style.display = "none"; this._isAutoKickActive = false; this._isAutoProhibitDrawingActive = false; if (this._autoKickInterval) clearInterval(this._autoKickInterval); if (this._autoProhibitDrawingInterval) clearInterval(this._autoProhibitDrawingInterval); const autoKickBtn = document.querySelector(`#${this._containerId} .auto-action-toggle:has(.fa-user-minus)`); if (autoKickBtn) autoKickBtn.classList.remove("active"); const autoProhibitBtn = document.querySelector(`#${this._containerId} .auto-action-toggle:has(.fa-user-slash)`); if (autoProhibitBtn) autoProhibitBtn.classList.remove("active"); }); const content = createElement("div", { className: "cubic-engine-mod-menu-content", style: `padding: 10px;` }); menuContainer.append(header, content); document.body.appendChild(menuContainer); makeDraggable(menuContainer, header); this.htmlElements = { section: content, details: menuContainer }; } _onStartup() { this._loadInterface(); this._setupObservers(); notify("info", " 'Kick Tools' Module Loaded.", "Kick Tools"); } _loadInterface() { const container = createElement("div"); // --- Section 1: Vote to Kick (You) --- const humanKickSection = createElement("div", { className: "kick-tools-section" }); humanKickSection.appendChild(createElement("div", { className: "kick-tools-section-title" }, ["Vote to Kick (You)"])); this._humanKickPlayerListContainer = createIconList([], "kick-tools-player-list"); humanKickSection.appendChild(this._humanKickPlayerListContainer); container.appendChild(humanKickSection); // --- Section 2: Kick with Bot --- const botKickSection = createElement("div", { className: "kick-tools-section" }); botKickSection.appendChild(createElement("div", { className: "kick-tools-section-title" }, ["Kick with Bot"])); this._botKickPlayerListContainer = createIconList([], "kick-tools-player-list"); botKickSection.appendChild(this._botKickPlayerListContainer); container.appendChild(botKickSection); // --- Section 3: Bot Actions Automation (Combined Section) --- const automationSection = createElement("div", { className: "kick-tools-section" }); automationSection.appendChild(createElement("div", { className: "kick-tools-section-title" }, ["Bot Actions Automation"])); // Auto Kick Toggle Button const autoKickRow = createRow([]); const autoKickButton = createButton('<i class="fas fa-user-minus"></i> Auto Kick'); autoKickButton.classList.add("auto-action-toggle"); autoKickButton.addEventListener("click", () => this._toggleAutoKick(autoKickButton)); autoKickRow.appendChild(autoKickButton); automationSection.appendChild(autoKickRow); // Auto Prohibit Drawing Toggle Button const autoProhibitDrawingRow = createRow([]); const autoProhibitDrawingButton = createButton('<i class="fas fa-user-slash"></i> Auto Prohibit Drawing'); autoProhibitDrawingButton.classList.add("auto-action-toggle"); autoProhibitDrawingButton.addEventListener("click", () => this._toggleAutoProhibitDrawing(autoProhibitDrawingButton)); autoProhibitDrawingRow.appendChild(autoProhibitDrawingButton); automationSection.appendChild(autoProhibitDrawingRow); container.appendChild(automationSection); this.htmlElements.section.appendChild(container); } _setupObservers() { const playerListElement = document.getElementById("playerlist"); if (playerListElement) { const observer = new MutationObserver(() => { this._updatePlayerLists(); // Execute continuous checks if toggles are active if (this._isAutoKickActive) { this._performAutoKick(); } if (this._isAutoProhibitDrawingActive) { this._performAutoProhibitDrawing(); } }); observer.observe(playerListElement, { childList: true, subtree: true, attributes: true, attributeFilter: ['data-playerid', 'style'] }); this._updatePlayerLists(); // Initial update } } _updatePlayerLists() { this._humanKickPlayerListContainer.innerHTML = ''; this._botKickPlayerListContainer.innerHTML = ''; this._cachedPlayers = []; const playerRows = document.querySelectorAll("#playerlist .playerlist-row"); const myPlayerIdElement = document.querySelector(".playerlist-name-self")?.parentElement; const myPlayerId = myPlayerIdElement ? myPlayerIdElement.dataset.playerid : null; if (playerRows.length <= 1) { const noPlayersMessage = createElement("span", {}, ["No other players."]); this._humanKickPlayerListContainer.appendChild(noPlayersMessage.cloneNode(true)); this._botKickPlayerListContainer.appendChild(noPlayersMessage.cloneNode(true)); return; } playerRows.forEach(playerRow => { const playerId = playerRow.dataset.playerid; if (playerId === myPlayerId) { return; } const playerName = playerRow.querySelector(".playerlist-name a")?.textContent || `Player ${playerId}`; const isDrawing = playerRow.querySelector(".playerlist-draw")?.style.display !== 'none'; this._cachedPlayers.push({ id: parseInt(playerId), name: playerName, isDrawing: isDrawing }); }); this._renderHumanKickButtons(); this._renderBotKickButtons(); } _renderHumanKickButtons() { this._humanKickPlayerListContainer.innerHTML = ''; if (this._cachedPlayers.length === 0) { this._humanKickPlayerListContainer.appendChild(document.createTextNode("No players.")); return; } this._cachedPlayers.forEach(player => { const playerButton = createButton(player.name, "btn btn-outline-secondary"); playerButton.title = `Vote to kick ${player.name} (ID: ${player.id}).`; playerButton.addEventListener("click", () => this._sendHumanVoteKick(player.id, player.name)); this._humanKickPlayerListContainer.appendChild(playerButton); }); } _renderBotKickButtons() { this._botKickPlayerListContainer.innerHTML = ''; if (this._cachedPlayers.length === 0) { this._botKickPlayerListContainer.appendChild(document.createTextNode("No players.")); return; } this._cachedPlayers.forEach(player => { const playerButton = createButton(player.name, "btn btn-outline-secondary"); playerButton.title = `Kick ${player.name} (ID: ${player.id}) with the selected bot.`; playerButton.addEventListener("click", () => this._sendBotKick(player.id, player.name)); this._botKickPlayerListContainer.appendChild(playerButton); }); } _sendHumanVoteKick(targetPlayerId, targetPlayerName) { const socket = getPrimarySocket(); if (socket) { const data = emits.sendvotekick(targetPlayerId); socket.send(data); notify("info", `Vote to kick sent for ${targetPlayerName} (ID: ${targetPlayerId}).`, "Kick Tools"); } } _sendBotKick(targetPlayerId, targetPlayerName) { const socket = getPrimarySocket(); if (!socket) return; // getPrimarySocket already notifies if no socket const data = emits.sendvotekick(targetPlayerId); socket.send(data); notify("success", `Bot (via socket) sent kick request for ${targetPlayerName}.`, "Kick Tools"); } _toggleAutoKick(button) { this._isAutoKickActive = !this._isAutoKickActive; button.classList.toggle("active", this._isAutoKickActive); button.innerHTML = this._isAutoKickActive ? '<i class="fas fa-user-minus"></i> Auto Kick Active' : '<i class="fas fa-user-minus"></i> Auto Kick'; if (this._isAutoKickActive) { const socketReady = getPrimarySocket(true); // Pass true to suppress initial notification if (!socketReady) { notify("error", "You need an active connection (human or bot) to use 'Auto Kick'.", "Kick Tools"); this._isAutoKickActive = false; button.classList.remove("active"); button.innerHTML = '<i class="fas fa-user-minus"></i> Auto Kick'; return; } notify("success", " 'Auto Kick' automation activated. It will attempt to kick all players.", "Kick Tools"); this._kickedPlayersThisSession.clear(); this._performAutoKick(); // Perform an initial sweep } else { clearInterval(this._autoKickInterval); this._autoKickInterval = null; notify("info", " 'Auto Kick' automation deactivated.", "Kick Tools"); } } _performAutoKick() { if (!this._isAutoKickActive) return; const socket = getPrimarySocket(true); const myPlayerIdElement = document.querySelector(".playerlist-name-self")?.parentElement; const myPlayerId = myPlayerIdElement ? parseInt(myPlayerIdElement.dataset.playerid) : null; if (!socket) { notify("warning", "Socket disconnected. Stopping 'Auto Kick'.", "Kick Tools"); const button = document.querySelector(`#${this._containerId} .auto-action-toggle:has(.fa-user-minus)`); if (button) { this._toggleAutoKick(button); } return; } const playersToTarget = this._cachedPlayers.filter(player => { return player.id !== myPlayerId && !this._kickedPlayersThisSession.has(player.id); }); if (playersToTarget.length === 0) { return; } playersToTarget.forEach(player => { const data = emits.sendvotekick(player.id); socket.send(data); // Send the kick command this._kickedPlayersThisSession.add(player.id); notify("info", ` (Auto) vote to kick ${player.name} (ID: ${player.id}).`, "Kick Tools"); }); } _toggleAutoProhibitDrawing(button) { this._isAutoProhibitDrawingActive = !this._isAutoProhibitDrawingActive; button.classList.toggle("active", this._isAutoProhibitDrawingActive); button.innerHTML = this._isAutoProhibitDrawingActive ? '<i class="fas fa-user-slash"></i> Auto Prohibit Drawing Active' : '<i class="fas fa-user-slash"></i> Auto Prohibit Drawing'; if (this._isAutoProhibitDrawingActive) { const socketReady = getPrimarySocket(true); if (!socketReady) { notify("error", "You need an active connection (bot) to use 'Auto Prohibit Drawing'.", "Kick Tools"); this._isAutoProhibitDrawingActive = false; button.classList.remove("active"); button.innerHTML = '<i class="fas fa-user-slash"></i> Auto Prohibit Drawing'; return; } notify("success", " 'Auto Prohibit Drawing' automation activated. Players who are drawing will be periodically prohibited.", "Kick Tools"); this._prohibitedPlayersThisSession.clear(); this._performAutoProhibitDrawing(); this._autoProhibitDrawingInterval = setInterval(() => { this._performAutoProhibitDrawing(); }, 5000); // Check every 5 seconds } else { clearInterval(this._autoProhibitDrawingInterval); this._autoProhibitDrawingInterval = null; notify("info", " 'Auto Prohibit Drawing' automation deactivated.", "Kick Tools"); } } _performAutoProhibitDrawing() { if (!this._isAutoProhibitDrawingActive) return; const socket = getPrimarySocket(true); if (!socket) { notify("warning", "Socket disconnected. Stopping 'Auto Prohibit Drawing'.", "Kick Tools"); const button = document.querySelector(`#${this._containerId} .auto-action-toggle:has(.fa-user-slash)`); if (button) { this._toggleAutoProhibitDrawing(button); } return; } const playersToProhibit = this._cachedPlayers.filter(player => { return player.isDrawing && !this._prohibitedPlayersThisSession.has(player.id); }); if (playersToProhibit.length === 0) { return; } playersToProhibit.forEach(player => { const data = emits.pgdrawvote(player.id, 0); // 0 means "prohibit drawing" socket.send(data); this._prohibitedPlayersThisSession.add(player.id); notify("info", ` (Auto) vote to prohibit drawing for ${player.name} (ID: ${player.id}).`, "Kick Tools"); }); } } // --- Style Injection --- GM_addStyle(` /* Variables and base styles adapted from the original for standalone operation */ :root { --light: #f8f9fa; --dark: #212529; --primary: #007bff; --secondary: #6c757d; --success: #28a745; --info: #17a2b8; --danger: #dc3545; --warning: #ffc107; --dark-blue-title: #0056b3; /* A slightly darker color for titles */ /* Adapt if the game uses its own vars or default to a fixed color */ --CE-bg_color: var(--light); --CE-color: var(--dark); --input-border-blue: #007bff; --input-bg: #e9ecef; --dark-text: #343a40; --orange: #fd7e14; } .cubic-engine-mod-menu { line-height: normal; font-size: 1rem; } .cubic-engine-mod-menu > details { position: relative; overflow: visible; z-index: 999; background-color: var(--CE-bg_color); border: var(--CE-color) 1px solid; border-radius: .25rem; } .cubic-engine-mod-menu .btn { padding: 5px 10px; font-size: 0.9em; cursor: pointer; border: 1px solid var(--secondary); border-radius: .25rem; background-color: var(--secondary); color: var(--dark); transition: background-color 0.2s, color 0.2s, border-color 0.2s; display: inline-flex; align-items: center; justify-content: center; margin: 2px; /* Space between buttons */ } .cubic-engine-mod-menu .btn:hover { opacity: 0.9; } .cubic-engine-mod-menu .btn i { margin-right: 5px; } .cubic-engine-mod-menu .btn-close-menu { background-color: var(--danger); color: white; padding: 3px 8px; font-size: 0.8em; border: none; line-height: 1; } .cubic-engine-mod-menu .btn-close-menu:hover { background-color: #c82333; } /* Specific Kick Tools module styles */ .kick-tools-section { border: 1px solid var(--CE-color); border-radius: .25rem; padding: 5px; margin-bottom: 10px; background-color: var(--CE-bg_color); } .kick-tools-section-title { font-weight: bold; margin-bottom: 5px; color: var(--dark-blue-title); text-align: center; } .kick-tools-player-list { display: flex; flex-wrap: wrap; gap: 5px; max-height: 150px; overflow-y: auto; padding: 5px; border: 1px dashed var(--CE-color); border-radius: .25rem; } .kick-tools-player-list .btn { flex: 0 0 auto; margin: 0; padding: 3px 8px; font-size: 0.8em; } .auto-action-toggle { width: 100%; background-color: var(--secondary); color: var(--dark); border: 1px solid var(--CE-color); border-radius: .25rem; padding: 8px; font-size: 1em; display: flex; align-items: center; justify-content: center; transition: background-color 0.2s, color 0.2s; } .auto-action-toggle i { margin-right: 8px; } .auto-action-toggle.active { background-color: var(--info); color: white; border-color: var(--info); } ._row { display: flex; width: 100%; gap: 5px; margin-bottom: 5px; } ._row > * { flex: 1; width: 100%; } .icon-list { display: flex; flex-flow: wrap; gap: 5px; } /* For the draggable menu header */ .cubic-engine-mod-menu-header { cursor: grab; } `); // --- Module Initialization --- function initializeKickTools() { // Ensure the DOM is fully loaded if (document.body) { new PlayerKickTools(); // Optional: Add a floating button to open/close the menu const toggleButton = createElement("button", { id: "open-kick-tools-menu", style: ` position: fixed; bottom: 20px; right: 20px; width: 50px; height: 50px; border-radius: 50%; background-color: var(--primary, #007bff); color: white; border: none; box-shadow: 0 2px 5px rgba(0,0,0,0.3); font-size: 1.5em; cursor: pointer; z-index: 9998; ` }, ['🔨']); // Judge's hammer icon document.body.appendChild(toggleButton); toggleButton.addEventListener("click", () => { const menu = document.getElementById("kick-tools-menu-container"); if (menu) { menu.style.display = menu.style.display === "none" ? "block" : "none"; if (menu.style.display === "block") { notify("info", "Kick Tools Menu opened.", "Kick Tools"); } else { notify("info", "Kick Tools Menu closed.", "Kick Tools"); } } }); notify("info", "Floating 'Kick Tools' button added.", "Kick Tools"); } else { setTimeout(initializeKickTools, 100); // Retry if body is not ready yet } } initializeKickTools(); })();