Spotless for eBay

Highlights and hides sponsored content on eBay

// ==UserScript==
// @name         Spotless for eBay
// @namespace    https://github.com/OsborneLabs
// @version      1.3.2
// @description  Highlights and hides sponsored content on eBay
// @author       Osborne Labs
// @license      GPL-3
// @homepageURL  https://github.com/OsborneLabs/Spotless
// @icon         data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHg9IjBweCIgeT0iMHB4IiB2aWV3Qm94PSIwIDAgNDcwLjYgNTEyIiB4bWw6c3BhY2U9InByZXNlcnZlIj4KICA8ZyB0cmFuc2Zvcm09InRyYW5zbGF0ZSgxLjQwNjU5MzQwNjU5MzQwMTYgMS40MDY1OTM0MDY1OTM0MDE2KSBzY2FsZSgyLjgxIDIuODEpIj4KICAgIDxwYXRoIGZpbGw9IiM4NDg0ODQiIGQ9Ik0xMjcuOCw0Ni4xSDM4LjdWNDJjMC0yMy40LDE5LjEtNDIuNSw0Mi41LTQyLjVoNC4xYzIzLjQsMCw0Mi41LDE5LjEsNDIuNSw0Mi41VjQ2LjF6IE00Mi43LDQyaDgxVjQyIGMwLTIxLjItMTcuMi0zOC40LTM4LjQtMzguNGgtNC4xQzYwLDMuNSw0Mi43LDIwLjgsNDIuNyw0Mkw0Mi43LDQyeiI+PC9wYXRoPgogICAgPHBhdGggZmlsbD0iI0U1MzIzOCIgZD0iTTM1LjEsMTgxLjdoLTkuNmMtMTQuMywwLTI1LjktMTEuNi0yNS45LTI1LjlWNDkuNGMwLTUuMiw0LjMtOS41LDkuNS05LjVoMjYuMUwzNS4xLDE4MS43TDM1LjEsMTgxLjd6Ij48L3BhdGg+CiAgICA8cmVjdCB4PSIzNS4xIiB5PSIzOS45IiBmaWxsPSIjMDA2NEQyIiB3aWR0aD0iNDguMSIgaGVpZ2h0PSIxNDEuOCI+PC9yZWN0PgogICAgPHJlY3QgeD0iODMuMiIgeT0iMzkuOSIgZmlsbD0iI0Y1QUYwMiIgd2lkdGg9IjQ4LjEiIGhlaWdodD0iMTQxLjgiPjwvcmVjdD4KICAgIDxwYXRoIGZpbGw9IiM4NkI4MTciIGQ9Ik0xNDEsMTgxLjdoLTkuNlYzOS45aDI2LjFjNS4yLDAsOS41LDQuMyw5LjUsOS41djEwNi40QzE2NywxNzAuMSwxNTUuMywxODEuNywxNDEsMTgxLjd6Ij48L3BhdGg+CiAgPC9nPgo8L3N2Zz4K
// @match        https://www.ebay.com/sch/*
// @match        https://www.ebay.at/sch/*
// @match        https://www.ebay.ca/sch/*
// @match        https://www.ebay.ch/sch/*
// @match        https://www.ebay.com.au/sch/*
// @match        https://www.ebay.com.hk/sch/*
// @match        https://www.ebay.com.my/sch/*
// @match        https://www.ebay.com.sg/sch/*
// @match        https://www.ebay.co.uk/sch/*
// @match        https://www.ebay.de/sch/*
// @match        https://www.ebay.es/sch/*
// @match        https://www.ebay.fr/sch/*
// @match        https://www.ebay.ie/sch/*
// @match        https://www.ebay.it/sch/*
// @match        https://www.ebay.nl/sch/*
// @match        https://www.ebay.pl/sch/*
// @run-at       document-start
// @grant        none
// ==/UserScript==

/* jshint esversion: 11 */

