BotPlayer

An SubModule for the Definable ModMenu for Drawaria.Online.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         BotPlayer
// @namespace    Definable
// @version      0.1.1
// @description  An SubModule for the Definable 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         data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGRSURBVHhe7dkxbsJAEAXQP3ZJwyWQLOUENKGg5xYRR+AMHAHlFvQUpOEEkSxxCRpKPGmwlIyUxesMMWj+65g12vXXsGsAICIioqjEFqzJZKLfXx+Pxx/vsePW0NfbcauwhWgYgC0QEUWSPCPR41wdWu56w58CyXSQmehsU1eNXNYA5iIY2fG/UMUZwK7QcrVfVrUdb+WsF54dcL35gwgW3jcPACIYiWDRyOUw29SVHe/LLYBGLmsRjG3dmwjG1y5z4RYAgLkt3JHbXG4B3KPtf+M5l1sAzyq5Q+Z4ff9Mfm/39vH24rL28B3AAGwhmpufo65PVo+yB3Rdbyt8BzAAW4iGAdhCNAzAFqJJnpE5HuU5IFf4DmAAthDNzc9R12frR9kDuq63Fb4D3AK4/m7/LzzncgsAwM4W7shtLrcACi1XqjjZujdVnAotV7bel1sA+2VVF1pOVbH1bNGWKs6q2BZaTlN/jeVK7pDosasOLXe9bh1ARPSMkjskOuyqdtwa+no7boU/BRiALRARERFF8QWXXJ5ITbASawAAAABJRU5ErkJggg==
// @grant        none
// @license      GNU GPLv3
// ==/UserScript==

//#region TypeDefinitions
/**
 * @typedef {Object} PlayerInstance
 * @property {string} name - The name of the player.
 * @property {string} uid - The unique identifier for the player.
 * @property {string} wt - The weight of the player.
 * @property {string} roomID - The room ID the player is in.
 * @property {WebSocket} socket - The WebSocket connection for the player.
 * @property {Map<string, Function[]>} events - The events map for the player.
 * @property {boolean} isConnected - Whether the player is connected.
 * @property {(invitelink:string)=>void} connect - Connects the player to a room.
 * @property {()=>void} disconnect - Disconnects the player.
 * @property {()=>void} reconnect - Reconnects the player.
 * @property {(invitelink:string)=>void} enterRoom - Enters a room.
 * @property {()=>void} nextRoom - Moves the player to the next room.
 * @property {()=>void} leaveRoom - Leaves the current room.
 * @property {(payload:string)=>void} send - Sends a message through the WebSocket.
 * @property {(event:string,handler:Function)=>void} addEventListener - Adds an event listener.
 * @property {(event:string)=>boolean} hasEventListener - Checks if an event listener exists.
 * @property {()=>void} __invokeEvent - Invokes an event.
 */

