// ==UserScript==
// @name Amazon Price Checker (FR, DE, ES, IT, BE, NL, COM) + AliExpress
// @namespace http://tampermonkey.net/
// @version 3.0
// @description Compare Amazon prices across FR, DE, ES, IT, BE, NL, COM. Detect coupon from label#couponText... or label#greenBadgepctch..., fade in each row, align columns left, etc.
// @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.com/*
// @grant GM_xmlhttpRequest
// @connect amazon.fr
// @connect amazon.es
// @connect amazon.it
// @connect amazon.de
// @connect amazon.com.be
// @connect amazon.nl
// @connect amazon.com
// @connect summarizer.mon-bnj.workers.dev
// @license MIT
// @downloadURL none
// ==/UserScript==
(function(){
'use strict';
const ASIN_REGEX = /\/([A-Z0-9]{10})(?:[/?]|$)/;
const PARTNER_IDS = {
fr: 'bnjmazon-21',
es: 'bnjmazon08-21',
it: 'bnjmazon0d-21',
de: 'geeksince190d-21',
'com.be': 'geeksince1900',
nl: 'bnjmazon-21',
com: 'bnjmazon-20'
};
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' },
{ name:'Amazon.be', country:'com.be', flag:'https://flagcdn.com/w20/be.png' },
{ name:'Amazon.nl', country:'nl', flag:'https://flagcdn.com/w20/nl.png' },
{ name:'Amazon.com', country:'com', flag:'https://flagcdn.com/w20/us.png' }
];
let asin, basePrice, selectedTimePeriod = 'all';
let priceResults = [], requestCount = 0, firstPriceLoaded = false;
let tableContainer, headerRow, priceContainer;
function main(){
if(!extractASIN() || !getBasePrice()) return;
injectStyles();
createLoadingContainer();
fetchPricesFromOtherSites();
}
function extractASIN(){
const m = window.location.href.match(ASIN_REGEX);
if(!m) return false;
asin = m[1];
return true;
}
function getBasePrice(){
basePrice = getPriceFromDocument(document);
return basePrice !== null;
}
/**
* Cherche d’abord un label#couponText..., sinon label#greenBadgepctch...
* Puis parse le texte pour trouver un pourcentage ou un montant.
*/
function getCouponFromDocument(doc, currentPrice) {
// On essaie d'abord label id^="couponText"
let label = doc.querySelector('label[id^="couponText"]');
// Si inexistant, on essaie label id^="greenBadgepctch"
if(!label) {
label = doc.querySelector('label[id^="greenBadgepctch"]');
}
if(!label) return 0;
let text = label.textContent || '';
text = text.replace(/\u00A0/g,' ').toLowerCase().trim();
let coupon = 0;
// 1) Pourcentage
const pctRegex = /(\d+(?:[.,]\d+)?)\s*%/;
const mPct = pctRegex.exec(text);
if(mPct){
const pctVal = parseFloat(mPct[1].replace(',', '.'));
if(!isNaN(pctVal) && pctVal>0 && pctVal<100){
coupon = currentPrice * (pctVal / 100);
}
}
// 2) Montant fixe (ex. "2,80€", "2.80 €")
const moneyRegex = /(?:€\s*(\d+(?:[.,]\d+)?)|(\d+(?:[.,]\d+))\s*€)/;
const mMoney = moneyRegex.exec(text);
if(mMoney){
const valStr = (mMoney[1] || mMoney[2] || '').replace(',', '.');
const val = parseFloat(valStr);
if(!isNaN(val) && val>0 && val<=currentPrice){
// On prend la plus grande des deux si on trouve un % et un montant
coupon = Math.max(coupon, val);
}
}
return coupon;
}
function injectStyles(){
const css=`
#amazonPriceComparisonContainer {
margin-top:20px;
padding:10px;
background:#f9f9f9;
border:1px solid #ccc;
border-radius:8px;
position:relative;
font-size:11px;
text-align:center
}
.comparison-row {
cursor:pointer;
display:flex;
justify-content:space-between;
padding:2px 0;
border-bottom:1px solid #ccc
}
.comparison-row:hover {
background:#f1f1f1
}
.comparison-row.header-row {
border-bottom:2px solid #000;
font-weight:bold;
pointer-events:none
}
.comparison-row>div {
flex:1;
margin:0 2px;
text-align:right !important;
}
.first-col {
flex: 0 0 100px;
white-space: nowrap;
text-align: left;
//overflow: hidden;
}
#loadingMessage {
text-align:center;
font-weight:bold;
font-size:14px;
display:flex;
flex-direction:column;
align-items:center;
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%}
}
.price-difference-positive { color:green }
.price-difference-negative { color:red }
.controls-container {
text-align:center;
margin:10px;
display:flex;
justify-content:space-around;
align-items:center
}
.aliexpress-container {
margin-top:20px;
padding:5px 10px;
border:1px solid #ccc;
border-radius:8px;
text-align:center;
max-width:200px;
margin:20px auto;
cursor:pointer;
background:transparent;
color:#ff5722;
font-weight:bold;
display:flex;
align-items:center;
justify-content:center
}
.aliexpress-icon {
width:24px;
margin-right:8px
}
.aliexpress-container:hover {
background:#ffe6cc
}
.loading-text {
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
}
.footer {
text-align:right;
font-size:.7em;
color:#666;
margin-top:10px
}
.footer-logo {
width:20px;
height:20px;
vertical-align:middle;
margin-right:5px
}
.chart-container {
text-align:center;
margin:20px 0
}
.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:rotation 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:rotation .5s linear infinite reverse
}
@keyframes rotation {
0%{transform:rotate(0deg)}
100%{transform:rotate(360deg)}
}
@keyframes fadeIn {
from{opacity:0}
to{opacity:1}
}
.fade-in {
animation:fadeIn .4s ease-in-out
}
`;
const st=document.createElement('style');
st.type='text/css';
st.innerText=css;
document.head.appendChild(st);
}
function createLoadingContainer(){
const priceElement = document.querySelector('.priceToPay,#priceblock_ourprice,#priceblock_dealprice,#priceblock_saleprice');
if(priceElement && priceElement.parentNode){
const c=document.createElement('div');
c.id='amazonPriceComparisonContainer';
c.innerHTML=`

Checking other Amazon sites...
`;
priceElement.parentNode.appendChild(c);
}
}
function fetchPricesFromOtherSites(){
amazonSites.forEach(s=>{
const url=`https://www.amazon.${s.country}/dp/${asin}?tag=${PARTNER_IDS[s.country]}`;
GM_xmlhttpRequest({
method:'GET',
url,
headers:{'User-Agent':'Mozilla/5.0','Accept-Language':'en-US,en;q=0.5'},
onload:r=>handleResponse(s,r),
onerror:()=>handleResponse(s,null)
});
});
}
function handleResponse(site,response){
requestCount++;
if(response && response.status===200){
const doc=new DOMParser().parseFromString(response.responseText,'text/html');
const p=getPriceFromDocument(doc);
const d=getDeliveryPriceFromDocument(doc);
if(p!==null){
const c=getCouponFromDocument(doc,p);
if(!firstPriceLoaded){
priceContainer = document.querySelector('#amazonPriceComparisonContainer');
if(!priceContainer) return;
priceContainer.innerHTML='';
createComparisonTableSkeleton(priceContainer);
addControls(priceContainer);
addCamelCamelCamelChart(priceContainer);
addAliExpressLink(priceContainer);
addFooter(priceContainer);
firstPriceLoaded=true;
}
insertPriceRow({site,price:p,delivery:d,coupon:c});
}
}
}
function createComparisonTableSkeleton(container){
tableContainer = document.createElement('div');
headerRow = document.createElement('div');
headerRow.className = 'comparison-row header-row';
['Site','Price','Coupon','Delivery','Total','Difference'].forEach(h=>{
headerRow.appendChild(createCell(h,true));
});
tableContainer.appendChild(headerRow);
container.appendChild(tableContainer);
}
function insertPriceRow({site,price,delivery,coupon}){
const total = price - (coupon||0) + (delivery||0);
const row = document.createElement('div');
row.className = 'comparison-row fade-in';
row.onclick = () => window.open(`https://www.amazon.${site.country}/dp/${asin}?tag=${PARTNER_IDS[site.country]}`, '_blank');
const diff = total - basePrice;
const perc = ((diff/basePrice)*100).toFixed(2);
const diffClass = diff<0 ? 'price-difference-positive' :
diff>0 ? 'price-difference-negative' :
'';
row.append(
createCell(`
${site.name}
`, false, 'first-col'),
createCell(`€${price.toFixed(2)}`),
createCell(
coupon>0
? `
-€${coupon.toFixed(2)}`
: '-'
),
createCell(
delivery
? `
€${delivery.toFixed(2)}`
: '-'
),
createCell(`€${total.toFixed(2)}`),
createCell(
diff!==0
? `
${diff>=0?'+':''}€${diff.toFixed(2)} (${perc}%)
`
: '-'
)
);
let inserted=false;
const rows=[...tableContainer.querySelectorAll('.comparison-row:not(.header-row)')];
for(let i=0;i{
const b=document.createElement('button');
b.id=tp.id;
b.textContent=tp.label;
b.className=`control-button ${tp.val===selectedTimePeriod?'active':''}`;
b.addEventListener('click',()=>{
selectedTimePeriod=tp.val;
document.querySelectorAll('.control-button').forEach(x=>x.classList.remove('active'));
b.classList.add('active');
updateChartUrl();
});
ctrls.appendChild(b);
});
const cbs=[
{id:'checkboxAmazon', label:'Amazon', fn:'amazon', dis:true, chk:true},
{id:'checkboxNew', label:'New', fn:'new', chk:true},
{id:'checkboxUsed', label:'Used', fn:'used', chk:false}
];
cbs.forEach(cb=>{
const wrap=document.createElement('div');
wrap.className='checkbox-container';
const i=document.createElement('input');
i.type='checkbox';
i.id=cb.id;
i.checked=cb.chk;
if(cb.dis) i.disabled=true;
i.addEventListener('change',updateChartUrl);
const lbl=document.createElement('label');
lbl.htmlFor=cb.id;
lbl.textContent=cb.label;
lbl.className='checkbox-label';
wrap.append(i,lbl);
ctrls.appendChild(wrap);
});
container.appendChild(ctrls);
}
function addCamelCamelCamelChart(container){
const c=document.createElement('div');
c.className='chart-container';
const cc=getCurrentCountryCode();
const url=getCamelChartUrl(cc,asin,selectedTimePeriod);
const camelUrl=`https://${cc}.camelcamelcamel.com/product/${asin}`;
const spin=document.createElement('div');
spin.className='loader';
const img=document.createElement('img');
img.alt=`Price history for ${asin}`;
img.className='chart-image';
img.style.display='none';
img.addEventListener('load',()=>{
spin.style.display='none';
img.style.display='block';
});
img.addEventListener('error',()=>{
spin.style.display='none';
img.style.display='block';
img.src='https://via.placeholder.com/600x300?text=Image+Unavailable';
});
img.src=url;
const a=document.createElement('a');
a.href=camelUrl;
a.target='_blank';
a.appendChild(img);
c.append(spin,a);
container.appendChild(c);
}
function getCamelChartUrl(cc,asin,tp){
const f=getSelectedFilenames();
const base=`https://charts.camelcamelcamel.com/${cc}/${asin}/${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 getSelectedFilenames(){
const cbs=[
{id:'checkboxAmazon',fn:'amazon'},
{id:'checkboxNew', fn:'new'},
{id:'checkboxUsed', fn:'used'}
];
return Array.from(document.querySelectorAll('input[type="checkbox"]:checked'))
.map(x=>cbs.find(z=>z.id===x.id)?.fn)
.filter(Boolean)
.join('-');
}
function updateChartUrl(){
const cc=getCurrentCountryCode();
const url=getCamelChartUrl(cc,asin,selectedTimePeriod);
const camelUrl=`https://${cc}.camelcamelcamel.com/product/${asin}`;
const i=document.querySelector('#amazonPriceComparisonContainer img.chart-image');
if(i){
const spin=i.parentElement.parentElement.querySelector('.loader');
if(spin) spin.style.display='inline-block';
i.style.display='none';
i.src=url;
i.parentElement.href=camelUrl;
}
}
function createAliExpressLink(title){
const d=document.createElement('div');
d.className='aliexpress-container';
d.innerHTML=`
Check on AliExpress`;
d.addEventListener('click',()=>{
const t=d.querySelector('.aliexpress-text');
t.className='loading-text';
t.textContent='Loading...';
GM_xmlhttpRequest({
method:'GET',
url:`https://summarizer.mon-bnj.workers.dev/?text=${encodeURIComponent(title)}`,
onload:r=>handleAliExpressResponse(r,d),
onerror:()=>{resetAliExpressButton(d);}
});
});
return d;
}
function handleAliExpressResponse(r,c){
try{
const j=JSON.parse(r.responseText);
if(j.summary){
const u=`https://www.aliexpress.com/wholesale?SearchText=${encodeURIComponent(j.summary)}`;
resetAliExpressButton(c);
setTimeout(()=>{window.open(u,'_blank');},100);
} else {
throw new Error('No summary');
}
}catch(e){
resetAliExpressButton(c);
}
}
function addAliExpressLink(c){
const t=document.querySelector('#productTitle');
const pt=t ? t.textContent.trim() : null;
if(!pt) return;
const ali=createAliExpressLink(pt);
c.appendChild(ali);
}
function resetAliExpressButton(c){
const ic=c.querySelector('.aliexpress-icon');
c.innerHTML='';
c.appendChild(ic);
const sp=document.createElement('span');
sp.className='aliexpress-text';
sp.textContent='Check on AliExpress';
c.appendChild(sp);
}
function addFooter(c){
const f=document.createElement('div');
f.className='footer';
f.innerHTML=`
Amazon Price Checker v${GM_info.script.version}
`;
c.appendChild(f);
}
function getCurrentCountryCode(){
const h=window.location.hostname;
if(h.includes('amazon.com') && !h.includes('amazon.com.be')) 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';
return 'fr';
}
function getPriceFromDocument(doc){
const el=doc.querySelector('.priceToPay,#priceblock_ourprice,#priceblock_dealprice,#priceblock_saleprice');
if(!el) return null;
return parsePrice(el.textContent);
}
function parsePrice(t){
if(!t) return null;
const c=t.replace(/[^0-9,\.]/g,'').replace(',','.');
const p=parseFloat(c);
return isNaN(p)?null:p;
}
function getDeliveryPriceFromDocument(doc){
const m=doc.body.innerHTML.match(/data-csa-c-delivery-price="[^"]*?(\d+[.,]\d{2})/);
if(m){
const x=m[1].replace(',', '.');
const p=parseFloat(x);
return isNaN(p)?0:p;
}
return 0;
}
main();
})();