Amazon Sponsored Products remover

Removes the terrible sponsored products from Amazon.

Terá de instalar uma extensão como Tampermonkey, Greasemonkey ou Violentmonkey para instalar este script.

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

Terá de instalar uma extensão como Tampermonkey ou Violentmonkey para instalar este script.

Terá de instalar uma extensão como Tampermonkey ou Userscripts para instalar este script.

Terá de instalar uma extensão como Tampermonkey para instalar este script.

Terá de instalar uma extensão de gestão de scripts de utilizador para instalar este script.

(Já tenho um gestor de scripts de utilizador, deixe-me instalá-lo!)

Terá de instalar uma extensão como Stylus para instalar este estilo.

Terá de instalar uma extensão como Stylus para instalar este estilo.

Terá de instalar uma extensão como Stylus para instalar este estilo.

Terá de instalar uma extensão de gestão de estilos de utilizador para instalar este estilo.

Terá de instalar uma extensão de gestão de estilos de utilizador para instalar este estilo.

Terá de instalar uma extensão de gestão de estilos de utilizador para instalar este estilo.

(Já tenho um gestor de estilos de utilizador, deixe-me instalá-lo!)

// ==UserScript==
// @name        Amazon Sponsored Products remover
// @namespace   https://greasyfork.org/en/users/2755-robotoilinc
// @author      RobotOilInc
// @version     0.5.3
// @license     MIT
// @description Removes the terrible sponsored products from Amazon.
// @include     http*://www.amazon.*/*
// @icon        https://i.imgur.com/LGHKHEs.png
// @run-at      document-body
// @grant       GM_registerMenuCommand
// @grant       GM_getValue
// @grant       GM_setValue
// ==/UserScript==

// ------------------------------------------------------------
// Persistent Settings
// ------------------------------------------------------------
const SETTINGS = {
    logging: GM_getValue("logging", false),
    showStats: GM_getValue("showStats", false),
    forceAllResults: GM_getValue("forceAllResults", false)
};

const toggleSetting = (key, { reload = false, after } = {}) => {
    SETTINGS[key] = !SETTINGS[key];
    GM_setValue(key, SETTINGS[key]);
    after?.();
    if (reload) location.reload();
};

// ------------------------------------------------------------
// Menu System
// ------------------------------------------------------------
const registerMenu = () => {
    GM_registerMenuCommand(`Logging: ${SETTINGS.logging ? "✔" : "✘"}`, () => {
        toggleSetting("logging", { reload: true });
    });

    GM_registerMenuCommand(`Stats: ${SETTINGS.showStats ? "✔" : "✘"}`, () => {
        toggleSetting("showStats", { after: updateStatsVisibility });
    });

    GM_registerMenuCommand(`Force all results: ${SETTINGS.forceAllResults ? "✔" : "✘"}`, () => {
        toggleSetting("forceAllResults", { reload: true });
    });
};

// ------------------------------------------------------------
// Stats Counter UI
// ------------------------------------------------------------
let stats = {
    total: 0,
    rules: {}
};

const createStatsBox = () => {
    if (document.getElementById("amazon-remover-stats")) return;

    const box = document.createElement("div");
    box.id = "amazon-remover-stats";
    box.innerHTML = `
        <strong>Amazon Remover</strong><br>
        Removed: <span id="ars-total">0</span>
        <hr id="ars-divider" style="margin:6px 0; border:0; border-top:1px solid rgba(255,255,255,0.25); display:none;">
        <div id="ars-breakdown"></div>
    `;

    // Add some CSS
    Object.assign(box.style, {
        position: "fixed",
        bottom: "10px",
        right: "10px",
        background: "rgba(0,0,0,0.75)",
        color: "white",
        padding: "10px 14px",
        fontSize: "12px",
        borderRadius: "6px",
        zIndex: "999999",
        maxWidth: "220px",
        lineHeight: "1.4",
        display: "none",
        contain: "layout style"
    });

    document.body.appendChild(box);
};

const updateStatsVisibility = () => {
    const box = document.getElementById("amazon-remover-stats");
    if (box) box.style.display = SETTINGS.showStats ? "block" : "none";
};

const updateStats = reason => {
    stats.total++;
    stats.rules[reason] = (stats.rules[reason] || 0) + 1;

    const totalEl = document.getElementById("ars-total");
    if (!totalEl) return;

    totalEl.textContent = stats.total;

    // Show divider only after first removal
    if (stats.total === 1) {
        document.getElementById("ars-divider").style.display = "block";
    }

    document.getElementById("ars-breakdown").innerHTML = Object.entries(stats.rules)
        .map(([k, v]) => `${k}: ${v}`)
        .join("<br>");
};

// ------------------------------------------------------------
// Logging Helper
// ------------------------------------------------------------
const log = (reason, el) => {
    if (SETTINGS.logging) {
        console.log(`[AmazonRemover] ${reason}`, el);
    }
};

