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.

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

// ==UserScript==
// @license      MIT
// @name         European Price Checker for Amazon (fr, de, es, it)
// @namespace    http://tampermonkey.net/
// @version      1.5
// @description  Compare product prices on Amazon.fr, Amazon.de, Amazon.es, and Amazon.it to find the best deal.
// @author       bNj
// @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
// ==/UserScript==

(function() {
    'use strict';

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

    if (!asinMatch) {
        return;
    }

    const asin = asinMatch[1];
    const amazonSites = [
        { name: 'Amazon.fr', url: `https://www.amazon.fr/dp/${asin}`, domain: 'amazon.fr', flag: 'https://flagcdn.com/w20/fr.png' },
        { name: 'Amazon.es', url: `https://www.amazon.es/dp/${asin}`, domain: 'amazon.es', flag: 'https://flagcdn.com/w20/es.png' },
        { name: 'Amazon.it', url: `https://www.amazon.it/dp/${asin}`, domain: 'amazon.it', flag: 'https://flagcdn.com/w20/it.png' },
        { name: 'Amazon.de', url: `https://www.amazon.de/dp/${asin}`, domain: 'amazon.de', flag: 'https://flagcdn.com/w20/de.png' }
    ];

    let basePrice = null;

    const createLoadingContainer = () => {
        const priceElement = document.querySelector('.priceToPay, #priceblock_ourprice, #priceblock_dealprice');
        if (priceElement) {
            const container = document.createElement('div');
            container.id = 'amazonPriceComparisonContainer';
            container.style.marginTop = '20px';
            container.style.padding = '10px';
            container.style.backgroundColor = '#f9f9f9';
            container.style.border = '1px solid #ccc';
            container.style.borderRadius = '8px';
            container.style.position = 'relative';
            container.innerHTML = `<div id="loadingMessage" style="text-align: center; font-weight: bold;">Checking other Amazon sites...</div>`;
            priceElement.parentNode.appendChild(container);
            basePrice = getPriceFromPage(priceElement.textContent);
            console.log(`Price found on Amazon.fr:`, basePrice);
        }
    };

    const removeLoadingIndicator = () => {
        const loadingMessage = document.getElementById('loadingMessage');
        if (loadingMessage) {
            loadingMessage.style.transition = 'opacity 1s';
            loadingMessage.style.opacity = '0';
            setTimeout(() => {
                if (loadingMessage.parentNode) {
                    loadingMessage.parentNode.removeChild(loadingMessage);
                }
                displayAllResults();
            }, 1000);
        }
    };

    const getPriceFromPage = (priceText) => {
        priceText = priceText.replace(/\s+/g, '');
        const priceMatch = priceText.match(/\d+[\.,]\d{2}/);
        if (priceMatch) {
            return parseFloat(priceMatch[0].replace(',', '.'));
        }
        return null;
    };

    const getPriceFromResponse = (responseText) => {
        // Try to extract price from specific Amazon.de HTML structure
        const dePriceMatch = responseText.match(/<span class="a-price-whole">(\d+)<span class="a-price-decimal">[.,]<\/span><\/span><span class="a-price-fraction">(\d{2})<\/span>/);
        if (dePriceMatch) {
            return parseFloat(`${dePriceMatch[1]}.${dePriceMatch[2]}`); // Convert to numeric value
        }
        // Try to extract price from specific Amazon.it HTML structure
        const itPriceMatch = responseText.match(/<span class="a-price aok-align-center reinventPricePriceToPayMargin priceToPay"[^>]*>.*?<span class="a-price-whole">(\d+)<span class="a-price-decimal">(.)<\/span><\/span><span class="a-price-fraction">(\d{2})<\/span>/);
        if (itPriceMatch) {
            return parseFloat(`${itPriceMatch[1]}.${itPriceMatch[3]}`); // Convert to numeric value
        }
        return null;
    };

    const getDeliveryPriceFromResponse = (responseText) => {
        const deliveryMatch = responseText.match(/data-csa-c-delivery-price="[^"]*(\d+[\.,]\d{2})/);
        if (deliveryMatch) {
            return parseFloat(deliveryMatch[1].replace(',', '.'));
        }
        return null;
    };

    const addPriceToPage = (siteName, price, deliveryPrice, url, flag, percentageDifference, totalPercentageDifference) => {
        const priceContainer = document.querySelector('#amazonPriceComparisonContainer');
        if (!priceContainer) {
            return;
        }

        const row = document.createElement('div');
        row.className = 'comparison-row';
        row.style.cursor = 'pointer';
        row.onmouseover = () => row.style.backgroundColor = '#f1f1f1';
        row.onmouseout = () => row.style.backgroundColor = 'inherit';
        row.onclick = () => window.open(url, '_blank');
        row.style.display = 'flex';
        row.style.justifyContent = 'space-between';
        row.style.padding = '5px 0';
        row.style.borderBottom = '1px solid #ccc';

        const flagCell = document.createElement('div');
        flagCell.style.flex = '1';
        flagCell.innerHTML = `<img src="${flag}" alt="${siteName} flag" style="vertical-align: middle; margin-right: 5px;"> ${siteName}`;

        const priceCell = document.createElement('div');
        priceCell.style.flex = '1';
        priceCell.style.textAlign = 'center';
        priceCell.innerHTML = `€${price.toFixed(2)}`;

        const percentagePriceCell = document.createElement('div');
        percentagePriceCell.style.flex = '1';
        percentagePriceCell.style.textAlign = 'center';
        const priceColor = percentageDifference > 0 ? 'red' : 'green';
        percentagePriceCell.innerHTML = percentageDifference !== null ? `<span style="color: ${priceColor};">${percentageDifference > 0 ? '+' : ''}${percentageDifference.toFixed(2)}% (Δ€${(price - basePrice).toFixed(2)})</span>` : '-';

        const deliveryCell = document.createElement('div');
        deliveryCell.style.flex = '1';
        deliveryCell.style.textAlign = 'center';
        deliveryCell.innerHTML = deliveryPrice !== null ? `<img src="https://img.icons8.com/?size=100&id=12248&format=png&color=000000" width="20"/> €${deliveryPrice.toFixed(2)}` : '-';

        const totalPrice = price + (deliveryPrice || 0);
        const totalCell = document.createElement('div');
        totalCell.style.flex = '1';
        totalCell.style.textAlign = 'center';
        totalCell.innerHTML = `<strong>€${totalPrice.toFixed(2)}</strong>`;

        const percentageCell = document.createElement('div');
        percentageCell.style.flex = '1';
        percentageCell.style.textAlign = 'center';
        const totalColor = totalPercentageDifference > 0 ? 'red' : 'green';
        percentageCell.innerHTML = totalPercentageDifference !== null ? `<span style="color: ${totalColor};">${totalPercentageDifference > 0 ? '+' : ''}${totalPercentageDifference.toFixed(2)}% (Δ€${(totalPrice - basePrice).toFixed(2)})</span>` : '-';

        row.appendChild(flagCell);
        row.appendChild(priceCell);
        row.appendChild(percentagePriceCell);
        row.appendChild(deliveryCell);
        row.appendChild(totalCell);
        row.appendChild(percentageCell);

        priceContainer.appendChild(row);
    };

    let priceResults = [];

    const storePriceResult = (siteName, price, deliveryPrice, url, flag) => {
        if (price !== null) {
            priceResults.push({ siteName, price, deliveryPrice, url, flag });
        }
    };

    const displayAllResults = () => {
        const priceContainer = document.querySelector('#amazonPriceComparisonContainer');
        if (!priceContainer) {
            return;
        }
        priceContainer.innerHTML = ''; // Clear loading message and icon

        const headerRow = document.createElement('div');
        headerRow.className = 'comparison-row header-row';
        headerRow.style.display = 'flex';
        headerRow.style.justifyContent = 'space-between';
        headerRow.style.padding = '5px 0';
        headerRow.style.borderBottom = '2px solid #000';
        headerRow.style.fontWeight = 'bold';

        ['Site', 'Price', 'Difference (Price %)', 'Delivery', 'Total', 'Difference (Total %)'].forEach(header => {
            const headerCell = document.createElement('div');
            headerCell.style.flex = '1';
            headerCell.style.textAlign = 'center';
            headerCell.textContent = header;
            headerRow.appendChild(headerCell);
        });

        priceContainer.appendChild(headerRow);

        if (priceResults.length > 0 && basePrice !== null) {
            priceResults.forEach(result => {
                const percentageDifference = ((result.price - basePrice) / basePrice) * 100;
                const totalPrice = result.price + (result.deliveryPrice || 0);
                const totalBasePrice = basePrice; // Assuming no delivery cost for Amazon.fr for simplicity
                const totalPercentageDifference = ((totalPrice - totalBasePrice) / totalBasePrice) * 100;
                addPriceToPage(result.siteName, result.price, result.deliveryPrice, result.url, result.flag, percentageDifference, totalPercentageDifference);
            });

            // Add footer with script info after the last row
            const footer = document.createElement('div');
            footer.style.textAlign = 'right';
            footer.style.fontSize = '0.8em';
            footer.style.color = '#666';
            footer.style.marginTop = '10px';
            footer.textContent = `European Price Checker for Amazon by bNj v${GM_info.script.version}`;
            priceContainer.appendChild(footer);
        } else {
            priceContainer.innerHTML = '<div style="text-align: center;">No prices available</div>';
        }
    };

    createLoadingContainer();

    let sitesChecked = 0;
    amazonSites.forEach(site => {
        GM_xmlhttpRequest({
            method: 'GET',
            url: site.url,
            headers: {
                'User-Agent': 'Mozilla/5.0',
                'Accept-Language': 'en-US,en;q=0.5'
            },
            onload: function(response) {
                console.log(`Response from ${site.name}:`, response);
                if (response.status === 200) {
                    const price = getPriceFromResponse(response.responseText);
                    const deliveryPrice = getDeliveryPriceFromResponse(response.responseText);
                    console.log(`Price found on ${site.name}:`, price);
                    console.log(`Delivery price found on ${site.name}:`, deliveryPrice);
                    storePriceResult(site.name, price, deliveryPrice, site.url, site.flag);
                } else {
                    console.error(`Failed to fetch price from ${site.name}, status:`, response.status);
                    storePriceResult(site.name, null, null, site.url, site.flag);
                }
                sitesChecked++;
                if (sitesChecked === amazonSites.length) {
                    removeLoadingIndicator();
                }
            },
            onerror: function(error) {
                console.error(`Error fetching price from ${site.name}:`, error);
                storePriceResult(site.name, null, null, site.url, site.flag);
                sitesChecked++;
                if (sitesChecked === amazonSites.length) {
                    removeLoadingIndicator();
                }
            }
        });
    });
})();