(function() {
    "use strict";

    const APP_TITLE = "Spotless";
    const SPONSORED_CONTENT_KEY = "hideSponsoredContent";

    let hidingEnabled = localStorage.getItem(SPONSORED_CONTENT_KEY);
    hidingEnabled = hidingEnabled !== "false";
    let highlightedSponsoredContent = [];

    let isProcessing = false;
    let updateScheduled = false;
    let observerInitialized = false;

    const APP_ICONS = {
        locked: `
            <svg class="lock-icon lock-icon-animation" id="lockedIcon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
                <path d="M12 2C9.79 2 8 3.79 8 6v4H7c-1.1 0-2 .9-2 2v8c0 1.1.9 2 2 2h10c1.1 0 2-.9 2-2v-8c0-1.1-.9-2-2-2h-1V6c0-2.21-1.79-4-4-4zm-2 8V6c0-1.1.9-2 2-2s2 .9 2 2v4h-4z"/>
            </svg>`,
        unlocked: `
            <svg class="lock-icon lock-icon-animation" id="unlockedIcon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
                <path d="M17 8V6c0-2.76-2.24-5-5-5S7 3.24 7 6h2c0-1.66 1.34-3 3-3s3 1.34 3 3v2H7c-1.1 0-2 .9-2 2v10c0 1.1.9 2 2 2h10c1.1 0 2-.9 2-2V10c0-1.1-.9-2-2-2h-1z"/>
            </svg>`,
        arrow: `
            <svg id="arrowIcon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
                <path d="M7 10l5 5 5-5z"/>
            </svg>`,
        heart: `
            <svg class="heart-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
                <path d="M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41 0.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z"/>
            </svg>`
    };

    function init() {
        initializeUI();
        processSponsoredContent();
    }

    function initializeUI() {
        createStyles();
        buildPanel();
    }

    function createStyles() {
        const style = document.createElement("style");
        style.textContent = `
            :root {

                --size-font-title: 18px;
                --size-font-body: 14px;
                --size-font-body-error: 16px;
                --size-font-footer: 12px;

                --color-bubble: #e74c3c;
                --color-divider-border: rgba(255, 255, 255, 0.1);
                --color-font-text: white;
                --color-font-link-hover: lightblue;
                --color-font-link-visited: lightblue;
                --color-highlight-background: #ffe6e6;
                --color-highlight-border: red;
                --color-panel: rgba(34, 50, 70, 0.85);
                --color-panel-shadow: 0 8px 20px rgba(0, 0, 0, 0.20);
                --color-row: rgba(20, 30, 45, 0.5);
                --color-svg-fill: white;
                --color-svg-fill-heart-hover: red;
                --color-switch-knob: white;
                --color-switch-off: #ccc;
                --color-switch-on: #2AA866;
                --color-switch-on-shadow: 0 0 4px rgba(39, 174, 96, 0.3);

                --thickness-highlight-border: 2px;
            }

            #panelWrapper {
                position: fixed;
                bottom: 10px;
                right: 5px;
                z-index: 2147483647;
                width: 100%;
                max-width: 320px;
                padding: 0 16px;
                box-sizing: border-box;
                font-family: "Segoe UI", sans-serif;
            }

            @media (max-width: 768px) {
                #panelWrapper {
                    bottom: 5px;
                    left: 50%;
                    transform: translateX(-50%);
                    width: 90% !important;
                    right: unset;
                    padding: 0 16px;
                }
            }

            #panelBox {
                display: flex;
                flex-direction: column;
                gap: 0px;
                background: var(--color-panel);
                backdrop-filter: blur(10px);
                color: var(--color-font-text);
                padding: 16px;
                border-radius: 12px;
                width: 100%;
                box-shadow: var(--color-panel-shadow);
                transition: transform 0.2s ease;
            }

            #panelBox:hover {
                transform: translateY(-2px);
            }

            #panelHeader {
                display: flex;
                align-items: center;
                gap: 8px;
            }

            #panelHeader h2.panel-title {
                font-size: var(--size-font-title);
                font-weight: 600;
                margin: 0;
                color: var(--color-font-text);
            }

            .panel-body-row {
                margin: 0;
                font-size: var(--size-font-body);
                display: flex;
                align-items: center;
                justify-content: space-between;
                background: var(--color-row);
                backdrop-filter: blur(12px);
                padding: 12px 16px;
                border-radius: 8px;
            }

            .panel-body-row + .panel-body-row {
                margin-top: 5px;
            }

            .panelFooter {
                display: flex;
                align-items: center;
                justify-content: flex-end;
                gap: 6px;
                font-size: var(--size-font-footer);
                color: var(--color-font-text);
            }

            .panelPageContainer {
                position: relative;
                width: 100%;
            }

            hr.section-divider {
                flex-grow: 1;
                border: none;
                border-top: 1px solid var(--color-divider-border);
                margin: 12px 0;
            }

            #minimizePanelButton {
                width: 28px;
                height: 28px;
                margin-left: auto;
                padding: 2px;
                border: none;
                background: none;
                display: flex;
                align-items: center;
                justify-content: center;
                cursor: pointer;
                box-sizing: content-box;
            }

            #panelBox.minimized #arrowIcon {
                transform: rotate(180deg);
            }

            #panelBox.minimized {
                padding: 12px;
                overflow: hidden;
            }

            .lock-icon {
                width: 24px;
                height: 24px;
                padding: 4px;
                border-radius: 50%;
                fill: var(--color-svg-fill);
            }

            .lock-icon-animation {
                position: absolute;
                top: 0;
                left: 0;
                width: 28px;
                height: 28px;
                opacity: 0;
                transition: opacity 0.4s ease, transform 0.4s ease;
                transform: rotate(0deg);
            }

            .lock-icon-animation.active {
                opacity: 1;
                transform: rotate(360deg);
            }

            #lockIconContainer {
                position: relative;
                width: 28px;
                height: 28px;
            }

            #arrowIcon {
                width: 28px;
                height: 28px;
                fill: var(--color-svg-fill);
                transition: transform 0.3s ease;
            }

            .heart-icon {
                width: 10px;
                height: 10px;
                vertical-align: middle;
                fill: var(--color-svg-fill);
            }

            .heart-icon:hover {
                fill: var(--color-svg-fill-heart-hover);
            }

            #countBubble {
                background-color: var(--color-bubble);
                color: var(--color-font-text);
                font-size: 12px;
                font-weight: bold;
                padding: 3px 8px;
                border-radius: 999px;
                min-width: 20px;
                text-align: center;
            }

            .switch {
                position: relative;
                display: inline-block;
                width: 42px;
                height: 22px;
            }

            .switch input {
                opacity: 0;
                width: 0;
                height: 0;
            }

            .switch-label {
                margin-right: 10px;
            }

            .slider {
                position: absolute;
                cursor: pointer;
                top: 0;
                left: 0;
                right: 0;
                bottom: 0;
                background-color: var(--color-switch-off);
                transition: 0.3s;
                border-radius: 34px;
            }

            .slider:before {
                position: absolute;
                content: "";
                height: 18px;
                width: 18px;
                top: 2px;
                left: 2px;
                background-color: var(--color-switch-knob);
                transition: 0.3s;
                border-radius: 50%;
            }

            input:checked + .slider {
                background-color: var(--color-switch-on);
                box-shadow: var(--color-switch-on-shadow);
            }

            input:checked + .slider:before {
                transform: translateX(20px);
            }

            #creator-page {
                color: var(--color-font-text);
                transition: color 0.3s ease;
            }

            #creator-page:hover, .outbound-status-page:hover, .retry-link:hover{
                color: var(--color-font-link-hover);
            }

            .error-page {
                text-align: center;
                font-size: var(--size-font-body-error);
            }

            .outbound-status-page, .retry-link {
                text-decoration: underline;
                color: var(--color-font-text);
            }

            .outbound-status-page:visited, .retry-link:visited {
                color: var(--color-font-link-visited);
            }

            .sponsored-highlight {
            border: var(--thickness-highlight-border) solid var(--color-highlight-border) !important;
            background-color: var(--color-highlight-background);
            }

            .sponsored-hidden {
                display: none !important;
            }

        `;
        document.head.appendChild(style);
    }

    function determineCurrentPage(sponsoredCount) {
        if (sponsoredCount === 0) {
            return buildPanelErrorPage();
        }
        return buildPanelHomePage();
    }

    function initializeObserver() {
        if (observerInitialized) return;
        observer.observe(document.body, {
            childList: true,
            subtree: true,
        });
        observerInitialized = true;
    }

    async function buildPanel() {
        const wrapper = document.createElement("div");
        wrapper.id = "panelWrapper";

        const box = document.createElement("div");
        box.id = "panelBox";

        const header = buildPanelHeader();
        const sponsoredCount = await processSponsoredContent();
        const body = determineCurrentPage(sponsoredCount, hidingEnabled);
        const footer = buildPanelFooter();

        const topDivider = document.createElement("hr");
        topDivider.className = "section-divider";
        const bottomDivider = document.createElement("hr");
        bottomDivider.className = "section-divider";

        box.appendChild(header);
        box.appendChild(topDivider);
        box.appendChild(body);
        box.appendChild(bottomDivider);
        box.appendChild(footer);

        wrapper.appendChild(box);
        document.body.appendChild(wrapper);

        const minimizePanelButton = document.getElementById("minimizePanelButton");
        minimizePanelButton.addEventListener("click", () => {

            const panelBox = document.getElementById("panelBox");
            const isCurrentlyMinimized = panelBox.classList.contains("minimized");
            const newState = !isCurrentlyMinimized;

            localStorage.setItem("panelMinimized", newState);
            setPanelMinimized(newState);
        });

        const isPanelMinimized = localStorage.getItem("panelMinimized") === "true";
        setPanelMinimized(isPanelMinimized);

        const toggleSponsoredContentSwitchInput = document.getElementById("toggleSponsoredContentSwitch");

        if (!toggleSponsoredContentSwitchInput) {
            updateLockIcons();
            return;
        }

        toggleSponsoredContentSwitchInput.addEventListener("change", (e) => {
            hidingEnabled = e.target.checked;
            localStorage.setItem(SPONSORED_CONTENT_KEY, hidingEnabled);
            updateLockIcons();
            debounceHighlighting();
        });
        updateLockIcons();
    }

    function buildPanelHeader() {
        const header = document.createElement("div");
        header.id = "panelHeader";

        header.innerHTML = `
            <div id="lockIconContainer">
                ${APP_ICONS.locked}
                ${APP_ICONS.unlocked}
            </div>
            <h2 class="panel-title" aria-level="1">${APP_TITLE}</h2>
            <button id="minimizePanelButton" aria-label="Expand or minimize the panel">
                ${APP_ICONS.arrow}
            </button>
        `;
        return header;
    }

    function buildPanelFooter() {
        const footer = document.createElement("div");
        footer.className = "panelFooter";

        const creatorPage = document.createElement("a");
        creatorPage.href = "https://github.com/OsborneLabs/Spotless";
        creatorPage.target = "_blank";
        creatorPage.style.textDecoration = "none";
        creatorPage.textContent = "Osborne";
        creatorPage.id = "creator-page";

        const separator = document.createElement("span");
        separator.textContent = " · ";

        const donatePage = document.createElement("a");
        donatePage.href = "https://ko-fi.com/osbornelabs";
        donatePage.target = "_blank";
        donatePage.innerHTML = APP_ICONS.heart;
        donatePage.style.display = "inline-flex";
        donatePage.style.alignItems = "center";
        donatePage.style.justifyContent = "center";

        footer.appendChild(creatorPage);
        footer.appendChild(separator);
        footer.appendChild(donatePage);
        return footer;
    }

    function buildPanelRow(innerHTML = "") {
        const row = document.createElement("div");
        row.className = "panel-body-row";
        row.innerHTML = innerHTML;
        return row;
    }

    function buildCountSponsoredContentRow() {
        const row = buildPanelRow(`
            <span>Content found</span>
            <span id="countBubble">0</span>
        `);
        row.id = "count-sponsored-content-row";
        return row;
    }

    function buildToggleSponsoredContentRow() {
        const row = buildPanelRow(`
            <span class="switch-label">Hide sponsored content</span>
            <label class="switch" aria-label="Toggle visibility of sponsored content">
                <input type="checkbox" id="toggleSponsoredContentSwitch" ${hidingEnabled ? "checked" : ""}>
                <span class="slider"></span>
            </label>
        `);
        row.id = "toggle-sponsored-content-row";
        return row;
    }

    function buildPanelHomePage() {
        const pageContainer = document.createElement("div");
        pageContainer.id = "panelPageContainer";
        pageContainer.classList.add("panelPageContainer");

        const homePage = document.createElement("div");
        homePage.id = "homePage";
        homePage.className = "panel-page";
        homePage.style.display = "block";

        const countSponsoredContentRow = buildCountSponsoredContentRow();
        const toggleSponsoredContentRow = buildToggleSponsoredContentRow();

        homePage.appendChild(countSponsoredContentRow);
        homePage.appendChild(toggleSponsoredContentRow);

        pageContainer.appendChild(homePage);
        return pageContainer;
    }

    function buildPanelErrorPage() {
        const errorPage = document.createElement("div");
        errorPage.classList.add("error-page", "panel-page");

        const errorMessage = document.createElement("p");
        errorMessage.textContent = "No sponsored content found. ";
        errorMessage.appendChild(document.createElement("br"));

        const outboundRetryPage = document.createElement("a");
        outboundRetryPage.textContent = "Retry";
        outboundRetryPage.href = "#";
        outboundRetryPage.addEventListener("click", function(event) {
            event.preventDefault();
            location.reload();
        });
        outboundRetryPage.classList.add("retry-link");

        const outboundStatusPage = document.createElement("a");
        outboundStatusPage.textContent = "check status";
        outboundStatusPage.href = "https://github.com/OsborneLabs/Spotless";
        outboundStatusPage.target = "_blank";
        outboundStatusPage.classList.add("outbound-status-page");

        errorMessage.appendChild(outboundRetryPage);
        errorMessage.appendChild(document.createTextNode(" or "));
        errorMessage.appendChild(outboundStatusPage);

        const endText = document.createTextNode(".");
        errorMessage.appendChild(endText);
        errorPage.appendChild(errorMessage);
        return errorPage;
    }

    function setPanelMinimized(minimized) {
        const panelBox = document.getElementById("panelBox");
        if (!panelBox) return;

        const panelPage = panelBox.querySelector(".panel-page");
        const sectionDividers = panelBox.querySelectorAll(".section-divider");
        const panelFooter = panelBox.querySelector(".panelFooter");

        panelBox.classList.toggle("minimized", minimized);

        if (panelPage) panelPage.style.display = minimized ? "none" : "block";
        sectionDividers.forEach(el => {
            el.style.display = minimized ? "none" : "";
        });
        if (panelFooter) panelFooter.style.display = minimized ? "none" : "";
    }

    function updateLockIcons() {
        const locked = document.getElementById("lockedIcon");
        const unlocked = document.getElementById("unlockedIcon");
        locked.classList.toggle("active", hidingEnabled);
        unlocked.classList.toggle("active", !hidingEnabled);
    }

    function getListingElements() {
        return Array.from(document.querySelectorAll("li[class*='s-']")).filter(
            (el) => el.className.split(/\s+/).some((cls) => /^s-[\w-]+$/.test(cls))
        );
    }

    function detectSponsoredListingByBase64(batchSize = 5) {
        return new Promise((resolve) => {
            const listings = getListingElements();
            const sponsoredElements = [];
            let index = 0;

            function processBatch() {
                const end = Math.min(index + batchSize, listings.length);
                const batch = listings.slice(index, end);
                let processedInBatch = 0;

                if (batch.length === 0) {
                    resolve(sponsoredElements);
                    return;
                }

                batch.forEach((listing) => {
                    const svgImage = listing.querySelector(".s-item__sep span[aria-hidden='true']");
                    if (!svgImage) return done();

                    const backgroundImage = getComputedStyle(svgImage.parentElement).backgroundImage;
                    const match = backgroundImage.match(/url\("data:image\/svg\+xml;base64,([^"]+)"\)/);

                    if (!match || !match[1]) return done();

                    const base64 = match[1];
                    const svgString = atob(base64);
                    const img = new Image();
                    const canvas = document.createElement("canvas");
                    const ctx = canvas.getContext("2d");

                    img.src = "data:image/svg+xml;base64," + btoa(svgString);

                    img.onload = () => {
                        canvas.width = img.naturalWidth || 25;
                        canvas.height = img.naturalHeight || 25;
                        ctx.drawImage(img, 0, 0);
                        const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height).data;

                        for (let i = 0; i < imageData.length; i += 4) {
                            const r = imageData[i];
                            const g = imageData[i + 1];
                            const b = imageData[i + 2];
                            const a = imageData[i + 3];

                            if (a > 0 && r === 112 && g === 112 && b === 112) {
                                sponsoredElements.push(listing);
                                break;
                            }
                        }
                        done();
                    };

                    img.onerror = () => {
                        done();
                    };

                    function done() {
                        processedInBatch++;
                        if (processedInBatch === batch.length) {
                            index += batchSize;
                            setTimeout(processBatch, 0);
                        }
                    }
                });
                if (batch.length === 0) resolve(sponsoredElements);
            }
            if (listings.length === 0) {
                resolve([]);
            } else {
                processBatch();
            }
        });
    }

    function detectSponsoredListingByAriaID(listings = getListingElements()) {
        const groupMap = {};
        const ariaLabelToGroup = {};
        let groupCounter = 0;

        listings.forEach(listing => {
            const labelSpans = Array.from(listing.querySelectorAll('span[aria-labelledby]'));
            if (labelSpans.length === 0) return;

            for (const spanElement of labelSpans) {
                const ariaLabel = spanElement.getAttribute('aria-labelledby');
                if (!ariaLabel || !ariaLabel.includes("s-")) continue;

                if (!ariaLabelToGroup[ariaLabel]) {
                    ariaLabelToGroup[ariaLabel] = `Group ${numberToLettersForAriaID(groupCounter)}`;
                    groupCounter++;
                }
                const group = ariaLabelToGroup[ariaLabel];
                if (!groupMap[group]) {
                    groupMap[group] = [];
                }
                groupMap[group].push(listing);
                break;
            }
        });

        let sponsoredGroup = null;
        let minCount = Infinity;

        for (const [group, groupListings] of Object.entries(groupMap)) {
            if (groupListings.length < minCount) {
                sponsoredGroup = group;
                minCount = groupListings.length;
            }
        }
        return sponsoredGroup ? groupMap[sponsoredGroup] : [];
    }

    function numberToLettersForAriaID(num) {
        let result = '';
        while (num >= 0) {
            result = String.fromCharCode((num % 26) + 65) + result;
            num = Math.floor(num / 26) - 1;
        }
        return result;
    }

    function detectSponsoredListingByInvertFilter() {
        const INVERT_REGEX = /div\.([a-zA-Z0-9_-]+)(?:\s+div)?\s*\{[^}]*color:\s*(black|white);[^}]*filter:\s*invert\(([-\d.]+)\)/g;

        const sponsoredGroups = {};
        const classToInvertMap = {};

        const styleTags = Array.from(document.querySelectorAll("style"));
        styleTags.forEach(styleTag => {
            const css = styleTag.textContent;
            let match;
            while ((match = INVERT_REGEX.exec(css)) !== null) {
                const [_, className, color, invertValue] = match;
                if (!classToInvertMap[className]) {
                    classToInvertMap[className] = [];
                }
                classToInvertMap[className].push({
                    color,
                    invert: parseFloat(invertValue)
                });
            }
        });
        const containers = Array.from(document.querySelectorAll('div[role="text"]')).filter(container => {
            return container.querySelector('div[aria-hidden="true"]');
        });

        containers.forEach(container => {
            const targetDiv = container.querySelector('div[aria-hidden="true"]');
            if (!targetDiv) return;

            const ancestorDiv = container.closest("div[class*='_']");
            if (!ancestorDiv) return;

            const classList = Array.from(ancestorDiv.classList);
            const dynamicClass = classList.find(cls => classToInvertMap[cls]);
            if (!dynamicClass) return;

            const candidates = classToInvertMap[dynamicClass];
            const invertEntry = candidates?.[0];
            if (!invertEntry) return;

            const key = invertEntry.invert;
            if (!sponsoredGroups[key]) {
                sponsoredGroups[key] = [];
            }
            sponsoredGroups[key].push(container);
        });

        const groupEntries = Object.entries(sponsoredGroups);
        if (groupEntries.length === 0) {
            return {
                allGroups: []
            };
        }

        const sortedGroups = groupEntries.sort((a, b) => a[1].length - b[1].length);
        const [sponsoredInvert, sponsoredList] = sortedGroups[0];

        return {
            invert: parseFloat(sponsoredInvert),
            elements: sponsoredList,
            allGroups: groupEntries
        };
    }

    function detectSponsoredBanner() {
        const tld = getEffectiveTLD();
        if (tld !== "co.uk" && tld !== "com.au" && tld !== "de") return [];

        return new Promise((resolve) => {
            setTimeout(() => {
                const banners = Array.from(
                    document.querySelectorAll(".s-answer-region-center-top.s-answer-region > div")
                ).filter((el) => {
                    return !el.className.includes("srp-controls") && el.offsetHeight >= 100;
                });
                resolve(banners);
            }, 525);
        });
    }

    async function processSponsoredContent() {
        if (isProcessing) return 0;
        isProcessing = true;

        try {
            observer.disconnect();
            clearDesignateSponsoredContent();

            const listings = getListingElements();
            const unprocessedListings = listings.filter(el => !el.hasAttribute("data-sponsored-processed"));
            const detectedSponsoredElements = new Set();

            const base64Results = await detectSponsoredListingByBase64();
            base64Results.forEach(el => {
                const li = el.closest("li");
                if (li) detectedSponsoredElements.add(li);
            });

            if (detectedSponsoredElements.size === 0) {
                const ariaMethod = detectSponsoredListingByAriaID(unprocessedListings);
                ariaMethod.forEach(listing => {
                    const li = listing.closest("li");
                    if (li) detectedSponsoredElements.add(li);
                });
            }
            if (detectedSponsoredElements.size === 0) {
                const invertMethod = detectSponsoredListingByInvertFilter();
                invertMethod.elements?.forEach(container => {
                    const li = container.closest("li");
                    if (li) detectedSponsoredElements.add(li);
                });
            }

            const banners = await detectSponsoredBanner();
            banners.forEach(banner => {
                detectedSponsoredElements.add(banner);
            });

            requestAnimationFrame(() => {
                for (const el of detectedSponsoredElements) {
                    if (!el.hasAttribute("data-sponsored-processed")) {
                        designateSponsoredContent(el);
                        highlightSponsoredContent(el);
                        hideShowSponsoredContent(el, hidingEnabled);
                    }
                }

                const count = detectedSponsoredElements.size;
                countSponsoredContent(count);

                initializeObserver();
                isProcessing = false;
            });
            return detectedSponsoredElements.size;
        } catch (err) {
            console.error("Error processing sponsored content:", err);
            isProcessing = false;
            initializeObserver();
            return 0;
        }
    }

    function designateSponsoredContent(el) {
        el.setAttribute("data-sponsored", "true");
        el.setAttribute("data-sponsored-processed", "true");
        highlightedSponsoredContent.push(el);
    }

    function clearDesignateSponsoredContent() {
        highlightedSponsoredContent.forEach(el => {
            el.classList.remove("sponsored-hidden");
            el.removeAttribute("data-sponsored");
            el.removeAttribute("data-sponsored-processed");
            el.style.border = "";
            el.style.backgroundColor = "";
        });
        highlightedSponsoredContent.length = 0;
    }

    function highlightSponsoredContent(element) {
        element.setAttribute("data-sponsored", "true");
        element.classList.add("sponsored-highlight");
    }

    function hideShowSponsoredContent(element, hide) {
        element.classList.toggle("sponsored-hidden", hide);
    }

    function countSponsoredContent(count) {
        const countBubble = document.getElementById("countBubble");
        if (countBubble) countBubble.textContent = count;
    }

    function debounceHighlighting() {
        if (updateScheduled || isProcessing) return;
        updateScheduled = true;
        requestAnimationFrame(() => {
            processSponsoredContent().finally(() => {
                updateScheduled = false;
            });
        });
    }

    function getEffectiveTLD() {
        const host = window.location.hostname;
        const tldMap = {
            "ebay.com.au": "com.au",
            "ebay.com.hk": "com.hk",
            "ebay.com.my": "com.my",
            "ebay.com.sg": "com.sg",
            "ebay.co.uk": "co.uk",
            "ebay.at": "at",
            "ebay.ca": "ca",
            "ebay.ch": "ch",
            "ebay.de": "de",
            "ebay.es": "es",
            "ebay.fr": "fr",
            "ebay.ie": "ie",
            "ebay.it": "it",
            "ebay.nl": "nl",
            "ebay.pl": "pl",
            "ebay.com": "com",
        };
        return Object.entries(tldMap).find(([domain]) => host.endsWith(domain))?.[1] || "Unknown";
    }

    const observer = new MutationObserver(() => {
        debounceHighlighting();
    });

    (async function() {
        if (document.readyState === "complete" || document.readyState === "interactive") {
            await new Promise(r => setTimeout(r, 200));
            init();
        } else {
            window.addEventListener("DOMContentLoaded", async () => {
                await new Promise(r => setTimeout(r, 200));
                init();
            });
        }
    })();

    window.addEventListener("storage", (event) => {
        if (event.key === SPONSORED_CONTENT_KEY) {
            const newValue = event.newValue === "true";
            if (newValue !== hidingEnabled) {
                hidingEnabled = newValue;
                const toggleSponsoredContentSwitchInput = document.getElementById("toggleSponsoredContentSwitch");
                if (toggleSponsoredContentSwitchInput) toggleSponsoredContentSwitchInput.checked = hidingEnabled;
                updateLockIcons();
                debounceHighlighting();
            }
        } else if (event.key === "panelMinimized") {
            setPanelMinimized(event.newValue === "true");
        }
    });
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址