Cubic Engine

Enhance your Experience

As of 2023-09-01. See the latest version.

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name         Cubic Engine
// @version      1.3
// @description  Enhance your Experience
// @namespace    drawaria.modded.fullspec
// @homepage     https://drawaria.online/profile/?uid=63196790-c7da-11ec-8266-c399f90709b7
// @author       ≺ᴄᴜʙᴇ³≻
// @match        https://drawaria.online/
// @match        https://drawaria.online/room/*
// @icon         https://drawaria.online/avatar/cache/e53693c0-18b1-11ec-b633-b7649fa52d3f.jpg
// @grant        none
// @license      GNU GPLv3
// @run-at       document-end
// ==/UserScript==

(function () {
  'use strict';

  const EL = (sel) => document.querySelector(sel);
  const ELL = (sel) => document.querySelectorAll(sel);

  const settings = (function enableSettingsContext(prefix) {
    const namespace = prefix;
    const settings = {
      config: {},
      save: function () {
        localStorage.setItem(namespace, JSON.stringify(this.config));
      },
      load: function () {
        this.config = JSON.parse(localStorage.getItem(namespace)) || {};
      },
    };

    window.addEventListener('load', () => {
      settings.load();
    });
    window.addEventListener(
      'beforeunload',
      () => {
        settings.save();
      },
      false
    );
    return settings;
  })('Engine');

  let drawing_active = false;
  let previewCanvas = document.createElement('canvas');
  let originalCanvas = document.getElementById('canvas');
  var data;

  let cw = previewCanvas.width;
  let ch = previewCanvas.height;

  let executionLine = [];

  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) => {
          // console.debug(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')) {
            // this.send(40)
          } else if (message.startsWith('430')) {
            let configs = JSON.parse(message.slice(3))[0];
            window.myRoom.players = configs.players;
            window.myRoom.id = configs.roomid;
          }
        });
      }
    }
    return originalSend.call(this, ...args);
  };

  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);
  }

  function CreateStylesheet() {
    let container = document.createElement('style');
    container.innerHTML =
      'input[type="number"] { text-align: center; -webkit-appearance: none; -moz-appearance: textfield; } ' +
      '.hidden { display: none; } ' +
      '.cheat-row { display:flex; width:100%; } ' +
      '.cheat-row > * { width:100%; } ' +
      '.cheat-border { width: 100%; text-align: center; line-height: inherit; margin: 1px; border: 1px solid coral; } ';
    document.head.appendChild(container);
  }

  function Engine() {
    let CheatContainer = document.createElement('div');
    CheatContainer.id = 'Engine-Cheatcontainer';
    CheatContainer.classList.toggle('hidden');
    document.getElementById('chatbox_messages').after(CheatContainer);

    function CreateToggleButton(Cheatcontainer) {
      let target = document.getElementById('chatbox_textinput');
      let btncontainer = document.createElement('div');
      btncontainer.id = 'togglecheats';
      btncontainer.className = 'input-group-append';

      let togglebtn;
      togglebtn = document.createElement('button');
      togglebtn.className = 'btn btn-outline-secondary';
      togglebtn.innerHTML = '<i class="bx bx-code-alt"></i>';
      togglebtn.addEventListener('click', (e) => {
        e.preventDefault();
        togglebtn.classList.toggle('active');
        togglebtn.classList.contains('active')
          ? Cheatcontainer.classList.remove('hidden')
          : Cheatcontainer.classList.add('hidden');
      });
      btncontainer.appendChild(togglebtn);
      target.after(btncontainer);
    }
    CreateToggleButton(CheatContainer);

    function ImageLoader(CheatContainer) {
      let container = document.createElement('div');

      let row = document.createElement('div');
      row.className = 'cheat-row';

      let IPutImage = document.createElement('input');
      IPutImage.type = 'file';
      IPutImage.id = 'IPutImage';
      IPutImage.className = 'cheat-border';

      function readImage() {
        if (!this.files || !this.files[0]) return;

        const FR = new FileReader();
        FR.addEventListener('load', (evt) => {
          // get base64 string of image and apply to image src
          let base64 = evt.target.result;
          // load the image
          loadImage(base64);
        });
        // read Image
        FR.readAsDataURL(this.files[0]);
      }

      IPutImage.addEventListener('change', readImage);
      row.appendChild(IPutImage);
      container.appendChild(row);
      CheatContainer.appendChild(container);
    }
    ImageLoader(CheatContainer);

    function BotControl(CheatContainer) {
      let container = document.createElement('div');
      container.innerHTML =
        "<div class='cheat-row'><i class='bx bx-user-plus cheat-border' id='botJoin'><span>Join</span></i><i class='bx bx-user-minus cheat-border' id='botLeave'><span>Leave</span></i><i class='bx bxs-eraser cheat-border' id='canvasClear'><span>Clear</span></i></div>";

      CheatContainer.appendChild(container);
      document.getElementById('botJoin').addEventListener('mousedown', (e) => {
        window['___BOT'].room.join(EL('#invurl').value);
      });
      document.getElementById('botLeave').addEventListener('mousedown', (e) => {
        window['___BOT'].conn.socket.close();
      });
      document.getElementById('canvasClear').addEventListener('mousedown', (e) => {
        window['___BOT'].action.DrawLine(50, 50, 50, 50, 2000);
      });
    }
    BotControl(CheatContainer);

    function DrawingControls(CheatContainer) {
      let container = document.createElement('div');
      container.innerHTML = [
        '<div class="cheat-row"><input type="number" id="engine_imagesize" min="1" max="10" value="4" title="Image Size. 1 = big. 10 = small"><input type="number" id="engine_brushsize" min="2" max="20" value="4" title="Your Brush Size"><input type="number" id="engine_pixelsize" min="2" max="20" value="2" title="Distance between Pixels\nBest use half of brushsize"><input type="number" id="engine_offset_x" min="0" max="100" value="0" title="Distance left"><input type="number" id="engine_offset_y" min="0" max="100" value="0" title="Distance top"></div>',
        '<div class="cheat-row"><i class="bx bx-play-circle cheat-border" id="botStartDrawing"><span>Start</span></i><i class="bx bx-stop-circle cheat-border" id="botStopDrawing"><span>Stop</span></i></div>',
      ].join('');
      CheatContainer.appendChild(container);
      document.getElementById('botStopDrawing').addEventListener('mousedown', (e) => {
        drawing_active = false;
      });
      document.getElementById('botStartDrawing').addEventListener('mousedown', (e) => {
        let size = document.getElementById('engine_imagesize').value;
        let modifier = document.getElementById('engine_pixelsize').value;
        let thickness = document.getElementById('engine_brushsize').value;
        let offset = {
          x: document.getElementById('engine_offset_x').value,
          y: document.getElementById('engine_offset_y').value,
        };
        drawImage(size, modifier, thickness, offset);
        execute(window['___BOT'].conn.socket);
      });
    }
    DrawingControls(CheatContainer);
  }

  function loadImage(url) {
    // load the image
    var img = new Image();
    img.addEventListener('load', () => {
      previewCanvas.width = originalCanvas.width;
      previewCanvas.height = originalCanvas.height;

      cw = previewCanvas.width;
      ch = previewCanvas.height;

      var ctx = previewCanvas.getContext('2d');

      // center and resize image

      let modifier = 1;
      if (img.width > previewCanvas.width) {
        modifier = previewCanvas.width / img.width;
      } else {
        modifier = previewCanvas.height / img.height;
      }

      // draw the image
      // (this time to grab the image's pixel data
      ctx.drawImage(img, 0, 0, img.width * modifier, img.height * modifier);

      // grab the image's pixel data
      var imgData = ctx.getImageData(0, 0, previewCanvas.width, previewCanvas.height);
      data = imgData.data;

      // clear the canvas to draw the glow
      ctx.clearRect(0, 0, previewCanvas.width, previewCanvas.height);
      console.debug('ready');
    });
    img.crossOrigin = 'anonymous';
    img.src = url;
  }

  function drawImage(size, modifier = 1, thickness = 5, offset = { x: 0, y: 0 }, ignorcolors = []) {
    executionLine = [];
    for (let y = 0; y < ch; y += size * modifier) {
      let start = [0, y];

      for (let x = 0; x < ch; x += size * modifier) {
        let end = [x, y];
        let index = (y * cw + x) * 4;

        let a = data[index + 3];

        if (a > 20) {
          end = [x, y];
          // Is not Transparent
          let r = data[index + 0],
            g = data[index + 1],
            b = data[index + 2];

          let color = `rgb(${r},${g},${b})`;

          if (!ignorcolors.includes(color)) {
            if (x < cw - 1) {
              let n_r = data[index + size * modifier * 4 + 4],
                n_g = data[index + size * modifier * 4 + 5],
                n_b = data[index + size * modifier * 4 + 6];

              let samecolor = true;
              // check if the next pixel is same color as the last
              if ((r != n_r && g != n_g && b != n_b) || data[index + 7] < 20) {
                samecolor = false;
              }
              if (!samecolor) {
                executionLine.push({
                  pos1: recalc(start, size, offset),
                  pos2: recalc(end, size, offset),
                  color: color,
                  thickness: thickness,
                });
                start = [x, y];
              }
            } else {
              executionLine.push({
                pos1: recalc(start, size, offset),
                pos2: recalc(end, size, offset),
                color: color,
                thickness: thickness,
              });
            }
          }
        } else {
          // Is Transparent
          start = [x, y];
        }
      }
    }
    console.debug('done Loading');
  }

  async function execute(socket) {
    drawing_active = true;
    for (let i = 0; i < executionLine.length; i++) {
      if (!drawing_active) return;
      let currentLine = executionLine[i];
      let p1 = currentLine.pos1,
        p2 = currentLine.pos2,
        color = currentLine.color,
        thickness = currentLine.thickness;
      drawcmd(socket, p1, p2, color, thickness);
      await delay(10);
    }
    function drawcmd(s, start, end, color, thickness) {
      s.send(`42["drawcmd",0,[${start[0]},${start[1]},${end[0]},${end[1]},false,${0 - thickness},"${color}",0,0,{}]]`);
    }
  }

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

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

  function nullify(value = null) {
    return value == null ? null : String().concat('"', value, '"');
  }

  class Player {
    constructor(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);
    }
    annonymize(name) {
      this.name = name;
      this.uid = undefined;
      this.wt = undefined;
    }
  }

  class Connection {
    constructor(player) {
      this.player = player;
    }
    onopen(event) {
      // console.debug(event)
      this.Heartbeat(25000);
    }
    onclose(event) {
      // console.debug(event)
    }
    onerror(event) {
      // console.debug(event)
    }
    onmessage(event) {
      // console.debug(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;
      }
    }
    onbroadcast(payload) {
      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];
      }
    }
    onrequest() {}
    open(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);
    }
    close(code, reason) {
      this.socket.close(code, reason);
    }
    Heartbeat(interval) {
      let timeout = setTimeout(() => {
        if (this.socket.readyState == this.socket.OPEN) {
          this.socket.send(2);
          this.Heartbeat(interval);
        }
      }, interval);
    }
    serverconnect(server, room) {
      if (this.socket == undefined || this.socket.readyState != this.socket.OPEN) {
        this.open(server);
      } else {
        this.socket.send(41);
        this.socket.send(40);
      }
      this.onrequest = () => {
        this.socket.send(room);
      };
    }
  }

  class Room {
    constructor(conn) {
      this.conn = conn;
      this.id = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx';
      this.players = [];
    }
    join(invitelink) {
      let gamemode = 2;
      let server = '';
      if (invitelink == null) {
        this.id = null;
        server = 'sv3.';
      } else {
        // extract roomid
        this.id = invitelink.startsWith('http') ? invitelink.split('/').pop() : invitelink;
        // craft serverurl
        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;
      // craft response for joining of desired room
      let connectstring = `420["startplay","${player.name}",${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);
    }
    next() {
      if (this.conn.socket.readyState != this.conn.socket.OPEN) {
        this.join(null);
      } else {
        this.conn.socket.send('42["pgswtichroom"]');
      }
    }
  }

  class Actions {
    constructor(conn) {
      this.conn = conn;
    }
    SendMessage(message) {
      this.conn.socket.send(`42["chatmsg","${message}"]`);
    }
    SendGesture(gestureid) {
      this.conn.socket.send(`42["sendgesture",${gestureid}]`);
    }
    SendToken(playerid, tokenid) {
      this.conn.socket.send(`42["clientcmd",2,[${playerid},${tokenid}]]`);
    }
    SendToggleAllow(playerid) {
      this.conn.socket.send(`42["pgdrawvote",${playerid},0]`);
    }
    SendVote() {
      this.conn.socket.send('42["sendvote"]');
    }
    SetStatus(statusid, value) {
      this.conn.socket.send(`42["clientcmd",3,[${statusid},${value}]]`);
    }
    AvatarSpawn() {
      this.conn.socket.send('42["clientcmd",101]');
    }
    AvatarMove(x, y) {
      this.conn.socket.send(`42["clientcmd",103,[${String(x * 100) + String(y * 100).padStart(4, '0')},false]]`);
    }
    AvatarChange() {
      this.conn.socket.send('42["clientcmd",115]');
    }
    DrawLine(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;
      /* algo = 101/201 */
      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}]]`
      );
    }
  }

  if (!document.getElementById('Engine-Cheatcontainer')) {
    window['___BOT'] = new Player('Your Best and Only Friend');
    window['___ENGINE'] = { loadImage, drawImage };
    addBoxIcons();
    CreateStylesheet();
    Engine();
  }
})();