/**
 * @typedef {Object} DrawariaOnlineMessageTypes
 * @property {(message: string) => string} chatmsg - Sends a chat message.
 * @property {() => string} passturn - Passes the turn.
 * @property {(playerid: number|string) => string} pgdrawvote - Votes for a player to draw.
 * @property {() => string} pgswtichroom - Switches the room.
 * @property {() => string} playerafk - Marks the player as AFK.
 * @property {() => string} playerrated - Rates the player.
 * @property {(gestureid: number|string) => string} sendgesture - Sends a gesture.
 * @property {() => string} sendvote - Sends a vote.
 * @property {(playerid: number|string) => string} sendvotekick - Votes to kick a player.
 * @property {(wordid: number|string) => string} wordselected - Selects a word.
 * @property {Object} clientcmd - Client commands.
 * @property {(itemid: number|string, isactive: boolean) => string} clientcmd.activateitem - Activates an item.
 * @property {(itemid: number|string) => string} clientcmd.buyitem - Buys an item.
 * @property {(itemid: number|string, target: "zindex"|"shared", value: any) => string} clientcmd.canvasobj_changeattr - Changes an attribute of a canvas object.
 * @property {() => string} clientcmd.canvasobj_getobjects - Gets canvas objects.
 * @property {(itemid: number|string) => string} clientcmd.canvasobj_remove - Removes a canvas object.
 * @property {(itemid: number|string, positionX: number|string, positionY: number|string, speed: number|string) => string} clientcmd.canvasobj_setposition - Sets the position of a canvas object.
 * @property {(itemid: number|string, rotation: number|string) => string} clientcmd.canvasobj_setrotation - Sets the rotation of a canvas object.
 * @property {(value: any) => string} clientcmd.customvoting_setvote - Sets a custom vote.
 * @property {(value: any) => string} clientcmd.getfpid - Gets the FPID.
 * @property {() => string} clientcmd.getinventory - Gets the inventory.
 * @property {() => string} clientcmd.getspawnsstate - Gets the spawn state.
 * @property {(positionX: number|string, positionY: number|string) => string} clientcmd.moveavatar - Moves the avatar.
 * @property {() => string} clientcmd.setavatarprop - Sets the avatar properties.
 * @property {(flagid: number|string, isactive: boolean) => string} clientcmd.setstatusflag - Sets a status flag.
 * @property {(playerid: number|string, tokenid: number|string) => string} clientcmd.settoken - Sets a token.
 * @property {(playerid: number|string, value: any) => string} clientcmd.snapchatmessage - Sends a Snapchat message.
 * @property {() => string} clientcmd.spawnavatar - Spawns an avatar.
 * @property {() => string} clientcmd.startrollbackvoting - Starts rollback voting.
 * @property {() => string} clientcmd.trackforwardvoting - Tracks forward voting.
 * @property {(trackid: number|string) => string} clientcmd.votetrack - Votes for a track.
 * @property {(roomID: string, name?: string, uid?: string, wt?: string) => string} startplay - Starts the play.
 * @property {Object} clientnotify - Client notifications.
 * @property {(playerid: number|string) => string} clientnotify.requestcanvas - Requests a canvas.
 * @property {(playerid: number|string, base64: string) => string} clientnotify.respondcanvas - Responds with a canvas.
 * @property {(playerid: number|string, imageid: number|string) => string} clientnotify.galleryupload - Uploads to the gallery.
 * @property {(playerid: number|string, type: any) => string} clientnotify.warning - Sends a warning.
 * @property {(playerid: number|string, targetname: string, mute?: boolean) => string} clientnotify.mute - Mutes a player.
 * @property {(playerid: number|string, targetname: string, hide?: boolean) => string} clientnotify.hide - Hides a player.
 * @property {(playerid: number|string, reason: string, targetname: string) => string} clientnotify.report - Reports a player.
 * @property {Object} drawcmd - Drawing commands.
 * @property {(x1: number|string, y1: number|string, x2: number|string, y2: number|string, color: number|string, size?: number|string, ispixel?: boolean, playerid?: number|string) => string} drawcmd.line - Draws a line.
 * @property {(x1: number|string, y1: number|string, x2: number|string, y2: number|string, color: number|string, size: number|string, ispixel?: boolean, playerid?: number|string) => string} drawcmd.erase - Erases a part of the drawing.
 * @property {(x: number|string, y: number|string, color: number|string, tolerance: number|string, r: number|string, g: number|string, b: number|string, a: number|string) => string} drawcmd.flood - Flood fills an area.
 * @property {(playerid: number|string) => string} drawcmd.undo - Undoes the last action.
 * @property {() => string} drawcmd.clear - Clears the drawing.
 */

/**
 * @typedef {Object} PlayerClass
 * @property {PlayerInstance[]} instances
 * @property {PlayerInstance} noConflict
 * @property {(inviteLink:string)=>URL} getSocketServerURL
 * @property {(inviteLink:string)=>string} getRoomID
 * @property {DrawariaOnlineMessageTypes} parseMessage
 */

/**
 * @typedef {Object} UI
 * @property {(selectors: string, parentElement?: ParentNode) => Element|null} querySelect - Returns the first element that is a descendant of node that matches selectors.
 * @property {(selectors: string, parentElement?: ParentNode) => NodeListOf<Element>} querySelectAll - Returns all element descendants of node that match selectors.
 * @property {(tagName: string, properties?: object) => Element} createElement - Creates an element and assigns properties to it.
 * @property {(element: Element, attributes: object) => void} setAttributes - Assigns attributes to an element.
 * @property {(element: Element, styles: object) => void} setStyles - Assigns styles to an element.
 * @property {(name?: string) => HTMLDivElement} createContainer - Creates a container element.
 * @property {() => HTMLDivElement} createRow - Creates a row element.
 * @property {(name: string) => HTMLElement} createIcon - Creates an icon element.
 * @property {(type: string, properties?: object) => HTMLInputElement} createInput - Creates an input element.
 * @property {(input: HTMLInputElement, properties?: object) => HTMLLabelElement} createLabelFor - Creates a label for an input element.
 * @property {(className?: string) => HTMLElement & { show: Function, hide: Function }} createSpinner - Creates a spinner element.
 * @property {(input: HTMLInputElement, addon: HTMLLabelElement|HTMLInputElement|HTMLButtonElement|HTMLElement) => HTMLDivElement} createGroup - Creates an input group element.
 * @property {(inputs: Array<HTMLLabelElement|HTMLInputElement|HTMLButtonElement|HTMLElement>) => HTMLDivElement} createInputGroup - Creates an input group element.
 */

