// ==UserScript==
// @name Amazon Price Checker (FR, DE, ES, IT, BE, NL, UK, COM, PL) by bNj
// @namespace http://tampermonkey.net/
// @version 4.01
// @description Compare Amazon prices across different country sites with a leaner, faster script.
// @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/*
// @match https://www.amazon.com.be/*
// @match https://www.amazon.nl/*
// @match https://www.amazon.co.uk/*
// @match https://www.amazon.com/*
// @match https://www.amazon.pl/*
// @grant GM_xmlhttpRequest
// @connect amazon.fr
// @connect amazon.de
// @connect amazon.es
// @connect amazon.it
// @connect amazon.com.be
// @connect amazon.nl
// @connect amazon.pl
// @connect amazon.co.uk
// @connect amazon.com
// @connect summarizer.mon-bnj.workers.dev
// @connect api.frankfurter.app
// @connect alisearch.bnjnas.synology.me
// @license All Rights Reserved
// @antifeature referral-link
// @antifeature tracking
// ==/UserScript==
(function(){
'use strict';
const ASIN_RE = /\/([A-Z0-9]{10})(?:[/?]|$)/,
PARTNER_IDS = {
fr:'bnjmazon-21',
es:'bnjmazon08-21',
it:'bnjmazon0d-21',
de:'geeksince190d-21',
'com.be':'geeksince1900',
nl:'bnjmazon-21',
pl:'bnjmazon-20',
'co.uk':'bnjmazon-UK-21',
com:'bnjmazon-20'
},
sites = [
{name:'Amazon.fr', c:'fr', f:'https://flagcdn.com/w20/fr.png', cur:'EUR'},
{name:'Amazon.es', c:'es', f:'https://flagcdn.com/w20/es.png', cur:'EUR'},
{name:'Amazon.it', c:'it', f:'https://flagcdn.com/w20/it.png', cur:'EUR'},
{name:'Amazon.de', c:'de', f:'https://flagcdn.com/w20/de.png', cur:'EUR'},
{name:'Amazon.be', c:'com.be', f:'https://flagcdn.com/w20/be.png', cur:'EUR'},
{name:'Amazon.nl', c:'nl', f:'https://flagcdn.com/w20/nl.png', cur:'EUR'},
{name:'Amazon.pl', c:'pl', f:'https://flagcdn.com/w20/pl.png', cur:'PLN'},
{name:'Amazon.co.uk', c:'co.uk', f:'https://flagcdn.com/w20/gb.png', cur:'GBP'},
{name:'Amazon.com', c:'com', f:'https://flagcdn.com/w20/us.png', cur:'USD'}
];
let asin, basePrice, selPeriod = 'all', firstLoaded = false, exRates,
tableCont, chartCont, selEl, checks = [];
// Modification : Charger les taux avant d'obtenir le prix de référence.
function main(){
if(!extractASIN()) return;
fetchExRates().then(() => {
if(!getBasePrice()) return;
injectStyles();
createBaseUI();
fetchPrices();
});
}
function extractASIN(){
const m = location.href.match(ASIN_RE);
if(!m) return false;
asin = m[1];
return true;
}
function getBasePrice(){
basePrice = getPrice(document, getCurrentCountry());
return basePrice !== null;
}
function injectStyles(){
const css = `:root{--a:#FF9900;--bg:#fff;--font:Arial,sans-serif;--tc:#333;--bc:#ddd}
body{font-family:var(--font)!important}
#amz-checker-container{background:var(--bg);border:1px solid var(--bc);border-radius:10px;box-shadow:0 2px 6px rgba(0,0,0,0.1);font-size:12px;color:var(--tc);margin:0 auto;display:flex;flex-direction:column}
#amz-checker-header{background:var(--a);color:#fff;padding:5px 10px;border-radius:10px 10px 0 0;display:flex;align-items:center;gap:10px}
#amz-checker-header img{width:36px;height:36px}
#amz-checker-title{font-size:14px;font-weight:bold}
.loading-text-gradient{background-clip:text;color:transparent;background-image:linear-gradient(270deg,black 0%,black 20%,var(--a) 50%,black 80%,black 100%);background-size:200% 100%;animation:loadAnim 2s linear infinite}
@keyframes loadAnim{0%{background-position:100% 50%}100%{background-position:0 50%}}
#loadingMessage{text-align:center;font-weight:bold;font-size:13px;display:flex;flex-direction:column;align-items:center;margin:10px 0}
.amz-checker-content{padding:10px;flex:1}
#comparison-table{border:1px solid var(--bc);border-radius:8px;overflow:hidden;margin-bottom:15px}
.comparison-row{display:flex;justify-content:space-between;padding:5px 10px;border-bottom:1px solid var(--bc);cursor:pointer;transition:background 0.2s}
.comparison-row:hover{background:#f5f5f5}
.comparison-row.header-row{background:#eee;font-weight:bold;cursor:default}
.comparison-row.header-row:hover{background:#eee}
.comparison-row:last-child{border-bottom:none}
.comparison-row>div{text-align:center;flex:1}
.first-col{flex:0 0 120px;text-align:left !important;overflow:hidden}
.price-difference-positive{color:#008000}
.price-difference-negative{color:#f00}
.chart-container{margin-bottom:15px;border:1px solid var(--bc);border-radius:8px;padding:10px;position:relative;min-height:300px;text-align:center}
.chart-container .loader{position:absolute;top:50%;left:50%;margin:-24px 0 0 -24px}
.chart-controls{display:flex;align-items:center;gap:15px;margin-bottom:10px;flex-wrap:wrap;justify-content:center}
.chart-controls .checkbox-container{display:flex;align-items:center;font-size:12px}
.chart-controls .checkbox-label{margin-left:4px}
.chart-controls select{padding:3px 6px;font-size:12px}
.loader{position:relative;width:48px;height:48px;border-radius:50%;display:inline-block;border-top:4px solid #FFF;border-right:4px solid transparent;box-sizing:border-box;animation:rot 1s linear infinite}
.loader::after{content:'';box-sizing:border-box;position:absolute;left:0;top:0;width:48px;height:48px;border-radius:50%;border-left:4px solid #FF3D00;border-bottom:4px solid transparent;animation:rot .5s linear infinite reverse}
@keyframes rot{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}
.chart-image{max-width:100%;margin-top:10px}
.aliexpress-wrapper{margin-bottom:15px}
.aliexpress-container{display:flex;align-items:center;justify-content:center;gap:8px;color:#ff5722;font-weight:bold;border:1px solid var(--bc);border-radius:6px;padding:8px 12px;cursor:pointer;transition:background 0.2s}
.aliexpress-container:hover{background:#fff8f0}
.aliexpress-icon{width:24px}
.aliexpress-results{margin-top:10px;display:flex;flex-wrap:wrap;gap:10px;justify-content:space-evenly}
.aliexpress-card{border:1px solid var(--bc);border-radius:4px;padding:5px;width:140px;text-align:center;box-shadow:0 2px 4px rgba(0,0,0,0.1);background:#fff}
.aliexpress-card img{width:100%;border-radius:4px 4px 0 0}
.product-summary-encart{border:1px solid var(--bc);border-radius:8px;padding:10px;background:#f9f9f9;margin-bottom:15px}
._Y3Itc_selected_2-xMA{font-weight:bold!important}
#amz-checker-footer{text-align:right;font-size:0.8em;color:#666;background:#fafafa;border-top:1px solid var(--bc);border-radius:0 0 10px 10px;padding:5px 10px}
#amz-checker-footer .footer-logo{width:18px;height:18px;vertical-align:middle;margin-right:5px}`;
let s = document.createElement('style');
s.type = 'text/css';
s.textContent = css;
document.head.appendChild(s);
}
function createBaseUI(){
let c = document.createElement('div');
c.id = 'amz-checker-container';
c.innerHTML = `<div id="amz-checker-header"><img src="https://i.ibb.co/qrjrcVy/amz-price-checker.png" alt="Logo"/><span id="amz-checker-title">Amazon Price Checker</span></div>
<div class="amz-checker-content"><div id="loadingMessage" class="loading-text-gradient">Checking other Amazon sites...</div></div>`;
let p = document.querySelector('.priceToPay,#priceblock_ourprice,#priceblock_dealprice,#priceblock_saleprice');
(p ? p.parentNode : document.body).appendChild(c);
}
function buildFinalUI(){
let cnt = document.querySelector('#amz-checker-container .amz-checker-content');
if(!cnt)return; cnt.innerHTML = '';
addTable(cnt); addChart(cnt); /*addAliExpress(cnt);*/ addProductSummary(cnt); addFooter();
updateChart();
}
function addTable(cnt){
let tw = document.createElement('div'); tw.id = 'comparison-table';
tableCont = document.createElement('div');
let hr = document.createElement('div'); hr.className = 'comparison-row header-row';
['Site','Price (EUR)','Coupon','Delivery','Total','Difference'].forEach((h,i) => hr.appendChild(cell(h,true,i===0 ? 'first-col' : '')));
tableCont.appendChild(hr); tw.appendChild(tableCont); cnt.appendChild(tw);
}
const cell = (txt, isH, ex) => {
let d = document.createElement('div');
d.innerHTML = txt;
if(isH) d.style.fontWeight = 'bold';
if(ex) d.classList.add(ex);
return d;
};
function insertRow({ s, price, del, coupon, cur }){
let tot = price - coupon + del, row = document.createElement('div'); row.className = 'comparison-row';
row.onclick = () => window.open(`https://www.amazon.${s.c}/dp/${asin}?tag=${PARTNER_IDS[s.c]}`, '_blank');
let diff = tot - basePrice, perc = ((diff/basePrice)*100).toFixed(0),
dc = diff < 0 ? 'price-difference-positive' : diff > 0 ? 'price-difference-negative' : '';
row.append(
cell(`<img src="${s.f}" style="vertical-align:middle;margin-right:5px;width:20px;height:13px;"> ${s.name}`, false, 'first-col'),
cell(showPrice(price, cur)),
cell(coupon > 0 ? `- €${coupon.toFixed(2)}` : '-'),
cell(del > 0 ? `+ €${del.toFixed(2)}` : '-'),
cell(showPrice(tot, cur)),
cell(diff !== 0 ? `<span class="${dc}">${diff >= 0 ? '+' : ''}€${diff.toFixed(2)} (${perc}%)</span>` : '-')
);
let rows = [...tableCont.querySelectorAll('.comparison-row:not(.header-row)')];
let inserted = false;
for(let r of rows){
let t = parseFloat(r.children[4].textContent.replace(/[^0-9.,-]/g, '').replace(',', '.')) || 999999;
if(tot < t){ tableCont.insertBefore(row, r); inserted = true; break; }
}
if(!inserted) tableCont.appendChild(row);
}
function addChart(cnt){
chartCont = document.createElement('div'); chartCont.className = 'chart-container';
let ctrl = document.createElement('div'); ctrl.className = 'chart-controls';
selEl = document.createElement('select');
[['1m','1 Month'], ['3m','3 Months'], ['6m','6 Months'], ['1y','1 Year'], ['all','All']].forEach(([v, l]) => {
let o = document.createElement('option'); o.value = v; o.textContent = l; if(v === selPeriod) o.selected = true; selEl.appendChild(o);
});
selEl.onchange = () => { selPeriod = selEl.value; updateChart(); }; ctrl.appendChild(selEl);
// Three checkboxes: Amazon (disabled), New, Used
[['checkboxAmazon','Amazon','amazon', true, true], ['checkboxNew','New','new', false, true], ['checkboxUsed','Used','used', false, false]]
.forEach(([id, label, fn, dis, chk]) => {
let wrap = document.createElement('div'); wrap.className = 'checkbox-container';
let inp = document.createElement('input'); inp.type = 'checkbox'; inp.id = id; inp.disabled = dis; inp.checked = chk; inp.onchange = updateChart;
let lbl = document.createElement('label'); lbl.htmlFor = id; lbl.textContent = label; lbl.className = 'checkbox-label';
wrap.append(inp, lbl); ctrl.appendChild(wrap); checks.push({ inp, fn });
});
chartCont.appendChild(ctrl);
let spin = document.createElement('div'); spin.className = 'loader';
let img = document.createElement('img'); img.alt = `Price history for ${asin}`; img.className = 'chart-image'; img.style.display = 'none';
chartCont.append(spin, img); cnt.appendChild(chartCont);
}
function updateChart(){
if(!chartCont)return;
let cc = getCurrentCountry(), url = getChartUrl(cc, asin, selPeriod),
spin = chartCont.querySelector('.loader'),
img = chartCont.querySelector('.chart-image');
spin.style.display = 'inline-block'; img.style.display = 'none';
img.src = url;
img.onload = () => { spin.style.display = 'none'; img.style.display = 'block'; };
img.onerror = () => { spin.style.display = 'none'; img.style.display = 'block'; img.src = 'https://dummyimage.com/600x200/ccc/000&text=Image+Unavailable'; };
}
function getChartUrl(cc, a, tp){
let f = checks.filter(c => c.inp.checked).map(c => c.fn).join('-'),
base = `https://charts.camelcamelcamel.com/${cc}/${a}/${f}.png?force=1&zero=0&w=600&h=300&desired=false&legend=1&ilt=1&tp=${tp}&fo=0&lang=en`;
return `https://camelcamelcamel.mon-bnj.workers.dev/?target=${encodeURIComponent(base)}`;
}
function addAliExpress(cnt){
let wrap = document.createElement('div'); wrap.className = 'aliexpress-wrapper';
let btn = document.createElement('div'); btn.className = 'aliexpress-container';
btn.innerHTML = `<img src="https://img.icons8.com/color/48/aliexpress.png" class="aliexpress-icon"><span class="aliexpress-text">Check on AliExpress</span>`;
btn.onclick = () => {
let txt = btn.querySelector('.aliexpress-text');
txt.textContent = 'Loading...'; txt.classList.add('loading-text-gradient');
let imgEl = document.querySelector('#landingImage') || document.querySelector('#imgTagWrapperId img'),
imgUrl = imgEl ? imgEl.src : "https://m.media-amazon.com/images/I/71sAMz1x82L.__AC_SX300_SY300_QL70_ML2_.jpg",
url = "https://alisearch.bnjnas.synology.me/search?image_url=" + encodeURIComponent(imgUrl);
GM_xmlhttpRequest({
method:'GET', url,
onload: r => {
txt.classList.remove('loading-text-gradient'); txt.textContent = 'Check on AliExpress';
try { displayAliRes(wrap, JSON.parse(r.responseText)); }
catch(e){ txt.textContent = 'Error parsing result'; }
},
onerror: () => { txt.classList.remove('loading-text-gradient'); txt.textContent = 'Error fetching data'; }
});
};
wrap.appendChild(btn); cnt.appendChild(wrap);
}
function displayAliRes(container, results){
results.sort((a, b) => parsePrice(a.prix) - parsePrice(b.prix));
let resCont = container.querySelector('.aliexpress-results') || document.createElement('div');
resCont.className = 'aliexpress-results'; resCont.innerHTML = '';
results.forEach(item => {
let card = document.createElement('div'); card.className = 'aliexpress-card';
let a = document.createElement('a'); a.href = item.lien; a.target = '_blank'; a.style.textDecoration = 'none'; a.style.color = 'inherit';
let img = document.createElement('img'); img.src = item.image; img.alt = item.titre;
let title = document.createElement('div'); title.textContent = item.titre;
title.style.cssText = "font-size:12px;margin-top:5px;font-weight:bold;height:40px;overflow:hidden";
let price = document.createElement('div'); price.textContent = item.prix;
price.style.cssText = "font-size:12px;color:#ff5722;margin-top:5px";
a.append(img, title, price); card.appendChild(a); resCont.appendChild(card);
});
if(!container.contains(resCont)) container.appendChild(resCont);
}
const parsePrice = s => { let n = parseFloat(s.replace(/[^\d.,-]/g, '').replace(',', '.')); return isNaN(n) ? 999999 : n; };
function addProductSummary(cnt){
let sum = document.querySelector('#cr-product-insights-cards');
if(sum){
let clone = sum.cloneNode(true);
clone.classList.add('product-summary-encart');
clone.querySelectorAll('i[id^="close-button-"]').forEach(i => i.remove());
cnt.appendChild(clone);
addAspectListeners(clone);
}
}
function addAspectListeners(clone){
clone.querySelectorAll('[id^="aspect-button-0-"]').forEach(btn => {
btn.onclick = () => {
let X = btn.id.split('-')[3], sheet = document.getElementById(`aspect-bottom-sheet-0-${X}`);
if(!sheet)return;
clone.querySelectorAll('[id^="aspect-bottom-sheet-0-"]').forEach(s => s.style.display = 'none');
sheet.style.display = 'block';
clone.querySelectorAll('[id^="aspect-button-0-"]').forEach(b => b.classList.remove('_Y3Itc_selected_2-xMA'));
btn.classList.add('_Y3Itc_selected_2-xMA');
};
});
}
let footerDone = false;
function addFooter(){
if(footerDone)return; footerDone = true;
let cont = document.getElementById('amz-checker-container');
if(!cont)return;
let f = document.createElement('div'); f.id = 'amz-checker-footer';
let ver = (typeof GM_info !== 'undefined' && GM_info.script && GM_info.script.version) ? GM_info.script.version : '4.x';
f.innerHTML = `<img src="https://i.ibb.co/qrjrcVy/amz-price-checker.png" class="footer-logo"> Amazon Price Checker v${ver}`;
cont.appendChild(f);
}
function getCurrentCountry(){
let h = location.hostname;
if(h.includes('amazon.com') && !h.includes('amazon.com.be') && !h.includes('amazon.co.uk')) return 'com';
if(h.includes('amazon.de')) return 'de';
if(h.includes('amazon.es')) return 'es';
if(h.includes('amazon.it')) return 'it';
if(h.includes('amazon.com.be')) return 'com.be';
if(h.includes('amazon.nl')) return 'nl';
if(h.includes('amazon.pl')) return 'pl';
if(h.includes('amazon.co.uk')) return 'co.uk';
return 'fr';
}
function getPrice(doc, ctry){
let el = doc.querySelector('.priceToPay,#priceblock_ourprice,#priceblock_dealprice,#priceblock_saleprice');
if(!el)return null;
let raw = parseFloat(el.textContent.replace(/[^0-9,\.]/g, '').replace(',', '.'));
return toEUR(raw, getCurrency(ctry));
}
function getCurrency(ctry){
let s = sites.find(x => x.c === ctry);
return s ? s.cur : 'EUR';
}
function toEUR(amt, cur){
if(!exRates || typeof amt !== 'number') return amt;
if(cur === 'EUR') return amt;
let r = exRates[cur];
return r ? amt / r : amt;
}
function fetchExRates(){
return new Promise(resolve => {
let cached = localStorage.getItem('exchangeRates'),
ts = localStorage.getItem('exchangeRatesTimestamp'),
now = Date.now();
if(cached && ts && (now - ts < 3600000)){
exRates = JSON.parse(cached); return resolve();
}
GM_xmlhttpRequest({
method:'GET',
url:'https://api.frankfurter.app/latest?from=EUR&to=USD,GBP,PLN,EUR',
onload: r => {
if(r.status === 200){
let data = JSON.parse(r.responseText);
exRates = data.rates;
localStorage.setItem('exchangeRates', JSON.stringify(exRates));
localStorage.setItem('exchangeRatesTimestamp', now);
} else {
// Fallback incluant le taux pour PLN
exRates = { USD:0.90, GBP:1.15, PLN:4.50, EUR:1 };
}
resolve();
},
onerror: () => {
exRates = { USD:0.90, GBP:1.15, PLN:4.50, EUR:1 };
resolve();
}
});
});
}
function fetchPrices(){
sites.forEach(s => {
let url = `https://www.amazon.${s.c}/dp/${asin}?tag=${PARTNER_IDS[s.c]}`;
GM_xmlhttpRequest({
method:'GET',
url,
headers: { 'User-Agent':'Mozilla/5.0','Accept-Language':'en-US,en;q=0.5' },
onload: r => {
if(r && r.status === 200){
let doc = new DOMParser().parseFromString(r.responseText, 'text/html'),
p = getPrice(doc, s.c);
if(p !== null){
let d = getDelivery(doc),
c = getCoupon(doc, p),
convP = p, convD = toEUR(d, s.cur), convC = toEUR(c, s.cur);
if(!firstLoaded){ firstLoaded = true; buildFinalUI(); }
insertRow({ s, price: convP, del: convD, coupon: convC, cur: s.cur });
}
}
},
onerror: () => {}
});
});
}
function getDelivery(doc){
let m = doc.body.innerHTML.match(/data-csa-c-delivery-price="[^"]*?(\d+[.,]\d{2})/);
if(m){
let p = parseFloat(m[1].replace(',','.'));
return isNaN(p) ? 0 : p;
}
return 0;
}
function getCoupon(doc, curPrice){
let lbl = doc.querySelector('label[id^="couponText"],label[id^="greenBadgepctch"]');
if(!lbl)return 0;
let txt = (lbl.textContent || '').replace(/\u00A0/g, ' ').toLowerCase().trim(), cp = 0,
m = txt.match(/(\d+(?:[.,]\d+)?)\s*%/);
if(m){
let p = parseFloat(m[1].replace(',','.'));
if(!isNaN(p) && p > 0 && p < 100) cp = curPrice * (p / 100);
}
m = txt.match(/(?:€\s*(\d+(?:[.,]\d+)?)|(\d+(?:[.,]\d+))\s*€)/);
if(m){
let val = parseFloat((m[1] || m[2] || '').replace(',','.'));
if(!isNaN(val) && val > 0 && val <= curPrice) cp = Math.max(cp, val);
}
return cp;
}
function showPrice(amt, cur){
if(!exRates || cur === 'EUR') return `€${amt.toFixed(2)}`;
return `€${amt.toFixed(2)}<span style="font-size:0.8em; color:#888;" title="Exchange Rate: 1 EUR = ${exRates[cur]} ${cur}">ℹ️</span>`;
}
main();
})();