// ------------------------------------------------------------
// Force all results (browse → search)
// ------------------------------------------------------------
const redirectToAllResults = () => {
    if (!SETTINGS.forceAllResults) return false;

    // Already on a real search results list
    if (document.querySelector("#search .s-result-list.s-search-results")) return false;

    const link = document.querySelector("#apb-desktop-browse-search-see-all, a.apb-desktop-browse-search-see-all");
    if (link?.href && link.href !== location.href) {
        location.replace(link.href);
        return true;
    }

    return false;
};

// ------------------------------------------------------------
// Rufus early CSS (hide + kill dock layout vars before paint settles)
// ------------------------------------------------------------
const injectRufusStyle = () => {
    if (document.getElementById("amazon-remover-rufus-css")) return;

    const style = document.createElement("style");
    style.id = "amazon-remover-rufus-css";
    style.textContent = `
        [id*="rufus" i]:not(body):not(html),
        [class*="rufus" i]:not(body):not(html) {
            display: none !important;
        }
        body {
            padding-left: 0 !important;
            padding-right: 0 !important;
            --rufus-docked-panel-width: 0px !important;
            --total-rufus-panel-full-width: 0px !important;
            --total-rufus-panel-half-width: 0px !important;
        }
    `;
    (document.head || document.documentElement).appendChild(style);
};

// ------------------------------------------------------------
// Rule Engine
// ------------------------------------------------------------
const RULES = [
    // Search
    { selector: '[data-component-type="sp-sponsored-result"]', parent: '[data-asin]', reason: 'Sponsored search result' },
    { selector: '.puis-sponsored-label-text', parent: '[class*="apbSearchResultItem"], [data-asin], .s-result-item', reason: 'Sponsored product' },
    { selector: '.s-widget-sponsored-label-text', parent: '[data-asin], .s-result-item, .a-carousel-container', reason: 'Sponsored product' },
    { selector: '.AdHolder', reason: 'Sponsored product' },
    { selector: '[data-adfeedbackdetails]', parent: '.celwidget', reason: 'Sponsored product' },
    { selector: '[data-video-type="sponsored"]', reason: 'Sponsored video' },
    { selector: '.sp_desktop_sponsored_label', parent: '.a-carousel-container', reason: 'Sponsored carousel' },

    // Skyscraper / side rail
    { selector: '[class*="_adPlacements"]', reason: 'Skyscraper ad' },
    { selector: '[data-cel-widget*="adplacements"]', reason: 'Skyscraper ad' },

    // Product page ads
    { selector: '#discovery-and-inspiration_feature_div', reason: 'Product page ad' },
    { selector: '#sims-themis-sponsored-products-2_feature_div', reason: 'Product page ad' },
    { selector: '#dp-ads-center-promo-dramabot_feature_div', reason: 'Product page ad' },
    { selector: '[data-cel-widget="desktop-dp-lpo_feature_div_0"]', reason: 'Product page ad' },
    { selector: '[data-cel-widget="dp-ads-center-promo_feature_div"]', reason: 'Product page ad' },
    { selector: '[data-cel-widget*="desktop-dp-atf_ad-placements"]', reason: 'Product page ad' },
    { selector: '[data-cel-widget="p13n-desktop-carousel_desktop-rhf_1"]', reason: 'Product page ad' },
    { selector: '[data-feature-name="amsDetailRight-dramabot"]', reason: 'Product page ad' },
    { selector: '[data-feature-name="dp-ads-center-promo-top-dramabot"]', reason: 'Product page ad' },
    { selector: '[data-feature-name="ad-endcap-1-dramabot"]', reason: 'Product page ad' },
    { selector: '[data-feature-name="sponsoredProducts2-2"]', reason: 'Product page ad' },
    { selector: '[data-feature-name="sims-sponsoredProducts2"]', reason: 'Product page ad' },
    { selector: '[id^="sponsoredProducts2"]', reason: 'Product page ad' },
    { selector: '#percolate-ui-ilm_div', reason: 'Product page ad' },
    { selector: '#rhf-shoveler', reason: 'Product page ad' },
    { selector: '[data-feature-name="desktop-dp-ilm"]', reason: 'Product page ad' },

    // Product page promos / upsells
    { selector: '[data-feature-name="valuePick"]', reason: 'Product page promo' },
    { selector: '[data-feature-name="heroQuickPromoContainer"]', reason: 'Product page promo' },
    { selector: '[data-feature-name*="primeDPUpsell"], [id*="primeDPUpsell"]', reason: 'Prime upsell' },

    // Brand / SIMS blocks
    { selector: '[data-feature-name="sims-discoveryAndInspiration"]', reason: 'Brand promotion' },
    { selector: '[data-feature-name="sims-simsContainer"]', reason: 'Brand promotion' },
    { selector: '[data-feature-name="sims-productBundle"]', reason: 'Brand promotion' },

    // Reviews / Live
    { selector: '[data-csa-c-owner="CustomerReviews"]', reason: 'AI review insights' },
    { selector: '[data-id*="AmazonLiveDram"]', reason: 'Amazon Live' },

    // Tracking page
    { selector: '[class*="spSponsored"]', parent: '#recsWidget', reason: 'Tracking page ad' },
    { selector: '[class*="dynamic-sponsored-behaviour-container"]', parent: '.a-carousel-container', reason: 'Tracking page ad' },

    // Category / browse
    { selector: '[data-csa-c-painter="OctopusDramAsinPainter"]', reason: 'Category promo carousel' },
    { selector: '[data-csa-c-painter="content-grid-card-cards"]', parent: '.apb-default-slot', reason: 'Category brand banner' },

    // Homepage
    { selector: '[class*="asin-sponsored-badge-container"]', parent: '[data-card-metrics-id]', reason: 'Homepage ad' },
    { selector: '[class*="widget-sponsored-badge-container"]', parent: '[data-card-metrics-id]', reason: 'Homepage ad' },
    { selector: '[class*="windowDisplaySponsoredBadgeContainer"]', parent: '[data-card-metrics-id]', reason: 'Homepage ad' },
    { selector: '[class*="sponsoredBadgeContainer"]', parent: '[data-card-metrics-id]', reason: 'Homepage ad' },
    { selector: '[cel_widget_id="desktop-hero-order"]', reason: 'Homepage hero' },

    // Rufus
    { selector: '#nav-rufus-disco', reason: 'Rufus' },
    { selector: '#nav-flyout-rufus', reason: 'Rufus' },
    { selector: '.rufus-panel-container', reason: 'Rufus' },
    { selector: '.rufus-teaser-cx-container', reason: 'Rufus' },
    { selector: '#nile-inline_feature_div', reason: 'Rufus' },
    { selector: '[data-feature-name="nile-inline"]', reason: 'Rufus' },
    { selector: '[id^="nile-inline"]', reason: 'Rufus' },
    { selector: '[data-feature-name^="nile-inline"]', reason: 'Rufus' },
    { selector: '[id*="rufus"]:not(body):not(html)', reason: 'Rufus' },
    { selector: '[class*="rufus"]:not(body):not(html)', reason: 'Rufus' },
    { selector: '[data-component-type*="rufus"]', reason: 'Rufus' },
    { selector: '[data-action*="rufus"]', reason: 'Rufus' },
    { selector: '[data-csa-c-content-id*="rufus"]', reason: 'Rufus' },
    { selector: '[data-csa-c-slot-id*="rufus"]', reason: 'Rufus' },
];

