Greasy Fork

Greasy Fork is available in English.

CyberSoul Animes Alliance Overlay

Overlay da aliança CyberSoul Animes no WPlace

当前为 2025-08-18 提交的版本,查看 最新版本

// ==UserScript==

// @name         CyberSoul Animes Alliance Overlay

// @namespace    https://discord.gg/7bYHWYgb

// @version      1.3.8

// @description  Overlay da aliança CyberSoul Animes no WPlace

// @author       Ví­kish

// @match        https://wplace.live/*

// @icon         https://i.imgur.com/TBeJokm.jpeg

// @grant        none

// @license      MIT



// ==/UserScript==

(async function () {

    'use strict';

    const CHUNK_WIDTH = 1000;

    const CHUNK_HEIGHT = 1000;

    const overlays = [

        {

            url: "https://i.imgur.com/jkHLyev.png",

            chunk: [731, 1193],

            coords: [731250, 1193250]

        },

        {

            url: "https://i.imgur.com/t69kYzr.png",

            chunk: [751, 1116],

            coords: [751650, 1116030]

        }

    ];

    for (const obj of overlays) {

        obj.chunksString = `/${obj.chunk[0]}/${obj.chunk[1]}.png`;

        const { img, width, height } = await loadImage(obj.url);

        const overlayCanvas = new OffscreenCanvas(CHUNK_WIDTH, CHUNK_HEIGHT);

        const overlayCtx = overlayCanvas.getContext("2d");

        overlayCtx.drawImage(img, obj.coords[0] % 1000, obj.coords[1] % 1000, width, height);

        obj.imageData = overlayCtx.getImageData(0, 0, CHUNK_WIDTH, CHUNK_HEIGHT);

        obj.totalPixels = countOverlayPixels(obj.imageData.data);

    }

    const OVERLAY_MODES = ["overlay", "original", "chunks"];

    let overlayMode = OVERLAY_MODES[0];

    // === CONTADOR CENTRAL ===

    let pixelErrorCounter = document.createElement("div");

    pixelErrorCounter.id = "pixel-error-counter";

    Object.assign(pixelErrorCounter.style, {

        position: "fixed",

        top: "10px",

        left: "50%",

        transform: "translateX(-50%)",

        backgroundColor: "rgba(0, 0, 0, 0.7)",

        color: "white",

        fontWeight: "bold",

        fontSize: "11px",

        padding: "5px 10px",

        borderRadius: "8px",

        zIndex: "9999",

        pointerEvents: "none",

        textAlign: "center",

        lineHeight: "1.5"

    });

    const pixelsText = document.createElement("div");

    pixelsText.textContent = `Pixeis restantes: 0`;

    const progressText = document.createElement("div");

    progressText.textContent = `Progresso atual: 0,00%`;

    pixelErrorCounter.appendChild(pixelsText);

    pixelErrorCounter.appendChild(progressText);

    document.body.appendChild(pixelErrorCounter);

    // === TABELA DE CORES ===

    const colorStatsContainer = document.createElement("div");

    Object.assign(colorStatsContainer.style, {

        position: "fixed",

        top: "170px",

        left: "10px",

        backgroundColor: "rgba(0,0,0,0.8)",

        color: "white",

        fontSize: "10px",

        padding: "5px",

        borderRadius: "8px",

        zIndex: "9999",

        maxHeight: "250px",

        overflowY: "auto",

        maxWidth: "150px",

        minWidth: "25px"

    });

    const toggleButton = document.createElement("div");

    toggleButton.textContent = "↑";

    Object.assign(toggleButton.style, {

        cursor: "pointer",

        textAlign: "center",

        marginBottom: "5px",

        fontSize: "14px"

    });

    const colorList = document.createElement("div");

    colorStatsContainer.appendChild(toggleButton);

    colorStatsContainer.appendChild(colorList);

    document.body.appendChild(colorStatsContainer);

    let listVisible = true;

    toggleButton.addEventListener("click", () => {

        listVisible = !listVisible;

        colorList.style.display = listVisible ? "block" : "none";

        toggleButton.textContent = listVisible ? "↑" : "↓";

    });

    fetch = new Proxy(fetch, {

        apply: async (target, thisArg, argList) => {

            const urlString = typeof argList[0] === "object" ? argList[0].url : argList[0];

            let url;

            try {

                url = new URL(urlString);

            } catch {

                throw new Error("Invalid URL provided to fetch");

            }

            if (overlayMode === "overlay") {

                if (url.hostname === "backend.wplace.live" && url.pathname.startsWith("/files/")) {

                    for (const obj of overlays) {

                        if (url.pathname.endsWith(obj.chunksString)) {

                            const originalResponse = await target.apply(thisArg, argList);

                            const originalBlob = await originalResponse.blob();

                            const originalImage = await blobToImage(originalBlob);

                            const width = originalImage.width;

                            const height = originalImage.height;

                            const canvas = new OffscreenCanvas(width, height);

                            const ctx = canvas.getContext("2d", { willReadFrequently: true });

                            ctx.drawImage(originalImage, 0, 0, width, height);

                            const originalData = ctx.getImageData(0, 0, width, height);

                            const resultData = ctx.getImageData(0, 0, width, height);

                            const d1 = originalData.data;

                            const d2 = obj.imageData.data;

                            const dr = resultData.data;

                            let pixelErrorCount = 0;

                            const localColorCount = {};

                            for (let i = 0; i < d1.length; i += 4) {

                                const isTransparent = d2[i + 3] === 0;

                                // compare only RGB, ignore alpha differences to handle semi-transparent border pixels

                                const samePixel =

                                    d1[i] === d2[i] &&

                                    d1[i + 1] === d2[i + 1] &&

                                    d1[i + 2] === d2[i + 2];

                                if (samePixel && !isTransparent) {

                                    dr[i] = 0;

                                    dr[i + 1] = 255;

                                    dr[i + 2] = 0;

                                    dr[i + 3] = 255;

                                } else if (!isTransparent) {

                                    dr[i] = d2[i];

                                    dr[i + 1] = d2[i + 1];

                                    dr[i + 2] = d2[i + 2];

                                    dr[i + 3] = d2[i + 3];

                                    pixelErrorCount++;

                                    const colorKey = `${d2[i]},${d2[i + 1]},${d2[i + 2]}`;

                                    localColorCount[colorKey] = (localColorCount[colorKey] || 0) + 1;

                                }

                            }

                            // Atualizar contadores visuais

                            const total = obj.totalPixels || 1;

                            const progresso = (((total - pixelErrorCount) / total) * 100).toFixed(2).replace('.', ',');

                            pixelsText.textContent = `Pixeis restantes: ${pixelErrorCount}`;

                            progressText.textContent = `Progresso atual: ${progresso}%`;

                            // Atualizar lista de cores

                            colorList.innerHTML = "";

                            const sortedColors = Object.entries(localColorCount).sort((a, b) => b[1] - a[1]);

                            for (const [key, count] of sortedColors) {

                                const [r, g, b] = key.split(',').map(Number);

                                const hex = rgbToHex(r, g, b);

                                const item = document.createElement("div");

                                item.style.display = "flex";

                                item.style.alignItems = "center";

                                item.style.marginBottom = "2px";

                                const colorBox = document.createElement("div");

                                colorBox.style.backgroundColor = hex;

                                colorBox.style.width = "12px";

                                colorBox.style.height = "12px";

                                colorBox.style.marginRight = "5px";

                                const label = document.createElement("span");

                                label.textContent = `: ${count}`;

                                item.appendChild(colorBox);

                                item.appendChild(label);

                                colorList.appendChild(item);

                            }

                            ctx.putImageData(resultData, 0, 0);

                            const mergedBlob = await canvas.convertToBlob();

                            return new Response(mergedBlob, {

                                headers: { "Content-Type": "image/png" }

                            });

                        }

                    }

                }

            }

            return target.apply(thisArg, argList);

        }

    });

    function blobToImage(blob) {

        return new Promise((resolve, reject) => {

            const img = new Image();

            img.onload = () => resolve(img);

            img.onerror = reject;

            img.src = URL.createObjectURL(blob);

        });

    }

    function loadImage(src) {

        return new Promise((resolve, reject) => {

            const img = new Image();

            img.crossOrigin = "anonymous";

            img.onload = () => {

                resolve({

                    img,

                    width: img.naturalWidth,

                    height: img.naturalHeight

                });

            };

            img.onerror = reject;

            img.src = src;

        });

    }

    function countOverlayPixels(data) {

        let count = 0;

        for (let i = 0; i < data.length; i += 4) {

            if (data[i + 3] !== 0) count++;

        }

        return count;

    }

    function rgbToHex(r, g, b) {

        return "#" + [r, g, b].map(x => x.toString(16).padStart(2, "0")).join("");

    }

    function patchUI() {

        if (document.getElementById("overlay-blend-button")) return;

        const blendButton = document.createElement("button");

        blendButton.id = "overlay-blend-button";

        blendButton.textContent = overlayMode.charAt(0).toUpperCase() + overlayMode.slice(1);

        Object.assign(blendButton.style, {

            backgroundColor: "#0e0e0e7f",

            color: "white",

            border: "solid",

            borderColor: "#1d1d1d7f",

            borderRadius: "4px",

            padding: "5px 10px",

            cursor: "pointer",

            backdropFilter: "blur(2px)"

        });

        blendButton.addEventListener("click", () => {

            overlayMode = OVERLAY_MODES[(OVERLAY_MODES.indexOf(overlayMode) + 1) % OVERLAY_MODES.length];

            blendButton.textContent = overlayMode.charAt(0).toUpperCase() + overlayMode.slice(1);

        });

        const buttonContainer = document.querySelector("div.gap-4:nth-child(1) > div:nth-child(2)");

        const leftSidebar = document.querySelector("html body div div.disable-pinch-zoom.relative.h-full.overflow-hidden.svelte-6wmtgk div.absolute.right-2.top-2.z-30 div.flex.flex-col.gap-4.items-center");

        if (buttonContainer) {

            buttonContainer.appendChild(blendButton);

            buttonContainer.classList.remove("items-center");

            buttonContainer.classList.add("items-end");

        }

        if (leftSidebar) {

            leftSidebar.classList.add("items-end");

            leftSidebar.classList.remove("items-center");

        }

    }

    const observer = new MutationObserver(() => {

        patchUI();

    });

    observer.observe(document.querySelector("div.gap-4:nth-child(1)"), {

        childList: true,

        subtree: true

    });

    patchUI();

})();