1// Read the price rendered by Shoptet for this customer's current session. 2// No public-price fallback, group whitelist, discount arithmetic or shared cache. 3const clean=value=>String(value??'').replace(/\s+/g,' ').trim(); 4const content=(root,property)=>root.querySelector('meta[itemprop="'+property+'"]')?.getAttribute('content'); 5export function displayedPrice(element){ 6 if(!element)throw Error('customer price missing'); 7 const copy=element.cloneNode(true); 8 copy.querySelectorAll('.pr-list-unit').forEach(node=>node.remove()); 9 const text=clean(copy.textContent); 10 if(!/^\d[\d ]*(?:[,.]\d{1,2})? KÄ$/.test(text))throw Error('customer price ambiguous'); 11 const value=Number(text.replace(/ KÄ$/,'').replace(/ /g,'').replace(',','.')); 12 if(!Number.isFinite(value)||value<=0)throw Error('customer price invalid'); 13 return value; 14} 15export function readOffer(page,code){ 16 const detail=page.querySelector('#product-detail-form'); 17 if(!detail)throw Error('product detail missing'); 18 const offers=[...detail.querySelectorAll('[itemprop="offers"]')]; 19 const exact=offers.filter(offer=>(content(offer,'sku')||(offers.length===1?content(detail,'sku'):null))===code); 20 if(exact.length!==1)throw Error('exact SKU offer missing or ambiguous'); 21 const offer=exact[0]; 22 if(content(offer,'priceCurrency')!=='CZK')throw Error('unsupported currency'); 23 const inStock=offer.querySelector('[itemprop="availability"]')?.getAttribute('href')?.endsWith('/InStock'); 24 25 const rows=[...page.querySelectorAll('[data-testid="productVariant"]')]; 26 let source=detail,priceElement,variant="",variantImage=""; 27 if(rows.length){ 28 const exactRows=rows.filter(row=>{ 29 const label=row.querySelector('[data-testid="productVariantName"]')?.parentElement; 30 const text=label?[...label.childNodes].filter(node=>node.nodeType===3).map(node=>node.textContent).join(' '):''; 31 return clean(text).replace(/^\|\s*/,'')===code; 32 }); 33 if(exactRows.length!==1)throw Error('exact variant row missing or ambiguous'); 34 variant=clean(exactRows[0].querySelector('[data-testid="productVariantName"]')?.textContent); 35 variantImage=exactRows[0].querySelector('img')?.closest('a.lightbox')?.getAttribute('href')||exactRows[0].querySelector('img')?.getAttribute('data-src')||exactRows[0].querySelector('img')?.getAttribute('src')||''; 36 source=exactRows[0].querySelector('form[action="/action/Cart/addCartItem/"]'); 37 priceElement=exactRows[0].querySelector('[data-testid="productVariantPrice"]'); 38 }else{ 39 if(offers.length!==1)throw Error('variant prices missing'); 40 priceElement=detail.querySelector('[data-testid="productCardPrice"]'); 41 } 42 const price=displayedPrice(priceElement); 43 const priceId=source?.querySelector('input[name="priceId"]')?.value; 44 const productId=detail.querySelector('input[name="productId"]')?.value||''; 45 const purchasable=!!inStock&&!!priceId&&/^\d+$/.test(priceId)&&/^\d+$/.test(productId)&&!source?.querySelector('button[type="submit"][disabled]'); 46 const name=clean(page.querySelector('h1')?.textContent); 47 const imageSource=content(offer,'image')||variantImage||content(detail,'image')||content(page,'image'); 48 if(!name||!imageSource||!/^https:\/\//.test(imageSource))throw Error('product metadata unavailable'); 49 return {code,name,variant,imageSource,price,currency:'CZK',available:purchasable,priceId,productId,language:detail.querySelector('input[name="language"]')?.value||'cs'}; 50} 51export async function fetchOffer(product){ 52 const url=new URL(product.url,location.href); 53 if(url.origin!==location.origin)throw Error('product origin mismatch'); 54 const response=await fetch(url.href,{credentials:'same-origin',cache:'no-store',signal:AbortSignal.timeout(12000)}); 55 if(!response.ok||new URL(response.url||url.href).origin!==location.origin)throw Error('product unavailable'); 56 return readOffer(new DOMParser().parseFromString(await response.text(),'text/html'),product.code); 57}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.