Definable ModMenu

Definable is a modular ModMenu for Drawaria.Online

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

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

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

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         Definable ModMenu
// @namespace    Definable
// @version      0.1.2
// @description  Definable is a modular ModMenu for Drawaria.Online
// @homepage     https://drawaria.online/profile/?uid=63196790-c7da-11ec-8266-c399f90709b7
// @author       ≺ᴄᴜʙᴇ³≻
// @match        https://drawaria.online/
// @match        https://drawaria.online/test
// @match        https://drawaria.online/room/*
// @icon         https://i.ibb.co/8Xb1gXg/Andrew-T-Austin-brightly-lit-tesseract-hypercube.png
// @grant        none
// @license      GNU GPLv3
// @run-at       document-end
// ==/UserScript==

(() => {
  String.prototype.toFunction = function () {
    return new Function("(" + this + ")()");
  };
  String.prototype.__invokeAsFunction = function (...args) {
    new Function("(" + this + ")()")();
  };

  Function.prototype.callAsWorker = function (...args) {
    return new Promise((resolve, reject) => {
      const code = `self.onmessage = e => self.postMessage((${this.toString()}).call(null, ...e.data));`,
        blob = new Blob([code], { type: "text/javascript" }),
        worker = new Worker((window.URL ?? window.webkitURL).createObjectURL(blob));
      worker.onmessage = (e) => (resolve(e.data), worker.terminate());
      worker.onerror = (e) => (reject(e.message), worker.terminate());
      worker.postMessage(args);
    });
  };

  const originalElementPrototypeAppend = Element.prototype.append;
  Element.prototype.append = function (...elements) {
    elements = elements.filter((appendingElement) => {
      if (appendingElement instanceof HTMLElement) return true;
      return false;
    });
    originalElementPrototypeAppend.apply(this, elements);
    return this;
  };
  // Function.toString().replace(/^\s*\/\/.+$/gm, "").split("\n").filter((s) => s).map((s) => s.trim()).join("");
})();

