Library For Mxobot

Library for Mxobot

このスクリプトは単体で利用できません。右のようなメタデータを含むスクリプトから、ライブラリとして読み込まれます: // @require https://update.greasyfork.org/scripts/461063/1374966/Library%20For%20Mxobot.js

このスクリプトの質問や評価の投稿はこちら通報はこちらへお寄せください。
// ==UserScript==
// @name         Mxobot Library
// @namespace    http://tampermonkey.net/<3nevin
// @version      1.3
// @description  Library for Mxobot
// @author       @ngixl
// @match        https://pixelplace.io/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=pixelplace.io
// @grant        unsafeWindow
// @require      https://update.greasyfork.org/scripts/461063/1371348/Library%20For%20MxoBot.js
 
/* globals unsafeWindow*/
/*jshint esversion: 11 */
 
Object.defineProperty(unsafeWindow, "console", {
  value: console,
  writable: false,
});
 
class NevinLoggerFactory {
  static TEMPLATE = "%c[NevinCore] %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 = NevinLoggerFactory.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 <= NevinLoggerFactory.LEVEL_INFO) {
      this.dispatch(
        NevinLoggerFactory.TEMPLATE,
        NevinLoggerFactory.CSS_INFO,
        NevinLoggerFactory.TEMPLATE_INFO,
        msg
      );
    }
  }
  warning(msg) {
    if (this.LEVEL <= NevinLoggerFactory.LEVEL_WARNING) {
      this.dispatch(
        NevinLoggerFactory.TEMPLATE,
        NevinLoggerFactory.CSS_WARNING,
        NevinLoggerFactory.TEMPLATE_WARNING,
        msg
      );
    }
  }
  error(msg) {
    if (this.LEVEL <= NevinLoggerFactory.LEVEL_ERROR) {
      this.dispatch(
        NevinLoggerFactory.TEMPLATE,
        NevinLoggerFactory.CSS_ERROR,
        NevinLoggerFactory.TEMPLATE_ERROR,
        msg
      );
      throw Error(msg);
    }
  }
}
 
class NevinImageConverter {
    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.BOT_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 = NevinImageConverter.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 NevinLogger = new NevinLoggerFactory();
 
class NevinPalette {
  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 = NevinPalette.PALETTE_LOAD_STATIC;
      NevinLogger.warning(
        "NevinPalette invoked without specifying the loading type."
      );
    }
    NevinLogger.info(
      "NevinPalette loading with type: " +
        (type == NevinPalette.PALETTE_LOAD_DYNAMIC ? "DYNAMIC" : "STATIC")
    );
    if (type == NevinPalette.PALETTE_LOAD_DYNAMIC) {
      const palette = document.getElementById("palette-buttons");
      if (!palette) {
        NevinLogger.error(
          "Palette requested to be loaded dynamically but HTML is not loaded yet."
        );
      }
      this.colors = [];
      this.indexes = [];
      const palette_buttons = Array.from(palette.children);
      NevinLogger.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(NevinPalette.hexStrToHex(color.hex));
        this.indexes.push(parseInt(color.index));
      }
    } else {
      this.colors = NevinPalette.STATIC_COLORS;
      this.indexes = NevinPalette.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 {
      NevinLogger.error("Argument is neither type of Array nor a number");
    }
  }
  constructor(type) {
    this.colors = undefined;
    this.indexes = undefined;
    this.initalizePalette(type);
  }
}
 
class NevinOriginalWebSocket extends WebSocket {}
 
class NevinWS {
  constructor(nevinPalette, webSocket) {
    if (webSocket) {
      this.ws = webSocket;
      if (nevinPalette) {
        this.nevinMapCache = new NevinMapCache(nevinPalette, this.ws);
        this.nevinMapCache.addPixelChangeListener(this);
      }
    } else {
      this.ws = undefined;
      var proxy = this;
      this.hook = class extends WebSocket {
        constructor(a, b) {
          super(a, b);
          NevinLogger.info("NevinWS has hooked the game WebSocket connection.");
          proxy.ws = this;
          proxy.nevinMapCache.addPixelChangeListener(proxy);
        }
      };
      if (typeof unsafeWindow !== undefined) {
        if (unsafeWindow.WebSocket != NevinWS) {
          unsafeWindow.WebSocket = this.hook;
        }
      }
      this.nevinMapCache = new NevinMapCache(nevinPalette, this);
    }
  }
}
 
