Greasy Fork

Greasy Fork is available in English.

SOBERANA Wplace Overlay

Overlay for Wplace

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

// ==UserScript==
// @name         SOBERANA Wplace Overlay
// @namespace    http://tampermonkey.net/
// @version      0.1.3
// @description  Overlay for Wplace
// @author       C3B
// @match        https://wplace.live/*
// @icon         https://soberana.tv/images/logo.svg
// @license      MIT
// @grant        none
// ==/UserScript==

(async function() {
    'use strict';

    const [chunk1, chunk2] = ["758", "1161"]
    const OVERLAY_MODES = ["overlay", "original"];
    let overlayMode = OVERLAY_MODES[0];
    let darken = false;

    const chunksString = `/${chunk1}/${chunk2}.png`

    let cachedOverlayImagePromise = null;

    const overlayImage = await getOverlayImage();
    const overlayCanvas = new OffscreenCanvas(1000, 1000);
    const overlayCtx = overlayCanvas.getContext("2d");
    overlayCtx.drawImage(overlayImage, 0, 0, 1000, 1000);
    const overlayData = overlayCtx.getImageData(0, 0, 1000, 1000);

    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 (e) {
                throw new Error("Invalid URL provided to fetch");
            }

            if (url.hostname === "backend.wplace.live" && url.pathname.startsWith("/files/") && url.pathname.endsWith(chunksString)) {
                if (overlayMode !== "original") {
                    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");

                    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 = overlayData.data;
                    const dr = resultData.data;

                    for (let i = 0; i < d1.length; i += 4) {
                        const isTransparent =
                              d2[i] === 0 &&
                              d2[i + 1] === 0 &&
                              d2[i + 2] === 0 &&
                              d2[i + 3] === 0;

                        const samePixel =
                              d1[i] === d2[i] &&
                              d1[i + 1] === d2[i + 1] &&
                              d1[i + 2] === d2[i + 2] &&
                              d1[i + 3] === d2[i + 3];

                        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];
                        }
                    }

                    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);
            img.onerror = reject;
            img.src = src;
        });
    }

    function getOverlayImage() {
        if (!cachedOverlayImagePromise) {
            cachedOverlayImagePromise = loadImage("data:image/webp;base64,UklGRiYEAABXRUJQVlA4TBkEAAAvYUAeACdAIEAZDDeMcwWBpM06LOH68wwnyLYZzLDmd74bzH8AO+WZ8w6ya9uq2+qAG0BJClBUgT18C7Ad918U7L3O0X19R/QfoiRJVqIanZY9YXufJ3xC/P643ld8SmwWzRibh9iS610mtxXblnwqfNjE3SLXyCKTRaYN++eihYnMlGiDkjsUDJaJKn3tnsX6FsvlikqlaSHISqbpOx5xxRdJu4K29TmBXsNlJlB7BwEvF0mq8DaDARVc9eWykg+8GyxV4PXNNEiRyySsAhJNptZVajBisqyHV3MxmcnV+vURGlCBGRKRZDq0zhJqyKUN1zMpkctI9DMqDBBNrhcZjRyv9moyk2vEmO5LqCkRSaY/7/f7/q7Gr59ZbiJpefcWnahgiSSZLLfbb+B6azGUWEP8G5BLLlRvz7Msb0sUGfkE3bkQ9aZEyK7SDN7RAPMWIQydQcBGIWBbF80aEjU2LNY/TLSVNxiFGPwyrDULa8kTDLVEYNSs+U0aMq1hH2UCLV5+mjOaoWuAoVFTItSLsW1leagrO2/hXVqRs5hUGZt6e0P4kLzlvC918Yb42prAcxclwoSS0cAINV/cjASrSKwAKNRSZ6PmFmiPJkaYcQqYcXr4e3j+RiKZWKNKI57+9YariRkpj8gkp6W0R4DkBBHANARSYAjJpG68W7UMm2L7N2MVfGuABWD/nun5LoQuD3GUrZOvJFkFKNVUAP+cF49aYimmLSWRUUnk3GpJZIekXmb6mNEZkw+KbogHPRX59goommLXEe/coq5bMoq1WJxY9yd9ZjVRihNJ3XdllNOxwjAXZuyw6J6WpzzhaIB0Tno5mkHQlkeA6DCrqy6Xw+XxYYEeXmyry6kB6SiFDW+IdOku4qMUgQtbTJxOHTkOpUNTS0TKiP0kL8f+0hbHLC/TH85+OKEtcLKvBqD9MmnWw8YrOT2AXRmLnXnwpSOlLzaja2/i1IxADkV/2CG8RVQIoSH9NEPZoTgy0uKuzEOYGGd4yLH8Ild5nGdfar/5F+/zsc72u4oIPTjKafflavcteiijX8VNDbgz0rW1+r+E+llK914NZmEF2pAG3t3BGu8VYD77/aPJCUD9DtXkxIEr3AO7nCg2hbtsl91Pwx23C2KE7uNdQhPWQkQMelKQiTU9qALV0rOIMGsH42W1px3Nth3Gl7TMw5sllNgZ3KPLT2yCPx78Br1lz4RK/weWlVNPndoqTEvHhpo814Jff7RDrveZJ2flB2GsQv7orClemqhAD+dKD/NyvV/i/xQ4tw86T/Z5MhXmJ2Bp0HlI/yuJMj1BduHkSLZkOFq4B6ELycFZjs7XoeGqIr7KhLMfSGQcRJfTAVtqlXPyQTsUXflnLrSc9d/BAXjBvgih73+QzpP+NP9ABAA=")
        }
        return cachedOverlayImagePromise;
    }

    function patchUI() {
        if (document.getElementById("overlay-blend-button")) {
            return;
        }
        let blendButton = document.createElement("button");
        blendButton.id = "overlay-blend-button";
        blendButton.textContent = overlayMode.charAt(0).toUpperCase() + overlayMode.slice(1);
        blendButton.style.backgroundColor = "#0e0e0e7f";
        blendButton.style.color = "white";
        blendButton.style.border = "solid";
        blendButton.style.borderColor = "#1d1d1d7f";
        blendButton.style.borderRadius = "4px";
        blendButton.style.padding = "5px 10px";
        blendButton.style.cursor = "pointer";
        blendButton.style.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();
})();