您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Бот для автоматического рисования на Pixel Battles
// ==UserScript== // @license MIT // @name Pixel Battles Bot // @namespace http://tampermonkey.net/ // @version 3.0 // @description Бот для автоматического рисования на Pixel Battles // @author .hilkach. // @match https://pixelbattles.ru/* // @grant GM_addStyle // @grant GM_xmlhttpRequest // @connect pixelbattles.ru // ==/UserScript== (function() { 'use strict'; // ===== ГЛОБАЛЬНЫЕ НАСТРОЙКИ ===== // const SETTINGS = { modes: { "top-down": "⬇️ Сверху вниз", "bottom-up": "⬆️ Снизу вверх", "left-right": "➡️ Слева направо", "right-left": "⬅️ Справа налево", "random": "🎲 Случайные точки", "diagonal-lt-rb": "↘️ Лев.верх → прав.низ", "diagonal-rt-lb": "↙️ Прав.верх → лев.низ", "diagonal-lb-rt": "↗️ Лев.низ → прав.верх", "diagonal-rb-lt": "↖️ Прав.низ → лев.верх" }, colors: { "deepcarmine": "Красный", "flame": "Оранжево-красный", "yelloworange": "Жёлто-оранжевый", "naplesyellow": "Жёлтый", "mediumseagreen": "Зелёный", "emerald": "Изумрудный", "inchworm": "Салатовый", "myrtlegreen": "Тёмно-зелёный", "verdigris": "Бирюзовый", "cyancobaltblue": "Синий кобальт", "unitednationsblue": "Синий", "mediumskyblue": "Голубой", "oceanblue": "Морской волны", "VeryLightBlue": "Светло-голубой", "grape": "Фиолетовый", "purpleplum": "Сливовый", "darkpink": "Розовый", "mauvelous": "Розоватый", "coffee": "Коричневый", "coconut": "Бежевый", "black": "Чёрный", "philippinegray": "Серый", "lightsilver": "Серебристый", "white": "Белый" }, colorHexes: [ "#ae233d", "#ec5427", "#f4ab3c", "#f9d759", "#48a06d", "#5cc87f", "#9ae96c", "#317270", "#469ca8", "#2d519e", "#4d90e3", "#7ee6f2", "#4440ba", "#6662f6", "#772b99", "#a754ba", "#eb4e81", "#f19eab", "#684a34", "#956a34", "#000000", "#898d90", "#d5d7d9", "#ffffff" ], color: "black", delay: 1000, isRunning: false, abortController: null, configs: [] }; // ===== ФУНКЦИИ РИСОВАНИЯ ===== // async function placePixel(x, y, color, signal) { try { if (signal?.aborted) throw new Error("Операция прервана"); const response = await fetch("https://api.pixelbattles.ru/pix", { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ x, y, color }), credentials: "include", signal }); console.log(`✅ (${x}, ${y})`); return true; } catch (error) { if (error.name !== "AbortError") console.error(`❌ (${x}, ${y}):`, error.message); return false; } } // ===== ФУНКЦИИ РИСОВАНИЯ (РЕЖИМЫ) ===== // const drawingModes = { "top-down": async function*(x, y, w, h) { for (let row = y; row < y + h; row++) { for (let col = x; col < x + w; col++) { yield {x: col, y: row}; } } }, "bottom-up": async function*(x, y, w, h) { for (let row = y + h - 1; row >= y; row--) { for (let col = x; col < x + w; col++) { yield {x: col, y: row}; } } }, "left-right": async function*(x, y, w, h) { for (let col = x; col < x + w; col++) { for (let row = y; row < y + h; row++) { yield {x: col, y: row}; } } }, "right-left": async function*(x, y, w, h) { for (let col = x + w - 1; col >= x; col--) { for (let row = y; row < y + h; row++) { yield {x: col, y: row}; } } }, "random": async function*(x, y, w, h) { const pixels = []; for (let row = y; row < y + h; row++) { for (let col = x; col < x + w; col++) { pixels.push({x: col, y: row}); } } for (let i = pixels.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [pixels[i], pixels[j]] = [pixels[j], pixels[i]]; } for (const pixel of pixels) yield pixel; }, "diagonal-lt-rb": async function*(x, y, w, h) { for (let d = 0; d < w + h - 1; d++) { const startCol = Math.max(0, d - h + 1); const endCol = Math.min(d, w - 1); for (let col = startCol; col <= endCol; col++) { yield {x: x + col, y: y + (d - col)}; } } }, "diagonal-rt-lb": async function*(x, y, w, h) { for (let d = 0; d < w + h - 1; d++) { const startCol = Math.max(0, (w - 1) - d); const endCol = Math.min(w - 1, (w + h - 2) - d); for (let col = startCol; col <= endCol; col++) { yield {x: x + col, y: y + (d - ((w - 1) - col))}; } } }, "diagonal-lb-rt": async function*(x, y, w, h) { for (let d = 0; d < w + h - 1; d++) { const startCol = Math.max(0, d - h + 1); const endCol = Math.min(d, w - 1); for (let col = startCol; col <= endCol; col++) { yield {x: x + col, y: y + (h - 1) - (d - col)}; } } }, "diagonal-rb-lt": async function*(x, y, w, h) { for (let d = 0; d < w + h - 1; d++) { const startCol = Math.max(0, (w - 1) - d); const endCol = Math.min(w - 1, (w + h - 2) - d); for (let col = startCol; col <= endCol; col++) { yield {x: x + col, y: y + (h - 1) - (d - ((w - 1) - col))}; } } } }; // ===== СИСТЕМА КОНФИГУРАЦИЙ ===== // function saveConfig(name) { if (!name.trim()) { alert("Введите название конфига!"); return; } const config = { name: name.trim(), date: new Date().toISOString(), settings: { mode: document.querySelector('#mode-select').value, color: SETTINGS.color, x: parseInt(document.querySelector('#start-x').value), y: parseInt(document.querySelector('#start-y').value), w: parseInt(document.querySelector('#width').value), h: parseInt(document.querySelector('#height').value), delay: parseInt(document.querySelector('#delay').value) } }; const existingIndex = SETTINGS.configs.findIndex(c => c.name === config.name); if (existingIndex >= 0) { if (!confirm(`Конфиг "${config.name}" уже существует. Перезаписать?`)) return; SETTINGS.configs[existingIndex] = config; } else { SETTINGS.configs.push(config); } localStorage.setItem('pixelBotConfigs', JSON.stringify(SETTINGS.configs)); updateConfigsList(); alert(`Конфиг "${config.name}" сохранён!`); } function applyConfig(config) { const { settings } = config; document.querySelector('#mode-select').value = settings.mode; document.querySelector('#color-select').value = settings.color; document.querySelector('#start-x').value = settings.x; document.querySelector('#start-y').value = settings.y; document.querySelector('#width').value = settings.w; document.querySelector('#height').value = settings.h; document.querySelector('#delay').value = settings.delay; document.querySelector('#config-name').value = config.name; SETTINGS.color = settings.color; const colorIndex = Object.keys(SETTINGS.colors).indexOf(SETTINGS.color); document.querySelector('#current-color').style.background = SETTINGS.colorHexes[colorIndex]; } function renameConfig(oldName, newName) { if (!newName || !newName.trim()) { alert("Введите новое название!"); return; } newName = newName.trim(); if (oldName === newName) return; if (SETTINGS.configs.some(c => c.name === newName)) { alert("Конфиг с таким именем уже существует!"); return; } const configIndex = SETTINGS.configs.findIndex(c => c.name === oldName); if (configIndex >= 0) { SETTINGS.configs[configIndex].name = newName; SETTINGS.configs[configIndex].date = new Date().toISOString(); localStorage.setItem('pixelBotConfigs', JSON.stringify(SETTINGS.configs)); updateConfigsList(); } } function deleteConfig(name) { if (!confirm(`Удалить конфиг "${name}"?`)) return; SETTINGS.configs = SETTINGS.configs.filter(c => c.name !== name); localStorage.setItem('pixelBotConfigs', JSON.stringify(SETTINGS.configs)); updateConfigsList(); } function exportConfig() { const configName = document.querySelector('#config-name').value.trim() || "pixelbot_config"; const config = { name: configName, date: new Date().toISOString(), settings: { mode: document.querySelector('#mode-select').value, color: SETTINGS.color, x: parseInt(document.querySelector('#start-x').value), y: parseInt(document.querySelector('#start-y').value), w: parseInt(document.querySelector('#width').value), h: parseInt(document.querySelector('#height').value), delay: parseInt(document.querySelector('#delay').value) } }; const blob = new Blob([JSON.stringify(config, null, 2)], {type: 'application/json'}); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `pixelbot_${configName.replace(/\s+/g, '_')}.json`; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); } function importConfig(event) { const file = event.target.files[0]; if (!file) return; const reader = new FileReader(); reader.onload = (e) => { try { const config = JSON.parse(e.target.result); if (!config.name || !config.settings) { throw new Error("Некорректный формат конфига"); } if (confirm(`Импортировать конфиг "${config.name}"?`)) { const existingIndex = SETTINGS.configs.findIndex(c => c.name === config.name); if (existingIndex >= 0) { if (!confirm(`Конфиг "${config.name}" уже существует. Перезаписать?`)) return; SETTINGS.configs[existingIndex] = config; } else { SETTINGS.configs.push(config); } localStorage.setItem('pixelBotConfigs', JSON.stringify(SETTINGS.configs)); updateConfigsList(); document.querySelector('#config-name').value = config.name; alert(`Конфиг "${config.name}" успешно импортирован!`); } } catch (error) { console.error("Ошибка импорта:", error); alert("Ошибка при импорте конфига: " + error.message); } }; reader.readAsText(file); event.target.value = ''; } function updateConfigsList() { const configsList = document.querySelector('#configs-list'); if (!configsList) { console.warn('Элемент #configs-list не найден'); return; } // Сортируем конфиги по дате (новые сверху) const sortedConfigs = [...SETTINGS.configs].sort((a, b) => new Date(b.date) - new Date(a.date) ); configsList.innerHTML = sortedConfigs.map(config => ` <div class="config-item" style="display: flex; justify-content: space-between; align-items: center; padding: 5px; margin: 2px 0; background: #f9f9f9; border-radius: 3px;"> <span style="flex: 1; cursor: pointer;" onclick="applyConfig(${JSON.stringify(config).replace(/"/g, '"')})"> ${config.name} <span style="font-size: 0.8em; color: #666; margin-left: 5px;"> ${new Date(config.date).toLocaleString()} </span> </span> <div> <button onclick="promptRenameConfig('${config.name.replace(/'/g, "\\'")}')" style="background: none; border: none; cursor: pointer; font-size: 0.9em; margin-left: 5px;" title="Переименовать"> ✏️ </button> <button onclick="deleteConfig('${config.name.replace(/'/g, "\\'")}')" style="background: none; border: none; cursor: pointer; color: red; font-size: 0.9em; margin-left: 5px;" title="Удалить"> ✖ </button> </div> </div> `).join(''); } function promptRenameConfig(oldName) { const newName = prompt("Введите новое название конфига:", oldName); if (newName && newName !== oldName) { renameConfig(oldName, newName); } } function loadConfigs() { try { const savedConfigs = localStorage.getItem('pixelBotConfigs'); if (savedConfigs) { SETTINGS.configs = JSON.parse(savedConfigs); // Добавляем небольшую задержку для гарантированного обновления DOM setTimeout(updateConfigsList, 50); } } catch (e) { console.error("Ошибка загрузки конфигов:", e); } } // ===== ПАНЕЛЬ УПРАВЛЕНИЯ ===== // function createControlPanel() { const panel = document.createElement('div'); panel.style.cssText = ` position: fixed; top: 20px; right: 20px; background: white; padding: 15px; border: 1px solid #ddd; border-radius: 10px; z-index: 9999; font-family: Arial, sans-serif; font-size: 13px; width: 280px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); user-select: none; `; panel.innerHTML = ` <div id="panel-header" style="cursor: move; padding: 8px 10px; margin: -15px -15px 15px -15px; background: #f5f5f5; border-radius: 8px 8px 0 0; font-size: 14px; display: flex; justify-content: space-between; align-items: center;"> <strong>🎨 PixelBot v3.0</strong> <span id="current-color" style="display: inline-block; width: 18px; height: 18px; background: ${SETTINGS.colorHexes[Object.keys(SETTINGS.colors).indexOf(SETTINGS.color)]}; border: 1px solid #ccc; border-radius: 3px;"></span> </div> <div style="margin-bottom: 12px;"> <label style="display: block; margin-bottom: 3px; font-weight: bold; color: #555;">Режим рисования:</label> <select id="mode-select" style="width: 100%; padding: 6px; border: 1px solid #ddd; border-radius: 4px; font-size: 13px; background: white;"> ${Object.entries(SETTINGS.modes).map(([key, desc]) => `<option value="${key}">${desc}</option>`).join('')} </select> </div> <div style="margin-bottom: 12px;"> <label style="display: block; margin-bottom: 3px; font-weight: bold; color: #555;">Цвет:</label> <select id="color-select" style="width: 100%; padding: 6px; border: 1px solid #ddd; border-radius: 4px; font-size: 13px; background: white;"> ${Object.keys(SETTINGS.colors).map((key, index) => `<option value="${key}" ${key === SETTINGS.color ? 'selected' : ''}> ${SETTINGS.colors[key]} <span style="float: right; display: inline-block; width: 12px; height: 12px; background: ${SETTINGS.colorHexes[index]}; border: 1px solid #ccc; border-radius: 2px;"></span> </option>`).join('')} </select> </div> <div style="margin-bottom: 12px;"> <label style="display: block; margin-bottom: 3px; font-weight: bold; color: #555;">Область рисования:</label> <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 8px; font-size: 13px;"> <div> <label style="font-size: 0.9em; color: #666;">X:</label> <input type="number" id="start-x" value="0" style="width: 100%; padding: 6px; border: 1px solid #ddd; border-radius: 4px;"> </div> <div> <label style="font-size: 0.9em; color: #666;">Y:</label> <input type="number" id="start-y" value="0" style="width: 100%; padding: 6px; border: 1px solid #ddd; border-radius: 4px;"> </div> <div> <label style="font-size: 0.9em; color: #666;">Ширина:</label> <input type="number" id="width" value="10" style="width: 100%; padding: 6px; border: 1px solid #ddd; border-radius: 4px;"> </div> <div> <label style="font-size: 0.9em; color: #666;">Высота:</label> <input type="number" id="height" value="10" style="width: 100%; padding: 6px; border: 1px solid #ddd; border-radius: 4px;"> </div> </div> </div> <div style="margin-bottom: 15px;"> <label style="display: block; margin-bottom: 3px; font-weight: bold; color: #555;">Задержка (мс):</label> <input type="number" id="delay" value="1000" style="width: 100%; padding: 6px; border: 1px solid #ddd; border-radius: 4px;"> </div> <div style="margin: 15px 0; border-top: 1px solid #eee; padding-top: 15px;"> <div style="margin-bottom: 10px;"> <label style="display: block; margin-bottom: 5px; font-weight: bold; color: #555;">Управление конфигами:</label> <div style="display: flex; gap: 8px; margin-bottom: 8px;"> <input type="text" id="config-name" placeholder="Название конфига" style="flex: 1; padding: 6px; border: 1px solid #ddd; border-radius: 4px;"> <button id="save-config" style="padding: 6px 10px; background: #4CAF50; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 13px;">Сохранить</button> </div> <div id="configs-list" style="max-height: 150px; overflow-y: auto; border: 1px solid #ddd; border-radius: 4px; padding: 5px; background: #fafafa;"></div> <div style="display: flex; gap: 8px; margin-top: 10px;"> <button id="export-config" style="flex: 1; padding: 6px; background: #2196F3; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 13px;">Экспорт</button> <label for="import-file" style="flex: 1; padding: 6px; background: #FF9800; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 13px; text-align: center;"> Импорт <input type="file" id="import-file" accept=".json" style="display: none;"> </label> </div> </div> </div> <div style="margin-bottom: 15px;"> <div id="progress-text" style="font-size: 12px; text-align: center; margin-bottom: 5px; color: #555;">Готов к работе</div> <div id="progress-bar" style="height: 6px; background: #eee; border-radius: 3px;"> <div id="progress-fill" style="height: 100%; width: 0%; background: #4CAF50; border-radius: 3px; transition: width 0.3s;"></div> </div> </div> <div style="display: flex; gap: 8px;"> <button id="start-btn" style="flex: 1; padding: 8px; background: #2196F3; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 13px; font-weight: bold;"> ▶ Начать рисование </button> <button id="stop-btn" style="flex: 1; padding: 8px; background: #f44336; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 13px; display: none;"> ⏹ Остановить </button> </div> `; // Элементы управления const startBtn = panel.querySelector('#start-btn'); const stopBtn = panel.querySelector('#stop-btn'); const colorSelect = panel.querySelector('#color-select'); const currentColorIndicator = panel.querySelector('#current-color'); const saveConfigBtn = panel.querySelector('#save-config'); const configNameInput = panel.querySelector('#config-name'); const exportBtn = panel.querySelector('#export-config'); const importInput = panel.querySelector('#import-file'); // Обработчики событий colorSelect.addEventListener('change', (e) => { SETTINGS.color = e.target.value; const colorIndex = Object.keys(SETTINGS.colors).indexOf(SETTINGS.color); currentColorIndicator.style.background = SETTINGS.colorHexes[colorIndex]; }); saveConfigBtn.addEventListener('click', () => { saveConfig(configNameInput.value); }); exportBtn.addEventListener('click', exportConfig); importInput.addEventListener('change', importConfig); // Перетаскивание панели const header = panel.querySelector('#panel-header'); let isDragging = false; let offsetX, offsetY; header.addEventListener('mousedown', (e) => { isDragging = true; offsetX = e.clientX - panel.getBoundingClientRect().left; offsetY = e.clientY - panel.getBoundingClientRect().top; panel.style.cursor = 'grabbing'; e.preventDefault(); }); document.addEventListener('mousemove', (e) => { if (!isDragging) return; panel.style.left = `${e.clientX - offsetX}px`; panel.style.top = `${e.clientY - offsetY}px`; }); document.addEventListener('mouseup', () => { isDragging = false; panel.style.cursor = 'default'; }); // Управление рисованием startBtn.addEventListener('click', async () => { if (SETTINGS.isRunning) return; const mode = panel.querySelector('#mode-select').value; const config = { x: parseInt(panel.querySelector('#start-x').value), y: parseInt(panel.querySelector('#start-y').value), w: parseInt(panel.querySelector('#width').value), h: parseInt(panel.querySelector('#height').value), delay: parseInt(panel.querySelector('#delay').value) }; if (isNaN(config.x) || isNaN(config.y) || isNaN(config.w) || isNaN(config.h) || isNaN(config.delay)) { alert('Пожалуйста, введите корректные значения!'); return; } const totalPixels = config.w * config.h; let processedPixels = 0; SETTINGS.delay = config.delay; SETTINGS.isRunning = true; startBtn.style.display = 'none'; stopBtn.style.display = 'block'; SETTINGS.abortController = new AbortController(); try { const generator = drawingModes[mode](config.x, config.y, config.w, config.h); for await (const pixel of generator) { if (SETTINGS.abortController.signal.aborted) break; await placePixel(pixel.x, pixel.y, SETTINGS.color, SETTINGS.abortController.signal); processedPixels++; const progress = Math.round((processedPixels / totalPixels) * 100); panel.querySelector('#progress-fill').style.width = `${progress}%`; panel.querySelector('#progress-text').textContent = `Прогресс: ${progress}% (${processedPixels}/${totalPixels})`; await new Promise(r => setTimeout(r, SETTINGS.delay)); } panel.querySelector('#progress-text').textContent = "Рисование завершено!"; } catch (err) { if (err.name !== 'AbortError') { console.error(err); panel.querySelector('#progress-text').textContent = "Ошибка: " + err.message; } else { panel.querySelector('#progress-text').textContent = "Рисование прервано"; } } finally { SETTINGS.isRunning = false; startBtn.style.display = 'block'; stopBtn.style.display = 'none'; } }); stopBtn.addEventListener('click', () => { if (SETTINGS.abortController) { SETTINGS.abortController.abort(); } }); // Добавляем глобальные функции window.applyConfig = applyConfig; window.deleteConfig = deleteConfig; window.promptRenameConfig = promptRenameConfig; document.body.appendChild(panel); // Загрузка конфигов после добавления панели в DOM loadConfigs(); } // ===== ЗАПУСК ПРИЛОЖЕНИЯ ===== // if (document.readyState === 'complete') { createControlPanel(); } else { document.addEventListener('DOMContentLoaded', createControlPanel); } })();