var map_cache;
class NevinMapCache {
  init(nevinPalette, nevinWS) {
    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 = () => {
      NevinLogger.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 = nevinPalette.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(nevinPalette, nevinWS) {
    this.init(nevinPalette, nevinWS);
  }
  getPixel(x, y) {
    var i = y * this.map_width + x;
    return this.cache[i];
  }
  addPixelChangeListener(nevinWS) {
    nevinWS.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 NevinImagePicker {
  static requestImageFromFileDialog(NevinPalette) {
    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) {
          NevinLogger.info("Image loaded");
          resolve(new NevinImage(e.target.result, NevinPalette));
        };
        if (input.files && input.files[0]) {
          reader.readAsDataURL(input.files[0]);
        }
      });
    });
  }
  static addClipboardListener(NevinPalette, callback) {
    document.addEventListener("paste", function (paste_e) {
      var items = (paste_e.clipboardData || paste_e.originalEvent.clipboardData)
        .items;
      NevinLogger.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) {
          NevinLogger.info("Readed image from clipboard!");
          callback(new NevinImage(e.target.result, NevinPalette));
        };
        reader.readAsDataURL(blob);
      }
    });
  }
}
 
function NevinWaitForElm(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 NevinCreateWorker(code) {
  var blob = new Blob([code], { type: "text/javascript" });
 
  var url = URL.createObjectURL(blob);
 
  var worker = new Worker(url);
  return worker;
}
 
class NevinImage {
  constructor(x, palette) {
    this.NevinPalette = 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) {
      NevinLogger.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
      );
      NevinLogger.info('Dithering loaded image!');
      this.image_data = NevinImageConverter.floydSteinberg(this.image_data, this.image.width, this.image.height, palette);
    };
  }
  convertToTasks(sx, sy, nevinWS) {
    if (typeof sx != "number" || typeof sy != "number") {
      NevinLogger.error(
        "Tried to convert an image to tasks yet the starting coordinates are not a number."
      );
    }
    if (!(nevinWS instanceof NevinWS)) {
      NevinLogger.error(
        "NevinImage.convertToTasks requires an NevinWS 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.NevinPalette.getIndex([r, g, b]);
      const c_color = nevinWS.nevinMapCache.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 NevinEngine {
  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(NevinWS, timeout) {
    if (!NevinWS || !timeout) {
      return;
    }
    this.tasks = [];
    this.NevinWS = NevinWS;
    this.intervalID = setInterval(() => {
      const task = this.tasks.shift();
      if (!task) {
        return;
      }
      if (this.NevinWS.nevinMapCache.getPixel(task[0], task[1]) == task[2]) {
        return;
      }
      this.NevinWS.ws.send(NevinEngine.convertToTask(...task));
    }, timeout);
  }
}
 
class NevinEngineMultiBot extends NevinEngine {
  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) {
      NevinLogger.warning(
        "Auth informations are not defined. (Maybe not logged in?)"
      );
      return;
    }
    const boardId = parseInt(location.pathname.replace("/", "").split("-")[0]);
    const socket = new NevinOriginalWebSocket(
      "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();
        NevinLogger.error(json);
      }
    };
    socket.onclose = () => {
      NevinLogger.info("User Disconnected");
    };
    const nevinWS = new NevinWS(undefined, socket);
    this.sockets.push(nevinWS);
  }
  addAccountFromNevinWS(nevinWS) {
    this.sockets.push(nevinWS);
  }
  constructor(timeout, nevinPalette) {
    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(NevinEngine.convertToTask(...task));
      setTimeout(this.interval, timeout / this.sockets.length);
    }
    interval = interval.bind(this);
    interval();
    this.interval = interval;
  }
}
class NevinProtect {
  constructor(core) {
    NevinLogger.info("NevinProtect has been opened.");
    this.core = core;
    this.nimage = undefined;
    this.coordinates = undefined;
    this.working = false;
    this.core.nevinWS.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) {
            NevinLogger.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 NevinCore {
  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") {
      NevinLogger.warning("User is not logged in!");
    } else {
      NevinLogger.info("Logged in as " + json.user.name);
    }
  }
  constructor(options) {
    this.testAccountValidation();
    this.palette = new NevinPalette(NevinPalette.PALETTE_LOAD_STATIC);
    //        this.accountManager = new NevinAccountManager();
    this.nevinWS = new NevinWS(this.palette); //, this.accountManager);
    if (options.multibot) {
      this.engine = new NevinEngineMultiBot(options.timeout);
      localStorage.nevinAccounts = localStorage.nevinAccounts || "[]";
      const nevinAccounts = JSON.parse(localStorage.nevinAccounts);
      unsafeWindow.addThisAccount = function () {
        const session_account = NevinEngineMultiBot.getAccountDetailsFromCookie(
          document.cookie
        ).join("_AND_");
        if (session_account[0] && !nevinAccounts.includes(session_account)) {
          nevinAccounts.push(session_account);
        }
        localStorage.nevinAccounts = JSON.stringify(nevinAccounts);
      };
      for (let account of nevinAccounts) {
        const [authId, authToken, authKey] = account.split("_AND_");
        if (!authId || !authToken || !authKey) {
          console.error(account);
          NevinLogger.error("Local account is corrupted");
        }
        this.engine.addAccountFromCookies(authId, authToken, authKey);
      }
    } else {
      this.engine = new NevinEngine(this.nevinWS, options.timeout);
    }
    this.picker = NevinImagePicker;
    this.logger = NevinLogger;
  }
}