您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Drawaria World Generator with improved UI, image fitting, optimization controls, and spiral drawing for ~5s rendering.
// ==UserScript== // @name Drawaria World Generator // @namespace http://tampermonkey.net/ // @version 1.0 // @description Drawaria World Generator with improved UI, image fitting, optimization controls, and spiral drawing for ~5s rendering. // @author YouTubeDrawaria // @include https://drawaria.online* // @icon https://www.google.com/s2/favicons?sz=64&domain=drawaria.online/room/ // @grant none // @license MIT // ==/UserScript== (() => { 'use strict'; const EL = (sel) => document.querySelector(sel); const ELL = (sel) => document.querySelectorAll(sel); // Drawing variables let drawing_active = false; // Flag to stop drawing let previewCanvas = document.createElement('canvas'); let originalCanvas = null; // Will be set when canvas is found let cw = 0; // Canvas width let ch = 0; // Canvas height var data; // Image pixel data from previewCanvas let executionLine = []; // Array to store drawing commands // Room & Socket Control (Keep existing logic) window.myRoom = {}; window.sockets = []; const originalSend = WebSocket.prototype.send; WebSocket.prototype.send = function (...args) { if (window.sockets.indexOf(this) === -1) { window.sockets.push(this); // Listen only to the first socket connection established by the game itself if (window.sockets.indexOf(this) === 0) { this.addEventListener('message', (event) => { let message = String(event.data); if (message.startsWith('42')) { let payload = JSON.parse(message.slice(2)); // Update room info on relevant messages if (payload[0] == 'bc_uc_freedrawsession_changedroom') { if (payload.length > 3) window.myRoom.players = payload[3]; if (payload.length > 4) window.myRoom.id = payload[4]; // Room ID might be here } if (payload[0] == 'mc_roomplayerschange') { if (payload.length > 3) window.myRoom.players = payload[3]; } // Additional checks for potential room ID updates if (payload[0] === 'gamestart' && payload[1]?.roomid) { window.myRoom.id = payload[1].roomid; if (payload[1].players) window.myRoom.players = payload[1].players; } // Listen for the drawing canvas to become available if (originalCanvas === null) { originalCanvas = document.getElementById('canvas'); if (originalCanvas) { cw = originalCanvas.width; ch = originalCanvas.height; console.log(`Drawaria canvas found: ${cw}x${ch}`); } } } else if (message.startsWith('41')) { // Server acknowledges connection upgrade } else if (message.startsWith('430')) { // Initial room configuration upon connection let configs = JSON.parse(message.slice(3))[0]; if (configs) { window.myRoom.players = configs.players; window.myRoom.id = configs.roomid; } // Check for canvas again after initial connection if (originalCanvas === null) { originalCanvas = document.getElementById('canvas'); if (originalCanvas) { cw = originalCanvas.width; ch = originalCanvas.height; console.log(`Drawaria canvas found: ${cw}x${ch}`); } } } }); } } return originalSend.call(this, ...args); }; // Add Boxicons Stylesheet function addBoxIcons() { let boxicons = document.createElement('link'); boxicons.href = 'https://unpkg.com/[email protected]/css/boxicons.min.css'; boxicons.rel = 'stylesheet'; document.head.appendChild(boxicons); } // Add Custom Stylesheet function CreateStylesheet() { let container = document.createElement('style'); container.innerHTML = '#world-generator { position: absolute; top: 10px; left: 10px; background-color: #ffffff; border: 2px solid #555; border-radius: 8px; padding: 15px; cursor: grab; z-index: 1000; font-family: sans-serif; box-shadow: 3px 3px 8px rgba(0,0,0,0.3); } ' + '#world-generator.dragging { cursor: grabbing; }' + '#world-generator h2 { margin-top: 0; margin-bottom: 15px; color: #333; text-align: center; border-bottom: 1px solid #eee; padding-bottom: 10px; } ' + '#world-generator .world-list { max-height: 200px; overflow-y: auto; margin BOTTOM: 15px; padding-right: 5px; }' + '#world-generator .world-item { margin: 5px 0; padding: 10px; background-color: #eef; border: 1px solid #ccf; border-radius: 5px; cursor: pointer; transition: background-color 0.2s ease, transform 0.1s ease; color: #333; } ' + '#world-generator .world-item:hover { background-color: #ddf; transform: translateY(-1px); } ' + '#world-generator .controls { margin-bottom: 15px; padding: 10px; background-color: #f9f9f9; border: 1px solid #eee; border-radius: 5px; } ' + '#world-generator .control-group { display: grid; grid-template-columns: repeat(auto-fit, minmax(100px, 1fr)); gap: 10px; margin-bottom: 10px; }' + '#world-generator .control-item label { display: block; margin-bottom: 3px; font-size: 0.9em; color: #555; } ' + '#world-generator .control-item input[type="number"] { width: 100%; padding: 5px; border: 1px solid #ccc; border-radius: 3px; box-sizing: border-box; text-align: center; -webkit-appearance: none; -moz-appearance: textfield; } ' + '#world-generator .control-item input[type="number"]::-webkit-outer-spin-button, #world-generator .control-item input[type="number"]::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; } ' + '#start-button, #stop-button { width: 100%; padding: 10px; border: none; border-radius: 5px; cursor: pointer; font-size: 1em; transition: background-color 0.2s ease, transform 0.1s ease; color: white; text-align: center; } ' + '#start-button { background-color: #5cb85c; } ' + '#start-button:hover { background-color: #4cae4c; transform: translateY(-1px); } ' + '#stop-button { background-color: #d9534f; margin-top: 10px; } ' + '#stop-button:hover { background-color: #c9302c; transform: translateY(-1px); } ' + '#status { margin-top: 10px; text-align: center; font-size: 0.9em; color: #555; min-height: 1.2em; }'; document.head.appendChild(container); } // Add World Generator Menu function CreateWorldGenerator() { originalCanvas = document.getElementById('canvas'); if (!originalCanvas) { console.warn("Drawaria canvas not found. World Generator will wait for it."); const observer = new MutationObserver((mutations, obs) => { originalCanvas = document.getElementById('canvas'); if (originalCanvas) { cw = originalCanvas.width; ch = originalCanvas.height; console.log(`Drawaria canvas found: ${cw}x${ch}`); createUI(); obs.disconnect(); } }); observer.observe(document.body, { childList: true, subtree: true }); return; } else { cw = originalCanvas.width; ch = originalCanvas.height; console.log(`Drawaria canvas found immediately: ${cw}x${ch}`); createUI(); } } function createUI() { if (document.getElementById('world-generator')) { console.log("World Generator UI already exists."); return; } let worldGenerator = document.createElement('div'); worldGenerator.id = 'world-generator'; worldGenerator.innerHTML = ` <h2>Drawaria World Generator</h2> <div class="world-list"> <div class="world-item" data-world="Superflat">Superflat</div> <div class="world-item" data-world="Lava Cave">Lava Cave</div> <div class="world-item" data-world="Desert Vista">Desert Vista</div> <div class="world-item" data-world="Night Landscape">Night Landscape</div> <div class="world-item" data-world="House">House</div> <div class="world-item" data-world="Classroom">Classroom</div> <div class="world-item" data-world="Castle">Castle</div> <div class="world-item" data-world="School">School</div> </div> <div class="controls"> <div class="control-group"> <div class="control-item"> <label for="draw-size">Step Size</label> <input type="number" id="draw-size" value="4" min="1" step="1"> </div> <div class="control-item"> <label for="draw-modifier">Step Modifier</label> <input type="number" id="draw-modifier" value="3" min="1" step="1"> </div> <div class="control-item"> <label for="draw-thickness">Thickness</label> <input type="number" id="draw-thickness" value="50" min="1" step="1"> </div> <div class="control-item"> <label for="draw-delay">Delay (ms)</label> <input type="number" id="draw-delay" value="1" min="0" step="1"> <!-- Reduced to 1ms --> </div> <div class="control-item"> <label for="speed-factor">Speed Factor (1-10)</label> <input type="number" id="speed-factor" value="2" min="1" max="10" step="1"> <!-- New control --> </div> </div> <div class="control-item"> <label for="ignore-colors">Ignore Colors (hex/rgb, comma-separated)</label> <input type="text" id="ignore-colors" value=""> </div> </div> <button id="start-button">Load & Generate</button> <button id="stop-button" class="hidden">Stop Drawing</button> <div id="status">Select a world to load.</div> `; document.body.appendChild(worldGenerator); const startButton = document.getElementById('start-button'); const stopButton = document.getElementById('stop-button'); const statusDiv = document.getElementById('status'); // Make the menu draggable let pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0; worldGenerator.onmousedown = dragMouseDown; function dragMouseDown(e) { e = e || window.event; if (e.target.tagName === 'INPUT' || e.target.tagName === 'BUTTON' || e.target.classList.contains('world-item') || e.target.closest('.controls')) { return; } e.preventDefault(); worldGenerator.classList.add('dragging'); pos3 = e.clientX; pos4 = e.clientY; document.onmouseup = closeDragElement; document.onmousemove = elementDrag; } function elementDrag(e) { e = e || window.event; e.preventDefault(); pos1 = pos3 - e.clientX; pos2 = pos4 - e.clientY; pos3 = e.clientX; pos4 = e.clientY; worldGenerator.style.top = (worldGenerator.offsetTop - pos2) + "px"; worldGenerator.style.left = (worldGenerator.offsetLeft - pos1) + "px"; } function closeDragElement() { worldGenerator.classList.remove('dragging'); document.onmouseup = null; document.onmousemove = null; } // Add event listeners to world items let worldItems = worldGenerator.querySelectorAll('.world-item'); worldItems.forEach(item => { item.addEventListener('click', async () => { if (drawing_active) { alert("Please stop the current drawing first."); return; } const worldName = item.getAttribute('data-world'); const worldUrl = WORLDS[worldName]; if (worldUrl) { statusDiv.textContent = `Loading "${worldName}"...`; startButton.disabled = true; stopButton.classList.add('hidden'); drawing_active = false; try { await loadImage(worldUrl); statusDiv.textContent = `"${worldName}" loaded. Ready to draw (${executionLine.length} commands).`; startButton.textContent = "Start Drawing"; startButton.disabled = false; } catch (error) { console.error("Error loading image:", error); statusDiv.textContent = `Error loading "${worldName}". See console.`; startButton.textContent = "Load & Generate"; startButton.disabled = false; } } else { statusDiv.textContent = `Error: World "${worldName}" not found.`; } }); }); // Start button startButton.addEventListener('click', async () => { if (executionLine.length === 0) { statusDiv.textContent = 'Please load an image first.'; return; } if (window['___BOT'] && window['___BOT'].conn && window['___BOT'].conn.socket && window['___BOT'].conn.socket.readyState === WebSocket.OPEN) { drawing_active = true; startButton.classList.add('hidden'); stopButton.classList.remove('hidden'); statusDiv.textContent = `Drawing... (0/${executionLine.length})`; await execute(window['___BOT'].conn.socket); drawing_active = false; startButton.classList.remove('hidden'); stopButton.classList.add('hidden'); startButton.textContent = "Load & Generate"; statusDiv.textContent = `Drawing finished (${executionLine.length}/${executionLine.length}).`; } else { statusDiv.textContent = 'Error: Bot socket not available or not open.'; console.error("Bot socket not available or not open.", window['___BOT']); } }); // Stop button stopButton.addEventListener('click', () => { drawing_active = false; stopButton.disabled = true; statusDiv.textContent = 'Stopping drawing...'; }); // Initial status statusDiv.textContent = originalCanvas ? 'Select a world to load.' : 'Waiting for game canvas...'; if (!originalCanvas) startButton.disabled = true; } const WORLDS = { "Superflat": "https://static.vecteezy.com/system/resources/thumbnails/027/879/859/small/side-view-land-and-cloud-in-pixel-art-style-vector.jpg", "Lava Cave": "https://pics.craiyon.com/2023-09-23/40aa7363eec448ea836d2fb2cd4cce6a.webp", "Desert Vista": "https://i.ibb.co/pv9sVTtP/9e14aa6d-2348-4654-b235-c41ebbe678f2.png", "Night Landscape": "https://images.stockcake.com/public/2/b/c/2bc47920-6b02-49b8-aac8-81d965e2ab70_large/moonlit-pixel-city-stockcake.jpg", "House": "https://static.vecteezy.com/system/resources/previews/010/963/629/non_2x/house-pixel-icon-free-vector.jpg", "Classroom": "https://i.ibb.co/hFHRGT3j/download.jpg", "Castle": "https://i.ibb.co/8gRg8rtZ/cwtdyva7.png", "School": "https://static.vecteezy.com/system/resources/previews/045/712/354/non_2x/school-building-in-pixel-art-style-vector.jpg" }; // Convert hex to RGB function hexToRgb(hex) { const bigint = parseInt(hex.replace('#', ''), 16); const r = (bigint >> 16) & 255; const g = (bigint >> 8) & 255; const b = bigint & 255; return `rgb(${r},${g},${b})`; } // Parse ignore colors input function parseIgnoreColors(input) { const colors = input.split(',').map(c => c.trim()).filter(c => c); const rgbColors = []; colors.forEach(color => { if (color.startsWith('#')) { try { rgbColors.push(hexToRgb(color)); } catch (e) { console.warn(`Invalid hex color: ${color}`); } } else if (color.startsWith('rgb(') && color.endsWith(')')) { const rgbValues = color.substring(4, color.length - 1).split(',').map(Number); if (rgbValues.length === 3 && rgbValues.every(v => v >= 0 && v <= 255)) { rgbColors.push(color); } else { console.warn(`Invalid rgb color format or values: ${color}`); } } else { console.warn(`Unrecognized color format: ${color}`); } }); return rgbColors; } // Load and process image async function loadImage(url) { return new Promise((resolve, reject) => { if (!originalCanvas) { reject("Drawaria canvas not found."); return; } const img = new Image(); img.crossOrigin = 'Anonymous'; img.addEventListener('load', () => { previewCanvas.width = cw; previewCanvas.height = ch; const ctx = previewCanvas.getContext('2d'); // Scale and center image const scaleW = cw / img.width; const scaleH = ch / img.height; const scale = Math.max(scaleW, scaleH); const scaledWidth = img.width * scale; const scaledHeight = img.height * scale; const offsetX = (cw - scaledWidth) / 2; const offsetY = (ch - scaledHeight) / 2; ctx.clearRect(0, 0, cw, ch); ctx.fillStyle = '#FFFFFF'; ctx.fillRect(0, 0, cw, ch); ctx.drawImage(img, 0, 0, img.width, img.height, offsetX, offsetY, scaledWidth, scaledHeight); const imgData = ctx.getImageData(0, 0, cw, ch); data = imgData.data; ctx.clearRect(0, 0, cw, ch); // Generate commands with spiral pattern const size = parseInt(document.getElementById('draw-size').value, 10) || 4; const modifier = parseInt(document.getElementById('draw-modifier').value, 10) || 4; const thickness = parseInt(document.getElementById('draw-thickness').value, 10) || 27; const speedFactor = parseInt(document.getElementById('speed-factor').value, 10) || 2; const ignoreInput = document.getElementById('ignore-colors').value; const ignoreColors = parseIgnoreColors(ignoreInput); generateDrawingCommands(size, modifier, thickness, speedFactor, ignoreColors); console.log('Image loaded and commands generated.'); resolve(); }); img.addEventListener('error', (e) => { console.error("Failed to load image:", url, e); reject(`Failed to load image: ${url}`); }); img.src = url; }); } // Generate drawing commands using rectangular spiral pattern function generateDrawingCommands(size, modifier, thickness, speedFactor, ignoreColors) { executionLine = []; const step = Math.max(1, size * modifier * speedFactor); // Scale step with speedFactor if (step <= 0) { console.error("Invalid step size:", step); alert("Error generating commands: Invalid step size."); return; } // Spiral traversal: outer to inner rectangular spiral let left = 0, right = cw - 1, top = 0, bottom = ch - 1; let currentSegmentStart = null; // Start pixel coordinate [x, y] let currentSegmentColor = null; // Current segment color // Create reference objects to pass to processPixel const segmentRefs = { currentSegmentStart: { value: null }, currentSegmentColor: { value: null } }; while (left <= right && top <= bottom) { // Traverse top row (left to right) for (let x = left; x <= right; x += step) { processPixel(x, top, step, thickness, ignoreColors, segmentRefs.currentSegmentStart, segmentRefs.currentSegmentColor); } top += step; // Traverse right column (top to bottom) for (let y = top; y <= bottom && left <= right; y += step) { processPixel(right, y, step, thickness, ignoreColors, segmentRefs.currentSegmentStart, segmentRefs.currentSegmentColor); } right -= step; // Traverse bottom row (right to left) for (let x = right; x >= left && top <= bottom; x -= step) { processPixel(x, bottom, step, thickness, ignoreColors, segmentRefs.currentSegmentStart, segmentRefs.currentSegmentColor); } bottom -= step; // Traverse left column (bottom to top) for (let y = bottom; y >= top && left <= right; y -= step) { processPixel(left, y, step, thickness, ignoreColors, segmentRefs.currentSegmentStart, segmentRefs.currentSegmentColor); } left += step; } // End any active segment if (segmentRefs.currentSegmentStart.value !== null) { executionLine.push({ pos1: recalc(segmentRefs.currentSegmentStart.value), pos2: recalc(segmentRefs.currentSegmentStart.value), // End at same point for single pixel color: segmentRefs.currentSegmentColor.value, thickness: thickness, }); } console.log(`Generated ${executionLine.length} drawing commands.`); } // Helper to process a pixel and manage segments function processPixel(x, y, step, thickness, ignoreColors, currentSegmentStartRef, currentSegmentColorRef) { if (x < 0 || x >= cw || y < 0 || y >= ch) return; const pixelIndex = (y * cw + x) * 4; const alpha = data[pixelIndex + 3]; if (alpha > 20) { const r = data[pixelIndex + 0]; const g = data[pixelIndex + 1]; const b = data[pixelIndex + 2]; const color = `rgb(${r},${g},${b})`; if (!ignoreColors.includes(color)) { if (currentSegmentStartRef.value === null) { currentSegmentStartRef.value = [x, y]; currentSegmentColorRef.value = color; } else if (color !== currentSegmentColorRef.value) { executionLine.push({ pos1: recalc(currentSegmentStartRef.value), pos2: recalc([x, y]), color: currentSegmentColorRef.value, thickness: thickness, }); currentSegmentStartRef.value = [x, y]; currentSegmentColorRef.value = color; } // Continue segment if color is the same } else if (currentSegmentStartRef.value !== null) { executionLine.push({ pos1: recalc(currentSegmentStartRef.value), pos2: recalc([x, y]), color: currentSegmentColorRef.value, thickness: thickness, }); currentSegmentStartRef.value = null; currentSegmentColorRef.value = null; } } else if (currentSegmentStartRef.value !== null) { executionLine.push({ pos1: recalc(currentSegmentStartRef.value), pos2: recalc([x, y]), color: currentSegmentColorRef.value, thickness: thickness, }); currentSegmentStartRef.value = null; currentSegmentColorRef.value = null; } } // Helper to process a pixel and manage segments function processPixel(x, y, step, thickness, ignoreColors, currentSegmentStartRef, currentSegmentColorRef) { if (x < 0 || x >= cw || y < 0 || y >= ch) return; const pixelIndex = (y * cw + x) * 4; const alpha = data[pixelIndex + 3]; if (alpha > 20) { const r = data[pixelIndex + 0]; const g = data[pixelIndex + 1]; const b = data[pixelIndex + 2]; const color = `rgb(${r},${g},${b})`; if (!ignoreColors.includes(color)) { if (currentSegmentStartRef.value === null) { currentSegmentStartRef.value = [x, y]; currentSegmentColorRef.value = color; } else if (color !== currentSegmentColorRef.value) { executionLine.push({ pos1: recalc(currentSegmentStartRef.value), pos2: recalc([x, y]), color: currentSegmentColorRef.value, thickness: thickness, }); currentSegmentStartRef.value = [x, y]; currentSegmentColorRef.value = color; } } else if (currentSegmentStartRef.value !== null) { executionLine.push({ pos1: recalc(currentSegmentStartRef.value), pos2: recalc([x, y]), color: currentSegmentColorRef.value, thickness: thickness, }); currentSegmentStartRef.value = null; currentSegmentColorRef.value = null; } } else if (currentSegmentStartRef.value !== null) { executionLine.push({ pos1: recalc(currentSegmentStartRef.value), pos2: recalc([x, y]), color: currentSegmentColorRef.value, thickness: thickness, }); currentSegmentStartRef.value = null; currentSegmentColorRef.value = null; } } // Execute drawing commands with optimized timing async function execute(socket) { const delayMs = parseInt(document.getElementById('draw-delay').value, 10) || 1; const statusDiv = document.getElementById('status'); const stopButton = document.getElementById('stop-button'); stopButton.disabled = false; const maxCommandsPerSecond = 500; // Prevent server overload const batchSize = Math.floor(1000 / maxCommandsPerSecond / delayMs) || 1; for (let i = 0; i < executionLine.length; i += batchSize) { if (!drawing_active) { console.log("Drawing stopped by user."); statusDiv.textContent = `Drawing stopped (${i}/${executionLine.length}).`; break; } // Send batch of commands for (let j = 0; j < batchSize && i + j < executionLine.length; j++) { let currentLine = executionLine[i + j]; let p1 = currentLine.pos1; let p2 = currentLine.pos2; let color = currentLine.color; let thickness = currentLine.thickness; if (p1 && p2 && typeof p1[0] === 'number' && typeof p1[1] === 'number' && typeof p2[0] === 'number' && typeof p2[1] === 'number' && !isNaN(p1[0]) && !isNaN(p1[1]) && !isNaN(p2[0]) && !isNaN(p2[1])) { drawcmd(socket, p1, p2, color, thickness); } else { console.warn("Skipping invalid command:", currentLine); } } // Update status every 50 commands or at end if ((i + batchSize) % 50 === 0 || i + batchSize >= executionLine.length) { statusDiv.textContent = `Drawing... (${Math.min(i + batchSize, executionLine.length)}/${executionLine.length})`; } // Check WebSocket buffer to prevent overload if (socket.bufferedAmount > 100000) { console.warn("WebSocket buffer high, pausing..."); await delay(100); } await delay(delayMs); } stopButton.disabled = true; } // Send drawing command function drawcmd(s, start, end, color, thickness) { const cmd = `42["drawcmd",0,[${start[0].toFixed(4)},${start[1].toFixed(4)},${end[0].toFixed(4)},${end[1].toFixed(4)},false,${0 - thickness},"${color}",0,0,{}]]`; s.send(cmd); } // Delay function function delay(ms) { return new Promise((resolve) => { if (ms > 0) { setTimeout(resolve, ms); } else { resolve(); } }); } // Recalculate coordinates to normalized function recalc(pixelCoords) { if (cw === 0 || ch === 0) { console.error("Canvas dimensions not set!", cw, ch); return [NaN, NaN]; } return [ pixelCoords[0] / cw, pixelCoords[1] / ch ]; } // Helper from original bot var nullify = (value = null) => { return value == null ? null : String().concat('"', value, '"'); }; // Initialize script function init() { if (!document.getElementById('world-generator')) { addBoxIcons(); CreateStylesheet(); CreateWorldGenerator(); if (!window['___BOT']) { window['___BOT'] = new Player('bot'); } if (!window['___ENGINE']) { window['___ENGINE'] = { loadImage: loadImage, drawImage: generateDrawingCommands, execute: execute, recalc: recalc }; } } else { console.log("World Generator script already running."); } } window.addEventListener('load', init); if (document.readyState === 'complete' || document.readyState === 'interactive') { init(); } else { document.addEventListener('DOMContentLoaded', init); } // Player/Connection/Room/Actions classes const Player = function (name = undefined) { this.name = name; this.sid1 = null; this.uid = ''; this.wt = ''; this.conn = new Connection(this); this.room = new Room(this.conn); this.action = new Actions(this.conn); }; Player.prototype.annonymize = function (name) { this.name = name; this.uid = undefined; this.wt = undefined; }; const Connection = function (player) { this.player = player; this.socket = null; }; Connection.prototype.onopen = function (event) { this.Heartbeat(25000); }; Connection.prototype.onclose = function (event) { }; Connection.prototype.onerror = function (event) { console.error("Socket error:", event); }; Connection.prototype.onmessage = function (event) { let message = String(event.data); if (message.startsWith('42')) { this.onbroadcast(message.slice(2)); } else if (message.startsWith('40')) { this.onrequest(); } else if (message.startsWith('41')) { } else if (message.startsWith('430')) { let configs = JSON.parse(message.slice(3))[0]; if (configs) { this.player.room.players = configs.players; this.player.room.id = configs.roomid; console.log(`Bot joined room ${this.player.room.id}`); } } }; Connection.prototype.onbroadcast = function (payload) { try { payload = JSON.parse(payload); if (payload[0] == 'bc_uc_freedrawsession_changedroom') { if (payload.length > 3) this.player.room.players = payload[3]; if (payload.length > 4) this.player.room.id = payload[4]; } if (payload[0] == 'mc_roomplayerschange') { if (payload.length > 3) this.player.room.players = payload[3]; } } catch(e) { console.error("Error parsing broadcast payload:", payload, e); } }; Connection.prototype.onrequest = function () { }; Connection.prototype.open = function (url) { if (this.socket && this.socket.readyState !== WebSocket.CLOSED) { console.log("Socket already open or connecting."); return; } this.socket = new WebSocket(url); this.socket.onopen = this.onopen.bind(this); this.socket.onclose = this.onclose.bind(this); this.socket.onerror = this.onerror.bind(this); this.socket.onmessage = this.onmessage.bind(this); console.log("Attempting to open WebSocket:", url); }; Connection.prototype.close = function (code, reason) { if (this.socket) { console.log("Closing WebSocket:", code, reason); this.socket.close(code, reason); this.socket = null; } }; Connection.prototype.Heartbeat = function (interval) { this.heartbeatTimeout = setTimeout(() => { if (this.socket && this.socket.readyState == this.socket.OPEN) { this.socket.send(2); this.Heartbeat(interval); } else { console.log("Heartbeat stopped, socket not open."); } }, interval); }; Connection.prototype.stopHeartbeat = function() { if (this.heartbeatTimeout) { clearTimeout(this.heartbeatTimeout); this.heartbeatTimeout = null; } }; Connection.prototype.serverconnect = function (server, connectstring) { this.stopHeartbeat(); this.close(); this.open(server); this.onrequest = () => { if (this.socket && this.socket.readyState === WebSocket.OPEN) { this.socket.send(connectstring); this.onrequest = function() {}; } else { console.error("Socket not open when onrequest was called."); } }; }; const Room = function (conn) { this.conn = conn; this.id = null; this.players = []; }; Room.prototype.join = function (invitelink) { let gamemode = 2; let server = ''; let roomIdToSend = null; if (invitelink != null) { if (invitelink.startsWith('http')) { const urlParts = invitelink.split('/'); this.id = urlParts.pop(); } else { this.id = invitelink; } roomIdToSend = nullify(this.id); if (this.id && typeof this.id === 'string') { if (this.id.endsWith('.3')) { server = 'sv3.'; gamemode = 2; } else if (this.id.endsWith('.2')) { server = 'sv2.'; gamemode = 2; } else { server = ''; gamemode = 2; } } else { console.warn("Could not parse room ID from invitelink:", invitelink); roomIdToSend = null; server = 'sv3.'; gamemode = 2; this.id = null; } } else { this.id = null; server = 'sv3.'; gamemode = 2; roomIdToSend = null; } let serverurl = `wss://${server}drawaria.online/socket.io/?sid1=undefined&hostname=drawaria.online&EIO=3&transport=websocket`; let player = this.conn.player; let connectstring = `420["startplay",${nullify(player.name)},${gamemode},"en",${roomIdToSend},null,[${nullify(player.sid1)},${nullify(player.uid)},${nullify(player.wt)}],null]]`; console.log("Attempting to connect to server:", serverurl, "with command:", connectstring); this.conn.serverconnect(serverurl, connectstring); }; Room.prototype.next = function () { if (this.conn.socket && this.conn.socket.readyState === this.conn.socket.OPEN) { this.conn.socket.send('42["pgswtichroom"]'); } else { console.warn("Socket not open, cannot switch room."); this.join(null); } }; const Actions = function (conn) { this.conn = conn; }; Actions.prototype.DrawLine = function (bx = 50, by = 50, ex = 50, ey = 50, thickness = 50, color = '#FFFFFF', algo = 0) { bx = bx / 100; by = by / 100; ex = ex / 100; ey = ey / 100; if (this.conn.socket && this.conn.socket.readyState === this.conn.socket.OPEN) { this.conn.socket.send(`42["drawcmd",0,[${bx},${by},${ex},${ey},true,${0 - thickness},"${color}",0,0,{"2":${algo},"3":0.5,"4":0.5}]]`); this.conn.socket.send(`42["drawcmd",0,[${bx},${by},${ex},${ey},false,${0 - thickness},"${color}",0,0,{"2":${algo},"3":0.5,"4":0.5}]]`); } else { console.warn("Socket not open, cannot draw line."); } }; })();