Greasy Fork

Amazon Enhancer: ReviewMeta + Camel + Keepa + UI Panel + Dark Mode + Review Highlighter

Adds ReviewMeta, CamelCamelCamel, and Keepa charts to Amazon product pages with a draggable UI panel, dark mode, best review highlighting, ad hiding, and more.

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

// ==UserScript==
// @name         Amazon Enhancer: ReviewMeta + Camel + Keepa + UI Panel + Dark Mode + Review Highlighter
// @namespace    Eliminater74
// @version      1.3.0
// @description  Adds ReviewMeta, CamelCamelCamel, and Keepa charts to Amazon product pages with a draggable UI panel, dark mode, best review highlighting, ad hiding, and more.
// @author       Eliminater74
// @license      MIT
// @match        https://www.amazon.com/*
// @match        https://www.amazon.co.uk/*
// @match        https://www.amazon.de/*
// @match        https://www.amazon.fr/*
// @match        https://www.amazon.it/*
// @match        https://www.amazon.es/*
// @match        https://www.amazon.ca/*
// @match        https://smile.amazon.com/*
// @grant        GM_xmlhttpRequest
// @connect      reviewmeta.com
// @run-at       document-end
// ==/UserScript==

(function () {
    'use strict';

    const SETTINGS_KEY = 'amazonEnhancerSettings';
    const defaultSettings = {
        showReviewMeta: true,
        showCamel: true,
        showKeepa: true,
        theme: 'auto',
        highlightBestReviews: true,
        hideAds: true,
        showSoldBy: true,
        stickyPriceBox: true,
        autoSortReviews: true,
        expandReviewsQA: true
    };

    const settings = JSON.parse(localStorage.getItem(SETTINGS_KEY)) || defaultSettings;
    const locale = getLocale();
    const asin = findASIN();
    if (!asin) return;

    applyTheme(settings.theme);

    if (settings.showReviewMeta) injectReviewMeta(asin, locale);
    if (settings.showCamel) injectCamel(asin, locale);
    if (settings.showKeepa) injectKeepa(asin, locale);
    if (settings.highlightBestReviews) highlightReviews();
    if (settings.hideAds) hideSponsored();
    if (settings.showSoldBy) showSoldByBox();
    if (settings.stickyPriceBox) makeStickyPriceBox();
    if (settings.autoSortReviews) autoSortReviews();
    if (settings.expandReviewsQA) expandSections();

    createToggleUI();

    function saveSettings() {
        localStorage.setItem(SETTINGS_KEY, JSON.stringify(settings));
    }

    function applyTheme(mode) {
        const html = document.documentElement;
        const theme = (mode === 'auto') ? (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light') : mode;
        html.setAttribute('data-enhancer-theme', theme);
        
        let style = document.getElementById('amazon-enhancer-theme-style');
        if (style) style.remove();
        style = document.createElement('style');
        style.id = 'amazon-enhancer-theme-style';
        style.textContent = `
        [data-enhancer-theme="dark"] .amazon-enhancer-box {
            background: #1d1d1d !important;
            color: #f0f0f0 !important;
            border-color: #555 !important;
        }
        [data-enhancer-theme="dark"] .amazon-enhancer-box a {
            color: #7dddf2 !important;
        }
        [data-enhancer-theme="dark"] .amazon-enhancer-panel {
            background: #2c2c2c !important;
            color: #eee !important;
            border-color: #555;
        }
        .highlighted-review {
            border: 2px solid gold !important;
            background-color: #fffbea !important;
        }
        `;
        document.head.appendChild(style);
    }

    function getLocale() {
        const host = location.hostname;
        if (host.includes(".co.uk")) return "uk";
        if (host.includes(".de")) return "de";
        if (host.includes(".fr")) return "fr";
        if (host.includes(".es")) return "es";
        if (host.includes(".it")) return "it";
        if (host.includes(".ca")) return "ca";
        return "us";
    }

    function findASIN() {
        const match = location.href.match(/\/([A-Z0-9]{10})(?:[/?]|$)/);
        if (match) return match[1];
        const el = document.getElementById("ASIN") || document.querySelector('[name="ASIN.0"]');
        return el?.value || null;
    }

    function injectCamel(asin, locale) {
        const div = document.createElement("div");
        div.className = 'amazon-enhancer-box';
        div.innerHTML = `<a href='https://${locale}.camelcamelcamel.com/product/${asin}' target='_blank'>
            <img src='https://charts.camelcamelcamel.com/${locale}/${asin}/amazon-new-used.png?force=1&zero=0&w=500&h=300' style='max-width:100%;'>
        </a>`;
        appendToTarget(div);
    }

    function injectKeepa(asin, locale) {
        const div = document.createElement("div");
        div.className = 'amazon-enhancer-box';
        div.innerHTML = `<a href='https://keepa.com/#!product/1-${asin}' target='_blank'>
            <img src='https://graph.keepa.com/pricehistory.png?used=1&amazon=1&new=1&domain=${locale}&asin=${asin}' style='max-width:100%;height:auto;'>
        </a>`;
        appendToTarget(div);
    }

    function injectReviewMeta(asin, locale) {
        const url = `https://reviewmeta.com/amazon${locale === 'us' ? '' : '-' + locale}/${asin}`;
        GM_xmlhttpRequest({
            method: 'GET',
            url,
            onload: res => {
                const doc = new DOMParser().parseFromString(res.responseText, "text/html");
                const stars = doc.querySelector('#adjusted-rating-large')?.textContent?.trim();
                const percent = Array.from(doc.querySelectorAll('small'))
                    .find(e => e.textContent.includes('potentially unnatural'))?.querySelector('span span')?.textContent?.trim();

                const div = document.createElement('div');
                div.className = 'amazon-enhancer-box';
                div.style = 'margin-top:10px;padding:10px;border:1px solid #ccc;';
                div.innerHTML = stars
                    ? `<b>ReviewMeta Adjusted:</b> <span style='color:firebrick'>${stars}/5</span><br/>
                       <b>Fake Reviews:</b> <span style='color:firebrick'>${percent}</span><br/>
                       <a href='${url}' target='_blank' style='color:green;'>View on ReviewMeta</a>`
                    : `<b style='color:red;'>ReviewMeta data not found.</b><br/><a href='${url}' target='_blank'>Submit product</a>`;
                appendToTarget(div);
            }
        });
    }

    function appendToTarget(el) {
        const target = document.getElementById("unifiedPrice_feature_div") || document.getElementById("title")?.closest(".a-section");
        if (target) target.appendChild(el);
    }

    function createToggleUI() {
        const gear = document.createElement("div");
        gear.innerText = "⚙️";
        Object.assign(gear.style, {
            position: "fixed",
            bottom: "20px",
            right: "20px",
            width: "40px",
            height: "40px",
            background: "#fff",
            border: "1px solid #ccc",
            borderRadius: "50%",
            display: "flex",
            justifyContent: "center",
            alignItems: "center",
            cursor: "move",
            zIndex: 99999
        });

        const panelWrapper = document.createElement("div");
        panelWrapper.className = 'amazon-enhancer-panel';
        panelWrapper.style.cssText = 'position:fixed;bottom:70px;right:20px;border:1px solid #ccc;padding:10px;border-radius:8px;z-index:99999;background:#fff;';
        panelWrapper.style.display = 'none';

        const toggleItems = [
            ['showReviewMeta', 'ReviewMeta'],
            ['showCamel', 'CamelCamelCamel'],
            ['showKeepa', 'Keepa'],
            ['highlightBestReviews', 'Highlight Best Reviews'],
            ['hideAds', 'Hide Sponsored Ads'],
            ['showSoldBy', 'Show Sold By'],
            ['stickyPriceBox', 'Sticky Price Box'],
            ['autoSortReviews', 'Auto Sort Reviews'],
            ['expandReviewsQA', 'Expand Q&A/Reviews']
        ];

        panelWrapper.innerHTML = toggleItems.map(([key, label]) =>
            `<label><input type='checkbox' id='${key}' ${settings[key] ? 'checked' : ''}/> ${label}</label><br/>`
        ).join('') + `
        <label>Theme:
            <select id='themeSelect'>
                <option value='auto' ${settings.theme === 'auto' ? 'selected' : ''}>Auto</option>
                <option value='light' ${settings.theme === 'light' ? 'selected' : ''}>Light</option>
                <option value='dark' ${settings.theme === 'dark' ? 'selected' : ''}>Dark</option>
            </select>
        </label>`;

        toggleItems.forEach(([key]) => {
            panelWrapper.querySelector(`#${key}`).addEventListener('change', (e) => {
                settings[key] = e.target.checked;
                saveSettings();
                location.reload();
            });
        });

        panelWrapper.querySelector('#themeSelect').addEventListener('change', (e) => {
            settings.theme = e.target.value;
            saveSettings();
            applyTheme(settings.theme);
        });

        gear.addEventListener('click', () => {
            panelWrapper.style.display = panelWrapper.style.display === 'none' ? 'block' : 'none';
        });

        gear.addEventListener('mousedown', function (e) {
            const shiftX = e.clientX - gear.getBoundingClientRect().left;
            const shiftY = e.clientY - gear.getBoundingClientRect().top;
            function moveAt(pageX, pageY) {
                gear.style.left = pageX - shiftX + 'px';
                gear.style.top = pageY - shiftY + 'px';
                gear.style.right = 'auto';
                gear.style.bottom = 'auto';
            }
            function onMouseMove(e) {
                moveAt(e.pageX, e.pageY);
            }
            document.addEventListener('mousemove', onMouseMove);
            gear.onmouseup = function () {
                document.removeEventListener('mousemove', onMouseMove);
                gear.onmouseup = null;
            };
        });
        gear.ondragstart = () => false;

        document.body.appendChild(gear);
        document.body.appendChild(panelWrapper);
    }

    function highlightReviews() {
        const reviews = [...document.querySelectorAll('.review')];
        const scored = reviews.map(el => {
            const helpful = parseInt(el.innerText.match(/(\d+,?\d*) people found this helpful/)?.[1]?.replace(',', '') || '0');
            const verified = el.innerHTML.includes('Verified Purchase') ? 5 : 0;
            const longText = el.innerText.length > 500 ? 5 : 0;
            const hasMedia = el.querySelectorAll('img').length ? 5 : 0;
            return { el, score: helpful + verified + longText + hasMedia };
        });
        scored.sort((a, b) => b.score - a.score);
        scored.slice(0, 3).forEach(r => r.el.classList.add('highlighted-review'));
    }

    function hideSponsored() {
        document.querySelectorAll("[data-component-type='sp-sponsored-result']").forEach(el => el.remove());
    }

    function showSoldByBox() {
        const el = document.querySelector("#merchant-info");
        if (el) el.style.border = "2px dashed orange";
    }

    function makeStickyPriceBox() {
        const box = document.getElementById("corePrice_feature_div");
        if (box) {
            box.style.position = "sticky";
            box.style.top = "0";
            box.style.background = "#fff";
            box.style.zIndex = 9999;
            box.style.borderBottom = "2px solid #ccc";
        }
    }

    function autoSortReviews() {
        const sortSelect = document.querySelector('select[name="sortBy"]');
        if (sortSelect) sortSelect.value = "recent";
    }

    function expandSections() {
        document.querySelectorAll(".a-expander-prompt").forEach(e => e.click());
    }
})();