您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
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.
当前为
// ==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 // ==/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(); })();