Skip to content

Instantly share code, notes, and snippets.

@GREEB
Created April 11, 2022 14:24
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save GREEB/c2b9a118fbcf5061adf238fb0a6f8f6f to your computer and use it in GitHub Desktop.
Save GREEB/c2b9a118fbcf5061adf238fb0a6f8f6f to your computer and use it in GitHub Desktop.
// ==UserScript==
// @name VeggieBot
// @namespace https://discord.gg/grHtzeRFAf
// @version 1.49
// @description Bot for vegan banners on pixelcanvas.io
// @author Vegans
// @match https://pixelcanvas.io/*
// @icon https://pixelcanvas.io/favicon.ico
// @updateURL https://www.thechristmasstation.org/vegan.user.js
// @downloadURL https://www.thechristmasstation.org/vegan.user.js
// @grant none
// ==/UserScript==
//jshint esversion: 10
(function() {
'use strict';
//check or generate bot ID
window.botID = Math.floor(Date.now() / 1000)
//load library for png manipulation
const pngLib = document.createElement("script");
pngLib.src = "https://www.thechristmasstation.org/veggiebot/pngtoy.min.js";
pngLib.type = "application/javascript";
document.body.appendChild(pngLib);
//info panel popup
const infoPanel = document.createElement("div");
infoPanel.classList.add("infoPanel");
infoPanel.style.display = "none";
infoPanel.innerHTML = '<div style="position: absolute;z-index: 999;width: 100vw;height: 100vh;background-color: #000a;display: flex;justify-content: center;align-items: center;"><div style="background-color: white;padding: 20px;border-radius: 10px;"><strong>Debug Info</strong><br>Version: <span class="version">a</span><br>Bot ID: <span class="botID">123</span><br><button class="closeInfoButton" style="background-color: cornflowerblue;padding: 10px;border-radius: 6px;margin-top: 15px;">Close</button></div></div>';
document.body.appendChild(infoPanel);
//close button function
document.querySelector(".closeInfoButton").onclick = function closeInfo() {document.querySelector(".infoPanel").style.display = "none";};
//load values into info panel
document.querySelector(".botID").innerHTML = botID;
document.querySelector(".version").innerHTML = GM_info.script.version;
//create flex container for UI
const flex = document.createElement("div");
flex.style = "position: absolute; display: flex; flex-flow: row wrap; gap: 5px; padding: 5px; background-color: black; border-radius: 0 0 13px 0;";
document.body.appendChild(flex);
//add loading indicator
const loadingIndicator = document.createElement("div");
loadingIndicator.innerHTML = "Loading...";
loadingIndicator.style = "background-color: cornflowerblue; border-radius: 10px; padding: 10px;";
flex.appendChild(loadingIndicator);
//add pixels placed counter to UI
const pixelCounter = document.createElement("div");
pixelCounter.style = "background-color: cornflowerblue; border-radius: 10px; padding: 10px; display: none;";
//add todo counter to UI
const todoCounter = document.createElement("div");
todoCounter.style = "background-color: cornflowerblue; border-radius: 10px; padding: 10px; display: none;";
todoCounter.innerHTML = "Loading...";
flex.appendChild(todoCounter);
//add info button to UI
const infoButton = document.createElement("button");
infoButton.style = "background-color: cornflowerblue; border-radius: 10px; padding: 10px; font-weight: 900; width: 44px; text-align: center;";
infoButton.onclick = function showInfo() {document.querySelector(".infoPanel").style.display = "block";};
infoButton.innerHTML = "?";
flex.appendChild(infoButton);
let design = { //old design variable
xCoord: -148,
yCoord: 9950,
};
const designArray = []; //array of design objects
//when page is done loading, start bot
window.onload = async function() {
var pngtoy = new PngToy();
const rawDesignArray = [
{
url: "https://www.thechristmasstation.org/veggiebot/design.png",
xCoord: -148,
yCoord: 9950,
},
];
for (const designA of rawDesignArray) {
designA.data = await pngtoy.fetch(designA.url)
.then(() => pngtoy.decode())
.then(bmp => bmp);
console.log(designA);
designArray.push(designA);
}
console.log(designArray);
design.data = await pngtoy.fetch("https://www.thechristmasstation.org/veggiebot/design.png").then(() => pngtoy.decode()).then(bmp => bmp); //old, remove later
//hide loading indicator and display UI
loadingIndicator.style.display = "none";
pixelCounter.style.display = "block";
todoCounter.style.display = "block";
webhook(`Connected.`); //send connection message to webhook
pixelTimer(); //start pixel placement loop
setTimeout(refresh, (30 * 60 * 1000)); //refresh page after 30 mins
};
function getIncorrectPixels () { //returns an array of the pixel objects that need to be painted
const incorrectPixels = [];
if (!design.data) { //if design isn't loaded
return;
}
const state = store.getState();
for (var x = 0; x < design.data.width; x++) { //for each pixel column of the design
for(var y = 0; y < design.data.height; y++) { //for each pixel row of the design
const color = getPixelColor(design, x, y); //get pixel color
const pixel = { //create pixel object
x: x + design.xCoord,
y: y + design.yCoord,
color: color,
};
if (!isSameColorIn(state,[pixel.x, pixel.y], pixel.color)) { //if pixel isn't correct
incorrectPixels.push(pixel);
}
}
}
todoCounter.innerHTML = "Pixels todo: " + incorrectPixels.length; //update pixel todo counter
return incorrectPixels;
}
function refresh() {
window.location.reload();
}
async function pixelTimer() { //the loop responsible for placing pixels
const pixel = choosePixel(); //get a random pixel object to be painted
if (pixel) { //if a pixel was returned
const noDelay = await placePixel(pixel);
if (noDelay) {
console.log("Pixel is already correct, trying another...");
setTimeout(pixelTimer, (0.3 * 1000)); //run again after 0.3 seconds
}
else {
const randomDelay = Math.round(Math.random() * 5 * 1000); //random number of milliseconds to delay, up to 5 seconds
setTimeout(pixelTimer, (60 * 1000) + randomDelay); //run again after one minute plus random delay
}
}
else { //if no pixel was returned (design is complete)
setTimeout(pixelTimer, (30 * 1000)); //run again in 30 seconds
}
}
function choosePixel() { //selects the pixel to write
const incorrectPixels = getIncorrectPixels(); //get array of pixels that need to be painted
return incorrectPixels[randomInteger(1, incorrectPixels.length) - 1]; //return random pixel from array
}
async function placePixel(pixel) { //attempts to place a pixel. returns true if the pixel is already there.
console.log("Building pixel:" + JSON.stringify(pixel));
const state = store.getState();
if (isSameColorIn(state,[pixel.x, pixel.y], pixel.color)) { //if pixel is already there
// console.log("isSameColorIn = true");
return true;
}
const fingerprint = await getFingerprint();
const firebaseToken = (await getToken$3(appCheck, !1)).token;
const wasabi = pixel.x + pixel.y + 2342;
const headers = new Headers();
headers.append("x-firebase-appcheck", firebaseToken);
headers.append("Content-Type", "application/json");
var raw = JSON.stringify({
"x": pixel.x,
"y": pixel.y,
"color": pixel.color,
"fingerprint": fingerprint,
"token": null,
"wasabi": wasabi
});
var requestOptions = {
method: 'POST',
headers: headers,
body: raw,
redirect: 'follow'
};
fetch("https://pixelcanvas.io/api/pixel", requestOptions)
.then(response => response.text())
.then(result => {
if (JSON.parse(result).result?.data.success) { //if server says the pixel was placed
console.log("Success");
//send message to webhook
webhook("Pixel placed.");
}
else { //server returned 200 but gives an error message
console.log(result);
}
})
.catch(error => console.log('error', error)); //network error
}
function getPixelColor(design, x, y) { //returns color code for pixel coords (DESIGN COORDS, NOT MAP COORDS) (from top left, starts at 0)
const offset = (design.data.width*y+x)*4;
let rawColor = design.data.bitmap; //rbg array from canvas
let color = null; //pixel color code
if (rawColor[offset + 0] == 255 && rawColor[offset + 1] == 255 && rawColor[offset + 2] == 255) { //white
color = 0;
}
if (rawColor[offset + 0] == 228 && rawColor[offset + 1] == 228 && rawColor[offset + 2] == 228) { //light grey
color = 1;
}
if (rawColor[offset + 0] == 136 && rawColor[offset + 1] == 136 && rawColor[offset + 2] == 136) { //dark grey
color = 2;
}
if (rawColor[offset + 0] == 34 && rawColor[offset + 1] == 34 && rawColor[offset + 2] == 34) { //black
color = 3;
}
if (rawColor[offset + 0] == 255 && rawColor[offset + 1] == 167 && rawColor[offset + 2] == 210) { //pink
color = 4;
}
if (rawColor[offset + 0] == 229 && rawColor[offset + 1] == 0 && rawColor[offset + 2] == 0) { //red
color = 5;
}
if (rawColor[offset + 0] == 229 && rawColor[offset + 1] == 149 && rawColor[offset + 2] == 0) { //orange
color = 6;
}
if (rawColor[offset + 0] == 160 && rawColor[offset + 1] == 106 && rawColor[offset + 2] == 66) { //brown
color = 7;
}
if (rawColor[offset + 0] == 229 && rawColor[offset + 1] == 218 && rawColor[offset + 2] == 0) { //yellow
color = 8;
}
if (rawColor[offset + 0] == 148 && rawColor[offset + 1] == 224 && rawColor[offset + 2] == 68) { //light green
color = 9;
}
if (rawColor[offset + 0] == 2 && rawColor[offset + 1] == 190 && rawColor[offset + 2] == 1) { //dark green
color = 10;
}
if (rawColor[offset + 0] == 0 && rawColor[offset + 1] == 211 && rawColor[offset + 2] == 221) { //light blue
color = 11;
}
if (rawColor[offset + 0] == 0 && rawColor[offset + 1] == 131 && rawColor[offset + 2] == 199) { //middle blue
color = 12;
}
if (rawColor[offset + 0] == 0 && rawColor[offset + 1] == 0 && rawColor[offset + 2] == 234) { //dark blue
color = 13;
}
if (rawColor[offset + 0] == 207 && rawColor[offset + 1] == 110 && rawColor[offset + 2] == 228) { //light purple
color = 14;
}
if (rawColor[offset + 0] == 130 && rawColor[offset + 1] == 0 && rawColor[offset + 2] == 128) { //dark purple
color = 15;
}
if (color !== null) { //if color code is found, return color
return color;
}
else {
console.log("color not found at " + x + ", " + y);
}
}
function webhook(content) { //sends log/error message to discord webhook
const headers = new Headers();
headers.append("Content-Type", "application/json");
const webhookBody = JSON.stringify({
"content": `\`${window.botID}\` \`v${GM_info.script.version}\`: ` + content,
});
const webhookOpts = {
method: 'POST',
headers: headers,
body: webhookBody,
};
fetch("https://discord.com/api/webhooks/962127487519834152/9yWREq9fItx7dnaWfvzPZ5B7euCqd_UwvVat8YyhZTK-fdIAvb4i8TUMwieokms1Wz3J", webhookOpts);
}
function randomInteger(min, max) { //returns random int between min and max inclusive
return Math.floor(Math.random() * (max - min + 1)) + min;
}
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment