Amazon Price Checker (FR, DE, ES, IT, BE, NL, UK, COM, PL, SE) by bNj

Compare Amazon prices across different country sites with a leaner, faster script.

在您安装前,Greasy Fork 希望您知道此脚本声明其包含了一些负面功能。这些功能也许会使脚本作者获利,而不能给您带来任何直接的金钱收益。

作者可从这份脚本获得佣金,例如通过修改链接地址或提供优惠券代码以包含推荐或附属代码。

此脚本含有追踪您的操作的代码。

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Amazon Price Checker (FR, DE, ES, IT, BE, NL, UK, COM, PL, SE) by bNj
// @namespace    http://tampermonkey.net/
// @version      4.02
// @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/*
// @match        https://www.amazon.se/*
// @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.se
// @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',
          se:'bnjmazon-se-21',
          '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.se',    c:'se',     f:'https://flagcdn.com/w20/se.png', cur:'SEK'},
          {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 = [];

  function main() {
      if (!extractASIN()) return;

      fetchExRates().then(() => {
          if (!getBasePrice()) return;
          injectStyles();
          createBaseUI();
          fetchPrices(); // ✅ Appelé uniquement après récupération des taux
      });
  }

  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);
    // Trois cases à cocher: Amazon (désactivée), 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';
    if(h.includes('amazon.se')) return 'se';
    return 'fr';
  }
  function getPrice(doc, ctry) {
      // Essai standard
      let el = doc.querySelector('.priceToPay,#priceblock_ourprice,#priceblock_dealprice,#priceblock_saleprice');
      if (el) {
          let rawText = el.textContent.replace(/\u00A0/g, '').replace(/\s/g, '');
          let raw = parseFloat(rawText.replace(/[^0-9,\.]/g, '').replace(',', '.'));
          return raw;
      }

      // Cas spécial (Amazon.se, prix éclaté)
      const wholeEl = doc.querySelector('.a-price-whole');
      if (wholeEl) {
          const tempWhole = wholeEl.cloneNode(true);
          const decimal = tempWhole.querySelector('.a-price-decimal');
          if (decimal) decimal.remove();
          const whole = tempWhole.textContent.replace(/[^\d]/g, '');
          let frac = "00";
          const fracEl = doc.querySelector('.a-price-fraction');
          if (fracEl) {
              frac = fracEl.textContent.replace(/[^\d]/g, '');
          }
          const raw = parseFloat(`${whole}.${frac}`);
          return raw;
      }

      return null;
  }
  function getCurrency(ctry) {
      const s = sites.find(x => x.c.toLowerCase() === ctry.toLowerCase());
      return s ? s.cur : 'EUR';
  }
  function toEUR(amt, cur){
      if (!exRates || typeof amt !== 'number') return amt;
      if (cur === 'EUR') return amt;
      const rate = exRates[cur];
      console.log(`[toEUR] ${amt} ${cur} @ ${rate} => ${rate ? amt / rate : amt}`);
      return rate ? amt / rate : amt;
  }
  // Modification de fetchExRates pour forcer la requête si le cache ne contient pas SEK
  function fetchExRates(){
    return new Promise(resolve => {
      let cached = localStorage.getItem('exchangeRates'),
          ts = localStorage.getItem('exchangeRatesTimestamp'),
          now = Date.now();
      if(cached && ts && (now - ts < 3600000)){
        let storedRates = JSON.parse(cached);
        if(storedRates["SEK"] !== undefined){
          exRates = storedRates;
          return resolve();
        }
      }
      GM_xmlhttpRequest({
        method:'GET',
        url:'https://api.frankfurter.app/latest?from=EUR&to=USD,GBP,PLN,SEK',
        onload: r => {
          if(r.status === 200){
            let data = JSON.parse(r.responseText);
            exRates = data.rates;
            console.log('[Frankfurter RAW rates]', data.rates);
            console.log('[exRates["SEK"]]', data.rates['SEK']);
            localStorage.setItem('exchangeRates', JSON.stringify(exRates));
            localStorage.setItem('exchangeRatesTimestamp', now);
          } else {
            exRates = { USD:0.90, GBP:1.15, PLN:4.50, SEK:11.5, EUR:1 };
          }
          resolve();
        },
        onerror: () => {
          exRates = { USD:0.90, GBP:1.15, PLN:4.50, SEK:11.5, 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');
                      let p = getPrice(doc, s.c); // toujours en devise locale

                      if (p !== null) {
                          let d = getDelivery(doc);
                          let c = getCoupon(doc, p); // coupon calculé à partir de p (non converti)
                          let convertedPrice = toEUR(p, s.cur);
                          let convertedDelivery = toEUR(d, s.cur);
                          let convertedCoupon = toEUR(c, s.cur);

                          if (!firstLoaded) {
                              firstLoaded = true;
                              buildFinalUI();
                          }

                          insertRow({
                              s,
                              price: convertedPrice,
                              del: convertedDelivery,
                              coupon: convertedCoupon,
                              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();
})();