Spotless for eBay

Highlights, hides, and cleans sponsored eBay listings

当前为 2025-09-24 提交的版本,查看 最新版本

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

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

/* jshint esversion: 11 */

(function() {
    "use strict";

    const APP_NAME = "Spotless";
    const APP_NAME_DEBUG = "SPOTLESS FOR EBAY";
    const APP_KEY_SPONSORED_CONTENT = "hideSponsoredContent";

    let hidingEnabled = localStorage.getItem(APP_KEY_SPONSORED_CONTENT);
    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>`
    };

    const APP_BLOCK_PARAMETERS = ["campaign", "promoted_items", "source", "sr"];

    async function init() {
        observeURLMutation();
        createStyles();
        buildPanel();
        hideShowPanel();
        await processSponsoredContent();
    }

    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-font-text: white;
                --color-font-link-hover: lightblue;
                --color-font-link-visited: lightblue;
                --color-panel: rgba(34, 50, 70, 0.85);
                --color-panel-shadow: 0 8px 20px rgba(0, 0, 0, 0.2);
                --color-row: rgba(20, 30, 45, 0.5);
                --color-divider-border: rgba(255, 255, 255, 0.1);
                --color-bubble: #e74c3c;
                --color-highlight-background: #ffe6e6;
                --color-highlight-border: red;
                --color-svg-fill: white;
                --color-svg-fill-heart-hover: red;
                --color-switch-knob: white;
                --color-switch-on: #2AA866;
                --color-switch-off: #ccc;
                --thickness-highlight-border: 2px;
            }
            #panelWrapper, #panelBox, .lock-icon-animation, .lock-icon-animation.active {
                box-sizing: border-box;
            }
            #panelWrapper {
                position: fixed;
                bottom: 10px;
                right: 5px;
                z-index: 2147483647;
                width: 100%;
                max-width: 320px;
                padding: 0 16px;
                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: none;
            }
            #panelBox.show {
                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);
            }
            #panelBox.minimized #arrowIcon {
                transform: rotate(180deg);
            }
            #panelBox.minimized {
                padding: 12px;
                overflow: hidden;
            }
            #panelHeader {
                display: flex;
                align-items: center;
                height: 30px;
                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;
            }
            .panel-footer {
                height: 15px;
                display: flex;
                align-items: center;
                justify-content: flex-end;
                gap: 6px;
                font-size: var(--size-font-footer);
                color: var(--color-font-text);
            }
            .panel-page-container {
                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;
            }
            .lock-icon {
                width: 28px;
                height: 28px;
                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);
            }
            input:checked + .slider:before {
                transform: translateX(20px);
            }
            #creatorPage {
                color: var(--color-font-text);
                transition: color 0.3s ease;
            }
            #creatorPage:hover, .outbound-status-page:hover, .outbound-update-page:hover{
                color: var(--color-font-link-hover);
            }
            .error-page {
                text-align: center;
                font-size: var(--size-font-body-error);
            }
            .outbound-status-page, .outbound-update-page {
                text-decoration: underline;
                color: var(--color-font-text);
            }
            .outbound-status-page:visited, .outbound-update-page: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;
            }
            .sponsored-hidden-banner {
            display: none !important;
            }
        `;
        document.head.appendChild(style);
    }

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

    function validateCurrentURL() {
        const url = new URL(location.href);
        const params = url.searchParams;

        const isSearchPage = /^https:\/\/www\.ebay\.[a-z.]+\/sch\//i.test(url.href);
        const isAdvancedSearchPage = url.href.includes("ebayadvsearch");
        const isSellerPage = params.has("_ssn");
        const isSoldPage = params.get("LH_Sold") === "1";

        return isSearchPage && !isAdvancedSearchPage && !isSellerPage && !isSoldPage;
    }

    function observeURLMutation() {
        let lastUrl = location.href;
        const observeURL = () => {
            const currentUrl = location.href;
            if (currentUrl !== lastUrl) {
                lastUrl = currentUrl;
                hideShowPanel();
                scheduleHighlightUpdate();
            }
        };
        const pushState = history.pushState;
        history.pushState = function() {
            pushState.apply(history, arguments);
            observeURL();
        };
        const replaceState = history.replaceState;
        history.replaceState = function() {
            replaceState.apply(history, arguments);
            observeURL();
        };
        window.addEventListener("popstate", observeURL);
        window.addEventListener("hashchange", observeURL);
        setInterval(observeURL, 1000);
    }

    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 = determinePanelState(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);
            minimizePanel(newState);
        });

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

        const toggleSponsoredContentSwitchInput = document.getElementById("toggleSponsoredContentSwitch");
        if (!toggleSponsoredContentSwitchInput) {
            updateLockIcon();
            return;
        }
        toggleSponsoredContentSwitchInput.addEventListener("change", (e) => {
            hidingEnabled = e.target.checked;
            localStorage.setItem(APP_KEY_SPONSORED_CONTENT, hidingEnabled);
            updateLockIcon();
            scheduleHighlightUpdate();
        });
        updateLockIcon();
    }

    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_NAME}</h2>
            <button id="minimizePanelButton" aria-label="Expands or minimizes the panel">
                ${APP_ICONS.arrow}
            </button>
        `;
        return header;
    }

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

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

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

        const donatePage = document.createElement("a");
        donatePage.href = "https://ko-fi.com/osbornelabs";
        donatePage.target = "_blank";
        donatePage.rel = "noopener noreferrer";
        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 = "countSponsoredContentRow";
        return row;
    }

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

    function buildPanelHomePage() {
        const pageContainer = document.createElement("div");
        pageContainer.id = "panelPagecontainer";
        pageContainer.classList.add("panel-page-container");

        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 outboundUpdatePage = document.createElement("a");
        outboundUpdatePage.textContent = "Update";
        outboundUpdatePage.href = "https://greasyfork.org/en/scripts/541981-spotless-for-ebay";
        outboundUpdatePage.target = "_blank";
        outboundUpdatePage.rel = "noopener noreferrer";
        outboundUpdatePage.classList.add("outbound-update-page");

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

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

        errorPage.appendChild(errorMessage);
        return errorPage;
    }

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

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

        panelBox.classList.toggle("minimized", minimized);
        if (panelPage) panelPage.style.display = minimized ? "none" : "block";
        sectionDivider.forEach(el => {
            el.style.display = minimized ? "none" : "";
        });
        if (panelFooter) panelFooter.style.display = minimized ? "none" : "";
    }

    function hideShowPanel() {
        const panelBox = document.getElementById("panelBox");
        if (!panelBox) return;
        const isSearchPage = validateCurrentURL();
        if (isSearchPage) {
            panelBox.classList.add("show");
        } else {
            panelBox.classList.remove("show");
        }
    }

    function determinePanelState(sponsoredCount) {
        if (sponsoredCount < 2 || sponsoredCount > 20) {
            return buildPanelErrorPage();
        }
        return buildPanelHomePage();
    }

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

    function detectSponsoredListingBySVG(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 || 20;
                        canvas.height = img.naturalHeight || 20;
                        ctx.drawImage(img, 0, 0);
                        const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height).data;

                        const colors = new Set();
                        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) {
                                colors.add(`${r},${g},${b}`);
                            }
                        }
                        if (colors.size > 1) {
                            sponsoredElements.push(listing);
                        }
                        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 detectSponsoredListingBySeparatorSize() {
        const listings = getListingElements();
        const sponsoredListings = [];
        listings.forEach(listing => {
            const separatorSpan = listing.querySelector('span.s-item__sep');

            if (!separatorSpan) return;
            const innerSpan = separatorSpan.querySelector('span');

            const width = innerSpan?.offsetWidth || 0;
            const height = innerSpan?.offsetHeight || 0;
            const isSponsored = width > 0 && height > 0;

            if (isSponsored) {
                sponsoredListings.push(listing);
            }
        });
        return sponsoredListings;
    }

    function detectSponsoredListingByInvertStyle() {
        const invertStyleMatch = /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 = invertStyleMatch.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 detectSponsoredByAriaGroup() {
        function generateAriaGroupLabel(num) {
            let letters = '';
            do {
                letters = String.fromCharCode(65 + (num % 26)) + letters;
                num = Math.floor(num / 26) - 1;
            } while (num >= 0);
            return letters;
        }

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

        listings.forEach(listing => {
            const labelSpan = listing.querySelector('span[aria-labelledby]');
            if (!labelSpan) return;

            const ariaLabel = labelSpan.getAttribute('aria-labelledby');
            if (!ariaLabel || !ariaLabel.includes("s-")) return;

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

        let sponsoredGroup = null;
        let minCount = Infinity;

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

    function detectSponsoredBanner() {
        return new Promise((resolve) => {
            setTimeout(() => {
                const banners = Array.from(
                    document.querySelectorAll(".s-answer-region-center-top.s-answer-region > div")
                ).filter((el) => el.offsetHeight >= 140);
                banners.forEach(banner => {
                    banner.classList.add("sponsored-hidden-banner");
                });
                resolve(banners);
            }, 600);
        });
    }

    function detectSponsoredRibbon() {
        const breadcrumb = document.querySelector('.x-breadcrumb');
        if (breadcrumb) {
            breadcrumb.remove();
        }
        const placements = document.querySelector('.x-pda-placements');
        if (placements) {
            placements.remove();
        }
        const ribbon = document.querySelector('.x-evo-atf-top-river.vi-grid.vim > .d-vi-evo-region.vim > div');
        const whitelistMatch = ['statusmessage', 'x-alert'];
        const hasWhitelistedClass = ribbon && Array.from(ribbon.classList).some(cls =>
            whitelistMatch.some(pattern => cls.includes(pattern))
        );
        if (ribbon && !hasWhitelistedClass) {
            ribbon.style.minHeight = '48px';
            ribbon.style.maxHeight = '100px';
            ribbon.remove();
        }
    }

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

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

            const detectedSponsoredElements = new Set();

            const base64Results = await detectSponsoredListingBySVG();
            base64Results.forEach(el => {
                const li = el.closest("li");
                if (li) detectedSponsoredElements.add(li);
            });
            if (detectedSponsoredElements.size === 0) {
                const dimensionBasedResults = detectSponsoredListingBySeparatorSize();
                dimensionBasedResults.forEach(li => detectedSponsoredElements.add(li));
            }
            if (detectedSponsoredElements.size === 0) {
                const invertMethod = detectSponsoredListingByInvertStyle();
                invertMethod.elements?.forEach(container => {
                    const li = container.closest("li");
                    if (li) detectedSponsoredElements.add(li);
                });
            }
            if (detectedSponsoredElements.size === 0) {
                const ariaGroupResults = detectSponsoredByAriaGroup();
                ariaGroupResults.forEach(li => detectedSponsoredElements.add(li));
            }
            requestAnimationFrame(() => {
                for (const el of detectedSponsoredElements) {
                    if (!el.hasAttribute("data-sponsored-processed")) {
                        designateSponsoredContent(el);
                        highlightSponsoredContent(el);
                        hideShowSponsoredContent(el, hidingEnabled);
                    }
                }
                detectSponsoredRibbon();
                cleanListingURLS();
                cleanGeneralURLs();
                hideShowPanel();

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

                initializeObserver();
                isProcessing = false;
            });
            return detectedSponsoredElements.size;
        } catch (err) {
            console.error(`${APP_NAME_DEBUG}: UNABLE TO PROCESS SPONSORED CONTENT, SEE CONSOLE ERROR\n`, err);
            isProcessing = false;
            initializeObserver();
            return 0;
        }
    }

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

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

    function cleanListingURLS() {
        const listingURLMatch = /^https:\/\/www\.ebay\.([a-z.]+)\/itm\/(\d+)(?:[/?#].*)?/;
        const links = document.querySelectorAll("a[href*='/itm/']");
        links.forEach((link) => {
            const match = link.href.match(listingURLMatch);
            if (match) {
                const tld = match[1];
                const itemId = match[2];
                const cleanUrl = `https://www.ebay.${tld}/itm/${itemId}`;
                if (link.href !== cleanUrl) {
                    link.href = cleanUrl;
                }
            }
        });
    }

    function cleanGeneralURLs() {
        const links = document.querySelectorAll("a[href*='ebay.']");
        links.forEach((link) => {
            try {
                const url = new URL(link.href);
                const tldMatch = url.hostname.match(/(?:^|\.)ebay\.([a-z.]+)$/);
                if (!tldMatch) return;

                const params = new URLSearchParams(url.search);

                APP_BLOCK_PARAMETERS.forEach(param => {
                    if (params.has(param)) {
                        params.delete(param);
                    }
                });
                for (const key of [...params.keys()]) {
                    if (key.startsWith("utm_") || key.startsWith("_trk")) {
                        params.delete(key);
                    }
                }
                const cleanUrl = `${url.origin}${url.pathname}${params.toString() ? '?' + params.toString() : ''}${url.hash}`;
                if (link.href !== cleanUrl) {
                    link.href = cleanUrl;
                }
            } catch (e) {}
        });
    }

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

    function cleanListingObserver() {
        const urlCleanListingObserver = new MutationObserver(() => {
            cleanListingURLS();
        });
        urlCleanListingObserver.observe(document.body, {
            childList: true,
            subtree: true,
            attributes: true,
            attributeFilter: ['href'],
        });
    }

    const observer = new MutationObserver(() => {
        hideShowPanel();
        scheduleHighlightUpdate();
    });

    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', cleanListingObserver);
    } else {
        cleanListingObserver();
    }

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

    window.addEventListener("storage", (event) => {
        if (event.key === APP_KEY_SPONSORED_CONTENT) {
            const newValue = event.newValue === "true";
            if (newValue !== hidingEnabled) {
                hidingEnabled = newValue;
                const toggleSponsoredContentSwitchInput = document.getElementById("toggleSponsoredContentSwitch");
                if (toggleSponsoredContentSwitchInput) toggleSponsoredContentSwitchInput.checked = hidingEnabled;
                updateLockIcon();
                scheduleHighlightUpdate();
            }
        } else if (event.key === "panelMinimized") {
            minimizePanel(event.newValue === "true");
        }
    });
})();