Files

56 lines
2.4 KiB
JavaScript

// OBI-Adapter — holt Preis + Marktverfügbarkeit direkt von obi.de (öffentlich).
// Verifiziert 27.06.2026: Produktseite liefert serverseitig Preis (799,99 €) und
// Markt-Status ("Im OBI Markt … Derzeit nicht vorrätig"). Per-Markt-Abfrage über
// OBI Markt-ID; Endpoint via Netzwerk-Capture bestätigen (TODO: marketId-Param).
const UA = 'KuehlfinderBot/0.1 (+https://kuehlfinder.de/bot; kontakt@kuehlfinder.de)';
export const meta = { id: 'obi', name: 'OBI', kind: 'baumarkt' };
// Produkt-Map: unsere productId -> OBI Artikelnummer / URL
export const products = {
'portasplit': 'https://www.obi.de/p/8620890/midea-mobile-split-klimaanlage-portasplit',
};
function parseJsonLd(html) {
const out = [];
const re = /<script[^>]+type=["']application\/ld\+json["'][^>]*>([\s\S]*?)<\/script>/gi;
let m; while ((m = re.exec(html))) { try { out.push(JSON.parse(m[1])); } catch {} }
return out;
}
function findOffer(ld) {
const arr = Array.isArray(ld) ? ld : [ld];
for (const node of arr) {
const graph = node['@graph'] || [node];
for (const g of graph) {
if (g && (g['@type'] === 'Product' || g.offers)) {
const o = Array.isArray(g.offers) ? g.offers[0] : g.offers;
if (o) return { price: Number(o.price ?? o.lowPrice), availability: String(o.availability || '') };
}
}
}
return null;
}
export async function fetchProduct(productId, { marketId } = {}) {
const url = products[productId];
if (!url) return null;
const res = await fetch(url, { headers: { 'User-Agent': UA, 'Accept-Language': 'de-DE' } });
if (!res.ok) throw new Error('OBI ' + res.status);
const html = await res.text();
const ld = parseJsonLd(html).map(findOffer).find(Boolean);
// Fallback: HTML-Heuristik für Markt-Status
const inStore = /vorrätig/i.test(html) && !/Derzeit nicht vorrätig/i.test(html);
const price = ld?.price ?? (html.match(/(\d{2,4},\d{2})\s*€/)?.[1]?.replace(',', '.'));
const online = ld ? /InStock/i.test(ld.availability) : /Lieferung[^<]*möglich/i.test(html);
return {
productId, source: 'obi', marketId: marketId ?? null,
price: price ? Number(price) : null,
availability: inStore ? 'in_stock' : (online ? 'online_available' : 'out_of_stock'),
checkedAt: new Date().toISOString(), url,
};
}
// Markt-Verzeichnis von OBI selbst (eigene Quelle, nicht braucheklima).
// TODO: obi.de/markt liefert Marktliste; hier Regionseiten/Sitemap parsen.
export async function fetchStores() { return []; }