const removeTarget = (target, reason) => {
    // Already gone / not in document (prevents double-count across overlapping rules)
    if (!target?.isConnected) return;
    log(reason, target);
    target.remove();
    updateStats(reason);
};

const applyRule = rule => {
    document.querySelectorAll(rule.selector).forEach(el => {
        removeTarget(rule.parent ? el.closest(rule.parent) : el, rule.reason);
    });
};

const cleanSearchClutter = () => {
    // Special case: search clutter
    document.querySelectorAll('#search .s-result-list.s-search-results > div:not([data-component-type="s-search-result"])').forEach(el => {
        if (el.querySelector(".s-pagination-strip")) return;
        removeTarget(el, "Search clutter");
    });
};

const clearRufusDock = () => {
    if (!document.body) return;

    // Strip any rufus-* class Amazon re-applies for dock layout
    const kept = [...document.body.classList].filter(c => !/rufus/i.test(c));
    if (kept.length !== document.body.classList.length) {
        document.body.className = kept.join(" ");
    }

    // Rufus sets inline padding when docked
    document.body.style.paddingLeft = "";
    document.body.style.paddingRight = "";
};

const sweep = () => {
    // Skip DOM work if we're about to navigate away
    if (redirectToAllResults()) return;

    // Apply all rules
    RULES.forEach(applyRule);
    cleanSearchClutter();
    clearRufusDock();
};

// ------------------------------------------------------------
// Mutation Observer
// ------------------------------------------------------------
let scheduled = 0;
let sweeping = false;

const scheduleSweep = () => {
    // Coalesce bursts of Amazon DOM mutations into one rAF tick (keeps INP healthier)
    if (sweeping || scheduled) return;
    scheduled = requestAnimationFrame(() => {
        scheduled = 0;
        sweeping = true;
        try {
            sweep();
        } finally {
            sweeping = false;
        }
    });
};

// Initialize everything else
injectRufusStyle();
registerMenu();
createStatsBox();
updateStatsVisibility();

new MutationObserver(scheduleSweep).observe(document.body, { childList: true, subtree: true });

// Catch ads already in the initial HTML
sweep();