Amazon Price Checker (FR, DE, ES, IT, BE, NL, COM) + AliExpress

Maximize your savings by effortlessly comparing prices across Amazon.fr, .de, .es, .it, .be, .nl, and .com. Automatically detect coupons, enjoy fast dynamic result displays, access comprehensive price histories with CamelCamelCamel, and find the best product alternatives on AliExpress with intelligent searches. Save more with every purchase !

目前為 2025-01-05 提交的版本,檢視 最新版本

// ==UserScript==
// @name         Amazon Price Checker (FR, DE, ES, IT, BE, NL, COM) + AliExpress
// @namespace    http://tampermonkey.net/
// @version      3.01
// @description Maximize your savings by effortlessly comparing prices across Amazon.fr, .de, .es, .it, .be, .nl, and .com. Automatically detect coupons, enjoy fast dynamic result displays, access comprehensive price histories with CamelCamelCamel, and find the best product alternatives on AliExpress with intelligent searches. Save more with every purchase !
// @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
// ==/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=`
      <div id="loadingMessage">
        <img src="https://i.ibb.co/qrjrcVy/amz-price-checker.png" style="width:50px;height:50px;margin-bottom:10px;">
        Checking other Amazon sites...
      </div>`;
      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(`
        <img src="${site.flag}"
             style="vertical-align:middle;margin-right:5px;width:20px;height:13px;">
        ${site.name}
      `, false, 'first-col'),
      createCell(`€${price.toFixed(2)}`),
      createCell(
        coupon>0
          ? `<img src="https://img.icons8.com/arcade/64/discount-ticket.png"
                   width="20"
                   style="vertical-align:middle;margin-right:5px;">
             -€${coupon.toFixed(2)}`
          : '-'
      ),
      createCell(
        delivery
          ? `<img src="https://img.icons8.com/arcade/64/in-transit.png"
                   width="20"
                   style="vertical-align:middle;margin-right:5px;">
             €${delivery.toFixed(2)}`
          : '-'
      ),
      createCell(`€${total.toFixed(2)}`),
      createCell(
        diff!==0
          ? `<span class="${diffClass}">
               ${diff>=0?'+':''}€${diff.toFixed(2)} (${perc}%)
             </span>`
          : '-'
      )
    );

    let inserted=false;
    const rows=[...tableContainer.querySelectorAll('.comparison-row:not(.header-row)')];
    for(let i=0;i<rows.length;i++){
      const cells=rows[i].querySelectorAll('div');
      const existingTotalText = cells[4].textContent.replace(/[^\d.,-]/g,'').replace(',','.');
      const existingTotal = parseFloat(existingTotalText) || 999999;
      if(total < existingTotal){
        tableContainer.insertBefore(row,rows[i]);
        inserted=true;
        break;
      }
    }
    if(!inserted) tableContainer.appendChild(row);
  }

  function createCell(content,isHeader=false,extraClass=''){
    const c=document.createElement('div');
    c.style.flex='1';
    // On aligne tout à gauche par la CSS injectée:
    // text-align:left !important;
    c.innerHTML=content;
    if(isHeader) c.style.fontWeight='bold';
    if(extraClass) c.classList.add(extraClass);
    return c;
  }

  function addControls(container){
    const ctrls=document.createElement('div');
    ctrls.className='controls-container';
    const tps=[
      {id:'btn1M',label:'1 Month',val:'1m'},
      {id:'btn3M',label:'3 Months',val:'3m'},
      {id:'btn6M',label:'6 Months',val:'6m'},
      {id:'btn1Y',label:'1 Year',val:'1y'},
      {id:'btnAll',label:'All',val:'all'}
    ];
    tps.forEach(tp=>{
      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=`
      <img src="https://img.icons8.com/color/48/aliexpress.png" class="aliexpress-icon">
      <span class="aliexpress-text">Check on AliExpress</span>`;
    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=`
      <img src="https://i.ibb.co/qrjrcVy/amz-price-checker.png" class="footer-logo">
      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();
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址