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.

  1. // ==UserScript==
  2. // @name Amazon Price Checker (FR, DE, ES, IT, BE, NL, UK, COM, PL, SE) by bNj
  3. // @namespace http://tampermonkey.net/
  4. // @version 4.02
  5. // @description Compare Amazon prices across different country sites with a leaner, faster script.
  6. // @icon https://i.ibb.co/qrjrcVy/amz-price-checker.png
  7. // @match https://www.amazon.fr/*
  8. // @match https://www.amazon.de/*
  9. // @match https://www.amazon.es/*
  10. // @match https://www.amazon.it/*
  11. // @match https://www.amazon.com.be/*
  12. // @match https://www.amazon.nl/*
  13. // @match https://www.amazon.co.uk/*
  14. // @match https://www.amazon.com/*
  15. // @match https://www.amazon.pl/*
  16. // @match https://www.amazon.se/*
  17. // @grant GM_xmlhttpRequest
  18. // @connect amazon.fr
  19. // @connect amazon.de
  20. // @connect amazon.es
  21. // @connect amazon.it
  22. // @connect amazon.com.be
  23. // @connect amazon.nl
  24. // @connect amazon.pl
  25. // @connect amazon.se
  26. // @connect amazon.co.uk
  27. // @connect amazon.com
  28. // @connect summarizer.mon-bnj.workers.dev
  29. // @connect api.frankfurter.app
  30. // @connect alisearch.bnjnas.synology.me
  31. // @license All Rights Reserved
  32. // @antifeature referral-link
  33. // @antifeature tracking
  34. // ==/UserScript==
  35. (function(){
  36. 'use strict';
  37. const ASIN_RE = /\/([A-Z0-9]{10})(?:[/?]|$)/,
  38. PARTNER_IDS = {
  39. fr:'bnjmazon-21',
  40. es:'bnjmazon08-21',
  41. it:'bnjmazon0d-21',
  42. de:'geeksince190d-21',
  43. 'com.be':'geeksince1900',
  44. nl:'bnjmazon-21',
  45. pl:'bnjmazon-20',
  46. se:'bnjmazon-se-21',
  47. 'co.uk':'bnjmazon-UK-21',
  48. com:'bnjmazon-20'
  49. },
  50. sites = [
  51. {name:'Amazon.fr', c:'fr', f:'https://flagcdn.com/w20/fr.png', cur:'EUR'},
  52. {name:'Amazon.es', c:'es', f:'https://flagcdn.com/w20/es.png', cur:'EUR'},
  53. {name:'Amazon.it', c:'it', f:'https://flagcdn.com/w20/it.png', cur:'EUR'},
  54. {name:'Amazon.de', c:'de', f:'https://flagcdn.com/w20/de.png', cur:'EUR'},
  55. {name:'Amazon.be', c:'com.be', f:'https://flagcdn.com/w20/be.png', cur:'EUR'},
  56. {name:'Amazon.nl', c:'nl', f:'https://flagcdn.com/w20/nl.png', cur:'EUR'},
  57. {name:'Amazon.pl', c:'pl', f:'https://flagcdn.com/w20/pl.png', cur:'PLN'},
  58. {name:'Amazon.se', c:'se', f:'https://flagcdn.com/w20/se.png', cur:'SEK'},
  59. {name:'Amazon.co.uk', c:'co.uk', f:'https://flagcdn.com/w20/gb.png', cur:'GBP'},
  60. {name:'Amazon.com', c:'com', f:'https://flagcdn.com/w20/us.png', cur:'USD'}
  61. ];
  62.  
  63. let asin, basePrice, selPeriod = 'all', firstLoaded = false, exRates,
  64. tableCont, chartCont, selEl, checks = [];
  65.  
  66. function main() {
  67. if (!extractASIN()) return;
  68.  
  69. fetchExRates().then(() => {
  70. if (!getBasePrice()) return;
  71. injectStyles();
  72. createBaseUI();
  73. fetchPrices(); // ✅ Appelé uniquement après récupération des taux
  74. });
  75. }
  76.  
  77. function extractASIN(){
  78. const m = location.href.match(ASIN_RE);
  79. if(!m) return false;
  80. asin = m[1];
  81. return true;
  82. }
  83. function getBasePrice(){
  84. basePrice = getPrice(document, getCurrentCountry());
  85. return basePrice !== null;
  86. }
  87. function injectStyles(){
  88. const css = `:root{--a:#FF9900;--bg:#fff;--font:Arial,sans-serif;--tc:#333;--bc:#ddd}
  89. body{font-family:var(--font)!important}
  90. #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}
  91. #amz-checker-header{background:var(--a);color:#fff;padding:5px 10px;border-radius:10px 10px 0 0;display:flex;align-items:center;gap:10px}
  92. #amz-checker-header img{width:36px;height:36px}
  93. #amz-checker-title{font-size:14px;font-weight:bold}
  94. .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}
  95. @keyframes loadAnim{0%{background-position:100% 50%}100%{background-position:0 50%}}
  96. #loadingMessage{text-align:center;font-weight:bold;font-size:13px;display:flex;flex-direction:column;align-items:center;margin:10px 0}
  97. .amz-checker-content{padding:10px;flex:1}
  98. #comparison-table{border:1px solid var(--bc);border-radius:8px;overflow:hidden;margin-bottom:15px}
  99. .comparison-row{display:flex;justify-content:space-between;padding:5px 10px;border-bottom:1px solid var(--bc);cursor:pointer;transition:background 0.2s}
  100. .comparison-row:hover{background:#f5f5f5}
  101. .comparison-row.header-row{background:#eee;font-weight:bold;cursor:default}
  102. .comparison-row.header-row:hover{background:#eee}
  103. .comparison-row:last-child{border-bottom:none}
  104. .comparison-row>div{text-align:center;flex:1}
  105. .first-col{flex:0 0 120px;text-align:left !important;overflow:hidden}
  106. .price-difference-positive{color:#008000}
  107. .price-difference-negative{color:#f00}
  108. .chart-container{margin-bottom:15px;border:1px solid var(--bc);border-radius:8px;padding:10px;position:relative;min-height:300px;text-align:center}
  109. .chart-container .loader{position:absolute;top:50%;left:50%;margin:-24px 0 0 -24px}
  110. .chart-controls{display:flex;align-items:center;gap:15px;margin-bottom:10px;flex-wrap:wrap;justify-content:center}
  111. .chart-controls .checkbox-container{display:flex;align-items:center;font-size:12px}
  112. .chart-controls .checkbox-label{margin-left:4px}
  113. .chart-controls select{padding:3px 6px;font-size:12px}
  114. .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}
  115. .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}
  116. @keyframes rot{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}
  117. .chart-image{max-width:100%;margin-top:10px}
  118. .aliexpress-wrapper{margin-bottom:15px}
  119. .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}
  120. .aliexpress-container:hover{background:#fff8f0}
  121. .aliexpress-icon{width:24px}
  122. .aliexpress-results{margin-top:10px;display:flex;flex-wrap:wrap;gap:10px;justify-content:space-evenly}
  123. .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}
  124. .aliexpress-card img{width:100%;border-radius:4px 4px 0 0}
  125. .product-summary-encart{border:1px solid var(--bc);border-radius:8px;padding:10px;background:#f9f9f9;margin-bottom:15px}
  126. ._Y3Itc_selected_2-xMA{font-weight:bold!important}
  127. #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}
  128. #amz-checker-footer .footer-logo{width:18px;height:18px;vertical-align:middle;margin-right:5px}`;
  129. let s = document.createElement('style');
  130. s.type = 'text/css';
  131. s.textContent = css;
  132. document.head.appendChild(s);
  133. }
  134. function createBaseUI(){
  135. let c = document.createElement('div');
  136. c.id = 'amz-checker-container';
  137. 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>
  138. <div class="amz-checker-content"><div id="loadingMessage" class="loading-text-gradient">Checking other Amazon sites...</div></div>`;
  139. let p = document.querySelector('.priceToPay,#priceblock_ourprice,#priceblock_dealprice,#priceblock_saleprice');
  140. (p ? p.parentNode : document.body).appendChild(c);
  141. }
  142. function buildFinalUI(){
  143. let cnt = document.querySelector('#amz-checker-container .amz-checker-content');
  144. if(!cnt)return; cnt.innerHTML = '';
  145. addTable(cnt); addChart(cnt); /*addAliExpress(cnt);*/ addProductSummary(cnt); addFooter();
  146. updateChart();
  147. }
  148. function addTable(cnt){
  149. let tw = document.createElement('div'); tw.id = 'comparison-table';
  150. tableCont = document.createElement('div');
  151. let hr = document.createElement('div'); hr.className = 'comparison-row header-row';
  152. ['Site','Price (EUR)','Coupon','Delivery','Total','Difference'].forEach((h,i) => hr.appendChild(cell(h,true,i===0 ? 'first-col' : '')));
  153. tableCont.appendChild(hr); tw.appendChild(tableCont); cnt.appendChild(tw);
  154. }
  155. const cell = (txt, isH, ex) => {
  156. let d = document.createElement('div');
  157. d.innerHTML = txt;
  158. if(isH) d.style.fontWeight = 'bold';
  159. if(ex) d.classList.add(ex);
  160. return d;
  161. };
  162. function insertRow({ s, price, del, coupon, cur }){
  163. let tot = price - coupon + del, row = document.createElement('div'); row.className = 'comparison-row';
  164. row.onclick = () => window.open(`https://www.amazon.${s.c}/dp/${asin}?tag=${PARTNER_IDS[s.c]}`, '_blank');
  165. let diff = tot - basePrice, perc = ((diff/basePrice)*100).toFixed(0),
  166. dc = diff < 0 ? 'price-difference-positive' : diff > 0 ? 'price-difference-negative' : '';
  167. row.append(
  168. cell(`<img src="${s.f}" style="vertical-align:middle;margin-right:5px;width:20px;height:13px;"> ${s.name}`, false, 'first-col'),
  169. cell(showPrice(price, cur)),
  170. cell(coupon > 0 ? `- ${coupon.toFixed(2)}` : '-'),
  171. cell(del > 0 ? `+ ${del.toFixed(2)}` : '-'),
  172. cell(showPrice(tot, cur)),
  173. cell(diff !== 0 ? `<span class="${dc}">${diff >= 0 ? '+' : ''}€${diff.toFixed(2)} (${perc}%)</span>` : '-')
  174. );
  175. let rows = [...tableCont.querySelectorAll('.comparison-row:not(.header-row)')];
  176. let inserted = false;
  177. for(let r of rows){
  178. let t = parseFloat(r.children[4].textContent.replace(/[^0-9.,-]/g, '').replace(',', '.')) || 999999;
  179. if(tot < t){ tableCont.insertBefore(row, r); inserted = true; break; }
  180. }
  181. if(!inserted) tableCont.appendChild(row);
  182. }
  183. function addChart(cnt){
  184. chartCont = document.createElement('div'); chartCont.className = 'chart-container';
  185. let ctrl = document.createElement('div'); ctrl.className = 'chart-controls';
  186. selEl = document.createElement('select');
  187. [['1m','1 Month'], ['3m','3 Months'], ['6m','6 Months'], ['1y','1 Year'], ['all','All']].forEach(([v, l]) => {
  188. let o = document.createElement('option'); o.value = v; o.textContent = l; if(v === selPeriod) o.selected = true; selEl.appendChild(o);
  189. });
  190. selEl.onchange = () => { selPeriod = selEl.value; updateChart(); }; ctrl.appendChild(selEl);
  191. // Trois cases à cocher: Amazon (désactivée), New, Used
  192. [['checkboxAmazon','Amazon','amazon', true, true], ['checkboxNew','New','new', false, true], ['checkboxUsed','Used','used', false, false]]
  193. .forEach(([id, label, fn, dis, chk]) => {
  194. let wrap = document.createElement('div'); wrap.className = 'checkbox-container';
  195. let inp = document.createElement('input'); inp.type = 'checkbox'; inp.id = id; inp.disabled = dis; inp.checked = chk; inp.onchange = updateChart;
  196. let lbl = document.createElement('label'); lbl.htmlFor = id; lbl.textContent = label; lbl.className = 'checkbox-label';
  197. wrap.append(inp, lbl); ctrl.appendChild(wrap); checks.push({ inp, fn });
  198. });
  199. chartCont.appendChild(ctrl);
  200. let spin = document.createElement('div'); spin.className = 'loader';
  201. let img = document.createElement('img'); img.alt = `Price history for ${asin}`; img.className = 'chart-image'; img.style.display = 'none';
  202. chartCont.append(spin, img); cnt.appendChild(chartCont);
  203. }
  204. function updateChart(){
  205. if(!chartCont)return;
  206. let cc = getCurrentCountry(), url = getChartUrl(cc, asin, selPeriod),
  207. spin = chartCont.querySelector('.loader'),
  208. img = chartCont.querySelector('.chart-image');
  209. spin.style.display = 'inline-block'; img.style.display = 'none';
  210. img.src = url;
  211. img.onload = () => { spin.style.display = 'none'; img.style.display = 'block'; };
  212. img.onerror = () => { spin.style.display = 'none'; img.style.display = 'block'; img.src = 'https://dummyimage.com/600x200/ccc/000&text=Image+Unavailable'; };
  213. }
  214. function getChartUrl(cc, a, tp){
  215. let f = checks.filter(c => c.inp.checked).map(c => c.fn).join('-'),
  216. 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`;
  217. return `https://camelcamelcamel.mon-bnj.workers.dev/?target=${encodeURIComponent(base)}`;
  218. }
  219. function addAliExpress(cnt){
  220. let wrap = document.createElement('div'); wrap.className = 'aliexpress-wrapper';
  221. let btn = document.createElement('div'); btn.className = 'aliexpress-container';
  222. btn.innerHTML = `<img src="https://img.icons8.com/color/48/aliexpress.png" class="aliexpress-icon"><span class="aliexpress-text">Check on AliExpress</span>`;
  223. btn.onclick = () => {
  224. let txt = btn.querySelector('.aliexpress-text');
  225. txt.textContent = 'Loading...'; txt.classList.add('loading-text-gradient');
  226. let imgEl = document.querySelector('#landingImage') || document.querySelector('#imgTagWrapperId img'),
  227. imgUrl = imgEl ? imgEl.src : "https://m.media-amazon.com/images/I/71sAMz1x82L.__AC_SX300_SY300_QL70_ML2_.jpg",
  228. url = "https://alisearch.bnjnas.synology.me/search?image_url=" + encodeURIComponent(imgUrl);
  229. GM_xmlhttpRequest({
  230. method:'GET', url,
  231. onload: r => {
  232. txt.classList.remove('loading-text-gradient'); txt.textContent = 'Check on AliExpress';
  233. try { displayAliRes(wrap, JSON.parse(r.responseText)); }
  234. catch(e){ txt.textContent = 'Error parsing result'; }
  235. },
  236. onerror: () => { txt.classList.remove('loading-text-gradient'); txt.textContent = 'Error fetching data'; }
  237. });
  238. };
  239. wrap.appendChild(btn); cnt.appendChild(wrap);
  240. }
  241. function displayAliRes(container, results){
  242. results.sort((a, b) => parsePrice(a.prix) - parsePrice(b.prix));
  243. let resCont = container.querySelector('.aliexpress-results') || document.createElement('div');
  244. resCont.className = 'aliexpress-results'; resCont.innerHTML = '';
  245. results.forEach(item => {
  246. let card = document.createElement('div'); card.className = 'aliexpress-card';
  247. let a = document.createElement('a'); a.href = item.lien; a.target = '_blank'; a.style.textDecoration = 'none'; a.style.color = 'inherit';
  248. let img = document.createElement('img'); img.src = item.image; img.alt = item.titre;
  249. let title = document.createElement('div'); title.textContent = item.titre;
  250. title.style.cssText = "font-size:12px;margin-top:5px;font-weight:bold;height:40px;overflow:hidden";
  251. let price = document.createElement('div'); price.textContent = item.prix;
  252. price.style.cssText = "font-size:12px;color:#ff5722;margin-top:5px";
  253. a.append(img, title, price); card.appendChild(a); resCont.appendChild(card);
  254. });
  255. if(!container.contains(resCont)) container.appendChild(resCont);
  256. }
  257. const parsePrice = s => { let n = parseFloat(s.replace(/[^\d.,-]/g, '').replace(',', '.')); return isNaN(n) ? 999999 : n; };
  258. function addProductSummary(cnt){
  259. let sum = document.querySelector('#cr-product-insights-cards');
  260. if(sum){
  261. let clone = sum.cloneNode(true);
  262. clone.classList.add('product-summary-encart');
  263. clone.querySelectorAll('i[id^="close-button-"]').forEach(i => i.remove());
  264. cnt.appendChild(clone);
  265. addAspectListeners(clone);
  266. }
  267. }
  268. function addAspectListeners(clone){
  269. clone.querySelectorAll('[id^="aspect-button-0-"]').forEach(btn => {
  270. btn.onclick = () => {
  271. let X = btn.id.split('-')[3], sheet = document.getElementById(`aspect-bottom-sheet-0-${X}`);
  272. if(!sheet)return;
  273. clone.querySelectorAll('[id^="aspect-bottom-sheet-0-"]').forEach(s => s.style.display = 'none');
  274. sheet.style.display = 'block';
  275. clone.querySelectorAll('[id^="aspect-button-0-"]').forEach(b => b.classList.remove('_Y3Itc_selected_2-xMA'));
  276. btn.classList.add('_Y3Itc_selected_2-xMA');
  277. };
  278. });
  279. }
  280. let footerDone = false;
  281. function addFooter(){
  282. if(footerDone)return; footerDone = true;
  283. let cont = document.getElementById('amz-checker-container');
  284. if(!cont)return;
  285. let f = document.createElement('div'); f.id = 'amz-checker-footer';
  286. let ver = (typeof GM_info !== 'undefined' && GM_info.script && GM_info.script.version) ? GM_info.script.version : '4.x';
  287. f.innerHTML = `<img src="https://i.ibb.co/qrjrcVy/amz-price-checker.png" class="footer-logo"> Amazon Price Checker v${ver}`;
  288. cont.appendChild(f);
  289. }
  290. function getCurrentCountry(){
  291. let h = location.hostname;
  292. if(h.includes('amazon.com') && !h.includes('amazon.com.be') && !h.includes('amazon.co.uk')) return 'com';
  293. if(h.includes('amazon.de')) return 'de';
  294. if(h.includes('amazon.es')) return 'es';
  295. if(h.includes('amazon.it')) return 'it';
  296. if(h.includes('amazon.com.be')) return 'com.be';
  297. if(h.includes('amazon.nl')) return 'nl';
  298. if(h.includes('amazon.pl')) return 'pl';
  299. if(h.includes('amazon.co.uk')) return 'co.uk';
  300. if(h.includes('amazon.se')) return 'se';
  301. return 'fr';
  302. }
  303. function getPrice(doc, ctry) {
  304. // Essai standard
  305. let el = doc.querySelector('.priceToPay,#priceblock_ourprice,#priceblock_dealprice,#priceblock_saleprice');
  306. if (el) {
  307. let rawText = el.textContent.replace(/\u00A0/g, '').replace(/\s/g, '');
  308. let raw = parseFloat(rawText.replace(/[^0-9,\.]/g, '').replace(',', '.'));
  309. return raw;
  310. }
  311.  
  312. // Cas spécial (Amazon.se, prix éclaté)
  313. const wholeEl = doc.querySelector('.a-price-whole');
  314. if (wholeEl) {
  315. const tempWhole = wholeEl.cloneNode(true);
  316. const decimal = tempWhole.querySelector('.a-price-decimal');
  317. if (decimal) decimal.remove();
  318. const whole = tempWhole.textContent.replace(/[^\d]/g, '');
  319. let frac = "00";
  320. const fracEl = doc.querySelector('.a-price-fraction');
  321. if (fracEl) {
  322. frac = fracEl.textContent.replace(/[^\d]/g, '');
  323. }
  324. const raw = parseFloat(`${whole}.${frac}`);
  325. return raw;
  326. }
  327.  
  328. return null;
  329. }
  330. function getCurrency(ctry) {
  331. const s = sites.find(x => x.c.toLowerCase() === ctry.toLowerCase());
  332. return s ? s.cur : 'EUR';
  333. }
  334. function toEUR(amt, cur){
  335. if (!exRates || typeof amt !== 'number') return amt;
  336. if (cur === 'EUR') return amt;
  337. const rate = exRates[cur];
  338. console.log(`[toEUR] ${amt} ${cur} @ ${rate} => ${rate ? amt / rate : amt}`);
  339. return rate ? amt / rate : amt;
  340. }
  341. // Modification de fetchExRates pour forcer la requête si le cache ne contient pas SEK
  342. function fetchExRates(){
  343. return new Promise(resolve => {
  344. let cached = localStorage.getItem('exchangeRates'),
  345. ts = localStorage.getItem('exchangeRatesTimestamp'),
  346. now = Date.now();
  347. if(cached && ts && (now - ts < 3600000)){
  348. let storedRates = JSON.parse(cached);
  349. if(storedRates["SEK"] !== undefined){
  350. exRates = storedRates;
  351. return resolve();
  352. }
  353. }
  354. GM_xmlhttpRequest({
  355. method:'GET',
  356. url:'https://api.frankfurter.app/latest?from=EUR&to=USD,GBP,PLN,SEK',
  357. onload: r => {
  358. if(r.status === 200){
  359. let data = JSON.parse(r.responseText);
  360. exRates = data.rates;
  361. console.log('[Frankfurter RAW rates]', data.rates);
  362. console.log('[exRates["SEK"]]', data.rates['SEK']);
  363. localStorage.setItem('exchangeRates', JSON.stringify(exRates));
  364. localStorage.setItem('exchangeRatesTimestamp', now);
  365. } else {
  366. exRates = { USD:0.90, GBP:1.15, PLN:4.50, SEK:11.5, EUR:1 };
  367. }
  368. resolve();
  369. },
  370. onerror: () => {
  371. exRates = { USD:0.90, GBP:1.15, PLN:4.50, SEK:11.5, EUR:1 };
  372. resolve();
  373. }
  374. });
  375. });
  376. }
  377. function fetchPrices(){
  378. sites.forEach(s => {
  379. let url = `https://www.amazon.${s.c}/dp/${asin}?tag=${PARTNER_IDS[s.c]}`;
  380. GM_xmlhttpRequest({
  381. method:'GET',
  382. url,
  383. headers: { 'User-Agent':'Mozilla/5.0','Accept-Language':'en-US,en;q=0.5' },
  384. onload: r => {
  385. if (r && r.status === 200) {
  386. let doc = new DOMParser().parseFromString(r.responseText, 'text/html');
  387. let p = getPrice(doc, s.c); // toujours en devise locale
  388.  
  389. if (p !== null) {
  390. let d = getDelivery(doc);
  391. let c = getCoupon(doc, p); // coupon calculé à partir de p (non converti)
  392. let convertedPrice = toEUR(p, s.cur);
  393. let convertedDelivery = toEUR(d, s.cur);
  394. let convertedCoupon = toEUR(c, s.cur);
  395.  
  396. if (!firstLoaded) {
  397. firstLoaded = true;
  398. buildFinalUI();
  399. }
  400.  
  401. insertRow({
  402. s,
  403. price: convertedPrice,
  404. del: convertedDelivery,
  405. coupon: convertedCoupon,
  406. cur: s.cur
  407. });
  408. }
  409. }
  410. },
  411. onerror: () => {}
  412. });
  413. });
  414. }
  415. function getDelivery(doc){
  416. let m = doc.body.innerHTML.match(/data-csa-c-delivery-price="[^"]*?(\d+[.,]\d{2})/);
  417. if(m){
  418. let p = parseFloat(m[1].replace(',','.'));
  419. return isNaN(p) ? 0 : p;
  420. }
  421. return 0;
  422. }
  423. function getCoupon(doc, curPrice){
  424. let lbl = doc.querySelector('label[id^="couponText"],label[id^="greenBadgepctch"]');
  425. if(!lbl)return 0;
  426. let txt = (lbl.textContent || '').replace(/\u00A0/g, ' ').toLowerCase().trim(), cp = 0,
  427. m = txt.match(/(\d+(?:[.,]\d+)?)\s*%/);
  428. if(m){
  429. let p = parseFloat(m[1].replace(',','.'));
  430. if(!isNaN(p) && p > 0 && p < 100) cp = curPrice * (p / 100);
  431. }
  432. m = txt.match(/(?:€\s*(\d+(?:[.,]\d+)?)|(\d+(?:[.,]\d+))\s*€)/);
  433. if(m){
  434. let val = parseFloat((m[1] || m[2] || '').replace(',','.'));
  435. if(!isNaN(val) && val > 0 && val <= curPrice) cp = Math.max(cp, val);
  436. }
  437. return cp;
  438. }
  439. function showPrice(amt, cur){
  440. if(!exRates || cur === 'EUR') return `€${amt.toFixed(2)}`;
  441. return `€${amt.toFixed(2)}<span style="font-size:0.8em; color:#888;" title="Exchange Rate: 1 EUR = ${exRates[cur]} ${cur}">ℹ️</span>`;
  442. }
  443. main();
  444. })();

QingJ © 2025

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