/**
 * @typedef {Object} Other
 * @property {(message: string, styles?: string, application?: string) => void} log - Logs a message with styles.
 * @property {(size?: number) => string} uid - Generates a random UID.
 * @property {(byteArray: number[]) => string} toHexString - Converts a byte array to a hex string.
 * @property {(key: string, value: string) => void} setCookie - Sets a cookie.
 * @property {() => Array<*>&{addEventListener:(event:"delete"|"set",handler:(property:string,value:*)=>void)=>}} makeObservableArray - Creates an observable array.
 * @property {(message: string) => (Array<any> | object)} tryParseJSON - Tries to parse a JSON string.
 */

/**
 * @class
 * @typedef {Object} DefinableCore
 * @property {PlayerClass} Player
 * @property {UI} UI
 * @property {Other} helper
 */

/**
 * @typedef {Object} Definable
 * @property {PlayerClass} Player
 * @property {UI} UI
 * @property {()=>HTMLElement} createRow
 * @property {(submodule:Core)=>void} registerModule
 */

/**
 * @typedef {Object} Position
 * @prop {number} x
 * @prop {number} y
 */

/**
 * @typedef {Object} Color
 * @prop {number} r
 * @prop {number} g
 * @prop {number} b
 * @prop {number} a
 */

/**
 * @typedef {Object} Volume
 * @prop {number} width
 * @prop {number} height
 */

/**
 * @typedef {Position & Color} Pixel
 */

/**
 * @typedef {Pixel & Volume} Area
 */

/**
 * @typedef {Object} PromiseResponse
 * @prop {*} data
 * @prop {Error|string|undefined} error
 */
//#endregion TypeDefinitions

(function () {
  "use strict";

  /**
   * @param {Definable} definable
   * @param {DefinableCore} $core
   */
  function initialize(definable, $core) {
    /** @type {Definable} */
    const botplayer = new $core("Bot Player", "robot");
    const ui = $core.UI;
    definable.registerModule(botplayer);

    /* Row 1 */ {
      const row = botplayer.createRow();

      {
        const input = ui.createInput("button");
        const label = ui.createLabelFor(input, { title: "Enter Room" });
        label.appendChild(ui.createIcon("check"));
        label.classList.add("col");
        label.className = label.className.replace("secondary", "success");
        row.appendChild(label);

        input.addEventListener("click", function () {
          $core.Player.noConflict.enterRoom(ui.querySelect("#invurl").value);
        });
      }

      {
        const input = ui.createInput("button");
        const label = ui.createLabelFor(input, { title: "Switch Room" });
        label.appendChild(ui.createIcon("exchange-alt"));
        label.classList.add("col");
        row.appendChild(label);

        input.addEventListener("click", function () {
          $core.Player.noConflict.nextRoom();
        });
      }

      {
        const input = ui.createInput("button");
        const label = ui.createLabelFor(input, { title: "Leave Room" });
        label.appendChild(ui.createIcon("times"));
        label.classList.add("col");
        label.className = label.className.replace("secondary", "danger");
        row.appendChild(label);

        input.addEventListener("click", function () {
          $core.Player.noConflict.leaveRoom();
        });
      }
    }

    /* Row 2 */ {
      const row = botplayer.createRow();

      {
        const input = ui.createInput("file", { accept: "image/*" });
        const label = ui.createLabelFor(input, { title: "Upload Avatar" });
        label.appendChild(ui.createIcon("upload"));
        input.addEventListener("input", function () {
          loadFileAsImage(input.files[0]).then((response) => {
            if (response.data) {
              /** @type {HTMLImageElement} */
              const image = response.data;

              $core.helper.setCookie("uid", "_");

              fetch("https://drawaria.online/uploadavatarimage", {
                method: "POST",
                body: "imagedata=" + encodeURIComponent(image.src.replace("image/gif", "image/png")) + "&fromeditor=true",
                headers: { "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8" },
              })
                .then((res) => {
                  if (res.status === 200) {
                    res.text().then((body) => {
                      const data = body.split(".");
                      const player = $core.Player.noConflict;
                      player.uid = data[0];
                      player.wt = data[1];
                      if (player.isConnected) {
                        player.reconnect();
                      }

                      ui.querySelect("i,img", label).remove();
                      label.appendChild(
                        ui.createElement("img", {
                          src: `https://drawaria.online/avatar/cache/${body}.jpg`,
                          style: "width: 100%;",
                        })
                      );
                    });
                  } else {
                    ui.querySelect("#chatbox_messages").appendChild(ui.createElement("div", { textContent: res.statusText, className: "chatmessage systemchatmessage" }));
                  }
                })
                .catch((reason) => {
                  ui.querySelect("#chatbox_messages").appendChild(ui.createElement("div", { textContent: reason, className: "chatmessage systemchatmessage" }));
                });
              input.value = null;
            }
          });
        });
        row.appendChild(label);
      }

      {
        const botNameInput = ui.createInput("text");
        const botNameUpdateInput = ui.createInput("button");
        const botNameUpdateLabel = ui.createLabelFor(botNameUpdateInput, { title: "Update Name" });
        botNameUpdateLabel.appendChild(ui.createIcon("i-cursor"));

        botNameUpdateInput.addEventListener("click", function () {
          const player = $core.Player.noConflict;
          player.name = botNameInput.value;
          if (player.isConnected) {
            player.reconnect();
          }
        });

        const group = ui.createInputGroup(botNameInput, botNameUpdateLabel);
        group.style.flexGrow = "1";
        group.style.width = "min-content";
        row.appendChild(group);
      }
    }

    window.dispatchEvent(new CustomEvent("definable:playerbot:init", { detail: { main: botplayer, core: $core } }));
  }

  /**
   * @param {File} file
   * @returns {Promise<PromiseResponse>}
   */
  function loadFileAsImage(file) {
    return new Promise((resolve, reject) => {
      if (!(FileReader && file)) {
        reject({ data: undefined, error: "Native FileReader not present." });
      } else {
        const reader = new FileReader();
        reader.onload = function () {
          const image = new Image();
          image.src = reader.result;
          image.onload = function () {
            resolve({ data: image, error: undefined });
          };
        };
        reader.readAsDataURL(file);
      }
    });
  }

  window.addEventListener("definable:init", function (event) {
    const { main, core } = event.detail;
    if (!main || !core) return console.error("main: %o & core: %o", main, core);
    initialize(main, core);
  });
})();

