// ==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
// @downloadURL none
// ==/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 = `

Checking other Amazon sites...
`;
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(`
${result.name}`),
createCell(`€${result.price.toFixed(2)}`),
createCell(result.deliveryPrice ? `
€${result.deliveryPrice.toFixed(2)}` : '-'),
createCell(`€${totalPrice.toFixed(2)}`),
createCell(difference !== 0 ? `${difference >= 0 ? '+' : ''}€${difference.toFixed(2)} (${differencePercentage}%)` : '-')
);
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 = `
`;
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 = `
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;
}
}
})();