Před instalací, Greasy Fork chce abyste věděli, že tento skript obsahuje "antifunkce", což jsou věci, které spíše přispívají autorovi skriptu, než vám.
Tento skript obsahuje části, které sledují vaše vyhledávání.
Compare Amazon prices across different country sites with a leaner, faster script.
// ==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(); })();