(function () {
  "use strict";

  /**
   * @param {Definable} playerbot
   * @param {DefinableCore} $core
   */
  function initialize(playerbot, $core) {
    /** @type {Definable} */
    const sozials = new $core("Sozials", "comments");
    const ui = $core.UI;
    playerbot.registerModule(sozials);

    /* Row 1 */ {
      const row = sozials.createRow();

      const input = ui.createInput("button");
      const label = ui.createLabelFor(input, { title: "Send Message" });
      const messageInput = ui.createInput("text");
      label.appendChild(ui.createIcon("paper-plane"));
      input.addEventListener("click", function () {
        $core.Player.noConflict.send($core.Player.parseMessage.chatmsg(messageInput.value));
      });
      const group = ui.createInputGroup(messageInput, label);
      row.appendChild(group);
    }
    /* Row 2 */ {
      const row = sozials.createRow();
      ["thumbs-up", "heart", "paint-brush", "cocktail", "hand-peace", "feather-alt", "trophy", "mug-hot", "gift"].forEach((token, index) => {
        const input = ui.createInput("button");
        const label = ui.createLabelFor(input);
        label.appendChild(ui.createIcon(token));
        input.addEventListener("click", function () {
          ui.querySelectAll("#playerlist>.playerlist-row").forEach((player) => {
            $core.Player.noConflict.send($core.Player.parseMessage.clientcmd.settoken(Number(player.dataset.playerid), index));
          });
        });
        row.appendChild(label);
      });
    }

    /* Row 3 */ {
      const row = sozials.createRow();
      ui.querySelectAll("#gesturespickerselector .gesturespicker-container .gesturespicker-item img").forEach((node, index) => {
        const input = ui.createInput("button");
        const label = ui.createLabelFor(input);
        label.appendChild(ui.createElement("img", { src: node.src, style: "width: 100%;" }));
        input.addEventListener("click", function () {
          $core.Player.noConflict.send($core.Player.parseMessage.sendgesture(index));
        });
        row.appendChild(label);
      });
    }

    ui.querySelect("#definableStyles").textContent += "\n#definable .row>label:has(img) { width: 2rem; padding: 0; }";
  }

  window.addEventListener("definable:playerbot:init", function (event) {
    const { main, core } = event.detail;
    if (!main || !core) return console.error("main: %o & core: %o", main, core);
    initialize(main, core);
  });
})();