Library For Mxobot

Library for Mxobot

目前為 2023-08-10 提交的版本,檢視 最新版本

此腳本不應該直接安裝,它是一個供其他腳本使用的函式庫。欲使用本函式庫,請在腳本 metadata 寫上: // @require https://update.gf.qytechs.cn/scripts/461063/1233414/Library%20For%20Mxobot.js

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         MXO LİBRARY
// @namespace    mxo
// @version      1.2
// @description  LİBRARY FOR MXOBOT
// @author       @ngixl
// @match        https://pixelplace.io/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=pixelplace.io
// @grant        unsafeWindow
// @require      https://greasyfork.org/scripts/462620-mxobot-example-bot/code/MxoBot%20Example%20Bot.js

/* globals unsafeWindow*/
/*jshint esversion: 11 */

Object.defineProperty(unsafeWindow, "console", {
    value: console,
    writable: false,
  });
  class MxoLoggerFactory {
    static TEMPLATE = "%c[MxoCore] %s: %s";
    static CSS_INFO = "color:green";
    static CSS_WARNING = "color:yellow;";
    static CSS_ERROR = "color:red;font-weight: bold;";
    static TEMPLATE_INFO = "INFO";
    static TEMPLATE_WARNING = "WARNING";
    static TEMPLATE_ERROR = "ERROR";
    static LEVEL_INFO = 0;
    static LEVEL_WARNING = 1;
    static LEVEL_ERROR = 2;
    LEVEL = MxoLoggerFactory.LEVEL_INFO;
    constructor() {
      this.listeners = [];
      this.listeners.push(function (template, css, level, msg) {
        console.log(template, css, level, msg);
      });
    }
    dispatch(template, css, level, msg) {
      this.listeners.forEach((listener) => {
        listener(template, css, level, msg);
      });
    }
    info(msg) {
      if (this.LEVEL <= MxoLoggerFactory.LEVEL_INFO) {
        this.dispatch(
          MxoLoggerFactory.TEMPLATE,
          MxoLoggerFactory.CSS_INFO,
          MxoLoggerFactory.TEMPLATE_INFO,
          msg
        );
      }
    }
    warning(msg) {
      if (this.LEVEL <= MxoLoggerFactory.LEVEL_WARNING) {
        this.dispatch(
          MxoLoggerFactory.TEMPLATE,
          MxoLoggerFactory.CSS_WARNING,
          MxoLoggerFactory.TEMPLATE_WARNING,
          msg
        );
      }
    }
    error(msg) {
      if (this.LEVEL <= MxoLoggerFactory.LEVEL_ERROR) {
        this.dispatch(
          MxoLoggerFactory.TEMPLATE,
          MxoLoggerFactory.CSS_ERROR,
          MxoLoggerFactory.TEMPLATE_ERROR,
          msg
        );
        throw Error(msg);
      }
    }
  }

  class MxoImageConverter {
      static getClosestColor(r, g, b, palette) {
          let closestColor = {r: 0, g: 0, b: 0};
          let closestDistance = Number.MAX_VALUE;
          for (let i = 0;i < palette.colors.length; i++) {
              let bigint = palette.colors[i];
              let p_r = (bigint >> 16) & 255;
              let p_g = (bigint >> 8) & 255;
              let p_b = bigint & 255;
              let distance = (r - p_r)**2 + (g - p_g)**2 + (b - p_b)**2;
              if (distance < closestDistance) {
                  closestColor = {r: p_r, g: p_g, b: p_b};
                  closestDistance = distance;
              }
          }
          return closestColor;
      }
      static floydSteinberg(img_data, w, h, palette) {
          if (unsafeWindow.YURIBOT_DO_NOT_DITHER === true) {
              return img_data;
          }
          let dithered = new Uint8ClampedArray(img_data.data);
          let error_matrix = new Float32Array(w * h * 4);
          for (let y = 0; y < h; y++) {
              for (let x = 0;x < w; x++) {
                  let i = (y * w + x) * 4;
                  let r = img_data.data[i] + error_matrix[i];
                  let g = img_data.data[i + 1] + error_matrix[i + 1];
                  let b = img_data.data[i + 2] + error_matrix[i + 2];
                  let closest = MxoImageConverter.getClosestColor(r, g, b, palette);
                  dithered[i] = closest.r;
                  dithered[i + 1] = closest.g;
                  dithered[i + 2] = closest.b;
                  dithered[i + 3] = img_data.data[i + 3];
                  let err_r = r - closest.r;
                  let err_g = g - closest.g;
                  let err_b = b - closest.b;
                  if (x + 1 < w) {
                      error_matrix[i + 4] += err_r * 7 / 16;
                      error_matrix[i + 5] += err_g * 7 / 16;
                      error_matrix[i + 6] += err_b * 7 / 16;
                  }
                  if (y + 1 < h) {
                      if (x > 0) {
                          error_matrix[i + 4 * w - 4] += err_r * 3 / 16;
                          error_matrix[i + 4 * w - 3] += err_g * 3 / 16;
                          error_matrix[i + 4 * w - 2] += err_b * 3 / 16;
                      }
                      error_matrix[i + 4 * w] += err_r * 5 / 16;
                      error_matrix[i + 4 * w + 1] += err_g * 5 / 16;
                      error_matrix[i + 4 * w + 2] += err_b * 5 / 16;
                      if (x + 1 < w) {
                          error_matrix[i + 4 * w + 4] += err_r * 1 / 16;
                          error_matrix[i + 4 * w + 5] += err_g * 1 / 16;
                          error_matrix[i + 4 * w + 6] += err_b * 1 / 16;
                      }
                  }
              }
          }
          const dithered_img_data = new ImageData(dithered, w, h);
          return dithered_img_data;
      }
  }

  const MxoLogger = new MxoLoggerFactory();

  class MxoPalette {
    static PALETTE_LOAD_STATIC = 0;
    static PALETTE_LOAD_DYNAMIC = 1;
    static hexStrToHex(hex_str) {
      return parseInt(hex_str.slice(1), 16);
    }
    static STATIC_COLORS = [
      16777215, 12895428, 8947848, 5592405, 2236962, 0, 13880, 26112, 1799168,
      4681808, 2273612, 179713, 5366041, 9756740, 10025880, 16514907, 15063296,
      15121932, 15045888, 16740352, 16726276, 15007744, 13510969, 16728426,
      10420224, 7012352, 16741727, 10512962, 6503455, 10048269, 12275456,
      16762015, 16768972, 16754641, 13594340, 8201933, 15468780, 8519808, 3342455,
      132963, 5308671, 234, 281599, 23457, 6652879, 3586815, 33735, 54237,
      4587464, 11921646,
    ];
    static STATIC_INDEX = [
      0, 1, 2, 3, 4, 5, 39, 6, 49, 40, 7, 8, 9, 10, 41, 11, 12, 13, 14, 42, 21,
      20, 43, 44, 19, 18, 23, 15, 17, 16, 22, 24, 25, 26, 27, 45, 28, 29, 46, 31,
      30, 32, 33, 47, 34, 35, 36, 37, 38, 48,
    ];
    initalizePalette(type) {
      if (type == undefined) {
        type = MxoPalette.PALETTE_LOAD_STATIC;
        MxoLogger.warning(
          "MxoPalette invoked without specifying the loading type."
        );
      }
      MxoLogger.info(
        "MxoPalette loading with type: " +
          (type == MxoPalette.PALETTE_LOAD_DYNAMIC ? "DYNAMIC" : "STATIC")
      );
      if (type == MxoPalette.PALETTE_LOAD_DYNAMIC) {
        const palette = document.getElementById("palette-buttons");
        if (!palette) {
          MxoLogger.error(
            "Palette requested to be loaded dynamically but HTML is not loaded yet."
          );
        }
        this.colors = [];
        this.indexes = [];
        const palette_buttons = Array.from(palette.children);
        MxoLogger.info("Dynamic loading found these DOM elements:");
        console.log(palette_buttons);
        for (const palette_button of palette_buttons) {
          const color = {
            hex: palette_button.getAttribute("title"),
            index: palette_button.getAttribute("data-id"),
          };

          this.colors.push(MxoPalette.hexStrToHex(color.hex));
          this.indexes.push(parseInt(color.index));
        }
      } else {
        this.colors = MxoPalette.STATIC_COLORS;
        this.indexes = MxoPalette.STATIC_INDEX;
      }
    }
    getIndex(x) {
      if (x instanceof Array) {
        const [r, g, b] = x;
        const hex = (r << 16) | (g << 8) | b;
        return this.indexes[this.colors.indexOf(hex)] ?? -1;
      } else if (typeof x == "number") {
        return this.indexes[this.colors.indexOf(x)] ?? -1;
      } else {
        MxoLogger.error("Argument is neither type of Array nor a number");
      }
    }
    constructor(type) {
      this.colors = undefined;
      this.indexes = undefined;
      this.initalizePalette(type);
    }
  }

  class MxoOriginalWebSocket extends WebSocket {}

  class MxoWS {
    constructor(mxoPalette, webSocket) {
      if (webSocket) {
        this.ws = webSocket;
        if (mxoPalette) {
          this.mxoMapCache = new MxoMapCache(mxoPalette, this.ws);
          this.mxoMapCache.addPixelChangeListener(this);
        }
      } else {
        this.ws = undefined;
        var proxy = this;
        this.hook = class extends WebSocket {
          constructor(a, b) {
            super(a, b);
            MxoLogger.info("MxoWS has hooked the game WebSocket connection.");
            proxy.ws = this;
            proxy.mxoMapCache.addPixelChangeListener(proxy);
          }
        };
        if (typeof unsafeWindow !== undefined) {
          if (unsafeWindow.WebSocket != MxoWS) {
            unsafeWindow.WebSocket = this.hook;
          }
        }
        this.mxoMapCache = new MxoMapCache(mxoPalette, this);
      }
    }
  }

  var map_cache;
  class MxoMapCache {
    init(mxoPalette, mxoWS) {
      var canvas_id = parseInt(location.pathname.replace("/", "").split("-")[0]);
      var url = `https://pixelplace.io/canvas/${canvas_id}.png?a=${
        Math.floor(Math.random() * 1e9) + 1e9
      }`;
      var canvas_image = new Image();
      var spare_canvas = document.createElement("canvas");
      this.before_poll = [];
      this.cache = map_cache;
      if (this.cache) return;
      spare_canvas.ctx = spare_canvas.getContext("2d");
      canvas_image.onload = () => {
        MxoLogger.info("Map loaded");
        this.map_width = canvas_image.naturalWidth;
        this.map_height = canvas_image.naturalHeight;
        spare_canvas.width = this.map_width;
        spare_canvas.height = this.map_height;
        spare_canvas.ctx.drawImage(
          canvas_image,
          0,
          0,
          this.map_width,
          this.map_height
        );
        var data = spare_canvas.ctx.getImageData(
          0,
          0,
          this.map_width,
          this.map_height
        ).data;
        this.cache = new Int8Array(this.map_width * this.map_height);
        for (let i = 0; i < data.length; i += 4) {
          // slice is slower in custom arrays such as Int8Array
          var r = data[i];
          var g = data[i + 1];
          var b = data[i + 2];
          const i_color = mxoPalette.getIndex([r, g, b]);
          this.cache[i >> 2] = i_color;
        }
        for (let packet of this.before_poll) {
          this.cache[packet[0]] = packet[1];
        }
        this.before_poll = undefined;
      };
      canvas_image.src = url;
    }
    constructor(mxoPalette, mxoWS) {
      this.init(mxoPalette, mxoWS);
    }
    getPixel(x, y) {
      var i = y * this.map_width + x;
      return this.cache[i];
    }
    addPixelChangeListener(mxoWS) {
      mxoWS.ws.addEventListener("message", (e) => {
        var data = e.data;
        if (!data.startsWith('42["p",')) {
          return;
        }
        var packets = JSON.parse(data.replace("42", ""))[1];
        for (let packet of packets) {
          var [x, y, color] = packet;
          var i = this.map_width * y + x;
          if (this.cache) {
            this.cache[i] = color;
          } else {
            this.before_poll.push([i, color]);
          }
        }
      });
    }
  }

  class MxoImagePicker {
    static requestImageFromFileDialog(MxoPalette) {
      return new Promise((resolve) => {
        const input = document.createElement("input");
        input.type = "file";
        input.accept = "image/*";
        input.click();
        input.addEventListener("change", function () {
          const reader = new FileReader();
          reader.onload = function (e) {
            MxoLogger.info("Image loaded");
            resolve(new MxoImage(e.target.result, MxoPalette));
          };
          if (input.files && input.files[0]) {
            reader.readAsDataURL(input.files[0]);
          }
        });
      });
    }
    static addClipboardListener(MxoPalette, callback) {
      document.addEventListener("paste", function (paste_e) {
        var items = (paste_e.clipboardData || paste_e.originalEvent.clipboardData)
          .items;
        MxoLogger.info(
          "Recieved data from clipboard: " + JSON.stringify(items)
        );
        var blob = null;
        for (var i = 0; i < items.length; i++) {
          if (items[i].type.indexOf("image") === 0) {
            blob = items[i].getAsFile();
          }
        }
        if (blob !== null) {
          var reader = new FileReader();
          reader.onload = function (e) {
            MxoLogger.info("Readed image from clipboard!");
            callback(new MxoImage(e.target.result, MxoPalette));
          };
          reader.readAsDataURL(blob);
        }
      });
    }
  }

  function MxoWaitForElm(selector) {
    return new Promise((resolve) => {
      if (document.querySelector(selector)) {
        return resolve(document.querySelector(selector));
      }

      const observer = new MutationObserver((mutations) => {
        if (document.querySelector(selector)) {
          resolve(document.querySelector(selector));
          observer.disconnect();
        }
      });

      observer.observe(document.body, {
        childList: true,
        subtree: true,
      });
    });
  }

  function MxoCreateWorker(code) {
    var blob = new Blob([code], { type: "text/javascript" });

    var url = URL.createObjectURL(blob);

    var worker = new Worker(url);
    return worker;
  }

  class MxoImage {
    constructor(x, palette) {
      this.MxoPalette = palette;
      this.image = undefined;
      this.image_canvas = document.createElement("canvas");
      this.image_context = this.image_canvas.getContext("2d");
      if (x instanceof Image) {
        this.image = x;
      } else if (typeof x == "string") {
        this.image = new Image();
        this.image.src = x;
      }
      if (this.image == undefined) {
        MxoLogger.error("Argument is neither type of Image nor a string");
      }
      this.image_context.mozImageSmoothingEnabled = false;
      this.image.onload = () => {
        this.image_canvas.width = this.image.width;
        this.image_canvas.height = this.image.height;
        this.image_context.drawImage(this.image, 0, 0);
        this.image_data = this.image_context.getImageData(
          0,
          0,
          this.image_canvas.width,
          this.image_canvas.height
        );
        MxoLogger.info('Dithering loaded image!');
        this.image_data = MxoImageConverter.floydSteinberg(this.image_data, this.image.width, this.image.height, palette);
      };
    }
    convertToTasks(sx, sy, mxoWS) {
      if (typeof sx != "number" || typeof sy != "number") {
        MxoLogger.error(
          "Tried to convert an image to tasks yet the starting coordinates are not a number."
        );
      }
      if (!(mxoWS instanceof MxoWS)) {
        MxoLogger.error(
          "MxoImage.convertToTasks requires an MxoWS in new versions. Please update your code."
        );
      }
      var _tasks = [];
      for (let i = 0; i < this.image_data.data.length; i += 4) {
        var [r, g, b, a] = this.image_data.data.slice(i, i + 4);
        if (a == 0) {
          continue;
        }
        var x = (i / 4) % this.image_data.width;
        var y = Math.floor(i / 4 / this.image_data.width);
        var colorIndex = this.MxoPalette.getIndex([r, g, b]);
        const c_color = mxoWS.mxoMapCache.getPixel(sx + x, sy + y);
        if (colorIndex == -1) {
          console.log([r, g, b]);
        }
        if (c_color == colorIndex || c_color == -1 || colorIndex == -1) {
          continue;
        }
        _tasks.push([sx + x, sy + y, colorIndex]);
      }
      return _tasks;
    }
  }

  class MxoEngine {
    static convertToTask(x, y, colorIndex, packetType) {
      if (packetType == undefined) {
        packetType = 1;
      }
      return `42["p",${JSON.stringify([x, y, colorIndex, packetType])}]`;
    }
    putPixel(x, y, colorIndex) {
      this.tasks.push([x, y, colorIndex]);
    }
    putPixelWithPriority(x, y, colorIndex) {
      this.tasks.unshift([x, y, colorIndex]);
    }
    constructor(MxoWS, timeout) {
      if (!MxoWS || !timeout) {
        return;
      }
      this.tasks = [];
      this.MxoWS = MxoWS;
      this.intervalID = setInterval(() => {
        const task = this.tasks.shift();
        if (!task) {
          return;
        }
        if (this.MxoWS.mxoMapCache.getPixel(task[0], task[1]) == task[2]) {
          return;
        }
        this.MxoWS.ws.send(MxoEngine.convertToTask(...task));
      }, timeout);
    }
  }

  class MxoEngineMultiBot extends MxoEngine {
    static getAccountDetailsFromCookie(cookie) {
      const dict = cookie
        .split("; ")
        .map((a) => a.split("="))
        .reduce(function (b, a) {
          if (!["authKey", "authToken", "authId"].includes(a[0])) return b;
          b[a[0]] = a[1];
          return b;
        }, {});
      return [dict.authId, dict.authToken, dict.authKey];
    }
    addAccountFromCookies(authId, authToken, authKey) {
      if (!authId || !authToken || !authKey) {
        MxoLogger.warning(
          "Auth informations are not defined. (Maybe not logged in?)"
        );
        return;
      }
      const boardId = parseInt(location.pathname.replace("/", "").split("-")[0]);
      const socket = new MxoOriginalWebSocket(
        "wss://pixelplace.io/socket.io/?EIO=3&transport=websocket"
      );
      socket.headless = true;
      socket.onmessage = ({ data }) => {
        const [code, msg] = data.split(/(?<=^\d+)(?=[^\d])/);
        if (code == "40") {
          socket.send(
            "42" +
              JSON.stringify(["init", { authKey, authToken, authId, boardId }])
          );
        }

        const message = JSON.parse(msg || "[]");
        if (message.pingInterval)
          socket.ping = setInterval(() => socket.send("2"), message.pingInterval);

        if (!message.length) return arguments;
        const [event, json] = message;
        if (event == "throw.error") {
          socket.close();
          MxoLogger.error(json);
        }
      };
      socket.onclose = () => {
        MxoLogger.info("User Disconnected");
      };
      const mxoWS = new MxoWS(undefined, socket);
      this.sockets.push(mxoWS);
    }
    addAccountFromMxoWS(mxoWS) {
      this.sockets.push(mxoWS);
    }
    constructor(timeout, mxoPalette) {
      super();
      this.tasks = [];
      this.sockets = [];
      this.counter = 0;
      function interval() {
        if (this.sockets.length == 0) {
          setTimeout(interval, 100);
          return;
        }
        const task = this.tasks.shift();
        if (!task) {
          setTimeout(interval, timeout / this.sockets.length);
          return;
        }
        console.log(this);
        this.counter = (this.counter + 1) % this.sockets.length;
        this.sockets[this.counter]?.ws?.send(MxoEngine.convertToTask(...task));
        setTimeout(this.interval, timeout / this.sockets.length);
      }
      interval = interval.bind(this);
      interval();
      this.interval = interval;
    }
  }
  class MxoProtect {
    constructor(core) {
      MxoLogger.info("MxoProtect has been opened.");
      this.core = core;
      this.nimage = undefined;
      this.coordinates = undefined;
      this.working = false;
      this.core.mxoWS.ws.addEventListener(
        "message",
        function (e) {
          if (!this.working) return;
          if (!this.nimage) return;
          if (!this.coordinates) return;
          var data = e.data;
          if (!data.startsWith('42["p",')) {
            return;
          }
          var packets = JSON.parse(data.replace("42", ""))[1];
          for (let packet of packets) {
            var [x, y, color] = packet;
            var image_width = this.nimage.image.width;
            var image_height = this.nimage.image.height;
            var image_x = this.coordinates[0];
            var image_y = this.coordinates[1];
            var image_xmax = image_width + image_x;
            var image_ymax = image_height + image_y;
            if (!this.nimage) {
              continue;
            }
            if (
              x < image_x ||
              x >= image_xmax ||
              y < image_y ||
              y >= image_ymax
            ) {
              continue;
            }
            var img_data_index = 4 * (x - image_x + image_width * (y - image_y));
            var [r, g, b, a] = this.nimage.image_data.data.slice(
              img_data_index,
              img_data_index + 4
            );
            if (a == 0) continue;
            var image_color_i = this.core.palette.getIndex([r, g, b]);
            if (image_color_i == undefined) {
              MxoLogger.error(
                JSON.stringify([[r, g, b], image_color_i, img_data_index])
              );
            }
            if (image_color_i != color) {
              this.core.engine.putPixelWithPriority(x, y, image_color_i);
            }
          }
        }.bind(this)
      );
    }
    start() {
      this.working = true;
    }
    stop() {
      this.working = false;
    }
    load(nimage, coordinates) {
      this.nimage = nimage;
      this.coordinates = coordinates;
    }
  }

  class MxoCore {
    async testAccountValidation() {
      const req = await fetch(
        "https://pixelplace.io/api/get-painting.php?id=7&connected=1"
      );
      const json = await req.json();
      if (json.user.name == "Guest") {
        MxoLogger.warning("User is not logged in!");
      } else {
        MxoLogger.info("Logged in as " + json.user.name);
      }
    }
    constructor(options) {
      this.testAccountValidation();
      this.palette = new MxoPalette(MxoPalette.PALETTE_LOAD_STATIC);
      //        this.accountManager = new MxoAccountManager();
      this.mxoWS = new MxoWS(this.palette); //, this.accountManager);
      if (options.multibot) {
        this.engine = new MxoEngineMultiBot(options.timeout);
        localStorage.mxoAccounts = localStorage.mxoAccounts || "[]";
        const mxoAccounts = JSON.parse(localStorage.mxoAccounts);
        unsafeWindow.addThisAccount = function () {
          const session_account = MxoEngineMultiBot.getAccountDetailsFromCookie(
            document.cookie
          ).join("_AND_");
          if (session_account[0] && !mxoAccounts.includes(session_account)) {
            mxoAccounts.push(session_account);
          }
          localStorage.mxoAccounts = JSON.stringify(mxoAccounts);
        };
        for (let account of mxoAccounts) {
          const [authId, authToken, authKey] = account.split("_AND_");
          if (!authId || !authToken || !authKey) {
            console.error(account);
            MxoLogger.error("Local account is corrupted");
          }
          this.engine.addAccountFromCookies(authId, authToken, authKey);
        }
      } else {
        this.engine = new MxoEngine(this.mxoWS, options.timeout);
      }
      this.picker = MxoImagePicker;
      this.logger = MxoLogger;
    }
  }