YouTubeDrawaria Engine Pro

I Added a Hide Menu just press the 3 dots button!

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

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

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         YouTubeDrawaria Engine Pro
// @version      1.1
// @description  I Added a Hide Menu just press the 3 dots button!
// @namespace    drawaria.modded.fullspec
// @homepage     https://drawaria.online/profile/?uid=86e33830-86ea-11ec-8553-bff27824cf71
// @author       YouTubeDrawaria
// @match        https://drawaria.online/*
// @match        https://drawaria.online/room/*
// @match        http://*/*
// @icon         https://i.postimg.cc/44DLPR1F/1.png
// @grant        none
// @license      MIT
// @run-at       document-end
// @subscribe    https://www.youtube.com/@YouTubeDrawaria
// ==/UserScript==


(function (icon, name) {
  'use strict';

  /**
   * Utility :
   * CodeMaid is a collection of utility functions
   * it shall help with a variety 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: {},
      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');

  /**
   * 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: linear-gradient(90deg, rgba(131,58,180,1) 0%, rgba(253,29,29,1) 50%, rgba(252,176,69,1) 100%); --CE-color: white;}`, // Gradiente para el fondo
      `#${icon} {z-index: 999; background: var(--CE-bg_color); border-radius: 1rem; padding: 1rem;}`, // Bordes redondeados y gradiente
      `#${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: 2.5rem; max-height: 2.5rem; width: 2.5rem; height: 2.5rem; border-radius: 1rem; background-color: rgba(255, 255, 255, 0.2);}`, // Fondo translúcido y más redondeado
      `#${icon} .icon > * {margin: auto; text-align: center; max-height: 100%; max-width: 100%; color: white; text-shadow: 1px 1px 4px rgba(0, 0, 0, 0.6);}`, // Texto blanco con sombra
      `#${icon} .ce_row {display: flex; width: 100%;}`,
      `#${icon} .ce_row > * {width: 100%;}`,
      `#${icon} .btn {padding: 0; background-color: rgba(255, 255, 255, 0.1); border-radius: 0.5rem; color: white; text-shadow: 1px 1px 4px rgba(0, 0, 0, 0.6); border: 2px solid rgba(0, 0, 255, 0.3); transition: all 0.3s ease-in-out;}`, // Botones mejorados
      `#${icon} .btn:active {animation: pulseEffect 1s ease-in-out; box-shadow: 0 0 15px rgba(255, 255, 255, 0.8);}`, // Efecto de pulso y brillo al presionar
      `#${icon} .itext {text-align: center; background-color: rgba(255, 255, 255, 0.1); color: white; text-shadow: 1px 1px 4px rgba(0, 0, 0, 0.6); -webkit-appearance: none; -moz-appearance: textfield;}`, // Inputs estilizados
      `input[name][hidden]:not(:checked) + * {display: none !important;}`,
      `hr {margin: 10px 0; border-color: rgba(255, 255, 255, 0.2);}`,
      `.playerlist-row::after {content: attr(data-playerid); position: relative; float: right; top: -20px; color: white; text-shadow: 1px 1px 4px rgba(0, 0, 0, 0.6);}`, // Textos con sombra en lista de jugadores

      // Efecto de pulso suave
      `@keyframes pulseEffect {
        0% {
            transform: scale(1);
            box-shadow: 0 0 0 rgba(255, 255, 255, 0.8);
        }
        50% {
            transform: scale(1.1); /* Expansión suave */
            box-shadow: 0 0 20px rgba(255, 255, 255, 0.8);
        }
        100% {
            transform: scale(1);
            box-shadow: 0 0 0 rgba(255, 255, 255, 0.8);
        }
      }`
    ]);
    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://i.postimg.cc/44DLPR1F/1.png' : 'https://i.postimg.cc/44DLPR1F/1.png', {
        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://i.postimg.cc/44DLPR1F/1.png"><div class="playerlist-name"><span>Subscribe to YouTubeDrawaria!</span><br/><sub>by YouTubeDrawaria</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];
            }
            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);
  };

  // ================================================== //
  //             YouTube Engine --- BEGINNING             //
  // ================================================== //

  /**
   * The YouTube Engine itself
   * 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', function () {
        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));
          });
        }
      });

      // Make the menu draggable
      let menu = document.getElementById(Engine.icon);
      menu.style.position = 'flex';
      menu.style.zIndex = 1000;

      let isDragging = true;
      let offsetY;
      let menuRect;
      let rightbarRect;

      menu.addEventListener('mousedown', function (e) {
        isDragging = true;
        menuRect = menu.getBoundingClientRect();
        rightbarRect = document.getElementById('rightbar').getBoundingClientRect();
        offsetY = e.clientY - menuRect.bottom;
        menu.style.cursor = 'grabbing';
      });

      document.addEventListener('mousemove', function (e) {
        if (isDragging) {
          let newTop = e.clientY - offsetY;

          menu.style.bottom = `${newTop}px`;
        }
      });

      document.addEventListener('mouseup', function () {
        isDragging = true;
        menu.style.cursor = 'grab';
      });
    },
  };

  /**
   * The base Class for all component
   * YouTube 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) {
        let self = this;
        list.forEach(function (child, index) {
          if (child.name != self.name) {
            let reference = new child(self);
            Engine.Active.push(reference);
            self.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 YouTube 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', `Liked My Videos? Yes`);
    }
    disable() {
      this.active = false;
      this.drawwidthrangeSlider.max = 45;
      this.drawwidthrangeSlider.min = -50;

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

      this.log('warn', `Awww Like Now :(, Disabled`);
    }
    #row1() {
      let self = this;
      let row = CodeMaid.createDOM.Row();
      {
        this.enableButton = CodeMaid.createDOM.Button('Enable');

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

  /**
   * Component for YouTube 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() {
      let self = this;
      {
        let target = document.querySelector('.fa-parachute-box').parentElement;
        let resizeObserver = new MutationObserver(function (mutations) {
          if (self.active)
            if (mutations[0].target.disabled) {
              mutations[0].target.disabled = '';
            }
        });
        resizeObserver.observe(target, {
          attributes: true,
        });
      }
      this.#row1();
      this.enable();
    }
    enable() {
      this.active = true;

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

      this.log('ok', `Subscribed YouTubeDrawaria? Yes`);
    }
    disable() {
      this.active = false;

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

      this.log('warn', `Awww Subscribe Now :(, Disabled`);
    }

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

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

  /**
   * Component for YouTube Engine
   * Player as the name suggests is a slightly 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) {
          // this.disconnect();
          // this.connect();
        }
        return;
      }
      let code = data.data.slice(0, pos);
      let message = JSON.parse(data.data.slice(pos));

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

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

  /**
   * Component for YouTube Engine
   * PlayerControls is your UserInterface
   * Will automatically activate
   * Control your Player with the installed components
   */
  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 YouTube 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() {
      let self = this;
      let row = CodeMaid.createDOM.Row();
      // Create
      {
      }
      // Enter
      {
        let enterRoom_button = CodeMaid.createDOM.Button('Join');
        enterRoom_button.addEventListener('click', function (event) {
          self.parent.bot.connect();
          self.parent.bot.join(document.querySelector('#invurl').value);
        });
        row.append(enterRoom_button);
      }
      // Switch
      {
      }
      // Leave
      {
        let leaveRoom_button = CodeMaid.createDOM.Button('Exit');
        leaveRoom_button.addEventListener('click', function (event) {
          self.parent.bot.exit();
          self.parent.bot.disconnect();
        });
        row.append(leaveRoom_button);
      }
      self.body.append(row);
    }
    #row2() {
      let self = this;
      let 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 a = e.target.result.replace('image/gif', 'image/png');
            fetch('https://drawaria.online/uploadavatarimage', {
              method: 'POST',
              body: 'imagedata=' + encodeURIComponent(a) + '&fromeditor=true',
              headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' },
            }).then((res) =>
              res.text().then((body) => {
                self.parent.bot.avatar = body.split('.');
                self.parent.avatar_img.src = getAvatarCacheURL(self.parent.bot.avatar);
              })
            );
          });
          myFileReader.readAsDataURL(this.files[0]);
        }
        changeAvatar_input.addEventListener('change', onChange);

        let changeName_input = CodeMaid.createDOM.Tree('input', {
          type: 'text',
          value: self.parent.bot?.name || '',
          placeholder: 'Subscribe To YouTubeDrawaria😎',
        });
        changeName_input.addEventListener('keypress', function (event) {
          if (event.keyCode != 13) return;
          self.parent.bot.name = changeName_input.value;
          self.parent.label.title = changeName_input.value;
        });

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

  /**
   * Component for YouTube Engine
   * PlayerSozials adds interactions to Drawaria!! 😮
   * 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() {
      let self = this;
      let 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) {
          self.parent.bot.emit('chatmsg', message_input.value);
        };

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

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

    #row2() {
      let self = this;
      let 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', function (event) {
              self.parent.bot.emit('sendgesture', index);
            });
            row.append(clone);
          });
      }
      this.body.append(row);
    }

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

    #row4() {
      let self = this;
      let 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', function () {
            self.parent.bot.room.players.forEach(function (player) {
              self.parent.bot.emit('settoken', player.id, index);
            });
          });
          row.append(tokenSend_button);
        });
      }
      this.body.append(row);
    }

    #row5() {
      let self = this;
      let 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', function (event) {
          self.parent.bot.emit('spawnavatar');
          self.parent.bot.attributes.spawned = !self.parent.bot.attributes.spawned;
        });

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

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

        avatarPositionY_button.addEventListener('change', function (event) {
          avatarPosition.y = avatarPositionY_button.value;
          self.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() {
      let self = this;
      let 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');
            self.loadImage(base64);
          });
          myFileReader.readAsDataURL(this.files[0]);
        }
        changeAvatar_input.addEventListener('change', onChange);

        row.appendAll(change_avatar_label, changeAvatar_input);
      }
      self.body.append(row);
    }
    #row2() {
      let self = this;
      let 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', () => {
          self.size = sizeInput.value;
        });
        fuzzyModifierInput.addEventListener('change', () => {
          self.fuzziness = fuzzyModifierInput.value;
        });
        thicknessInput.addEventListener('change', () => {
          self.brushSize = thicknessInput.value;
        });
        offsetX.addEventListener('change', () => {
          self.offsetX = offsetX.value;
        });
        offsetY.addEventListener('change', () => {
          self.offsetY = offsetY.value;
        });

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

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

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

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

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

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

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

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

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

        row.appendAll(resetDrawingButton);
      }
      self.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 self = 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: self.recalculate(start, size, offset),
                    pos2: self.recalculate(end, size, offset),
                    color: color,
                    thickness: thickness,
                  });
                  start = [x, y];
                }
              } else {
                this.executionLine.push({
                  pos1: self.recalculate(start, size, offset),
                  pos2: self.recalculate(end, size, offset),
                  color: color,
                  thickness: thickness,
                });
              }
            }
          } else {
            // Is Transparent
            start = [x, y];
          }
        }
      }
      console.debug('done Loading');
    }

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

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

      setTimeout(() => {
        self.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 YouTube Engine!
   */
  Engine.initialize();


// Código del botón para ocultar y mostrar el menú
let toggleButton = CodeMaid.createDOM.Button('Hide/Show Menu');
toggleButton.style.position = 'absolute';
toggleButton.style.top = '10px';
toggleButton.style.right = '10px';
toggleButton.style.zIndex = 1001;
toggleButton.style.background = 'linear-gradient(90deg, #ff7e5f, #feb47b, #86a8e7)';
toggleButton.style.border = 'none';
toggleButton.style.borderRadius = '5px';
toggleButton.style.color = 'white';
toggleButton.style.padding = '10px 20px';
toggleButton.style.fontSize = '16px';
toggleButton.style.cursor = 'pointer';
toggleButton.style.transition = 'background 0.3s ease-in-out, box-shadow 0.3s ease-in-out';
toggleButton.style.display = 'none'; // Inicializar el botón como oculto

toggleButton.addEventListener('click', function () {
  let menu = document.getElementById('✅'); // Reemplaza '✅' con el ID real de tu menú
  if (menu.style.display === 'none') {
    menu.style.display = 'block';
  } else {
    menu.style.display = 'none';
  }
});

toggleButton.addEventListener('mousedown', function () {
  toggleButton.style.background = 'linear-gradient(90deg, #ff5f6d, #feb47b, #86a8e7)';
  toggleButton.style.boxShadow = '0 4px 8px rgba(0, 0, 0, 0.2)';
});

toggleButton.addEventListener('mouseup', function () {
  toggleButton.style.background = 'linear-gradient(90deg, #ff7e5f, #feb47b, #86a8e7)';
  toggleButton.style.boxShadow = 'none';
});

document.body.appendChild(toggleButton);

// Agregar evento de clic al botón con id="roomcontrols-menu" para mostrar el botón
let roomControlsMenu = document.getElementById('roomcontrols-menu');
roomControlsMenu.addEventListener('click', function () {
  toggleButton.style.display = 'block'; // Mostrar el botón al presionar el botón con id="roomcontrols-menu"
});



})('✅', 'YouTube Engine Pro😎');