56 lines
2.4 KiB
JavaScript
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 []; }
|