Definable ModMenu

Definable is a modular ModMenu for Drawaria.Online

// ==UserScript==
// @name         Definable ModMenu
// @namespace    Definable
// @version      0.2.5
// @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==

(() => {
  if (window.location.hostname === "drawaria.online" && window.location.pathname === "/") window.location.assign("/test");

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

  const sockets = [];
  const originalWebSocketSend = WebSocket.prototype.send;
  WebSocket.prototype.send = function (message) {
    let socket = this;
    if (sockets.indexOf(socket) === -1) {
      sockets.push(socket);
      originalWebSocketSend.call(socket, "2");
    }
    socket.addEventListener("close", function () {
      const pos = sockets.indexOf(socket);
      if (~pos) sockets.splice(pos, 1);
    });
    try {
      const msgObject = JSON.parse(message.replace(/^\d+/, ""));
      if (msgObject[0] === "startplay") {
        const width = msgObject[6][2];
        const height = msgObject[6][3];
        const canvas = document.querySelector("#canvas");
        canvas._width = width;
        canvas._height = height;
      }
    } catch (error) {}
    return originalWebSocketSend.call(socket, message);
  };
  globalThis["sockets"] = sockets;
  // Function.toString().replace(/^\s*\/\/.+$/gm, "").split("\n").filter((s) => s).map((s) => s.trim()).join("");
})();

