Amazon Plus (UK)

Auto-sort popular by default; re-applies on every search results update (SPA/pagination).

Você precisará instalar uma extensão como Tampermonkey, Greasemonkey ou Violentmonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey ou Violentmonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey ou Userscripts para instalar este script.

Você precisará instalar uma extensão como o Tampermonkey para instalar este script.

Você precisará instalar um gerenciador de scripts de usuário para instalar este script.

(Eu já tenho um gerenciador de scripts de usuário, me deixe instalá-lo!)

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

(Eu já possuo um gerenciador de estilos de usuário, me deixar fazer a instalação!)

// ==UserScript==
// @name         Amazon Plus (UK)
// @namespace    http://tampermonkey.net/
// @version      5.5
// @description  Auto-sort popular by default; re-applies on every search results update (SPA/pagination).
// @author       Gemini (and You!)
// @license      MIT
// @match        *://www.amazon.co.uk/*
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_setClipboard
// @run-at       document-idle
// ==/UserScript==

(function() {
    'use strict';

    // --- UTILS ---
    // Safe wrappers so the script degrades gracefully if GM_* APIs are missing
    const safeGMGet = (key, def) => {
        try {
            if (typeof GM_getValue === 'function') {
                return GM_getValue(key, def);
            }
        } catch (e) {
            console.warn('GM_getValue failed, falling back to localStorage.', e);
        }
        try {
            const raw = window.localStorage.getItem(`amz_plus_${key}`);
            if (raw === null) return def;
            return JSON.parse(raw);
        } catch {
            return def;
        }
    };

    const safeGMSet = (key, value) => {
        try {
            if (typeof GM_setValue === 'function') {
                GM_setValue(key, value);
            }
        } catch (e) {
            console.warn('GM_setValue failed, falling back to localStorage.', e);
        }
        try {
            window.localStorage.setItem(`amz_plus_${key}`, JSON.stringify(value));
        } catch {
            // ignore storage failures
        }
    };

    const getVal = (id, def) => safeGMGet(id, def);
    const setVal = (id, val) => safeGMSet(id, val);

    const FEATURE_DEFAULTS = {
        amz_master: true, // global ON/OFF switch (ON by default)
        amz_sort: true, // Auto-Sort Popular: ON by default on every search page
        amz_ads: true,
        amz_purist: false
    };

    /** Debounced re-run so SPA / pagination / lazy results don’t miss a sort pass */
    let searchApplyTimer = null;
    let searchObserver = null;
    const SEARCH_APPLY_DEBOUNCE_MS = 120;

    function scheduleSearchApply() {
        if (isProductPage()) return;
        if (searchApplyTimer) clearTimeout(searchApplyTimer);
        searchApplyTimer = setTimeout(() => {
            searchApplyTimer = null;
            try {
                setupSearchResultsWatcher(); // attach once `.s-main-slot` exists (late DOM)
                applySortAndFilter();
            } catch (e) {
                console.warn('Amazon Plus: applySortAndFilter failed', e);
            }
        }, SEARCH_APPLY_DEBOUNCE_MS);
    }

    function setupSearchResultsWatcher() {
        if (isProductPage()) return;
        const container = document.querySelector('.s-main-slot') || document.querySelector('.s-result-list');
        if (!container || searchObserver) return;

        searchObserver = new MutationObserver(() => scheduleSearchApply());
        searchObserver.observe(container, { childList: true, subtree: true });
    }

    let historyHooksInstalled = false;
    function hookHistoryForSearchPages() {
        if (historyHooksInstalled) return;
        historyHooksInstalled = true;
        const notify = () => scheduleSearchApply();
        window.addEventListener('popstate', notify);
        const wrap = (fn) =>
            function stateWrapped() {
                const ret = fn.apply(this, arguments);
                scheduleSearchApply();
                return ret;
            };
        if (history.pushState) history.pushState = wrap(history.pushState);
        if (history.replaceState) history.replaceState = wrap(history.replaceState);
    }

    const getFlag = (key) => {
        const def = Object.prototype.hasOwnProperty.call(FEATURE_DEFAULTS, key)
            ? FEATURE_DEFAULTS[key]
            : false;
        const stored = getVal(key, null);
        return stored === null ? def : stored;
    };

    function isProductPage() {
        return window.location.pathname.includes('/dp/') || window.location.pathname.includes('/gp/product/');
    }

    function extractRatingCount(el) {
        const node =
            el.querySelector('.haul-puis-ratings-container .a-size-micro:last-child') ||
            el.querySelector('span[aria-label*="ratings"]') ||
            el.querySelector('.s-underline-text') ||
            el.querySelector('[aria-label*="ratings"]');

        if (!node) return 0;

        const raw = (node.innerText || node.getAttribute('aria-label') || '0').replace(/,/g, '');
        const re = /([\d.]+)\s*(K)?/gi;
        let max = 0;
        let m;
        while ((m = re.exec(raw)) !== null) {
            let val = parseFloat(m[1]);
            if (!Number.isFinite(val)) continue;
            if (m[2]) val *= 1000;
            if (val > max) max = val;
        }
        return Math.round(max) || 0;
    }

    // --- SEARCH PAGE ENGINE ---
    function applySortAndFilter() {
        if (isProductPage()) return;
        const container = document.querySelector('.s-main-slot') || document.querySelector('.s-result-list');
        if (!container) return;

        const items = container.querySelectorAll('.s-result-item[data-asin]');
        if (items.length === 0) return;

        // Master switch off: restore defaults and exit.
        if (!getFlag('amz_master')) {
            items.forEach((item) => {
                if (!item.getAttribute('data-asin')) return;
                item.style.display = '';
                item.style.order = '';
            });
            return;
        }

        const doSort = getFlag('amz_sort');
        const doAds = getFlag('amz_ads');
        const doPurist = getFlag('amz_purist');

        items.forEach(item => {
            if (!item.getAttribute('data-asin')) return;
            const text = item.innerText;
            const isSponsored = item.classList.contains('AdHolder') || text.includes('Sponsored');
            const isThirdParty = doPurist && !text.includes('Prime') && !text.includes('Amazon');

            if ((doAds && isSponsored) || isThirdParty) {
                item.style.display = 'none';
            } else {
                item.style.display = ''; 
            }

            item.style.order = doSort ? -extractRatingCount(item) : ''; 
        });
    }

    // --- SIDEBAR UI (SEARCH PAGE) ---
    function injectSidebar() {
        if (isProductPage() || document.getElementById('god-mode-panel')) return;
        const sidebar = document.querySelector('#s-refinements') || document.querySelector('.sf-left-nav-reflow');
        if (!sidebar) return;

        const panel = document.createElement('div');
        panel.id = 'god-mode-panel';
        panel.style.cssText = "background: #fdfdfd; padding: 12px; border: 2px solid #B12704; border-radius: 6px; margin-bottom: 15px; font-family: Arial;";

        const createToggle = (id, label) => `
            <div style="display:flex; justify-content:space-between; margin-bottom:8px; font-size:12px; color:#0f1111;">
                <b>${label}</b>
                <input type="checkbox" id="${id}" ${getFlag(id) ? 'checked' : ''} style="cursor:pointer;">
            </div>`;

        panel.innerHTML = `
            <div style="font-weight:900; border-bottom:1px solid #ddd; margin-bottom:10px; padding-bottom:5px; color:#B12704;">⚡ GOD MODE TOOLS</div>
            ${createToggle('amz_master', '⏻ On/Off Switch')}
            ${createToggle('amz_sort', '📊 Auto-Sort Popular')}
            ${createToggle('amz_ads', '🚫 Block Sponsored Ads')}
            ${createToggle('amz_purist', '🏭 STRICT: Amazon/Prime Only')}
        `;

        sidebar.insertAdjacentElement('afterbegin', panel);
        
        panel.querySelectorAll('input').forEach(input => {
            input.addEventListener('change', (e) => {
                const id = e.target.id;
                setVal(id, e.target.checked);
                if (id === 'amz_master') {
                    // When master toggles, rerun engines so both product/search behavior update.
                    runEngines();
                } else {
                    applySortAndFilter();
                }
            });
        });
        
        // Ensure defaults are written once so future checks are consistent
        Object.keys(FEATURE_DEFAULTS).forEach((key) => {
            if (getVal(key, null) === null) {
                setVal(key, FEATURE_DEFAULTS[key]);
            }
        });
    }

    // --- PRODUCT PAGE: DASHBOARD ---
    function injectProductDashboard() {
        if (!isProductPage() || document.getElementById('god-mode-dashboard')) return;
        
        const targetContainer = document.querySelector('#titleSection') || document.querySelector('#centerCol');
        if (!targetContainer) return;

        const asinMatch = window.location.href.match(/\/(?:dp|gp\/product)\/([A-Z0-9]{10})/);
        const asin = asinMatch ? asinMatch[1] : '';
        if (!asin) return;

        const pageText = document.documentElement.textContent;
        
        const bsrMatch = pageText.match(/Best Sellers Rank:?\s*#?([0-9,]+)\s+in\s+([A-Za-z&\s]+)/i);
        const bsrText = bsrMatch ? `🏆 <b>BSR:</b> #${bsrMatch[1]} in ${bsrMatch[2].trim().substring(0, 20)}...` : '<span style="color:#888;">No BSR found</span>';

        const isNonReturnable = pageText.includes('Non-returnable') || pageText.includes('not returnable');
        const returnHtml = isNonReturnable ? `<div style="width:100%; background:#ffe3e3; color:#B12704; padding:6px; border:1px solid #B12704; font-weight:bold; text-align:center; margin-bottom:8px; border-radius:4px; font-size:12px;">🚨 WARNING: This item is NON-RETURNABLE</div>` : '';

        const hasUsed = pageText.includes('Used from') || document.querySelector('#olpLinkWidget_feature_div');
        const warehouseHtml = hasUsed ? `<a href="https://www.amazon.co.uk/dp/${asin}#aod" style="background:#007185; color:white; padding:4px 8px; border-radius:4px; text-decoration:none; font-weight:bold;">♻️ Open Box Deals</a>` : '';

        let ppuHtml = '';
        const ppuNode = document.querySelector('.a-size-small.a-color-price');
        if (ppuNode && ppuNode.textContent.includes('/')) {
            ppuHtml = `<span style="background:#fff3d4; color:#B12704; font-weight:900; padding:2px 6px; border-radius:4px; border:1px solid #f4bf42;">⚖️ ${ppuNode.textContent.trim()}</span>`;
            ppuNode.style.display = 'none'; 
        }

        const mainImg = document.querySelector('#landingImage') || document.querySelector('#imgBlkFront');
        const lensLink = mainImg ? `https://lens.google.com/uploadbyurl?url=${encodeURIComponent(mainImg.src)}` : '#';

        const dash = document.createElement('div');
        dash.id = 'god-mode-dashboard';
        dash.style.cssText = "margin: 10px 0; padding: 12px; background: #fdfdfd; border: 1px solid #ccc; border-top: 3px solid #007185; border-radius: 4px; display: flex; flex-direction: column; gap: 8px; font-size: 12px; width: 100%; box-sizing: border-box;";

        dash.innerHTML = `
            ${returnHtml}
            <div style="display:flex; flex-wrap:wrap; gap:12px; align-items:center;">
                ${ppuHtml}
                <span style="color:#333;">${bsrText}</span>
            </div>
            <div style="display:flex; flex-wrap:wrap; gap:10px; align-items:center; border-top:1px solid #eee; padding-top:8px;">
                <span id="as-copy-link" style="color:#007185; cursor:pointer; font-weight:bold;" title="Copy tracking-free URL">🔗 Copy Clean Link (${asin})</span>
                <span style="color:#ccc;">|</span>
                <a href="${lensLink}" target="_blank" style="color:#B12704; font-weight:bold; text-decoration:none;" title="Reverse image search on Google to find AliExpress/Dropshippers">🕵️ AliExpress Spotter</a>
                <span style="color:#ccc;">|</span>
                <a href="https://www.hagglezon.com/en/s/${asin}" target="_blank" style="color:#007185; font-weight:bold; text-decoration:none;">🌍 Compare EU Prices</a>
                ${warehouseHtml !== '' ? '<span style="color:#ccc;">|</span>' + warehouseHtml : ''}
            </div>
        `;

        if (targetContainer.id === 'titleSection') {
            targetContainer.insertAdjacentElement('afterend', dash);
        } else {
            targetContainer.insertAdjacentElement('afterbegin', dash);
        }

        document.getElementById('as-copy-link').addEventListener('click', (e) => {
            GM_setClipboard(`https://www.amazon.co.uk/dp/${asin}`);
            e.target.innerText = '✅ Clean Link Copied!';
            setTimeout(() => e.target.innerText = `🔗 Copy Clean Link (${asin})`, 1500);
        });
    }

    // --- PRODUCT PAGE: KEEPA (BOTTOM) ---
    function injectKeepa() {
        if (!isProductPage() || document.getElementById('as-keepa-box')) return;
        
        const targetContainer = document.querySelector('#feature-bullets') || document.querySelector('#centerCol');
        if (!targetContainer) return; 

        const asinMatch = window.location.href.match(/\/(?:dp|gp\/product)\/([A-Z0-9]{10})/);
        const asin = asinMatch ? asinMatch[1] : '';
        if (!asin) return;

        const box = document.createElement('div');
        box.id = 'as-keepa-box';
        box.style.cssText = "margin-top: 15px; border: 1px solid #ddd; padding: 15px; background: #fff; text-align: center; width: 100%; box-sizing: border-box; border-radius: 4px;";
        
        box.innerHTML = `
            <div style="font-weight:bold; margin-bottom:10px; font-size:13px; color:#0f1111; text-align:left;">📉 Price History (Keepa)</div>
            <img id="keepa-graph-img" src="https://graph.keepa.com/pricehistory.png?asin=${asin}&domain=co.uk" style="width:100%; max-width: 600px; cursor:pointer; border-radius:4px; border: 1px solid #eee;">
        `;
        
        if (targetContainer.id === 'feature-bullets') {
            targetContainer.insertAdjacentElement('afterend', box);
        } else {
            targetContainer.insertAdjacentElement('beforeend', box);
        }

        document.getElementById('keepa-graph-img').addEventListener('click', () => {
            window.open(`https://keepa.com/#!product/2-${asin}`);
        });
    }

    // --- RELIABLE EVENT LOOP ---
    function runEngines() {
        try {
            if (isProductPage()) {
                // On product pages, master OFF removes enhancements; ON injects them.
                if (!getFlag('amz_master')) {
                    const dash = document.getElementById('god-mode-dashboard');
                    if (dash && dash.parentElement) dash.parentElement.removeChild(dash);
                    const keepa = document.getElementById('as-keepa-box');
                    if (keepa && keepa.parentElement) keepa.parentElement.removeChild(keepa);
                    return;
                }
                injectProductDashboard();
                injectKeepa();
            } else {
                // Always show sidebar on search pages so the master switch is reachable.
                injectSidebar();
                applySortAndFilter();
                setupSearchResultsWatcher();
                scheduleSearchApply(); // catch late DOM (sidebar sometimes before results)
            }
        } catch (error) {
            console.warn("God Mode Script Encountered a DOM Error:", error);
        }
    }

    // Re-apply when Amazon swaps results (pagination, filters, SPA navigation).
    hookHistoryForSearchPages();

    runEngines();

})();