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希望您了解本腳本包含“負面功能”,可能幫助腳本的作者獲利,而不能給你帶來任何收益。

作者從這份腳本獲得佣金, 例如透過重寫連結或提供優惠券代碼以加入推薦或附屬代碼

此腳本內的代碼會追蹤您的瀏覽行為

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 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();
})();