Greasy Fork

European Price Checker for Amazon (fr, de, es, it)

Compare product prices on Amazon.fr, Amazon.de, Amazon.es, and Amazon.it to find the best deal. Integrates CamelCamelCamel for price history charts.

当前为 2024-10-31 提交的版本,查看 最新版本

// ==UserScript==
// @name         European Price Checker for Amazon (fr, de, es, it)
// @namespace    http://tampermonkey.net/
// @version      2.3
// @description  Compare product prices on Amazon.fr, Amazon.de, Amazon.es, and Amazon.it to find the best deal. Integrates CamelCamelCamel for price history charts.
// @author       bNj
// @icon         https://i.ibb.co/qrjrcVy/amz-price-checker.png
// @match        https://www.amazon.fr/*
// @match        https://www.amazon.de/*
// @match        https://www.amazon.es/*
// @match        https://www.amazon.it/*
// @grant        GM_xmlhttpRequest
// @connect      amazon.fr
// @connect      amazon.es
// @connect      amazon.it
// @connect      amazon.de
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    // CSS Styles
    const styles = `
    #amazonPriceComparisonContainer { margin-top: 20px; padding: 10px; background-color: #f9f9f9; border: 1px solid #ccc; border-radius: 8px; position: relative; font-size: 11px; }
    .comparison-row { cursor: pointer; display: flex; justify-content: space-between; padding: 5px 0; border-bottom: 1px solid #ccc; }
    .comparison-row:hover { background-color: #f1f1f1; }
    .comparison-row.header-row { border-bottom: 2px solid #000; font-weight: bold; pointer-events: none; }
    #loadingMessage { text-align: center; font-weight: bold; font-size: 14px; display: flex; flex-direction: column; align-items: center; background-clip: text; -webkit-background-clip: text; color: transparent; background-image: linear-gradient(270deg, black 0%, black 20%, #FF9900 50%, black 80%, black 100%); background-size: 200% 100%; animation: loadingAnimation 2s linear infinite; }
    @keyframes loadingAnimation { 0% { background-position: 100% 50%; } 100% { background-position: 0% 50%; } }
    .active-button { background-color: #ff9900 !important; }
    .price-difference-positive { color: green; }
    .price-difference-negative { color: red; }
    `;

    // Inject CSS Styles
    const styleSheet = document.createElement('style');
    styleSheet.type = 'text/css';
    styleSheet.innerText = styles;
    document.head.appendChild(styleSheet);

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

    const PARTNER_IDS = {
        fr: 'geeksince1983-21',
        es: 'geeksince1901-21',
        it: 'geeksince1903-21',
        de: 'geeksince190d-21'
    };

    const amazonSites = [
        { name: 'Amazon.fr', country: 'fr', flag: 'https://flagcdn.com/w20/fr.png' },
        { name: 'Amazon.es', country: 'es', flag: 'https://flagcdn.com/w20/es.png' },
        { name: 'Amazon.it', country: 'it', flag: 'https://flagcdn.com/w20/it.png' },
        { name: 'Amazon.de', country: 'de', flag: 'https://flagcdn.com/w20/de.png' }
    ];

    let selectedTimePeriod = 'all';
    const basePrice = getPriceFromDocument(document);
    if (basePrice === null) return;

    createLoadingContainer();

    let requestCount = 0;
    const priceResults = [];

    amazonSites.forEach(site => {
        const url = `https://www.amazon.${site.country}/dp/${asin}?tag=${PARTNER_IDS[site.country]}`;
        GM_xmlhttpRequest({
            method: 'GET',
            url: url,
            headers: { 'User-Agent': 'Mozilla/5.0', 'Accept-Language': 'en-US,en;q=0.5' },
            onload: response => handleResponse(site, response),
            onerror: () => handleResponse(site, null)
        });
    });

    function handleResponse(site, response) {
        requestCount++;
        if (response && response.status === 200) {
            const parser = new DOMParser();
            const doc = parser.parseFromString(response.responseText, 'text/html');
            const price = getPriceFromDocument(doc);
            const deliveryPrice = getDeliveryPriceFromDocument(doc);
            if (price !== null) {
                priceResults.push({ ...site, price, deliveryPrice });
            }
        }
        if (requestCount === amazonSites.length) {
            displayAllResults();
        }
    }

    function createLoadingContainer() {
        const priceElement = document.querySelector('.priceToPay, #priceblock_ourprice, #priceblock_dealprice, #priceblock_saleprice');
        if (priceElement && priceElement.parentNode) {
            const container = document.createElement('div');
            container.id = 'amazonPriceComparisonContainer';
            container.innerHTML = `
                <div id="loadingMessage">
                    <img src="https://i.ibb.co/qrjrcVy/amz-price-checker.png" alt="Loading logo" style="width: 50px; height: 50px; margin-bottom: 10px;">
                    Checking other Amazon sites...
                </div>`;
            priceElement.parentNode.appendChild(container);
        }
    }

    function getPriceFromDocument(doc) {
        const priceElement = doc.querySelector('.priceToPay, #priceblock_ourprice, #priceblock_dealprice, #priceblock_saleprice');
        if (!priceElement) return null;
        const priceText = priceElement.textContent;
        return parsePrice(priceText);
    }

    function parsePrice(priceText) {
        if (!priceText) return null;
        const cleanedText = priceText.replace(/[^0-9,\.]/g, '').replace(',', '.');
        const price = parseFloat(cleanedText);
        return isNaN(price) ? null : price;
    }

    function getDeliveryPriceFromDocument(doc) {
        const deliveryMatch = doc.body.innerHTML.match(/data-csa-c-delivery-price="[^"]*?(\d+[.,]\d{2})/);
        if (deliveryMatch) {
            const priceStr = deliveryMatch[1].replace(',', '.');
            const price = parseFloat(priceStr);
            return isNaN(price) ? 0 : price;
        }
        return 0;
    }

    function displayAllResults() {
        const priceContainer = document.querySelector('#amazonPriceComparisonContainer');
        if (!priceContainer) return;
        priceContainer.innerHTML = '';

        const headerRow = document.createElement('div');
        headerRow.className = 'comparison-row header-row';
        ['Site', 'Price', 'Delivery', 'Total', 'Difference'].forEach(header => {
            const headerCell = createCell(header, true);
            headerRow.appendChild(headerCell);
        });
        priceContainer.appendChild(headerRow);

        priceResults.sort((a, b) => (a.price + a.deliveryPrice) - (b.price + b.deliveryPrice));

        priceResults.forEach(result => {
            const row = document.createElement('div');
            row.className = 'comparison-row';
            row.onclick = () => window.open(`https://www.amazon.${result.country}/dp/${asin}?tag=${PARTNER_IDS[result.country]}`, '_blank');

            const totalPrice = result.price + (result.deliveryPrice || 0);
            const difference = totalPrice - basePrice;
            const differencePercentage = ((difference / basePrice) * 100).toFixed(2);
            const differenceClass = difference < 0 ? 'price-difference-positive' : difference > 0 ? 'price-difference-negative' : '';

            row.append(
                createCell(`<img src="${result.flag}" alt="${result.name} flag" style="vertical-align: middle; margin-right: 5px;"> ${result.name}`),
                createCell(`€${result.price.toFixed(2)}`),
                createCell(result.deliveryPrice ? `<img src="https://img.icons8.com/?size=100&id=12248&format=png&color=000000" width="20"/> €${result.deliveryPrice.toFixed(2)}` : '-'),
                createCell(`€${totalPrice.toFixed(2)}`),
                createCell(difference !== 0 ? `<span class="${differenceClass}">${difference >= 0 ? '+' : ''}€${difference.toFixed(2)} (${differencePercentage}%)</span>` : '-')
            );

            priceContainer.appendChild(row);
        });

        addControls();
        addCamelCamelCamelChart();
    }

    function createCell(content, isHeader = false) {
        const cell = document.createElement('div');
        cell.style.flex = '1';
        cell.style.textAlign = 'center';
        cell.innerHTML = content;
        if (isHeader) {
            cell.style.fontWeight = 'bold';
        }
        return cell;
    }

    function addControls() {
        const priceContainer = document.querySelector('#amazonPriceComparisonContainer');
        if (!priceContainer) return;

        const controlsContainer = document.createElement('div');
        controlsContainer.style.cssText = 'text-align: center; margin: 10px; display: flex; justify-content: center; align-items: center; gap: 10px;';

        const timePeriods = [
            { id: 'btn1Month', label: '1 Month', value: '1m' },
            { id: 'btn3Months', label: '3 Months', value: '3m' },
            { id: 'btn6Months', label: '6 Months', value: '6m' },
            { id: 'btn1Year', label: '1 Year', value: '1y' },
            { id: 'btnAll', label: 'All', value: 'all' }
        ];

        timePeriods.forEach(period => {
            const button = document.createElement('button');
            button.id = period.id;
            button.textContent = period.label;
            button.className = period.value === selectedTimePeriod ? 'active-button' : '';
            button.style.cssText = `
                padding: 5px 10px;
                border: 1px solid #ccc;
                border-radius: 5px;
                background-color: ${period.value === selectedTimePeriod ? '#ff9900' : '#f9f9f9'};
                cursor: pointer;
            `;

            button.addEventListener('click', () => {
                selectedTimePeriod = period.value;
                document.querySelectorAll('#amazonPriceComparisonContainer button').forEach(btn => {
                    btn.classList.remove('active-button');
                    btn.style.backgroundColor = '#f9f9f9';
                });
                button.classList.add('active-button');
                button.style.backgroundColor = '#ff9900';
                updateChartUrl();
            });

            controlsContainer.appendChild(button);
        });

        const checkboxes = [
            { id: 'checkboxAmazon', label: 'Amazon', filename: 'amazon', disabled: true, checked: true },
            { id: 'checkboxNew', label: 'New', filename: 'new', checked: false },
            { id: 'checkboxUsed', label: 'Used', filename: 'used', checked: false }
        ];

        checkboxes.forEach(checkbox => {
            const container = document.createElement('div');
            container.style.display = 'flex';
            container.style.alignItems = 'center';
            container.style.flexDirection = 'column';

            const checkboxElement = document.createElement('input');
            checkboxElement.type = 'checkbox';
            checkboxElement.id = checkbox.id;
            checkboxElement.checked = checkbox.checked;
            checkboxElement.style.marginBottom = '5px';
            if (checkbox.disabled) checkboxElement.disabled = true;

            checkboxElement.addEventListener('change', updateChartUrl);

            const labelElement = document.createElement('label');
            labelElement.htmlFor = checkbox.id;
            labelElement.textContent = checkbox.label;
            labelElement.style.fontWeight = 'normal';

            container.append(checkboxElement, labelElement);
            controlsContainer.appendChild(container);
        });

        priceContainer.appendChild(controlsContainer);
    }

    function addCamelCamelCamelChart() {
        const priceContainer = document.querySelector('#amazonPriceComparisonContainer');
        if (!priceContainer) return;

        const chartContainer = document.createElement('div');
        chartContainer.style.marginTop = '20px';
        chartContainer.style.textAlign = 'center';

        const countryCode = getCurrentCountryCode();
        const chartUrl = getCamelChartUrl(countryCode, asin, selectedTimePeriod);
        const camelUrl = `https://${countryCode}.camelcamelcamel.com/product/${asin}`;
        chartContainer.innerHTML = `<a href="${camelUrl}" target="_blank"><img src="${chartUrl}" alt="Price history for ${asin}" style="max-width: 100%; height: auto; border: 1px solid #ccc; border-radius: 8px;"></a>`;

        priceContainer.appendChild(chartContainer);

        const footer = document.createElement('div');
        footer.style.cssText = 'text-align: right; font-size: 0.7em; color: #666; margin-top: 10px;';
        footer.innerHTML = `<img src="https://i.ibb.co/qrjrcVy/amz-price-checker.png" alt="Logo" style="width: 20px; height: 20px; vertical-align: middle; margin-right: 5px;"> European Price Checker for Amazon by bNj v${GM_info.script.version}`;
        priceContainer.appendChild(footer);
    }

    function getCurrentCountryCode() {
        const hostname = window.location.hostname;
        if (hostname.includes('amazon.de')) return 'de';
        if (hostname.includes('amazon.es')) return 'es';
        if (hostname.includes('amazon.it')) return 'it';
        return 'fr';
    }

    function getCamelChartUrl(countryCode, asin, timePeriod) {
        const selectedFilenames = getSelectedFilenames();
        return `https://charts.camelcamelcamel.com/${countryCode}/${asin}/${selectedFilenames}.png?force=1&zero=0&w=600&h=300&desired=false&legend=1&ilt=1&tp=${timePeriod}&fo=0&lang=en`;
    }

    function getSelectedFilenames() {
        const checkboxes = [
            { id: 'checkboxAmazon', filename: 'amazon' },
            { id: 'checkboxNew', filename: 'new' },
            { id: 'checkboxUsed', filename: 'used' }
        ];

        return Array.from(document.querySelectorAll('input[type="checkbox"]:checked'))
            .map(checkbox => checkboxes.find(cb => cb.id === checkbox.id)?.filename)
            .filter(Boolean)
            .join('-');
    }

    function updateChartUrl() {
        const countryCode = getCurrentCountryCode();
        const chartUrl = getCamelChartUrl(countryCode, asin, selectedTimePeriod);
        const camelUrl = `https://${countryCode}.camelcamelcamel.com/product/${asin}`;
        const chartImage = document.querySelector('#amazonPriceComparisonContainer img[alt^="Price history"]');
        if (chartImage) {
            chartImage.src = chartUrl;
            chartImage.parentElement.href = camelUrl;
        }
    }
})();