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-30 提交的版本,查看 最新版本

// ==UserScript==
// @name         European Price Checker for Amazon (fr, de, es, it)
// @namespace    http://tampermonkey.net/
// @version      2.2
// @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';

    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;
        }
    `;

    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[0].slice(1, 11);

    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 = getPriceFromPage();
    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 price = getPriceFromResponse(response.responseText);
            const deliveryPrice = getDeliveryPriceFromResponse(response.responseText);
            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 getPriceFromPage() {
        const priceElement = document.querySelector('.priceToPay, #priceblock_ourprice, #priceblock_dealprice, #priceblock_saleprice');
        if (!priceElement) return null;
        return parseFloat(priceElement.textContent.replace(/[^0-9,\.]/g, '').replace(',', '.'));
    }

    function getPriceFromResponse(responseText) {
        const priceMatch = responseText.match(/<span class="a-price-whole">(\d+)<span class="a-price-decimal">[.,]<\/span><\/span><span class="a-price-fraction">(\d{2})<\/span>/);
        return priceMatch ? parseFloat(`${priceMatch[1]}.${priceMatch[2]}`) : null;
    }

    function getDeliveryPriceFromResponse(responseText) {
        const deliveryMatch = responseText.match(/data-csa-c-delivery-price="[^\"]*(\d+[.,]\d{2})/);
        return deliveryMatch ? parseFloat(deliveryMatch[1].replace(',', '.')) : 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 = document.createElement('div');
            headerCell.style.flex = '1';
            headerCell.style.textAlign = 'center';
            headerCell.textContent = header;
            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;
            if (difference !== 0) {
                const differencePercentage = ((difference / basePrice) * 100).toFixed(2);
                const differenceClass = difference < 0 ? 'price-difference-positive' : '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(`<span class="${differenceClass}">${difference >= 0 ? '+' : ''}€${difference.toFixed(2)} (${differencePercentage}%)</span>`)
                );
            } else {
                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('-')
                );
            }

            priceContainer.appendChild(row);
        });

        addSortingButtons();
        addCamelCamelCamelChart();
    }

    function createCell(content) {
        const cell = document.createElement('div');
        cell.style.flex = '1';
        cell.style.textAlign = 'center';
        cell.innerHTML = content;
        return cell;
    }

    function addSortingButtons() {
        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.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;
                timePeriods.forEach(p => {
                    const btn = document.getElementById(p.id);
                    if (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';

        let countryCode = 'fr';
        if (window.location.hostname.includes('amazon.de')) countryCode = 'de';
        else if (window.location.hostname.includes('amazon.es')) countryCode = 'es';
        else if (window.location.hostname.includes('amazon.it')) countryCode = 'it';

        const chartUrl = `https://charts.camelcamelcamel.com/${countryCode}/${asin}/amazon.png?force=1&zero=0&w=600&h=300&desired=false&legend=1&ilt=1&tp=all&fo=0&lang=en`;
        const camelUrl = `https://${countryCode}.camelcamelcamel.com/product/${asin}`;
        chartContainer.innerHTML = `<a href="${camelUrl}" target="_blank"><img src="${chartUrl}" alt="Price history chart 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 updateChartUrl() {
        const checkboxes = [
            { id: 'checkboxAmazon', filename: 'amazon' },
            { id: 'checkboxNew', filename: 'new' },
            { id: 'checkboxUsed', filename: 'used' }
        ];

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

        const countryCode = window.location.hostname.includes('amazon.de') ? 'de' :
                            window.location.hostname.includes('amazon.es') ? 'es' :
                            window.location.hostname.includes('amazon.it') ? 'it' : 'fr';

        const chartUrl = `https://charts.camelcamelcamel.com/${countryCode}/${asin}/${selectedFilenames}.png?force=1&zero=0&w=600&h=300&desired=false&legend=1&ilt=1&tp=${selectedTimePeriod}&fo=0&lang=en`;
        const camelUrl = `https://${countryCode}.camelcamelcamel.com/product/${asin}`;
        const chartContainer = document.querySelector('#amazonPriceComparisonContainer img[alt^="Price history chart"]');
        if (chartContainer) {
            chartContainer.parentElement.href = camelUrl;
            chartContainer.src = chartUrl;
        }
    }
})();