Drawaria Flag Drawing Tool

Modern draggable menu for flag drawing in Drawaria (Paste in conosole)

// ==UserScript==
// @name         Drawaria Flag Drawing Tool
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  Modern draggable menu for flag drawing in Drawaria (Paste in conosole)
// @author       YouTubeDrawaria
// @match        *://drawaria.online/*
// @grant        none
// @license MIT
// @require      https://unpkg.com/[email protected]/css/boxicons.min.css
// ==/UserScript==

(function() {
    'use strict';

    // Main styles
    const style = document.createElement('style');
    style.textContent = `
        .flag-tool-container {
            position: fixed;
            top: 20px;
            right: 20px;
            width: 320px;
            background: #2c3e50;
            border-radius: 10px;
            box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
            color: #ecf0f1;
            'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            z-index: 9999;
            overflow: hidden;
            transition: all 0.3s ease;
        }

        .flag-tool-header {
            background: #34495e;
            padding: 12px 15px;
            cursor: move;
            display: flex;
            justify-content: space-between;
            align-items: center;
            border-bottom: 1px solid #2c3e50;
        }

        .flag-tool-title {
            font-weight: 600;
            font-size: 16px;
            color: #ecf0f1;
        }

        .flag-tool-close {
            background: none;
            border: none;
            color: #ecf0f1;
            font-size: 18px;
            cursor: pointer;
            transition: color 0.2s;
        }

        .flag-tool-close:hover {
            color: #e74c3c;
        }

        .flag-tool-content {
            padding: 15px;
        }

        .form-group {
            15px;
        }

        .form-group label {
            display: block;
            5px;
            font-size: 13px;
            color: #bdc3c7;
        }

        .form-control {
            width: 100%;
            padding: 8px 12px;
            background: #34495e;
            border: 1px solid #2c3e50;
            border-radius: 4px;
            color: #ecf0f1;
            font-size: 13px;
            transition: border-color 0.2s;
        }

        .form-control:focus {
            outline: none;
            border-color: #3498db;
        }

        .btn {
            padding: 8px 15px;
            border-radius: 4px;
            font-size: 13px;
            font-weight: 500;
            cursor: pointer;
            transition: all 0.2s;
            display: flex;
            align-items: center;
            justify-content: center;
            gap: 5px;
        }

        .btn-primary {
            background: #3498db;
            color: white;
            border: none;
        }

        .btn-primary:hover {
            background: #2980b9;
        }

        .btn-danger {
            background: #e74c3c;
            color: white;
            border: none;
        }

        .btn-danger:hover {
            background: #c0392b;
        }

        .btn-outline {
            background: transparent;
            border: 1px solid #3498db;
            color: #3498db;
        }

        .btn-outline:hover {
            background: rgba(52, 152, 219, 0.1);
        }

        .btn-block {
            width: 100%;
        }

        .btn-group {
            display: flex;
            gap: 8px;
        }

        .btn-group .btn {
            flex: 1;
        }

        .select-control {
            position: relative;
        }

        .select-control:after {
            content: "▼";
            font-size: 10px;
            color: #bdc3c7;
            position: absolute;
            right: 10px;
            top: 50%;
            transform: translateY(-50%);
            pointer-events: none;
        }

        .slider-container {
            10px 0;
        }

        .slider-container label {
            display: block;
            5px;
            font-size: 13px;
            color: #bdc3c7;
        }

        .slider-value {
            display: flex;
            justify-content: space-between;
            5px;
            font-size: 12px;
            color: #7f8c8d;
        }

        input[type="range"] {
            width: 100%;
            height: 6px;
            -webkit-appearance: none;
            background: #34495e;
            border-radius: 3px;
            5px 0;
        }

        input[type="range"]::-webkit-slider-thumb {
            -webkit-appearance: none;
            width: 16px;
            height: 16px;
            background: #3498db;
            border-radius: 50%;
            cursor: pointer;
        }

        .section-title {
            font-size: 14px;
            color: #3498db;
            10px;
            padding-bottom: 5px;
            border-bottom: 1px solid #2c3e50;
        }

        .status-indicator {
            display: inline-block;
            width: 10px;
            height: 10px;
            border-radius: 50%;
            5px;
        }

        .status-connected {
            background-color: #2ecc71;
        }

        .status-disconnected {
            background-color: #e74c3c;
        }

        .dragging {
            opacity: 0.8;
            cursor: moving;
        }
    `;
    document.head.appendChild(style);

    // Flag configurations
    const flags = {
        Russia: 'https://flagcdn.com/w320/ru.png',
        Ukraine: 'https://flagcdn.com/w320/ua.png',
        Uzbekistan: 'https://flagcdn.com/w320/uz.png',
        India: 'https://flagcdn.com/w320/in.png',
        Armenia: 'https://flagcdn.com/w320/am.png',
        Latvia: 'https://flagcdn.com/w320/lv.png',
        Estonia: 'https://flagcdn.com/w320/ee.png',
        Bulgaria: 'https://flagcdn.com/w320/bg.png',
        Germany: 'https://flagcdn.com/w320/de.png',
        Netherlands: 'https://flagcdn.com/w320/nl.png',
        Hungary: 'https://flagcdn.com/w320/hu.png',
        Luxembourg: 'https://flagcdn.com/w320/lu.png'
    };

    // Drawing Tools
    let previewCanvas = document.createElement('canvas');
    let originalCanvas = null;
    let data;
    let cw, ch;
    let executionLine = [];
    let isDrawing = false;
    let socketStatus = 'disconnected';

    // Create the draggable container
    const container = document.createElement('div');
    container.className = 'flag-tool-container';
    container.innerHTML = `
        <div class="flag-tool-header">
            <div class="flag-tool-title">Flag Drawing Tool</div>
            <button class="flag-tool-close">&times;</button>
        </div>
        <div class="flag-tool-content">
            <div class="section-title">Connection Status</div>
            <div id="connectionStatus" class="status-indicator status-disconnected"></div>
            <span id="statusText">Disconnected</span>

            <div class="section-title">Bot Configuration</div>
            <div class="form-group">
                <label for="botNameInput">Bot Name</label>
                <input type="text" id="botNameInput" class="form-control" placeholder="FlagBot">
            </div>

            <div class="btn-group">
                <button id="botJoinBtn" class="btn btn-primary">
                    <i class='bx bx-user-plus'></i> Join
                </button>
                <button id="botLeaveBtn" class="btn btn-danger">
                    <i class='bx bx-user-minus'></i> Leave
                </button>
            </div>

            <div class="section-title">Flag Selection</div>
            <div class="form-group">
                <label for="flagSelect">Select Flag</label>
                <div class="select-control">
                    <select id="flagSelect" class="form-control">
                        <option value="">Select a Flag</option>
                        ${Object.keys(flags).map(flag => `<option value="${flag}">${flag}</option>`).join('')}
                    </select>
                </div>
            </div>

            <div class="section-title">Drawing Settings</div>
            <div class="slider-container">
                <label for="flag_imagesize">Image Size</label>
                <div class="slider-value">
                    <span>Big</span>
                    <span id="imagesizeValue">2</span>
                    <span>Small</span>
                </div>
                <input type="range" id="flag_imagesize" min="1" max="10" value="2">
            </div>

            <div class="slider-container">
                <label for="flag_brushsize">Brush Size</label>
                <div class="slider-value">
                    <span>Thin</span>
                    <span id="brushsizeValue">20</span>
                    <span>Thick</span>
                </div>
                <input type="range" id="flag_brushsize" min="2" max="20" value="20">
            </div>

            <div class="slider-container">
                <label for="flag_pixelsize">Pixel Distance</label>
                <div class="slider-value">
                    <span>Far</span>
                    <span id="pixelsizeValue">2</span>
                    <span>Close</span>
                </div>
                <input type="range" id="flag_pixelsize" min="2" max="20" value="2">
            </div>

            <div class="slider-container">
                <label for="flag_offset_x">Horizontal Offset</label>
                <div class="slider-value">
                    <span>0</span>
                    <span id="offsetXValue">0</span>
                    <span>100</span>
                </div>
                <input type="range" id="flag_offset_x" min="0" max="100" value="0">
            </div>

            <div class="slider-container">
                <label for="flag_offset_y">Vertical Offset</label>
                <div class="slider-value">
                    <span>0</span>
                    <span id="offsetYValue">0</span>
                    <span>100</span>
                </div>
                <input type="range" id="flag_offset_y" min="0" max="100" value="0">
            </div>

            <div class="btn-group">
                <button id="startFlagDrawing" class="btn btn-primary">
                    <i class='bx bx-play-circle'></i> Start
                </button>
                <button id="stopFlagDrawing" class="btn btn-danger">
                    <i class='bx bx-stop-circle'></i> Stop
                </button>
            </div>

            <div class="btn-group">
                <button id="canvasClearBtn" class="btn btn-outline">
                    <i class='bx bxs-eraser'></i> Clear Canvas
                </button>
            </div>
        </div>
    `;
    document.body.appendChild(container);

    // Make the container draggable
    let isDragging = false;
    let offsetX, offsetY;

    const header = container.querySelector('.flag-tool-header');
    header.addEventListener('mousedown', (e) => {
        isDragging = true;
        offsetX = e.clientX - container.getBoundingClientRect().left;
        offsetY = e.clientY - container.getBoundingClientRect().top;
        container.classList.add('dragging');
        e.preventDefault();
    });

    document.addEventListener('mousemove', (e) => {
        if (!isDragging) return;
        container.style.left = `${e.clientX - offsetX}px`;
        container.style.top = `${e.clientY - offsetY}px`;
    });

    document.addEventListener('mouseup', () => {
        isDragging = false;
        container.classList.remove('dragging');
    });

    // Close button
    container.querySelector('.flag-tool-close').addEventListener('click', () => {
        container.style.display = 'none';
    });

    // Update slider values
    const updateSliderValue = (sliderId, valueId) => {
        const slider = document.getElementById(sliderId);
        const value = document.getElementById(valueId);
        slider.addEventListener('input', () => {
            value.textContent = slider.value;
        });
    };

    updateSliderValue('flag_imagesize', 'imagesizeValue');
    updateSliderValue('flag_brushsize', 'brushsizeValue');
    updateSliderValue('flag_pixelsize', 'pixelsizeValue');
    updateSliderValue('flag_offset_x', 'offsetXValue');
    updateSliderValue('flag_offset_y', 'offsetYValue');

    // Room & Socket Control
    window.myRoom = {};
    window.sockets = [];

    const originalSend = WebSocket.prototype.send;
    WebSocket.prototype.send = function (...args) {
        if (window.sockets.indexOf(this) === -1) {
            window.sockets.push(this);
            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));
                        if (payload[0] == 'bc_uc_freedrawsession_changedroom') {
                            window.myRoom.players = payload[3];
                        }
                        if (payload[0] == 'mc_roomplayerschange') {
                            window.myRoom.players = payload[3];
                        }
                    } else if (message.startsWith('41')) {
                        console.debug('WebSocket disconnected');
                        updateConnectionStatus('disconnected');
                    } else if (message.startsWith('430')) {
                        let configs = JSON.parse(message.slice(3))[0];
                        window.myRoom.players = configs.players;
                        window.myRoom.id = configs.roomid;
                        console.debug('Room joined, ID:', window.myRoom.id);
                        updateConnectionStatus('connected');
                    }
                });
            }
        }
        return originalSend.call(this, ...args);
    };

    function updateConnectionStatus(status) {
        socketStatus = status;
        const statusIndicator = document.getElementById('connectionStatus');
        const statusText = document.getElementById('statusText');

        if (status === 'connected') {
            statusIndicator.className = 'status-indicator status-connected';
            statusText.textContent = 'Connected';
        } else {
            statusIndicator.className = 'status-indicator status-disconnected';
            statusText.textContent = 'Disconnected';
        }
    }

    // Button event listeners
    document.getElementById('botJoinBtn').addEventListener('click', () => {
        if (window['__FLAG_BOT'] && window['__FLAG_BOT'].room) {
            window['__FLAG_BOT'].room.join(document.getElementById('invurl')?.value || '');
        }
    });

    document.getElementById('botLeaveBtn').addEventListener('click', () => {
        if (window['__FLAG_BOT'] && window['__FLAG_BOT'].conn && window['__FLAG_BOT'].conn.socket) {
            window['__FLAG_BOT'].conn.socket.close();
            updateConnectionStatus('disconnected');
        }
    });

    document.getElementById('canvasClearBtn').addEventListener('click', () => {
        if (window['__FLAG_BOT'] && window['__FLAG_BOT'].action) {
            window['__FLAG_BOT'].action.DrawLine(50, 50, 50, 50, 2000);
        }
    });

    document.getElementById('stopFlagDrawing').addEventListener('click', () => {
        executionLine = [];
        isDrawing = false;
        console.debug('Drawing stopped');
    });

    document.getElementById('startFlagDrawing').addEventListener('click', () => {
        const selectedFlag = document.getElementById('flagSelect').value;
        if (!selectedFlag || !flags[selectedFlag]) {
            console.debug('No flag selected');
            return;
        }

        originalCanvas = document.getElementById('canvas');
        if (!originalCanvas) {
            console.debug('Canvas not found');
            return;
        }

        isDrawing = true;
        loadImage(flags[selectedFlag]);
    });

    // Image Processing
    function loadImage(url) {
        let img = new Image();
        img.crossOrigin = 'anonymous';
        img.src = url;
        img.addEventListener('load', () => {
            cw = originalCanvas?.width || 800;
            ch = originalCanvas?.height || 600;
            if (cw <= 0 || ch <= 0) {
                console.debug('Invalid canvas dimensions:', cw, ch);
                return;
            }
            previewCanvas.width = cw;
            previewCanvas.height = ch;

            let ctx = previewCanvas.getContext('2d');
            let scale = Math.max(cw / img.width, ch / img.height);
            let scaledWidth = img.width * scale;
            let scaledHeight = img.height * scale;
            let dx = (cw - scaledWidth) / 2;
            let dy = (ch - scaledHeight) / 2;

            ctx.drawImage(img, dx, dy, scaledWidth, scaledHeight);
            let imgData = ctx.getImageData(0, 0, cw, ch);
            data = imgData.data;
            ctx.clearRect(0, 0, cw, ch);
            console.debug('Flag loaded, dimensions:', cw, ch);

            let size = parseFloat(document.getElementById('flag_imagesize').value);
            let modifier = parseFloat(document.getElementById('flag_pixelsize').value);
            let thickness = parseFloat(document.getElementById('flag_brushsize').value);
            let offset = {
                x: parseFloat(document.getElementById('flag_offset_x').value),
                y: parseFloat(document.getElementById('flag_offset_y').value)
            };
            drawImage(size, modifier, thickness, offset);
            if (window['__FLAG_BOT'] && window['__FLAG_BOT'].conn && window['__FLAG_BOT'].conn.socket &&
                window['__FLAG_BOT'].conn.socket.readyState === WebSocket.OPEN) {
                execute(window['__FLAG_BOT'].conn.socket);
            } else {
                console.debug('WebSocket not open, state:', window['__FLAG_BOT']?.conn?.socket?.readyState);
            }
        });
        img.addEventListener('error', () => {
            console.debug('Failed to load flag image:', url);
        });
    }

    function drawImage(size, modifier = 1, thickness = 20, offset = { x: 0, y: 0 }) {
        executionLine = [];
        if (!data) {
            console.debug('No image data available');
            return;
        }
        const step = size * modifier * 2; // Reduced step for reliability

        for (let y = 0; y < ch; y += step) {
            let startX = 0;
            let currentColor = null;

            for (let x = 0; x < cw; x += step / 2) {
                let index = (Math.floor(y) * cw + Math.floor(x)) * 4;
                let a = data[index + 3] || 0;

                if (a > 20) {
                    let r = data[index + 0] || 0,
                        g = data[index + 1] || 0,
                        b = data[index + 2] || 0;
                    let color = `rgb(${r},${g},${b})`;

                    if (color !== currentColor || x >= cw - step / 2) {
                        if (currentColor && x - startX >= step / 2) {
                            executionLine.push({
                                pos1: recalc([startX, y], offset),
                                pos2: recalc([x, y], offset),
                                color: currentColor,
                                thickness: thickness
                            });
                        }
                        currentColor = color;
                        startX = x;
                    }
                } else {
                    if (currentColor && x - startX >= step / 2) {
                        executionLine.push({
                            pos1: recalc([startX, y], offset),
                            pos2: recalc([x, y], offset),
                            color: currentColor,
                            thickness: thickness
                        });
                    }
                    currentColor = null;
                }
            }

            if (currentColor && cw - startX >= step / 2) {
                executionLine.push({
                    pos1: recalc([startX, y], offset),
                    pos2: recalc([cw, y], offset),
                    color: currentColor,
                    thickness: thickness
                });
            }
        }
        console.debug('Flag processing complete, lines:', executionLine.length);
    }

    async function execute(socket) {
        if (executionLine.length === 0) {
            console.debug('No drawing commands generated');
            return;
        }
        for (let i = 0; i < executionLine.length; i++) {
            let line = executionLine[i];
            if (socket.readyState === WebSocket.OPEN) {
                drawCmd(socket, line.pos1, line.pos2, line.color, line.thickness);
            } else {
                console.debug('WebSocket closed during drawing, state:', socket.readyState);
                break;
            }
            await delay(1);
        }
        console.debug('Drawing complete');
    }

    function drawCmd(socket, start, end, color, thickness) {
        try {
            socket.send(`42["drawcmd",0,[${start[0]},${start[1]},${end[0]},${end[1]},false,${0 - thickness},"${color}",0,0,{}]]`);
        } catch (e) {
            console.debug('Error sending draw command:', e.message);
        }
    }

    function delay(ms) {
        return new Promise(resolve => setTimeout(resolve, ms));
    }

    function recalc(value, offset) {
        return [
            Math.min(1, Math.max(0, (value[0] / cw + offset.x / 100))).toFixed(4),
            Math.min(1, Math.max(0, (value[1] / ch + offset.y / 100))).toFixed(4)
        ];
    }

    // Player and Connection 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;
    };
    Connection.prototype.onopen = function () {
        this.Heartbeat(25000);
        console.debug('WebSocket opened');
        updateConnectionStatus('connected');
    };
    Connection.prototype.onclose = function () {
        console.debug('WebSocket closed');
        updateConnectionStatus('disconnected');
    };
    Connection.prototype.onerror = function (error) {
        console.debug('WebSocket error:', error);
        updateConnectionStatus('disconnected');
    };
    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')) {
            this.player.room.join(this.player.room.id);
        } else if (message.startsWith('430')) {
            let configs = JSON.parse(message.slice(3))[0];
            this.player.room.players = configs.players;
            this.player.room.id = configs.roomid;
        }
    };
    Connection.prototype.onBroadcast = function (payload) {
        try {
            payload = JSON.parse(payload);
            if (payload[0] == 'bc_uc_freedrawsession_changedroom') {
                this.player.room.players = payload[3];
                this.player.room.id = payload[4];
            }
            if (payload[0] == 'mc_roomplayerschange') {
                this.player.room.players = payload[3];
            }
        } catch (e) {
            console.debug('Error parsing broadcast:', e.message);
        }
    };
    Connection.prototype.onRequest = function () {};
    Connection.prototype.open = function (url) {
        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);
    };
    Connection.prototype.close = function (code, reason) {
        try {
            this.socket.close(code, reason);
        } catch (e) {
            console.debug('Error closing WebSocket:', e.message);
        }
    };
    Connection.prototype.Heartbeat = function (interval) {
        setTimeout(() => {
            if (this.socket.readyState == this.socket.OPEN) {
                try {
                    this.socket.send('2');
                    this.Heartbeat(interval);
                } catch (e) {
                    console.debug('Heartbeat error:', e.message);
                }
            }
        }, interval);
    };
    Connection.prototype.serverconnect = function (server, room) {
        if (!this.socket || this.socket.readyState != this.socket.OPEN) {
            this.open(server);
        } else {
            try {
                this.socket.send('41');
                this.socket.send('40');
            } catch (e) {
                console.debug('Error sending server connect:', e.message);
            }
        }
        this.onRequest = () => {
            try {
                this.socket.send(room);
            } catch (e) {
                console.debug('Error sending room connect:', e.message);
            }
        };
    };

    const Room = function (conn) {
        this.conn = conn;
        this.id = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx';
        this.players = [];
    };
    Room.prototype.join = function (inviteLink) {
        let gamemode = 2;
        let server = '';
        if (!inviteLink) {
            this.id = null;
            server = 'sv3.';
        } else {
            this.id = inviteLink.startsWith('http') ? inviteLink.split('/').pop() : inviteLink;
            if (inviteLink.endsWith('.3')) {
                server = 'sv3.';
                gamemode = 2;
            } else if (inviteLink.endsWith('.2')) {
                server = 'sv2.';
                gamemode = 2;
            } else {
                server = '';
                gamemode = 1;
            }
        }

        let serverUrl = `wss://${server}drawaria.online/socket.io/?sid1=undefined&hostname=drawaria.online&EIO=3&transport=websocket`;
        let player = this.conn.player;
        let playerName = document.getElementById('botNameInput')?.value || 'FlagBot';
        let connectString = `420["startplay","${playerName}",${gamemode},"en",${nullify(this.id)},null,[null,"https://drawaria.online/",1000,1000,[${nullify(player.sid1)},${nullify(player.uid)},${nullify(player.wt)}],null]]`;

        this.conn.serverconnect(serverUrl, connectString);
    };
    Room.prototype.next = function () {
        if (this.conn.socket.readyState != this.conn.socket.OPEN) {
            this.join(null);
        } else {
            try {
                this.conn.socket.send('42["pgswtichroom"]');
            } catch (e) {
                console.debug('Error switching room:', e.message);
            }
        }
    };

    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;
        try {
            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}]]`);
        } catch (e) {
            console.debug('Error drawing line:', e.message);
        }
    };

    const nullify = (value = null) => value == null ? null : `"${value}"`;

    // Initialize
    window['__FLAG_BOT'] = new Player('FlagBot');
    window['__FLAG_ENGINE'] = { loadImage, drawImage };
})();