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