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