Greasy Fork

Library For Mxobot

Library for Mxobot

目前为 2023-08-11 提交的版本。查看 最新版本

此脚本不应直接安装,它是一个供其他脚本使用的外部库。如果您需要使用该库,请在脚本元属性加入:// @require https://update.greasyfork.cloud/scripts/461063/1233866/Library%20For%20Mxobot.js

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