(async function ($name) {
  "use strict";
  let canvas = document.querySelector("canvas#canvas");
  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 = helpers.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-${index > 0 ? "append" : "prepend"}` });
          }
          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)}`;
    },
    /**
     * @param {string} roomID
     * @param {string} name
     * @param {string} uid
     * @param {string} wt
     * @returns {string}
     */
    startplay: function (roomID, name = undefined, uid = undefined, wt = undefined, width = 1000, height = 1000) {
      const data = `${420}${JSON.stringify(["startplay", name, 2, "en", roomID, null, [null, "https://drawaria.online/", width, height, [null, uid, wt], null]])}`;
      return 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)}`;
      },
    },
    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 * (1 / canvas.width), y1 * (1 / canvas.height), x2 * (1 / canvas.width), y2 * (1 / canvas.height), 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)}`;
      },
    },
  };
  const helpers = {
    /**
     * @param {string} message
     * @param {string} styles
     */
    log: (message, styles = "color:#5090C1", application = undefined) => console.log("%c" + [`[${application ?? $name}]`, message].join(" "), styles),

    /**
     * Generate radnow UID
     * @param {number} size
     * @returns {string}
     */
    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}
     */
    toHexString: (byteArray) => {
      return byteArray.map((byte) => byte.toString(16).padStart(2, "0")).join("");
    },

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

    /**
     * @returns {Array<*>&{addEventListener:(event:"delete"|"set",handler:(property:string,value:*)=>void))=>}}
     */
    makeObservableArray: () => {
      if (!("Proxy" in window)) {
        console.warn("Your browser doesn't support Proxies.");
        return [];
      }

      const onDeleteCall = [];
      const onSetCall = [];

      // a proxy for our array
      const proxy = new Proxy([], {
        deleteProperty: function (target, property) {
          onDeleteCall.forEach((callback) => callback(property, target[property]));
          delete target[property];
          return true;
        },
        set: function (target, property, value, receiver) {
          target[property] = value;
          onSetCall.forEach((callback) => callback(property, target[property]));
          return true;
        },
      });

      proxy.addEventListener = (event, handler) => {
        switch (event) {
          case "set":
            onSetCall.push(handler);
            return;
          case "delete":
            onDeleteCall.push(handler);
            return;

          default:
            break;
        }
      };

      return proxy;
    },

    /**
     * @param {string} message
     * @returns {Array<any>|object}
     */
    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 = 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 = 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);
    }
  };

  const clearAll = () => {
    clearEmbeds();
    clearScripts();
    clearStyles();
    UI.querySelectAll(".extimages").forEach((e) => e.remove());
    UI.querySelectAll(".promlinks").forEach((e) => e.remove());
  };

  class Player {
    /** @type {Array<Player>} */
    static instances = [];
    static get noConflict() {
      return Player.instances[0] ?? new Player(helpers.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);
    }
    /**
     * @param {MessageEvent} messageEvent
     * @returns {[string, string, Array<any>|object]}
     */
    static parseMessageEventData(messageEvent) {
      const rawData = String(messageEvent.data);
      const messageType = (rawData.match(/^\d+/i) ?? [""])[0];
      const messageData = messageType.length === rawData.length ? [] : helpers.tryParseJSON(rawData.slice(messageType.length));
      return [messageType, Array.isArray(messageData) ? messageData.shift() : "", messageData];
    }
    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] = Player.parseMessageEventData(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 {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 ?? helpers.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;
    static helper = helpers;
    static runtime = [];

    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);
      DefinableCore.runtime.push(submodule);
    }

    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 container = UI.createElement("section", { style: "position: relative" });

    const chatbox = ui.querySelect("#chatbox_messages");
    /** @type {HTMLElement} */
    const devider = chatbox.previousElementSibling;
    devider.before(container);
    container.appendChild(definable.wrapper);
    definable.wrapper.style.position = "absolute";
    container.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";
      definable.wrapper.ModMenu = definable;
    }, 500);
  });

  setTimeout(() => {
    window.dispatchEvent(new CustomEvent("definable:core:init"));
    clearEmbeds();
    clearScripts();
    clearStyles();
    document.head.appendChild(
      UI.createElement("style", {
        id: "definableStyles",
        textContent: [
          "#definable { position: relative; isolation: isolate; }",
          "#definable::before { position: absolute; content: ''; inset: 0; background-color: var(--light); z-index: -1; }",
          "#definable .row { gap: 0.125rem; margin-bottom: 0.125rem; }",
          "#definable label { margin: 0; }",
          "#definable .row>label { min-width: 2rem; }",
        ].join("\n"),
      })
    );
    jQuery.getScript("/3rd/lib/moveable.min.js?" + window.VERID, function (data, textStatus, jqxhr) {
      globalThis["transformable"] = initTransformable(Moveable);
    });
    jQuery.getScript("/pageres/stencils.js?" + window.VERID);
    // console.clear();
  }, 500);

  function initTransformable(TransformationController) {
    const transformable = new TransformationController(document.body, {
      target: null,
      container: document.body,
      draggable: true,
      scalable: true,
      rotatable: true,
      snappable: true,
      origin: true,
      keepRatio: true,
    });

    transformable.snapGridWidth = 64;
    transformable.snapGridHeight = 64;

    /* draggable */
    transformable.on("drag", ({ target, transform, left, top, right, bottom, beforeDelta, beforeDist, delta, dist, clientX, clientY }) => {
      target.style.left = `${left}px`;
      target.style.top = `${top}px`;
    });

    /* scalable */
    transformable.on("scale", ({ target, scale, dist, delta, transform, clientX, clientY }) => {
      target.style.transform = transform;
    });

    /* rotatable */
    transformable.on("rotate", ({ target, beforeDelta, delta, dist, transform, clientX, clientY }) => {
      target.style.transform = transform;
    });

    /* Handle Transformable target selection */
    window.addEventListener("mousedown", function (event) {
      /** @type {HTMLElement} */
      const target = event.target;
      if (!target) return;

      if (target.classList.contains("moveable-control-box") || this.document.querySelector(".moveable-control-box").contains(target)) return;

      if (target.classList.contains("transformable")) {
        transformable.target = target;
        return;
      }
      transformable.target = null;
    });

    /* Handle ZIndex changes via keybinds */
    window.addEventListener("keydown", function (event) {
      /** @type {HTMLElement|HTMLImageElement} */
      const target = transformable.target;
      if (!target) return;

      // console.log(event.key);

      const zIndex = Number(target.style.zIndex ?? "0");
      switch (event.key) {
        case "+":
          target.style.zIndex = zIndex + 1;
          event.preventDefault();
          event.stopPropagation();
          break;

        case "-":
          target.style.zIndex = zIndex - 1;
          event.preventDefault();
          event.stopPropagation();
          break;

        case "Delete":
          transformable.target = null;
          target.remove();
          break;

        case "ArrowUp":
          target.style.top = parseFloat(target.style.top) - 1 + "px";
          transformable.updateRect();
          break;

        case "ArrowDown":
          target.style.top = parseFloat(target.style.top) + 1 + "px";
          transformable.updateRect();
          break;

        case "ArrowLeft":
          target.style.left = parseFloat(target.style.left) - 1 + "px";
          transformable.updateRect();
          break;

        case "ArrowRight":
          target.style.left = parseFloat(target.style.left) + 1 + "px";
          transformable.updateRect();
          break;

        default:
          break;
      }
      if (event.ctrlKey && event.key == "d") {
        event.preventDefault();
        event.stopPropagation();

        const duplicate = new Image();
        duplicate.src = target.src;
        duplicate.classList.add("transformable");
        duplicate.style.top = parseFloat(target.style.top) + 20 + "px";
        duplicate.style.left = parseFloat(target.style.left) + 20 + "px";
        transformable.target = duplicate;
        transformable.updateRect();
        document.body.appendChild(duplicate);
      }
    });

    return transformable;
  }

  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")) {
          helpers.log("Not a valid Link: ".concat(scriptSourceLinkInput.value), "color: firebrick;");
          spinnerIcon.hide();
          defaultIcon.classList.remove("d-none");
          return;
        }

        const requestSourceLink = new URL(scriptSourceLinkInput.value);
        helpers.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);
            helpers.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");

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址