Cubic Engine

Enhance your Experience

Από την 01/09/2023. Δείτε την τελευταία έκδοση.

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