(async function ($name) {
  "use strict";
  const cache = {
    storage: await caches.open($name),
    /**
     * @param {string} url
     * @param {string} scriptContent
     */
    saveScript: function (url, scriptContent) {
      this.storage.put(url, new Response(scriptContent, { headers: { "Content-Type": "application/javascript" } }));
    },
    /**
     * @param {string} url
     * @param {object} json
     */
    saveObject: function (url, json) {
      this.storage.put(url, new Response(JSON.stringify(json), { headers: { "Content-Type": "application/json" } }));
    },
    /**
     * @param {string} url
     * @returns {Promise<string>}
     */
    loadScript: async function (url) {
      const response = (await this.storage.match(url)) || (await fetch(url));
      return await response.text();
    },
    /**
     * @param {string} url
     * @returns {Promise<object>}
     */
    loadObject: async function (url) {
      const response = (await this.storage.match(url)) || (await fetch(url));
      return await response.json();
    },
  };
  const UI = {
    /**
     * Returns the first element that is a descendant of node that matches selectors.
     * @param {string} selectors
     * @param {ParentNode} parentElement
     * @returns {Element|null}
     */
    querySelect: (selectors, parentElement = document) => parentElement.querySelector(selectors),
    /**
     * Returns all element descendants of node that match selectors.
     * @param {string} selectors
     * @param {ParentNode} parentElement
     * @returns {NodeListOf<Element>}
     */
    querySelectAll: (selectors, parentElement = document) => parentElement.querySelectorAll(selectors),
    /**
     * Create Element and assign properties to it.
     * @param {string} tagName
     * @param {object} properties
     * @returns {Element}
     */
    createElement: (tagName, properties = {}) => {
      /** @type {Element} */
      const element = Object.assign(document.createElement(tagName), properties);
      if (properties.title) {
        try {
          element.setAttribute("data-toggle", "tooltip");
          element.setAttribute("data-html", "true");
          jQuery(element).tooltip({ boundary: "window" });
        } catch (error) {}
      }
      return element;
    },
    /**
     * Assign attributes to element.
     * @param {Element} element
     * @param {object} attributes
     */
    setAttributes: (element, attributes) => Object.entries(attributes).forEach(([k, v]) => element.setAttribute(k, v)),
    /**
     * Assign styles to element.
     * @param {Element} element
     * @param {object} styles
     */
    setStyles: (element, styles) => Object.assign(element.style, styles),
    /**
     * @param {string} name
     * @returns {HTMLDivElement}
     */
    createContainer: function (name = "div") {
      return this.createElement(name, { className: "container" });
    },
    /**
     * @returns {HTMLDivElement}
     */
    createRow: function () {
      return this.createElement("div", { className: "row" });
    },
    /**
     * @param {string} name
     * @returns {HTMLElement}
     */
    createIcon: function (name) {
      return this.createElement("i", { className: `fas fa-${name}` });
    },
    /**
     * @param {string} type
     * @param {object} properties
     * @returns {HTMLInputElement}
     */
    createInput: function (type, properties = {}) {
      const input = this.createElement("input", { ...{ className: "form-control" }, ...properties, ...{ type: type } });
      if (!input.id) input.id = uid(8);
      return input;
    },
    /**
     * @param {HTMLInputElement} input
     * @param {object} properties
     * @returns {HTMLLabelElement}
     */
    createLabelFor: function (input, properties = {}) {
      const label = this.createElement("label", { ...{ className: "btn btn-sm btn-outline-secondary" }, ...properties, ...{ htmlFor: input.id } });
      label.appendChild(input);
      input.hidden = true;
      if (input.type === "checkbox" || input.type === "radio") {
        input.addEventListener("input", function () {
          label.classList[input.checked ? "add" : "remove"]("active");
        });
      }
      return label;
    },
    /**
     * @param {string} className
     * @returns {HTMLElement & { show: Function, hide: Function }}
     */
    createSpinner: function (className = "text-secondary") {
      const spinner = this.createElement("div", { className: [className, "spinner-border spinner-border-sm"].join(" ") });
      spinner.show = function () {
        spinner.classList.remove("d-none");
      };
      spinner.hide = function () {
        spinner.classList.add("d-none");
      };
      spinner.hide();
      return spinner;
    },
    /**
     * @param {HTMLInputElement} input
     * @param {HTMLLabelElement|HTMLInputElement|HTMLButtonElement|HTMLElement} addon
     * @returns {HTMLDivElement}
     */
    createGroup: function (input, addon) {
      const group = this.createElement("div", { className: "input-group input-group-sm" });
      const groupAppend = this.createElement("div", { className: "input-group-append" });

      if (!(addon instanceof HTMLInputElement || addon instanceof HTMLButtonElement)) {
        addon.classList.add("input-group-text");
      }
      groupAppend.appendChild(addon);

      group.appendChild(input);
      group.appendChild(groupAppend);

      return group;
    },
    /**
     * @param {Array<HTMLLabelElement|HTMLInputElement|HTMLButtonElement|HTMLElement>} inputs
     * @returns {HTMLDivElement}
     */
    createInputGroup: function (...inputs) {
      const group = this.createElement("div", { className: "input-group input-group-sm" });

      let previousElementSibling = inputs[0];
      let appendGroup = this.createElement("div", { className: "input-group-append" });
      for (let index = 0; index < inputs.length; index++) {
        const input = inputs[index];

        if (input instanceof HTMLInputElement) {
          if (!(previousElementSibling instanceof HTMLInputElement)) {
            group.appendChild(appendGroup);
            appendGroup = this.createElement("div", { className: "input-group-append" });
          }
          input.hidden = false;
          group.appendChild(input);
        } else {
          input.classList.add("input-group-text");
          appendGroup.appendChild(input);
          if (index + 1 === inputs.length) {
            group.appendChild(appendGroup);
          }
        }
        previousElementSibling = input;
      }

      return group;
    },
  };
  const DrawariaOnlineMessageTypes = {
    /**
     * @param {string} message
     * @returns {string}
     */
    chatmsg: function (message) {
      // 42["chatmsg","a"]
      const data = ["chatmsg", message];
      return `${42}${JSON.stringify(data)}`;
    },
    /**
     * @returns {string}
     */
    passturn: function () {
      // 42["passturn"]
      const data = ["passturn"];
      return `${42}${JSON.stringify(data)}`;
    },
    /**
     * @param {number|string} playerid
     * @returns {string}
     */
    pgdrawvote: function (playerid) {
      // 42["pgdrawvote",2,0]
      const data = ["pgdrawvote", playerid, 0];
      return `${42}${JSON.stringify(data)}`;
    },
    /**
     * @returns {string}
     */
    pgswtichroom: function () {
      // 42["pgswtichroom"]
      const data = ["pgswtichroom"];
      return `${42}${JSON.stringify(data)}`;
    },
    /**
     * @returns {string}
     */
    playerafk: function () {
      // 42["playerafk"]
      const data = ["playerafk"];
      return `${42}${JSON.stringify(data)}`;
    },
    /**
     * @returns {string}
     */
    playerrated: function () {
      // 42["playerrated"]
      const data = ["playerrated"];
      return `${42}${JSON.stringify(data)}`;
    },
    /**
     * @param {number|string} gestureid
     * @returns {string}
     */
    sendgesture: function (gestureid) {
      // 42["sendgesture",16]
      const data = ["sendgesture", gestureid];
      return `${42}${JSON.stringify(data)}`;
    },
    /**
     * @returns {string}
     */
    sendvote: function () {
      // 42["sendvote"]
      const data = ["sendvote"];
      return `${42}${JSON.stringify(data)}`;
    },
    /**
     * @param {number|string} playerid
     * @returns {string}
     */
    sendvotekick: function (playerid) {
      // 42["sendvotekick",93]
      const data = ["sendvotekick", playerid];
      return `${42}${JSON.stringify(data)}`;
    },
    /**
     * @param {number|string} wordid
     * @returns {string}
     */
    wordselected: function (wordid) {
      // 42["wordselected",0]
      const data = ["sendvotekick", wordid];
      return `${42}${JSON.stringify(data)}`;
    },
    clientcmd: {
      /**
       * @param {number|string} itemid
       * @param {boolean} isactive
       * @returns {string}
       */
      activateitem: function (itemid, isactive) {
        const data = ["clientcmd", 12, [itemid, isactive]];
        return `${42}${JSON.stringify(data)}`;
      },
      /**
       * @param {number|string} itemid
       * @returns {string}
       */
      buyitem: function (itemid) {
        const data = ["clientcmd", 11, [itemid]];
        return `${42}${JSON.stringify(data)}`;
      },
      /**
       * @param {number|string} itemid
       * @param {"zindex"|"shared"} target
       * @param {any} value
       * @returns {string}
       */
      canvasobj_changeattr: function (itemid, target, value) {
        const data = ["clientcmd", 234, [itemid, target, value]];
        return `${42}${JSON.stringify(data)}`;
      },
      /**
       * @returns {string}
       */
      canvasobj_getobjects: function () {
        const data = ["clientcmd", 233];
        return `${42}${JSON.stringify(data)}`;
      },
      /**
       * @param {number|string} itemid
       * @returns {string}
       */
      canvasobj_remove: function (itemid) {
        let data = ["clientcmd", 232, [itemid]];
        return `${42}${JSON.stringify(data)}`;
      },
      /**
       * @param {number|string} itemid
       * @param {number|string} positionX
       * @param {number|string} positionY
       * @param {number|string} speed
       * @returns {string}
       */
      canvasobj_setposition: function (itemid, positionX, positionY, speed) {
        const data = ["clientcmd", 230, [itemid, 100 / positionX, 100 / positionY, { movespeed: speed }]];
        return `${42}${JSON.stringify(data)}`;
      },
      /**
       * @param {number|string} itemid
       * @param {number|string} rotation
       * @returns {string}
       */
      canvasobj_setrotation: function (itemid, rotation) {
        const data = ["clientcmd", 231, [itemid, rotation]];
        return `${42}${JSON.stringify(data)}`;
      },
      /**
       * @param {any} value
       * @returns {string}
       */
      customvoting_setvote: function (value) {
        const data = ["clientcmd", 301, [value]];
        return `${42}${JSON.stringify(data)}`;
      },
      /**
       * @param {any} value
       * @returns {string}
       */
      getfpid: function (value) {
        const data = ["clientcmd", 901, [value]];
        return `${42}${JSON.stringify(data)}`;
      },
      /**
       * @returns {string}
       */
      getinventory: function () {
        const data = ["clientcmd", 10, [true]];
        return `${42}${JSON.stringify(data)}`;
      },
      /**
       * @returns {string}
       */
      getspawnsstate: function () {
        const data = ["clientcmd", 102];
        return `${42}${JSON.stringify(data)}`;
      },
      /**
       * @param {number|string} positionX
       * @param {number|string} positionY
       * @returns {string}
       */
      moveavatar: function (positionX, positionY) {
        const data = ["clientcmd", 103, [1e4 * Math.floor(positionX * 0.01 * 1e4) + Math.floor(positionY * 0.01 * 1e4), false]];
        return `${42}${JSON.stringify(data)}`;
      },
      /**
       * @returns {string}
       */
      setavatarprop: function () {
        const data = ["clientcmd", 115];
        return `${42}${JSON.stringify(data)}`;
      },
      /**
       * @param {number|string} flagid
       * @param {boolean} isactive
       * @returns {string}
       */
      setstatusflag: function (flagid, isactive) {
        const data = ["clientcmd", 3, [flagid, isactive]];
        return `${42}${JSON.stringify(data)}`;
      },
      /**
       * @param {number|string} playerid
       * @param {number|string} tokenid
       * @returns {string}
       */
      settoken: function (playerid, tokenid) {
        const data = ["clientcmd", 2, [playerid, tokenid]];
        return `${42}${JSON.stringify(data)}`;
      },
      /**
       * @param {number|string} playerid
       * @param {any} value
       * @returns {string}
       */
      snapchatmessage: function (playerid, value) {
        const data = ["clientcmd", 330, [playerid, value]];
        return `${42}${JSON.stringify(data)}`;
      },
      /**
       * @returns {string}
       */
      spawnavatar: function () {
        const data = ["clientcmd", 101];
        return `${42}${JSON.stringify(data)}`;
      },
      /**
       * @returns {string}
       */
      startrollbackvoting: function () {
        const data = ["clientcmd", 320];
        return `${42}${JSON.stringify(data)}`;
      },
      /**
       * @returns {string}
       */
      trackforwardvoting: function () {
        const data = ["clientcmd", 321];
        return `${42}${JSON.stringify(data)}`;
      },
      /**
       * @param {number|string} trackid
       * @returns {string}
       */
      votetrack: function (trackid) {
        const data = ["clientcmd", 1, [trackid]];
        return `${42}${JSON.stringify(data)}`;
      },
    },
    /**
     * @param {string} roomID
     * @param {string} name
     * @param {string} uid
     * @param {string} wt
     * @returns {string}
     */
    startplay: function (roomID, name = undefined, uid = undefined, wt = undefined) {
      const data = `${420}${JSON.stringify(["startplay", name, 2, "en", roomID, null, [null, "https://drawaria.online/", 1000, 1000, [null, uid, wt], null]])}`;
      return data;
    },
    clientnotify: {
      /**
       * @param {number|string} playerid
       * @returns {string}
       */
      requestcanvas: function (playerid) {
        const data = ["clientnotify", playerid, 10001];
        return `${42}${JSON.stringify(data)}`;
      },
      /**
       * @param {number|string} playerid
       * @param {string} base64
       * @returns {string}
       */
      respondcanvas: function (playerid, base64) {
        const data = ["clientnotify", playerid, 10002, [base64]];
        return `${42}${JSON.stringify(data)}`;
      },
      /**
       * @param {number|string} playerid
       * @param {number|string} imageid
       * @returns {string}
       */
      galleryupload: function (playerid, imageid) {
        const data = ["clientnotify", playerid, 11, [imageid]];
        return `${42}${JSON.stringify(data)}`;
      },
      /**
       * @param {number|string} playerid
       * @param {any} type
       * @returns {string}
       */
      warning: function (playerid, type) {
        const data = ["clientnotify", playerid, 100, [type]];
        return `${42}${JSON.stringify(data)}`;
      },
      /**
       * @param {number|string} playerid
       * @param {string} targetname
       * @param {boolean} mute
       * @returns {string}
       */
      mute: function (playerid, targetname, mute = false) {
        const data = ["clientnotify", playerid, 1, [mute, targetname]];
        return `${42}${JSON.stringify(data)}`;
      },
      /**
       * @param {number|string} playerid
       * @param {string} targetname
       * @param {boolean} hide
       * @returns {string}
       */
      hide: function (playerid, targetname, hide = false) {
        const data = ["clientnotify", playerid, 3, [hide, targetname]];
        return `${42}${JSON.stringify(data)}`;
      },
      /**
       * @param {number|string} playerid
       * @param {string} reason
       * @param {string} targetname
       * @returns {string}
       */
      report: function (playerid, reason, targetname) {
        const data = ["clientnotify", playerid, 2, [targetname, reason]];
        return `${42}${JSON.stringify(data)}`;
      },
    },
    drawcmd: {
      /**
       * @param {number|string} playerid
       * @param {number|string} x1
       * @param {number|string} y1
       * @param {number|string} x2
       * @param {number|string} y2
       * @param {number|string} size
       * @param {number|string} color
       * @param {boolean} ispixel
       * @returns {string}
       */
      line: function (x1, y1, x2, y2, color, size = 4, ispixel = true, playerid = 0) {
        const data = ["drawcmd", 0, [x1 * 0.001, y1 * 0.001, x2 * 0.001, y2 * 0.001, false, -size, color, playerid, ispixel]];
        return `${42}${JSON.stringify(data)}`;
      },
      /**
       * @param {number|string} playerid
       * @param {number|string} x1
       * @param {number|string} y1
       * @param {number|string} x2
       * @param {number|string} y2
       * @param {number|string} size
       * @param {number|string} color
       * @returns {string}
       */
      erase: function (x1, y1, x2, y2, color, size, ispixel = true, playerid = 0) {
        const data = ["drawcmd", 1, [x1 * 0.01, y1 * 0.01, x2 * 0.01, y2 * 0.01, false, -size, color, playerid, ispixel]];
        return `${42}${JSON.stringify(data)}`;
      },
      /**
       * @param {number|string} x
       * @param {number|string} y
       * @param {number|string} color
       * @param {number|string} tolerance
       * @param {number|string} r
       * @param {number|string} g
       * @param {number|string} b
       * @param {number|string} a
       * @returns {string}
       */
      flood: function (x, y, color, tolerance, r, g, b, a) {
        // 42["drawcmd",2,[x, y,color,{"0":r,"1":g,"2":b,"3":a},size]]
        const data = ["drawcmd", 2, [x * 0.01, y * 0.01, color, { 0: r, 1: g, 2: b, 3: a }, tolerance]];
        return `${42}${JSON.stringify(data)}`;
      },
      /**
       * @param {number|string} playerid
       * @returns {string}
       */
      undo: function (playerid) {
        // 42["drawcmd",3,[playerid]]
        const data = ["drawcmd", 3, [playerid]];
        return `${42}${JSON.stringify(data)}`;
      },
      /**
       * @returns {string}
       */
      clear: function () {
        // 42["drawcmd",4,[]]
        const data = ["drawcmd", 4, []];
        return `${42}${JSON.stringify(data)}`;
      },
    },
  };

  /**
   * @param {string} message
   * @param {string} styles
   */
  const log = (message, styles = "color:#5090C1", application = undefined) => console.log("%c" + [`[${application ?? $name}]`, message].join(" "), styles);

  /**
   * Generate radnow UID
   * @param {number} size
   * @returns {string}
   */
  const uid = (size = 8) => {
    const MASK = 0x3d;
    const LETTERS = "abcdefghijklmnopqrstuvwxyz";
    const NUMBERS = "1234567890";
    const SPECIALS = "-_";
    const charset = `${NUMBERS}${LETTERS}${LETTERS.toUpperCase()}${SPECIALS}`.split("");

    const bytes = new Uint8Array(size);
    crypto.getRandomValues(bytes);

    return bytes.reduce((acc, byte) => `${acc}${charset[byte & MASK]}`, "");
  };

  /**
   * @param {number[]} byteArray
   * @returns {string}
   */
  const toHexString = (byteArray) => {
    return byteArray.map((byte) => byte.toString(16).padStart(2, "0")).join("");
  };

  /**
   * @param {string} key
   * @param {string} value
   */
  const setCookie = (key, value) => {
    if (window.Cookie) Cookies.set(key, value);
    document.cookie = `${key}=${value}; Secure; Path=/; SameSite=None; Partitioned;`;
  };

  /**
   * @param {(index:number,value:*)=>{}} callbackOnDelete
   * @param {(index:number,value:*)=>{}} callbackOnSet
   * @returns {Array<*>}
   */
  const makeObservableArray = (callbackOnDelete, callbackOnSet) => {
    if (!("Proxy" in window)) {
      console.warn("Your browser doesn't support Proxies.");
      return;
    }

    // a proxy for our array
    const proxy = new Proxy([], {
      deleteProperty: function (target, property) {
        (callbackOnDelete ?? ___)(property, target[property]);
        delete target[property];
        return true;
      },
      set: function (target, property, value, receiver) {
        target[property] = value;
        (callbackOnSet ?? ___)(property, value);
        return true;
      },
    });

    Object.defineProperty(proxy, "isProxy", {
      value: true,
      writable: false,
    });

    return proxy;
  };

  /**
   * @param {string} message
   * @returns {Array<any>|object}
   */
  const tryParseJSON = (message) => {
    try {
      return JSON.parse(message);
    } catch (error) {
      return [];
    }
  };

  const clearScripts = (remove = true) => {
    try {
      let array = document.querySelectorAll('script[src]:not([data-codemaid="ignore"])');
      array.forEach((script) => {
        if (script.src != "") document.head.appendChild(script);
      });
    } catch (error) {
      console.error(error);
    }

    try {
      let unifiedScript = DefinableCore.UI.createElement("script");

      let scripts = document.querySelectorAll('script:not([src]):not([data-codemaid="ignore"])');
      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;

      if (!remove) document.head.appendChild(unifiedScript);
    } catch (error) {
      console.error(error);
    }
  };

  const clearStyles = (remove = false) => {
    try {
      let unifiedStyles = DefinableCore.UI.createElement("style");
      unifiedStyles.textContet = "";

      let styles = document.querySelectorAll('style:not([data-codemaid="ignore"])');
      styles.forEach((style) => {
        unifiedStyles.textContent += style.textContent;
        style.remove();
      });
      if (!remove) document.head.appendChild(unifiedStyles);
    } catch (error) {
      console.error(error);
    }
  };

  const clearEmbeds = () => {
    try {
      let array = document.querySelectorAll("iframe");
      array.forEach((iframe) => {
        iframe.remove();
      });
    } catch (error) {
      console.error(error);
    }
  };

  class Player {
    /** @type {Array<Player>} */
    static instances = [];
    static get noConflict() {
      return Player.instances[0] ?? new Player(uid(12));
    }
    /**
     * @param {string|undefined} inviteLink
     * @returns {URL}
     */
    static getSocketServerURL(inviteLink) {
      if (typeof inviteLink === "undefined") return `wss://sv3.drawaria.online/socket.io/?sid1=undefined&hostname=drawaria.online&EIO=3&transport=websocket`;
      const roomID = this.getRoomID(inviteLink);
      const [_voidable, serverPrefix] = roomID.split(".");
      return new URL(`wss://${typeof serverPrefix === "undefined" ? "" : "sv".concat(serverPrefix, ".")}drawaria.online/socket.io/?sid1=undefined&hostname=drawaria.online&EIO=3&transport=websocket`);
    }
    /**
     * @param {string|undefined} inviteLink
     * @returns {string}
     */
    static getRoomID(inviteLink) {
      const inviteURL = new URL(inviteLink);
      return inviteURL.pathname.slice(6);
    }
    static parseMessage = DrawariaOnlineMessageTypes;

    /** @type {string} */
    name;
    /** @type {string} */
    uid;
    /** @type {string} */
    wt;
    /** @type {string} */
    roomID;

    /** @type {WebSocket} */
    socket;

    /** @type {Map<string, Function[]>} */
    events;

    /**
     * @param {string} name
     */
    constructor(name = undefined) {
      this.name = name;
      this.uid = "_";
      this.wt = undefined;
      this.roomID = undefined;

      this.events = new Map();

      Player.instances.push(this);
    }

    /**
     * @param {string} inviteLink
     */
    connect = (inviteLink) => {
      if (this.isConnected) return;
      this.#createNewConnection(inviteLink);
    };

    disconnect = () => {
      this.send("41");
      if (this.isConnected) this.socket.close();
    };

    reconnect = () => {
      if (!this.isConnected) {
        this.connect(`https://drawaria.online/room/${this.roomID}`);
        return;
      }
      this.send("41");
      this.send("40");
    };

    /**
     * @param {string} inviteLink
     */
    enterRoom = (inviteLink = undefined) => {
      this.roomID = Player.getRoomID(inviteLink ?? document.querySelector("#invurl").value);
      this.reconnect();
    };

    nextRoom = () => {
      this.send(Player.parseMessage.pgswtichroom());
    };

    leaveRoom = () => {
      this.send("41");
    };

    /**
     * @param {string} payload
     */
    send = (payload) => {
      if (this.isConnected) this.socket.send(payload);
      else debug(`send failed! Connection is closed`);
    };

    /**
     * @returns {boolean}
     */
    get isConnected() {
      return typeof this.socket !== "undefined" && this.socket instanceof WebSocket && (this.socket.readyState === WebSocket.OPEN || this.socket.readyState === WebSocket.CONNECTING);
    }

    #sendHeartbeat = () => {
      if (this.isConnected) this.socket.send(2);
    };

    /**
     * @param {string} inviteLink
     */
    #createNewConnection = (inviteLink) => {
      this.socket = new WebSocket(Player.getSocketServerURL(inviteLink));
      this.roomID = Player.getRoomID(inviteLink);
      this.socket.addEventListener("open", this.#startHeartbeat);
      this.socket.addEventListener("message", this.#handleMessage);
    };

    #startHeartbeat = () => {
      this.heartbeatInterval = setInterval(this.#sendHeartbeat, 25000);
    };

    #handleMessage = (rawMessage) => {
      const [eventType, eventName, eventData] = this.#parseMessageEvent(rawMessage);

      switch (eventType) {
        case "40":
          this.send(Player.parseMessage.startplay(this.roomID, this.name, this.uid, this.wt));
          break;
        case "430":
          break;
        case "42":
          this.__invokeEvent(eventName, eventData);
          break;
        default:
      }
    };

    /**
     * @param {MessageEvent} messageEvent
     * @returns {[string, string, Array<any>|object]}
     */
    #parseMessageEvent = (messageEvent) => {
      const rawData = String(messageEvent.data);
      const messageType = (rawData.match(/^\d+/i) ?? [""])[0];
      const messageData = messageType.length === rawData.length ? [] : tryParseJSON(rawData.slice(messageType.length));
      return [messageType, Array.isArray(messageData) ? messageData.shift() : "", messageData];
    };

    /**
     * @param {DrawariaOnlineEvents} event
     * @param {Function} callback
     */
    addEventListener = (event, callback, id = undefined) => {
      if (!this.hasEventListener(event)) {
        this.events.set(event, []);
      }
      callback.id = id ?? this.addEventListener.caller.name ?? uid(8);
      try {
        this.events.get(event).push(callback);
      } catch (error) {
        debug(`addEventListener returned error \"${error}\"`, "color:firebrick");
      }
    };

    /**
     * @param {DrawariaOnlineEvents} event
     * @param {*} data
     */
    __invokeEvent = (event, data) => {
      if (this.hasEventListener(event)) {
        const listeners = this.events.get(event);
        listeners.forEach((listener) => listener(data));
      }
    };

    /**
     * @param {DrawariaOnlineEvents} name
     * @returns {boolean}
     */
    hasEventListener = (name) => {
      return this.events.has(name);
    };
  }

  class DefinableCore {
    static UI = UI;
    static Player = Player;

    constructor(moduleName, moduleIcon = "code") {
      this.name = moduleName;
      this.__initializeHead(moduleIcon);
      this.__initializeBody();
    }

    __initializeHead(moduleIcon) {
      const input = this.UI.createInput("checkbox");
      this.label = this.UI.createLabelFor(input, { title: this.name });
      const icon = this.UI.createIcon(moduleIcon);
      this.label.appendChild(icon);
    }

    __initializeBody() {
      const submoduleSelectionContainer = this.UI.createElement("nav", { className: "row" });
      const wrapper = this.UI.createElement("div");
      this.wrapper = wrapper;
      this.__contaier = this.UI.createElement("div");
      this.__contaier.appendChild(this.UI.createElement("b", { textContent: this.name }));
      // this.wrapper.appendChild(submoduleSelectionContainer);
      this.__contaier.appendChild(submoduleSelectionContainer);
      this.wrapper.appendChild(this.__contaier);
      const input = this.UI.querySelect("&>input", this.label);
      input.addEventListener("input", function () {
        wrapper.classList[input.checked ? "remove" : "add"]("d-none");
      });
      wrapper.classList.add("d-none");
    }

    createRow() {
      const row = this.UI.createRow();
      this.__contaier.appendChild(row);
      return row;
    }

    /**
     * @param {DefinableCore} submodule
     */
    registerModule(submodule) {
      // const submoduleSelectionContainer = this.UI.querySelect("&>nav", this.wrapper);
      const submoduleSelectionContainer = this.UI.querySelect("&>nav", this.__contaier);
      submoduleSelectionContainer.appendChild(submodule.label);
      this.wrapper.appendChild(submodule.wrapper);
    }

    get UI() {
      return DefinableCore.UI;
    }
    get Player() {
      return DefinableCore.Player;
    }
  }

  window.addEventListener("definable:core:init", function () {
    const ui = DefinableCore.UI;
    const definable = new DefinableCore("Definable", "code");

    const chatbox = ui.querySelect("#chatbox_messages");
    /** @type {HTMLElement} */
    const devider = chatbox.previousElementSibling;
    devider.before(definable.wrapper);
    definable.wrapper.before(devider.cloneNode(false));

    setTimeout(() => {
      window.dispatchEvent(new CustomEvent("definable:init", { detail: { main: definable, core: DefinableCore } }));
      definable.wrapper.classList.remove("d-none");
      definable.wrapper.classList.add("container");
      definable.wrapper.id = "definable";
    }, 500);
  });

  setTimeout(() => {
    window.dispatchEvent(new CustomEvent("definable:core:init"));
    clearEmbeds();
    clearScripts();
    clearStyles();
    document.head.appendChild(
      UI.createElement("style", { id: "definableStyles", textContent: ["#definable .row { gap: 0.125rem; margin-bottom: 0.125rem; }", "#definable label { margin: 0; }"].join("\n") })
    );
    // console.clear();
  }, 500);

  function launchScriptManager() {
    const ui = DefinableCore.UI;
    const container = ui.createContainer();

    {
      const row = ui.createRow();

      const scriptSourceLinkInput = ui.createInput("text", { className: "form-control" });
      const requestSourceInput = ui.createInput("button");
      const requestSourceLabel = ui.createLabelFor(requestSourceInput);
      const group = ui.createGroup(scriptSourceLinkInput, requestSourceLabel);
      const spinnerIcon = ui.createSpinner("");
      const defaultIcon = ui.createIcon("external-link-alt");

      requestSourceLabel.appendChild(defaultIcon);
      requestSourceLabel.appendChild(spinnerIcon);

      requestSourceInput.addEventListener("click", function () {
        defaultIcon.classList.add("d-none");
        spinnerIcon.show();

        if (!scriptSourceLinkInput.value.startsWith("http")) {
          log("Not a valid Link: ".concat(scriptSourceLinkInput.value), "color: firebrick;");
          spinnerIcon.hide();
          defaultIcon.classList.remove("d-none");
          return;
        }

        const requestSourceLink = new URL(scriptSourceLinkInput.value);
        log("Requesting Module: ".concat(requestSourceLink), "color: rebeccapurple;");

        fetch(requestSourceLink).then(async (response) => {
          const copy = response.clone();
          const content = await copy.text();

          if (copy.status === 200 && content) {
            cache.saveScript(requestSourceLink, content);
            log("Module installed!", "color: forestgreen;");
            spinnerIcon.hide();
            defaultIcon.classList.remove("d-none");
            scriptSourceLinkInput.value = "";
          }
        });
      });

      row.appendChild(group);
      container.appendChild(row);
    }

    DefinableCore.UI.querySelect("#login-leftcol").appendChild(container);
  }
})("definable");