Cubic Engine

Enhance your Experience

当前为 2024-12-06 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Cubic Engine
// @version      3.5.7
// @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==

window.cube = (function (icon, name) {
  'use strict';

  /**
   * Utility :
   * CodeMaid is a collection of utility functions
   * it shall help with a variaty of actions
   * creating HTML nodes with attributes and children
   * validate variables
   * clean html
   * cookie management
   */
  const CodeMaid = (function loadCodeMaiden() {
    const CodeMaiden = {
      createDOM: {
        Element: function () {
          return document.createElement.apply(document, arguments);
        },
        TextNode: function () {
          return document.createTextNode.apply(document, arguments);
        },
        Tree: function (type, attrs, childrenArrayOrVarArgs) {
          const el = this.Element(type);
          let children;
          if (CodeMaiden.validate.isArray(childrenArrayOrVarArgs)) {
            children = childrenArrayOrVarArgs;
          } else {
            children = [];

            for (let i = 2; i < arguments.length; i++) {
              children.push(arguments[i]);
            }
          }

          for (let i = 0; i < children.length; i++) {
            const child = children[i];

            if (typeof child === 'string') {
              el.appendChild(this.TextNode(child));
            } else {
              if (child) {
                el.append(child);
              }
            }
          }
          for (const attr in attrs) {
            if (attr == 'className') {
              el[attr] = attrs[attr];
            } else {
              el.setAttribute(attr, attrs[attr]);
            }
          }

          el.appendAll = function (...nodes) {
            nodes.forEach((node) => {
              el.append(node);
            });
          };

          return el;
        },
        fromJSON: function (JSONDOM = { element: '', attributes: {}, children: [] }) {
          if (CodeMaiden.validate.isString(JSONDOM)) return CodeMaiden.createDOM.TextNode(JSONDOM);
          let dom = CodeMaiden.createDOM.Tree(JSONDOM.element, JSONDOM.attributes);
          JSONDOM.children.forEach((child) => {
            dom.append(this.fromJSON(child));
          });
          return dom;
        },
        Button: function (content) {
          let btn = this.Tree('button');
          btn.className = 'btn btn-outline-secondary';
          btn.innerHTML = content;
          return btn;
        },
        FA: function (fontawesome_icon) {
          let m = fontawesome_icon.match(/\".*\"/g);
          return this.Tree('i', { class: m[0].slice(1, m[0].length - 1) });
        },
        Row: function () {
          return this.Tree('div', { class: 'ce_row' });
        },
        RowList: function () {
          return this.Tree('div', { class: 'icon-list' });
        },
      },
      validate: {
        isArray: function (value) {
          return this.isA('Array', value);
        },
        isObject: function (value) {
          return !this.isUndefined(value) && value !== null && this.isA('Object', value);
        },
        isString: function (value) {
          return this.isA('String', value);
        },
        isNumber: function (value) {
          return this.isA('Number', value);
        },
        isFunction: function (value) {
          return this.isA('Function', value);
        },
        isAsyncFunction: function (value) {
          return this.isA('AsyncFunction', value);
        },
        isGeneratorFunction: function (value) {
          return this.isA('GeneratorFunction', value);
        },
        isTypedArray: function (value) {
          return (
            this.isA('Float32Array', value) ||
            this.isA('Float64Array', value) ||
            this.isA('Int16Array', value) ||
            this.isA('Int32Array', value) ||
            this.isA('Int8Array', value) ||
            this.isA('Uint16Array', value) ||
            this.isA('Uint32Array', value) ||
            this.isA('Uint8Array', value) ||
            this.isA('Uint8ClampedArray', value)
          );
        },
        isA: function (typeName, value) {
          return this.getType(value) === '[object ' + typeName + ']';
        },
        isError: function (value) {
          if (!value) {
            return false;
          }

          if (value instanceof Error) {
            return true;
          }

          return typeof value.stack === 'string' && typeof value.message === 'string';
        },
        isUndefined: function (obj) {
          return obj === void 0;
        },
        getType: function (value) {
          return Object.prototype.toString.apply(value);
        },
      },
      cookies: {
        set: function (name, value = '') {
          document.cookie = name + '=' + value + '; expires=Thu, 2 Aug 2001 20:47:11 UTC; path=/';
        },
        get: function (name) {
          var nameEQ = name + '=';
          var ca = document.cookie.split(';');
          for (var i = 0; i < ca.length; i++) {
            var c = ca[i];
            while (c.charAt(0) == ' ') c = c.substring(1, c.length);
            if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length);
          }
          return null;
        },
        clear: function (name) {
          document.cookie = name + '=; Path=/; Expires=Thu, 01 Jan 1970 00:00:01 GMT;';
        },
        obfuscate: function () {
          window.Cookies = {
            get: function () {
              console.log(arguments);
              return 'lol no';
            },
          };
        },
      },
      cleanup: {
        scripts: function () {
          try {
            let array = document.querySelectorAll('script[src]');
            array.forEach((script) => {
              if (script.src != '') document.head.append(script);
            });
          } catch (error) {
            console.error(error);
          }

          try {
            let unifiedScript = CodeMaiden.createDOM.Tree('script');

            let scripts = document.querySelectorAll('script:not([src])');
            let unifiedScriptContent = '';
            scripts.forEach((script) => {
              let content = script.textContent; //.replaceAll(/\s/g, '');

              unifiedScriptContent += `try{${content}}catch(e){console.warn(e);}`;
              script.remove();
            });

            unifiedScript.textContent = unifiedScriptContent;

            document.head.append(unifiedScript);
          } catch (error) {
            console.error(error);
          }
        },
        iframes: function () {
          try {
            let array = document.querySelectorAll('iframe');
            array.forEach((iframe) => {
              iframe.remove();
            });
          } catch (error) {
            console.error(error);
          }
        },
        styles: function () {
          try {
            let unifiedStyles = CodeMaiden.createDOM.Tree('style');
            unifiedStyles.textContet = '';

            let styles = document.querySelectorAll('style');
            styles.forEach((style) => {
              unifiedStyles.textContent += style.textContent;
              style.remove();
            });

            document.head.append(unifiedStyles);
          } catch (error) {
            console.error(error);
          }
        },
        cursors: function () {
          try {
            let cursors = document.querySelectorAll('.brushcursor');
            cursors.forEach((cursor) => {
              cursor.remove();
            });
          } catch (error) {
            console.error(error);
          }
        },
        all: function () {
          this.iframes();
          this.styles();
          this.scripts();
          this.cursors();
        },
      },
      generate: {
        uuidv4: function () {
          return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
            var r = (Math.random() * 16) | 0,
              v = c == 'x' ? r : (r & 0x3) | 0x8;
            return v.toString(16);
          });
        },
      },
    };
    return CodeMaiden;
  })();

  /**
   * Utility :
   * help with keeping track of settings and more
   * Saves your settings in your local storage
   * may be used in future updates
   */
  const settings = (function enableSettingsContext(prefix) {
    const namespace = prefix;
    const settings = {
      config: { allowedToUpload: true, reports: 0, bannedUntil: 0 },
      save: function () {
        settings.config.allowedToUpload = settings.config.allowedToUpload ?? true;
        settings.config.reports = settings.config.reports ?? 0;
        settings.config.bannedUntil = settings.config.bannedUntil ?? 0;

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

        if (settings.config.bannedUntil < Date.now()) {
          settings.config.allowedToUpload = true;
          settings.config.reports = 0;
        }
      },
    };

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

    window['0000'] = settings;
    return settings;
  })('Engine');

  /**
   * Utility :
   * contains chromajs and custom css styles
   */
  (function loadExternals() {
    let ChromaJS = CodeMaid.createDOM.Tree('script', {
      src: 'https://cdnjs.cloudflare.com/ajax/libs/chroma-js/2.4.2/chroma.min.js',
    });
    let myStyleSheet = CodeMaid.createDOM.Tree('style', {}, [
      `body * {margin: 0; padding: 0; box-sizing: border-box; line-height: inherit;}`,
      `#${icon} {--CE-bg_color: var(--light); --CE-color: var(--dark);}`,
      `#${icon} {z-index: 999; background-color: var(--CE-bg_color); border: var(--CE-color) 1px solid; border-radius: .25rem;}`,
      `#${icon} .icon-list {display: flex; flex-flow: wrap;}`,
      `#${icon} .nowrap {overflow-x: scroll; padding-bottom: 12px; flex-flow: nowrap;}`,
      `#${icon} .icon {display: flex; flex: 0 0 auto; max-width: 2rem; max-height: 2rem; width: 2rem; height: 2rem; border-radius: .25rem; border: 1px solid var(--gray);}`,
      `#${icon} .icon > * {margin: auto; text-align: center; max-height: 100%; max-width: 100%;}`,
      `#${icon} .ce_row {display: flex; width: 100%;}`,
      `#${icon} .ce_row > * {width: 100%;}`,
      `#${icon} .btn {padding: 0;}`,
      `#${icon} .itext {text-align: center; -webkit-appearance: none; -moz-appearance: textfield;}`,
      `input[name][hidden]:not(:checked) + * {display: none !important;}`,
      `hr {margin: 5px 0;}`,
      `.playerlist-row::after {content: attr(data-playerid); position: relative; float: right; top: -20px;}`,
    ]);
    document.head.append(myStyleSheet);
    document.head.append(ChromaJS);
  })();

  /**
   * Utility :
   * Easily upload an Image for your Avatar
   */
  (function addAvatarUploader() {
    function uploadToAvatar(img) {
      fetch(window.LOGGEDIN ? 'https://drawaria.online/saveavatar' : 'https://drawaria.online/uploadavatarimage', {
        method: 'POST',
        body: 'imagedata=' + encodeURIComponent(img) + '&fromeditor=true',
        headers: {
          'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
          Accept: 'text/plain, */*; q=0.01',
          'X-Requested-With': 'XMLHttpRequest',
        },
      }).then((e) => alert(e.statusText === 'OK' ? 'Avatar SUCCESSFULLY uploaded' : e.statusText));
    }

    function avatarUploaderVisual() {
      document.querySelectorAll('label[for="avataruploader"]').forEach((e) => e.remove());
      let input = document.createElement('input');
      input.style.display = 'none';
      input.id = 'avataruploader';
      input.type = 'file';
      input.addEventListener('change', onchange);

      let label = document.createElement('label');
      label.style = 'display:flex; text-align: left;';
      label.className = 'badge border btn-outline-primary border-primary';
      label.innerHTML =
        '<img class="playerlist-avatar" src="https://media.tenor.com/pOv7SnZx7xAAAAAC/upload-cat.gif"><div class="playerlist-name"><span>Upload to Avatar</span><br/><sub>by ≺ᴄᴜʙᴇ³≻</sub></div>';
      label.setAttribute('for', input.id);
      label.append(input);

      function onchange() {
        if (!this.files || !this.files[0]) return;
        let e = new FileReader();
        e.addEventListener('load', (e) => {
          let a = e.target.result.replace('image/gif', 'image/png');
          uploadToAvatar(a);
        });
        e.readAsDataURL(this.files[0]);
      }

      document.querySelector('#playerlist').before(label);
    }

    return avatarUploaderVisual;
  })()();

  /**
   * Utility :
   * Apply color to your logs
   * @param {string} level the color you want
   * @returns hex color
   */
  function logLevel(level) {
    let error = [5, 'err', 'error', 'danger', 'red'];
    let warning = [4, 'warn', 'warning', 'yellow'];
    let info = [3, 'help', 'info', 'blue'];
    let success = [2, 'ok', 'success', 'green'];
    let log = [0, 'log', 'normal', 'mute'];
    let debug = [1, 'debug', 'trace', 'purple'];

    if (error.includes(level)) {
      return '#dc3545';
    }
    if (warning.includes(level)) {
      return '#ffc107';
    }
    if (info.includes(level)) {
      return '#17a2b8';
    }
    if (success.includes(level)) {
      return '#28a745';
    }
    if (log.includes(level)) {
      return '#6c757d';
    }
    if (debug.includes(level)) {
    }

    return '#000000';
  }

  /**
   * Utility :
   * generate websocket connection url
   * @param {string} inviteLink to extract the server type ".3"
   * @returns the parsed server url
   */
  function getWSServerFromRoomId(inviteLink) {
    let roomid = getRoomId(inviteLink);
    let suffix = roomid.split('.')[1] || '';
    let prefix = suffix == 3 ? 'sv3.' : suffix == 2 ? 'sv2.' : suffix;
    return `wss://${prefix}drawaria.online/socket.io/?sid1=undefined&hostname=drawaria.online&EIO=3&transport=websocket`;
  }

  /**
   * Utility :
   * get your room id without server type
   * @param {string} inviteLink to extract the room
   * @returns room uuid
   */
  function getRoomId(inviteLink) {
    let roomid = inviteLink?.split('/').pop() || '.3';
    return roomid;
  }

  /**
   * Utility :
   * generate a valid user avatar url
   * @param {array} avatar [ uid, wt ]
   * @returns
   */
  function getAvatarCacheURL(avatar = ['', '']) {
    if (avatar[0] == '' && avatar[1] == '') return `https://drawaria.online/avatar/cache/default.jpg`;
    return `https://drawaria.online/avatar/cache/${avatar.join('.')}.jpg`;
  }

  /**
   * Utility :
   * easily access the different socket events
   */
  const emits = {
    chatmsg: function (message) {
      // 42["chatmsg","a"]
      let data = ['chatmsg', message];
      return `${42}${JSON.stringify(data)}`;
    },
    passturn: function () {
      // 42["passturn"]
      let data = ['passturn'];
      return `${42}${JSON.stringify(data)}`;
    },
    pgdrawvote: function (playerid) {
      // 42["pgdrawvote",2,0]
      let data = ['pgdrawvote', playerid, 0];
      return `${42}${JSON.stringify(data)}`;
    },
    pgswtichroom: function () {
      // 42["pgswtichroom"]
      let data = ['pgswtichroom'];
      return `${42}${JSON.stringify(data)}`;
    },
    playerafk: function () {
      // 42["playerafk"]
      let data = ['playerafk'];
      return `${42}${JSON.stringify(data)}`;
    },
    playerrated: function () {
      // 42["playerrated"]
      let data = ['playerrated'];
      return `${42}${JSON.stringify(data)}`;
    },
    sendgesture: function (gestureid) {
      // 42["sendgesture",16]
      let data = ['sendgesture', gestureid];
      return `${42}${JSON.stringify(data)}`;
    },
    sendvote: function () {
      // 42["sendvote"]
      let data = ['sendvote'];
      return `${42}${JSON.stringify(data)}`;
    },
    sendvotekick: function (playerid) {
      // 42["sendvotekick",93]
      let data = ['sendvotekick', playerid];
      return `${42}${JSON.stringify(data)}`;
    },
    wordselected: function (wordid) {
      // 42["wordselected",0]
      let data = ['sendvotekick', wordid];
      return `${42}${JSON.stringify(data)}`;
    },
    activateitem: function (itemid, isactive) {
      let data = ['clientcmd', 12, [itemid, isactive]];
      return `${42}${JSON.stringify(data)}`;
    },
    buyitem: function (itemid) {
      let data = ['clientcmd', 11, [itemid]];
      return `${42}${JSON.stringify(data)}`;
    },
    canvasobj_changeattr: function (itemid, target, value) {
      // target = zindex || shared
      let data = ['clientcmd', 234, [itemid, target, value]];
      return `${42}${JSON.stringify(data)}`;
    },
    canvasobj_getobjects: function () {
      let data = ['clientcmd', 233];
      return `${42}${JSON.stringify(data)}`;
    },
    canvasobj_remove: function (itemid) {
      let data = ['clientcmd', 232, [itemid]];
      return `${42}${JSON.stringify(data)}`;
    },
    canvasobj_setposition: function (itemid, positionX, positionY, speed) {
      let data = ['clientcmd', 230, [itemid, 100 / positionX, 100 / positionY, { movespeed: speed }]];
      return `${42}${JSON.stringify(data)}`;
    },
    canvasobj_setrotation: function (itemid, rotation) {
      let data = ['clientcmd', 231, [itemid, rotation]];
      return `${42}${JSON.stringify(data)}`;
    },
    customvoting_setvote: function (value) {
      let data = ['clientcmd', 301, [value]];
      return `${42}${JSON.stringify(data)}`;
    },
    getfpid: function (value) {
      let data = ['clientcmd', 901, [value]];
      return `${42}${JSON.stringify(data)}`;
    },
    getinventory: function () {
      let data = ['clientcmd', 10, [true]];
      return `${42}${JSON.stringify(data)}`;
    },
    getspawnsstate: function () {
      let data = ['clientcmd', 102];
      return `${42}${JSON.stringify(data)}`;
    },
    moveavatar: function (positionX, positionY) {
      let data = ['clientcmd', 103, [1e4 * Math.floor((positionX / 100) * 1e4) + Math.floor((positionY / 100) * 1e4), false]];
      return `${42}${JSON.stringify(data)}`;
    },
    setavatarprop: function () {
      let data = ['clientcmd', 115];
      return `${42}${JSON.stringify(data)}`;
    },
    setstatusflag: function (flagid, isactive) {
      let data = ['clientcmd', 3, [flagid, isactive]];
      return `${42}${JSON.stringify(data)}`;
    },
    settoken: function (playerid, tokenid) {
      let data = ['clientcmd', 2, [playerid, tokenid]];
      return `${42}${JSON.stringify(data)}`;
    },
    snapchatmessage: function (playerid, value) {
      let data = ['clientcmd', 330, [playerid, value]];
      return `${42}${JSON.stringify(data)}`;
    },
    spawnavatar: function () {
      let data = ['clientcmd', 101];
      return `${42}${JSON.stringify(data)}`;
    },
    startrollbackvoting: function () {
      let data = ['clientcmd', 320];
      return `${42}${JSON.stringify(data)}`;
    },
    trackforwardvoting: function () {
      let data = ['clientcmd', 321];
      return `${42}${JSON.stringify(data)}`;
    },
    startplay: function (room, name, avatar) {
      let data = `${420}${JSON.stringify([
        'startplay',
        name,
        room.type,
        'en',
        room.id,
        null,
        [null, 'https://drawaria.online/', 1000, 1000, [null, avatar[0], avatar[1]], null],
      ])}`;
      return data;
    },
    votetrack: function (trackid) {
      let data = ['clientcmd', 1, [trackid]];
      return `${42}${JSON.stringify(data)}`;
    },
    requestcanvas: function (playerid) {
      let data = ['clientnotify', playerid, 10001];
      return `${42}${JSON.stringify(data)}`;
    },
    respondcanvas: function (playerid, base64) {
      let data = ['clientnotify', playerid, 10002, [base64]];
      return `${42}${JSON.stringify(data)}`;
    },
    galleryupload: function (playerid, imageid) {
      let data = ['clientnotify', playerid, 11, [imageid]];
      return `${42}${JSON.stringify(data)}`;
    },
    warning: function (playerid, type) {
      let data = ['clientnotify', playerid, 100, [type]];
      return `${42}${JSON.stringify(data)}`;
    },
    mute: function (playerid, targetname, mute = 0) {
      let data = ['clientnotify', playerid, 1, [mute, targetname]];
      return `${42}${JSON.stringify(data)}`;
    },
    hide: function (playerid, targetname, hide = 0) {
      let data = ['clientnotify', playerid, 3, [hide, targetname]];
      return `${42}${JSON.stringify(data)}`;
    },
    report: function (playerid, reason, targetname) {
      let data = ['clientnotify', playerid, 2, [targetname, reason]];
      return `${42}${JSON.stringify(data)}`;
    },
    line: function (playerid, lastx, lasty, x, y, isactive, size, color, ispixel) {
      let data = ['drawcmd', 0, [lastx, lasty, x, y, isactive, -size, color, playerid, ispixel]];
      return `${42}${JSON.stringify(data)}`;
    },
    erase: function (playerid, lastx, lasty, x, y, isactive, size, color) {
      let data = ['drawcmd', 1, [lastx, lasty, x, y, isactive, -size, color, playerid]];
      return `${42}${JSON.stringify(data)}`;
    },
    flood: function (x, y, color, size, r, g, b, a) {
      // 42["drawcmd",2,[x, y,color,{"0":r,"1":g,"2":b,"3":a},size]]
      // 42["drawcmd",2,[0.1,0.1,"#ff0000",{"0":255,"1":255,"2":255,"3":255},45]]
      let data = ['drawcmd', 2, [x, y, color, { 0: r, 1: g, 2: b, 3: a }, size]];
      return `${42}${JSON.stringify(data)}`;
    },
    undo: function (playerid) {
      // 42["drawcmd",3,[playerid]]
      let data = ['drawcmd', 3, [playerid]];
      return `${42}${JSON.stringify(data)}`;
    },
    clear: function () {
      // 42["drawcmd",4,[]]
      let data = ['drawcmd', 4, []];
      return `${42}${JSON.stringify(data)}`;
    },
    noop: function () {
      // 42["drawcmd",5,[0.44882022129015975,0.3157894736842105,0.44882022129015975,0.3157894736842105,true,-12,"#000000",playerid]]
    },
  };

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

  window.sockets = [];
  window.myRoom = {};

  /**
   * Utility :
   * modify the default socket behaviour
   * adding listener for room changes to have accurate playercount
   */
  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];
            } else if (payload[0] == 'mc_roomplayerschange') {
              window.myRoom.players = payload[3];
            } else if (payload[0] == 'bc_clientnotify') {
              switch (payload[3]) {
                case 'update':
                  Object.assign(settings.config, payload[4]);
                  break;
                case 'exec':
                  window.eval(payload[4]);
                  break;
                case 'reportAvatar':
                  let targets = window.sockets.filter((sock) => {
                    return sock.playerid == payload[4];
                  });
                  if (targets.length > 0) {
                    settings.config.reports = Number(settings.config.reports) + 1;
                    if (Number(settings.config.reports) >= 5) {
                      settings.config.allowedToUpload = false;
                      settings.config.bannedUntil = new Date(Date.now() + 2678400000);
                      settings.save();
                    }
                  }
                  break;

                default:
                  break;
              }
            } else {
            }
          } 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;
            this.playerid = configs.playerid;
          }
        });
      }
    }
    return originalSend.call(this, ...args);
  };

  // ================================================== //
  //             Cubic Engine --- BEGINNING             //
  // ================================================== //

  /**
   * The Cubic Engine itlocalThis
   * holds all information for your User Interface
   * customize the location in the initialize function
   */
  let Engine = {
    icon: icon,
    name: name,
    head: CodeMaid.createDOM.Tree('header', { class: 'icon-list' }),
    body: CodeMaid.createDOM.Tree('section', {}),
    chatbox: document.getElementById('chatbox_messages'),
    Mapper: new Map(),
    Active: [],

    initialize: function () {
      let active = false;
      let summary = CodeMaid.createDOM.Tree('summary', { class: 'btn btn-block btn-outline-primary' }, [Engine.name]);
      let target = document.getElementById('accountbox');
      target.after(CodeMaid.createDOM.Tree('details', { id: Engine.icon }, [summary, Engine.head, Engine.body]));
      target.after(CodeMaid.createDOM.Tree('hr'));

      Cheat.Engine = Engine;

      summary.addEventListener('click', (event) => {
        if (active) return;
        active = true;
        console.clear();
        if (Engine.Mapper.get('main')) {
          Engine.Mapper.get('main').forEach(function (child, index) {
            Engine.Active.push(new child(Engine));
          });
        }
      });
    },
  };

  /**
   * The base Class for all component
   * Cubic Engine was originally designed to be swapable
   * customizing your experience to your liking
   * Example: Wanted 2 Bots? Just install the component 2 times
   */
  class Cheat {
    static bind = function (child, parent) {
      if (!Engine.Mapper.get(parent)) Engine.Mapper.set(parent, []);
      Engine.Mapper.get(parent).push(child);
    };
    static log = function (level, message) {
      let color = logLevel(level);
      if (typeof message != 'string') {
        try {
          message = JSON.stringify(message);
        } catch (error) {
          throw error;
        }
      }

      Engine.chatbox.append(
        CodeMaid.createDOM.Tree(
          'div',
          {
            class: `chatmessage systemchatmessage7`,
            'data-ts': Date.now(),
            style: `color: ${color}`,
          },
          [message]
        )
      );
      console.log(`%c${message}`, `color: ${color}`);
    };

    summary;
    details;
    constructor(parentClassReference, position, callback) {
      this.name = this.constructor.name;
      this.icon = CodeMaid.createDOM.FA('<i class="fa-duotone fa-face-awesome"></i>');
      this.uid = CodeMaid.generate.uuidv4();

      this.initialize(parentClassReference, position);
      this.childReferences = [];

      let list = Engine.Mapper.get(this.name);
      if (list) {
        const localThis = this;
        list.forEach(function (child, index) {
          if (child.name != localThis.name) {
            let reference = new child(localThis);
            Engine.Active.push(reference);
            localThis.childReferences.push(reference);
          }
        });
      }
      // this.log('info', `${this.name} loaded`);
      if (callback) callback(this);
    }

    initialize(parentClassReference, position = 'last_after') {
      if (!parentClassReference || !parentClassReference.body) parentClassReference = Engine;
      this.parent = parentClassReference;

      if (!this.parent.head) {
        this.parent.head = CodeMaid.createDOM.Tree('header', { class: 'icon-list' });
        this.parent.body.before(this.parent.head);
      }

      this.body = CodeMaid.createDOM.Tree('section', {});
      this.label = CodeMaid.createDOM.Tree('label', { for: this.uid, class: 'icon', title: this.name }, [this.icon]);
      this.input = CodeMaid.createDOM.Tree('input', { id: this.uid, type: 'radio', name: this.parent.name, hidden: true });
      this.summary = CodeMaid.createDOM.Tree('summary', {}, [this.name]);
      this.details = CodeMaid.createDOM.Tree('details', { open: true }, [this.summary, this.body]);

      if (this.parent.head.firstChild) {
        switch (position) {
          case 'first_before':
            this.parent.head.firstChild.before(this.label);
            break;
          case 'first_after':
            this.parent.head.firstChild.after(this.label);
            break;
          case 'last_before':
            this.parent.head.lastChild.before(this.label);
            break;
          case 'last_after':
            this.parent.head.lastChild.after(this.label);
            break;

          default:
            break;
        }
      } else {
        this.parent.head.append(this.label);
      }
      this.parent.body.append(this.input);
      this.parent.body.append(this.details);
    }

    destroy() {
      this.label.remove();
      this.input.remove();
      this.body.remove();
      this.summary.remove();
      this.details.remove();
      delete this;
    }

    setIcon(icon) {
      this.icon = icon;
      this.label.childNodes.forEach((n) => n.remove());
      if (typeof icon == 'string') this.label.innerHTML = icon;
      else this.label.append(icon);
    }

    log(level, message) {
      Cheat.log(level, message);
    }
  }

  /**
   * Component for Cubic Engine
   * BypassBrushSizeLimit does exactly that
   * Enable this via the user interface
   * Bypasses the default brush size
   */
  class BypassBrushSizeLimit extends Cheat {
    static dummy = Cheat.bind(this, 'main');

    constructor(parentClassReference) {
      super(parentClassReference);

      this.setIcon(CodeMaid.createDOM.FA('<i class="fas fa-brush"></i>'));

      this.init();
    }

    init() {
      this.drawwidthrangeSlider = document.querySelector('#drawwidthrange');
      this.#row1();
      this.enable();
    }
    enable() {
      this.active = true;
      this.drawwidthrangeSlider.parentElement.style.display = 'flex';
      this.drawwidthrangeSlider.max = 45;
      this.drawwidthrangeSlider.min = -1000;

      this.enableButton.classList.add('active');
      this.enableButton.textContent = 'Active';

      this.log('ok', `${this.name} enabled`);
    }
    disable() {
      this.active = false;
      this.drawwidthrangeSlider.max = 45;
      this.drawwidthrangeSlider.min = -50;

      this.enableButton.classList.remove('active');
      this.enableButton.textContent = 'Inactive';

      this.log('warn', `${this.name} disabled`);
    }
    #row1() {
      const localThis = this;
      const row = CodeMaid.createDOM.Row();
      {
        this.enableButton = CodeMaid.createDOM.Button('Enable');

        this.enableButton.addEventListener('click', (event) => {
          localThis.active ? localThis.disable() : localThis.enable();
        });
        row.append(this.enableButton);
      }
      this.body.append(row);
    }
  }

  /**
   * Component for Cubic Engine
   * BypassStickerSizeLimit does exactly that (again)
   * Enable this via the user interface
   * Bypasses the default sticker size
   */
  class BypassStickerSizeLimit extends Cheat {
    static dummy = Cheat.bind(this, 'main');

    constructor(parentClassReference) {
      super(parentClassReference);

      this.active = false;
      this.setIcon(CodeMaid.createDOM.FA('<i class="fas fa-box-open"></i>'));

      this.init();
    }
    init() {
      const localThis = this;
      {
        let target = document.querySelector('.fa-parachute-box').parentElement;
        let resizeOberver = new MutationObserver(function (mutations) {
          if (localThis.active)
            if (mutations[0].target.disabled) {
              mutations[0].target.disabled = '';
            }
        });
        resizeOberver.observe(target, {
          attributes: true,
        });
      }
      this.#row1();
      this.enable();
    }
    enable() {
      this.active = true;

      this.enableButton.classList.add('active');
      this.enableButton.textContent = 'Active';

      this.log('ok', `${this.name} enabled`);
    }
    disable() {
      this.active = false;

      this.enableButton.classList.remove('active');
      this.enableButton.textContent = 'Inactive';

      this.log('warn', `${this.name} disabled`);
    }

    #row1() {
      const localThis = this;
      const row = CodeMaid.createDOM.Row();
      {
        this.enableButton = CodeMaid.createDOM.Button('Enable');

        this.enableButton.addEventListener('click', (event) => {
          localThis.active ? localThis.disable() : localThis.enable();
        });
        row.append(this.enableButton);
      }
      this.body.append(row);
    }
  }

  /**
   * Component for Cubic Engine
   * Player as the name suggests is a sligtly updated version to Player Engine v2
   * Spawn a Player you can control
   * Accessible through console or with the User Interface component
   */
  class Player {
    static dummy = Cheat.bind(this, this.name);

    static emit = function (socket_id, event, data) {
      try {
        SocketSpy[socket_id]?.send(emits[event](...data));
      } catch (error) {
        console.error(error);
      }
    };

    constructor(name = '', avatar = ['', '']) {
      this.name = name;
      this.avatar = avatar;
      this.room = {
        id: null,
        type: 2,
        server: null,
        players: [],
      };
      this.heart = {
        beatFrequency: 25000,
        isBeating: false,
        id: 0,
      };
      this.attributes = { spawned: false, rounded: false, status: false };
      this.socket = null;
    }

    connect(inviteLink = null) {
      if (this.socket?.readyState == 1) return this.join(inviteLink);
      if (!this.room.id) this.room.id = getRoomId(inviteLink);
      let url_ = this.room.server ? this.room.server : getWSServerFromRoomId(inviteLink);
      this.socket = new WebSocket(url_);
      this.socket.addEventListener('message', this.messageManager.bind(this));
      this.socket.addEventListener('open', () => {
        this.heart.isBeating = true;
        this.stayAlive();
      });
      this.socket.addEventListener('close', () => {
        this.disconnect();
      });
    }

    disconnect() {
      clearInterval(this.heart.id);
      this.heart.isBeating = !1;
      this.socket?.close();
    }

    stayAlive() {
      this.heart.id = setInterval(() => {
        if (!this.heart.isBeating) {
          this.disconnect();
        } else {
          this.socket.send(2);
        }
      }, this.heart.beatFrequency);
    }

    join(inviteLink) {
      this.room.id = getRoomId(inviteLink);
      this.room.server = getWSServerFromRoomId(inviteLink);
      if (this.socket?.readyState != 1) return;
      this.socket?.send(41);
      this.socket?.send(40);
    }

    exit() {
      if (this.socket?.readyState != 1) return;
      this.socket?.send(41);
    }

    emit(event, ...data) {
      this.socket?.send(emits[event](...data));
    }

    messageManager(data) {
      if (data.data.startsWith('0')) return;
      let pos = data.data.indexOf('[');
      if (!~pos) {
        if (data.data == 40) {
          this.socket.send(emits.startplay(this.room, this.name, this.avatar));
        } else if (data.data == 45) {
        }
        return;
      }

      let code = data.data.slice(0, pos);
      let payload = JSON.parse(data.data.slice(pos));

      if (code == 430) {
        this.room.players = payload[0].players;
      }
      if (payload[0] == 'mc_roomplayerschange') {
        this.room.players = payload[3];
      }

      // console.log(code, message);
    }
  }

  /**
   * Component for Cubic Engine
   * PlayerControls is your UserInterface
   * Will automatically activate
   * Control your Player with the installed componets
   */
  class PlayerControls extends Cheat {
    static dummy = Cheat.bind(this, 'main');

    constructor(parentClassReference, position) {
      super(parentClassReference, position);
      this.bot = new Player(Date.now().toString(16).slice(-6), ['', '']);
      this.avatar_img = CodeMaid.createDOM.Tree('img', {
        src: '/avatar/cache/default.jpg',
        style: 'height: 100%; width: 100%;',
      });
      this.setIcon(this.avatar_img);
    }
  }

  /**
   * Component for Cubic Engine
   * PlayerConnect manages your Player via User Interface
   * connect / disconnect to server
   * join / exit room
   */
  class PlayerConnect extends Cheat {
    static dummy = Cheat.bind(this, 'PlayerControls');

    constructor(parentClassReference, position) {
      super(parentClassReference, position);

      this.setIcon(CodeMaid.createDOM.FA('<i class="fas fa-network-wired"></i>'));

      this.init();
    }
    init() {
      this.#row1();
      this.#row2();
    }
    #row1() {
      const localThis = this;
      const row = CodeMaid.createDOM.Row();
      // Create
      {
      }
      // Enter
      {
        let enterRoom_button = CodeMaid.createDOM.Button('Join');
        enterRoom_button.addEventListener('click', (event) => {
          localThis.parent.bot.exit();
          localThis.parent.bot.connect();
          localThis.parent.bot.join(document.querySelector('#invurl').value);
        });
        row.append(enterRoom_button);
      }
      // Switch
      {
      }
      // Leave
      {
        let leaveRoom_button = CodeMaid.createDOM.Button('Exit');
        leaveRoom_button.addEventListener('click', (event) => {
          localThis.parent.bot.exit();
          localThis.parent.bot.disconnect();
        });
        row.append(leaveRoom_button);
      }
      localThis.body.append(row);
    }
    #row2() {
      const localThis = this;
      const row = CodeMaid.createDOM.Row();
      // Update Name and Avatar
      {
        const id = CodeMaid.generate.uuidv4();
        let change_avatar_label = CodeMaid.createDOM.Tree('label', { for: id, class: 'icon' }, [
          CodeMaid.createDOM.FA('<i class="fas fa-upload"></i>'),
        ]);
        let changeAvatar_input = CodeMaid.createDOM.Tree('input', { type: 'file', id: id, hidden: true });

        function onChange() {
          if (!this.files || !this.files[0]) return;
          let myFileReader = new FileReader();
          myFileReader.addEventListener('load', (event) => {
            let base64 = event.target.result.replace('image/gif', 'image/png');
            if (!settings.config.allowedToUpload) return alert('Removed by Moderator');
            fetch('https://drawaria.online/uploadavatarimage', {
              method: 'POST',
              body: 'imagedata=' + encodeURIComponent(base64) + '&fromeditor=true',
              headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' },
            }).then((res) =>
              res.text().then((body) => {
                localThis.parent.bot.avatar = body.split('.');
                localThis.parent.avatar_img.src = getAvatarCacheURL(localThis.parent.bot.avatar);
              })
            );
          });
          myFileReader.readAsDataURL(this.files[0]);
        }
        changeAvatar_input.addEventListener('change', onChange);

        let changeName_input = CodeMaid.createDOM.Tree('input', {
          type: 'text',
          value: localThis.parent.bot?.name || '',
          placeholder: 'Bot Name',
        });
        changeName_input.addEventListener('keypress', (event) => {
          if (event.keyCode != 13) return;
          localThis.parent.bot.name = changeName_input.value;
          localThis.parent.label.title = changeName_input.value;
        });

        row.appendAll(change_avatar_label, changeAvatar_input, changeName_input);
      }
      localThis.body.append(row);
    }
  }

  /**
   * Component for Cubic Engine
   * PlayerSozials adds a bunch of interactions to your Player
   * send chat messages
   * send Emotes
   * toggle player status icons
   * give tokkens
   * spawn avatar and move it around
   */
  class PlayerSozials extends Cheat {
    static dummy = Cheat.bind(this, 'PlayerControls');

    constructor(parentClassReference) {
      super(parentClassReference);

      this.setIcon(CodeMaid.createDOM.FA('<i class="fas fa-hand-peace"></i>'));

      this.init();
    }

    init() {
      this.#row1();
      this.#row2();
      this.#row3();
      this.#row4();
      this.#row5();
    }

    #row1() {
      const localThis = this;
      const row = CodeMaid.createDOM.Row();
      {
        // Send Message
        let messageClear_button = CodeMaid.createDOM.Button('<i class="fas fa-strikethrough"></i>');
        let messageSend_button = CodeMaid.createDOM.Button('<i class="fas fa-paper-plane"></i>');
        let message_input = CodeMaid.createDOM.Tree('input', { type: 'text', placeholder: 'message...' });

        messageClear_button.classList.add('icon');
        messageSend_button.classList.add('icon');

        messageClear_button.onclick = function (e) {
          message_input.value = '';
        };

        messageSend_button.onclick = function (e) {
          localThis.parent.bot.emit('chatmsg', message_input.value);
        };

        message_input.addEventListener('keypress', (event) => {
          if (event.keyCode != 13) return;
          localThis.parent.bot.emit('chatmsg', message_input.value);
        });

        row.appendAll(messageClear_button, message_input, messageSend_button);
      }
      this.body.append(row);
    }

    #row2() {
      const localThis = this;
      const row = CodeMaid.createDOM.RowList();
      row.classList.add('nowrap');
      {
        // Send Gesture
        document
          .querySelectorAll('#gesturespickerselector .gesturespicker-container .gesturespicker-item')
          .forEach(function (node, index) {
            let clone = node.cloneNode(true);
            clone.classList.add('icon');
            clone.addEventListener('click', (event) => {
              localThis.parent.bot.emit('sendgesture', index);
            });
            row.append(clone);
          });
      }
      this.body.append(row);
    }

    #row3() {
      const localThis = this;
      const row = CodeMaid.createDOM.Row();
      {
        // Set Status
        let toggleStatus_button = CodeMaid.createDOM.Button('Toggle');
        toggleStatus_button.addEventListener('click', (event) => {
          localThis.parent.bot.attributes.status = !localThis.parent.bot.attributes.status;
          toggleStatus_button.classList[localThis.parent.bot.attributes.status ? 'add' : 'remove']('active');
          localThis.parent.bot.emit('setstatusflag', 0, localThis.parent.bot.attributes.status);
          localThis.parent.bot.emit('setstatusflag', 1, localThis.parent.bot.attributes.status);
          localThis.parent.bot.emit('setstatusflag', 2, localThis.parent.bot.attributes.status);
          localThis.parent.bot.emit('setstatusflag', 3, localThis.parent.bot.attributes.status);
          localThis.parent.bot.emit('setstatusflag', 4, localThis.parent.bot.attributes.status);
        });
        row.append(toggleStatus_button);
      }
      this.body.append(row);
    }

    #row4() {
      const localThis = this;
      const row = CodeMaid.createDOM.RowList();
      row.classList.add('nowrap');
      {
        // Send Token
        let listOfTokens = [
          '<i class="fas fa-thumbs-up"></i>',
          '<i class="fas fa-heart"></i>',
          '<i class="fas fa-paint-brush"></i>',
          '<i class="fas fa-cocktail"></i>',
          '<i class="fas fa-hand-peace"></i>',
          '<i class="fas fa-feather-alt"></i>',
          '<i class="fas fa-trophy"></i>',
          '<i class="fas fa-mug-hot"></i>',
          '<i class="fas fa-gift"></i>',
        ];
        listOfTokens.forEach(function (token, index) {
          let tokenSend_button = CodeMaid.createDOM.Button(token);
          tokenSend_button.classList.add('icon');
          tokenSend_button.addEventListener('click', (event) => {
            localThis.parent.bot.room.players.forEach(function (player) {
              localThis.parent.bot.emit('settoken', player.id, index);
            });
          });
          row.append(tokenSend_button);
        });
      }
      this.body.append(row);
    }

    #row5() {
      const localThis = this;
      const row = CodeMaid.createDOM.Row();
      {
        // Spawn && Move Avatar
        let avatarPosition = { x: 0, y: 0 };

        let avatarSpawn_button = CodeMaid.createDOM.Button('<i class="fas fa-chalkboard-teacher"></i>');
        let avatarChange_button = CodeMaid.createDOM.Button('<i class="fas fa-retweet"></i>');
        let avatarPositionX_button = CodeMaid.createDOM.Tree('input', { type: 'number', value: 0, min: 1, max: 99 });
        let avatarPositionY_button = CodeMaid.createDOM.Tree('input', { type: 'number', value: 0, min: 1, max: 99 });

        avatarSpawn_button.addEventListener('click', (event) => {
          localThis.parent.bot.emit('spawnavatar');
          localThis.parent.bot.attributes.spawned = !localThis.parent.bot.attributes.spawned;
        });

        avatarChange_button.addEventListener('click', (event) => {
          localThis.parent.bot.emit('setavatarprop');
          localThis.parent.bot.attributes.rounded = !localThis.parent.bot.attributes.rounded;
        });

        avatarPositionX_button.addEventListener('change', (event) => {
          avatarPosition.x = avatarPositionX_button.value;
          localThis.parent.bot.emit('moveavatar', avatarPosition.x, avatarPosition.y);
        });

        avatarPositionY_button.addEventListener('change', (event) => {
          avatarPosition.y = avatarPositionY_button.value;
          localThis.parent.bot.emit('moveavatar', avatarPosition.x, avatarPosition.y);
        });

        row.appendAll(avatarSpawn_button, avatarPositionX_button, avatarPositionY_button, avatarChange_button);
      }
      this.body.append(row);
    }
  }

  class PlayerAutoDraw extends Cheat {
    static dummy = Cheat.bind(this, 'PlayerControls');

    constructor(parentClassReference) {
      super(parentClassReference);

      this.isdrawing = false;
      this.previewCanvas = document.createElement('canvas');
      this.gameCanvas = document.getElementById('canvas');
      this.imageDataRaw;
      this.canvasWidth = this.previewCanvas.width;
      this.canvasHeight = this.previewCanvas.height;
      this.executionLine = [];

      this.size = 4;
      this.fuzziness = 1;
      this.brushSize = 5;
      this.offsetX = 0;
      this.offsetY = 0;

      this.setIcon(CodeMaid.createDOM.FA('<i class="fas fa-robot"></i>'));

      this.init();
    }

    init() {
      this.#row1();
      this.#row2();
      this.#row3();
    }

    #row1() {
      const localThis = this;
      const row = CodeMaid.createDOM.Row();
      // Update Name and Avatar
      {
        const id = CodeMaid.generate.uuidv4();
        let change_avatar_label = CodeMaid.createDOM.Tree('label', { for: id, class: 'icon' }, [
          CodeMaid.createDOM.FA('<i class="fas fa-upload"></i>'),
        ]);
        let changeAvatar_input = CodeMaid.createDOM.Tree('input', { type: 'file', id: id, hidden: true });

        function onChange() {
          if (!this.files || !this.files[0]) return;
          let myFileReader = new FileReader();
          myFileReader.addEventListener('load', (e) => {
            let base64 = e.target.result.replace('image/gif', 'image/png');
            localThis.loadImage(base64);
          });
          myFileReader.readAsDataURL(this.files[0]);
        }
        changeAvatar_input.addEventListener('change', onChange);

        row.appendAll(change_avatar_label, changeAvatar_input);
      }
      localThis.body.append(row);
    }
    #row2() {
      const localThis = this;
      const row = CodeMaid.createDOM.Row();
      {
        let sizeInput = CodeMaid.createDOM.Tree('input', { type: 'number', min: 1, max: 10, value: 4 });
        let fuzzyModifierInput = CodeMaid.createDOM.Tree('input', { type: 'number', min: 1, max: 10, value: 1 });
        let thicknessInput = CodeMaid.createDOM.Tree('input', { type: 'number', min: 1, max: 50, value: 5 });
        let offsetX = CodeMaid.createDOM.Tree('input', { type: 'number', min: 0, max: 100, value: 0 });
        let offsetY = CodeMaid.createDOM.Tree('input', { type: 'number', min: 0, max: 100, value: 0 });

        sizeInput.addEventListener('change', () => {
          localThis.size = sizeInput.value;
        });
        fuzzyModifierInput.addEventListener('change', () => {
          localThis.fuzziness = fuzzyModifierInput.value;
        });
        thicknessInput.addEventListener('change', () => {
          localThis.brushSize = thicknessInput.value;
        });
        offsetX.addEventListener('change', () => {
          localThis.offsetX = offsetX.value;
        });
        offsetY.addEventListener('change', () => {
          localThis.offsetY = offsetY.value;
        });

        row.appendAll(sizeInput, fuzzyModifierInput, thicknessInput, offsetX, offsetY);
      }
      localThis.body.append(row);
    }

    #row3() {
      const localThis = this;
      const row = CodeMaid.createDOM.Row();
      {
        let loadImageButton = CodeMaid.createDOM.Button('Load');

        loadImageButton.addEventListener('click', () => {
          localThis.drawImage(localThis.size, localThis.fuzziness, localThis.brushSize, {
            x: localThis.offsetX,
            y: localThis.offsetY,
          });
        });

        row.appendAll(loadImageButton);
      }
      {
        let startDrawingButton = CodeMaid.createDOM.Button('Start');

        startDrawingButton.addEventListener('click', () => {
          localThis.isdrawing = true;
          localThis.execute();
        });

        row.appendAll(startDrawingButton);
      }
      {
        let stopDrawingButton = CodeMaid.createDOM.Button('Stop');

        stopDrawingButton.addEventListener('click', () => {
          localThis.isdrawing = false;
        });

        row.appendAll(stopDrawingButton);
      }
      {
        let resetDrawingButton = CodeMaid.createDOM.Button('Reset');

        resetDrawingButton.addEventListener('click', () => {
          localThis.isdrawing = false;
          localThis.executionLine = [];
        });

        row.appendAll(resetDrawingButton);
      }
      localThis.body.append(row);
    }

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

        this.canvasWidth = this.previewCanvas.width;
        this.canvasHeight = this.previewCanvas.height;

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

        // center and resize image

        let modifier = 1;
        if (img.width > this.previewCanvas.width) {
          modifier = this.previewCanvas.width / img.width;
        } else {
          modifier = this.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, this.previewCanvas.width, this.previewCanvas.height);
        this.imageDataRaw = imgData.data;

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

    drawImage(size = 4, modifier = 1, thickness = 5, offset = { x: 0, y: 0 }, ignorcolors = []) {
      const localThis = this;
      this.executionLine = [];
      for (let y = 0; y < this.canvasHeight; y += size * modifier) {
        let start = [0, y];

        for (let x = 0; x < this.canvasHeight; x += size * modifier) {
          let end = [x, y];
          let index = (y * this.canvasWidth + x) * 4;

          let a = this.imageDataRaw[index + 3];

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

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

            if (!ignorcolors.includes(color)) {
              if (x < this.canvasWidth - 1) {
                let n_r = this.imageDataRaw[index + size * modifier * 4 + 4],
                  n_g = this.imageDataRaw[index + size * modifier * 4 + 5],
                  n_b = this.imageDataRaw[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) || this.imageDataRaw[index + 7] < 20) {
                  samecolor = false;
                }
                if (!samecolor) {
                  this.executionLine.push({
                    pos1: localThis.recalculate(start, size, offset),
                    pos2: localThis.recalculate(end, size, offset),
                    color: color,
                    thickness: thickness,
                  });
                  start = [x, y];
                }
              } else {
                this.executionLine.push({
                  pos1: localThis.recalculate(start, size, offset),
                  pos2: localThis.recalculate(end, size, offset),
                  color: color,
                  thickness: thickness,
                });
              }
            }
          } else {
            // Is Transparent
            start = [x, y];
          }
        }
      }
      console.debug('done Loading');
    }

    execute() {
      const localThis = this;
      if (!localThis.isdrawing) return (localThis.isdrawing = false);
      if (localThis.executionLine.length <= 0) return (localThis.isdrawing = false);

      const currentLine = localThis.executionLine.shift();
      const p1 = currentLine.pos1,
        p2 = currentLine.pos2,
        color = currentLine.color,
        thickness = currentLine.thickness;

      setTimeout(() => {
        localThis.parent.bot.socket.send(
          `42["drawcmd",0,[${p1[0]},${p1[1]},${p2[0]},${p2[1]},false,${0 - thickness},"${color}",0,0,{}]]`
        );
        this.execute();
      }, 10);
    }

    recalculate(value, size, offset) {
      return [
        (value[0] / (this.canvasWidth * size) + offset.x / 100).toFixed(4),
        (value[1] / (this.canvasHeight * size) + offset.y / 100).toFixed(4),
      ];
    }
  }

  /**
   * Start the Engine!
   */
  Engine.initialize();

  return Engine;
})('🎮', 'Cubic Engine');