// ==UserScript==
// @name European Price Checker for Amazon (fr, de, es, it)
// @namespace http://tampermonkey.net/
// @version 2.0
// @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';
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.cssText = 'margin-top: 20px; padding: 10px; background-color: #f9f9f9; border: 1px solid #ccc; border-radius: 8px; position: relative; font-size: 12px;';
container.innerHTML = `
Checking other Amazon sites...
`;
priceElement.parentNode.appendChild(container);
animateLoadingText();
basePrice = getPriceFromPage(priceElement.textContent);
}
};
const animateLoadingText = () => {
const text = document.getElementById('animatedText');
if (text) {
let position = 0;
setInterval(() => {
position = (position + 2) % 100;
text.style.cssText = `
background-image: linear-gradient(90deg, black 0%, black ${position - 20}%, #FF9900 ${position}%, black ${position + 20}%, black 100%);
background-clip: text;
-webkit-background-clip: text;
color: transparent;
font-weight: bold;
font-size: 14px;
`;
}, 80);
}
};
const removeLoadingIndicator = () => {
const loadingMessage = document.getElementById('loadingMessage');
if (loadingMessage) {
loadingMessage.style.transition = 'opacity 1s';
loadingMessage.style.opacity = '0';
setTimeout(() => {
loadingMessage.remove();
displayAllResults();
}, 1000);
}
};
const getPriceFromPage = (priceText) => {
priceText = priceText.replace(/\s+/g, '');
const priceMatch = priceText.match(/\d+[\.,]\d{2}/);
return priceMatch ? parseFloat(priceMatch[0].replace(',', '.')) : null;
};
const getPriceFromResponse = (responseText) => {
const priceMatch = responseText.match(/(\d+)[.,]<\/span><\/span>(\d{2})<\/span>/) ||
responseText.match(/]*>.*?(\d+)(.)<\/span><\/span>(\d{2})<\/span>/);
return priceMatch ? parseFloat(`${priceMatch[1]}.${priceMatch[2]}`) : null;
};
const getDeliveryPriceFromResponse = (responseText) => {
const deliveryMatch = responseText.match(/data-csa-c-delivery-price="[^"]*(\d+[\.,]\d{2})/);
return deliveryMatch ? parseFloat(deliveryMatch[1].replace(',', '.')) : 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.dataset.totalPrice = price + (deliveryPrice || 0); // Store total price for sorting
row.style.cssText = 'cursor: pointer; display: flex; justify-content: space-between; padding: 5px 0; border-bottom: 1px solid #ccc;';
row.onmouseover = () => row.style.backgroundColor = '#f1f1f1';
row.onmouseout = () => row.style.backgroundColor = 'inherit';
row.onclick = () => window.open(url, '_blank');
const createCell = (content, style = '') => {
const cell = document.createElement('div');
cell.style.flex = '1';
cell.style.textAlign = 'center';
cell.style.cssText += style;
cell.innerHTML = content;
return cell;
};
const totalPrice = price + (deliveryPrice || 0);
row.append(
createCell(`
${siteName}`),
createCell(`€${price.toFixed(2)}`),
createCell(percentageDifference !== null ? `${percentageDifference > 0 ? '+' : ''}${percentageDifference.toFixed(2)}% (€${(price - basePrice).toFixed(2)})` : '-'),
createCell(deliveryPrice !== null ? `
€${deliveryPrice.toFixed(2)}` : '-'),
createCell(`€${totalPrice.toFixed(2)}`),
createCell(totalPercentageDifference !== null ? `${totalPercentageDifference > 0 ? '+' : ''}${totalPercentageDifference.toFixed(2)}% (€${(totalPrice - basePrice).toFixed(2)})` : '-')
);
priceContainer.appendChild(row);
};
const 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 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' }
];
let selectedTimePeriod = '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 => {
document.getElementById(p.id).style.backgroundColor = p.value === selectedTimePeriod ? '#ff9900' : '#f9f9f9';
});
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';
const checkboxElement = document.createElement('input');
checkboxElement.type = 'checkbox';
checkboxElement.id = checkbox.id;
checkboxElement.checked = checkbox.checked;
checkboxElement.style.marginRight = '5px';
if (checkbox.disabled) checkboxElement.disabled = true;
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);
const updateChartUrl = () => {
const selectedFilenames = checkboxes
.filter(checkbox => document.getElementById(checkbox.id).checked)
.map(checkbox => checkbox.filename)
.join('-');
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}`;
chartContainer.innerHTML = `
`;
};
checkboxes.forEach(checkbox => {
document.getElementById(checkbox.id).addEventListener('change', updateChartUrl);
});
updateChartUrl();
priceContainer.appendChild(chartContainer);
// Add footer with script info after the chart
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);
};
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.cssText = 'display: flex; justify-content: space-between; padding: 5px 0; border-bottom: 2px solid #000; font-weight: 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.sort((a, b) => (a.price + (a.deliveryPrice || 0)) - (b.price + (b.deliveryPrice || 0)));
priceResults.forEach(result => {
const percentageDifference = ((result.price - basePrice) / basePrice) * 100;
const totalPrice = result.price + (result.deliveryPrice || 0);
const totalBasePrice = basePrice;
const totalPercentageDifference = ((totalPrice - totalBasePrice) / totalBasePrice) * 100;
addPriceToPage(result.siteName, result.price, result.deliveryPrice, result.url, result.flag, percentageDifference, totalPercentageDifference);
});
} else {
priceContainer.innerHTML = 'No prices available
';
}
addCamelCamelCamelChart();
};
createLoadingContainer();
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) {
if (response.status === 200) {
const price = getPriceFromResponse(response.responseText);
const deliveryPrice = getDeliveryPriceFromResponse(response.responseText);
storePriceResult(site.name, price, deliveryPrice, site.url, site.flag);
} else {
storePriceResult(site.name, null, null, site.url, site.flag);
}
if (priceResults.length === amazonSites.length) {
removeLoadingIndicator();
}
},
onerror: function() {
storePriceResult(site.name, null, null, site.url, site.flag);
if (priceResults.length === amazonSites.length) {
removeLoadingIndicator();
}
}
});
});
})();