/** * Proxy für newsdata.io API. * Endpoint: https://newsdata.io/api/1/news?apikey=...&language=...&category=... * Pagination: counter = wievieltes Widget dieser Art (0 = erste Seite, 1 = zweite, …), damit News nicht doppelt gezeigt werden. */ const NEWS_BASE = 'https://newsdata.io/api/1/news'; // Cache für News-Ergebnisse (pro Sprache/Kategorie) const newsCache = new Map(); const CACHE_TTL = 5 * 60 * 1000; // 5 Minuten Cache /** * @param {object} options * @param {number} options.counter - 0 = erste Seite, 1 = zweite, … (für Pagination/nextPage) * @param {string} [options.language] - z. B. de, en * @param {string} [options.category] - z. B. top, technology * @returns {Promise<{ results: Array, nextPage: string|null }>} */ async function fetchNewsPage({ language = 'de', category = 'top', nextPageToken = null }) { const apiKey = process.env.NEWSDATA_IO_API_KEY; if (!apiKey || !apiKey.trim()) { throw new Error('NEWSDATA_IO_API_KEY is not set in .env'); } const params = new URLSearchParams(); params.set('apikey', apiKey.trim()); params.set('language', String(language)); params.set('category', String(category)); if (nextPageToken) params.set('page', nextPageToken); const url = `${NEWS_BASE}?${params.toString()}`; const res = await fetch(url); if (!res.ok) { const text = await res.text(); throw new Error(`newsdata.io: ${res.status} ${text.slice(0, 200)}`); } const data = await res.json(); return { results: data.results ?? [], nextPage: data.nextPage ?? null }; } /** * Holt gecachte Artikel oder lädt sie von der API */ async function getCachedNews({ language = 'de', category = 'top', minArticles = 10 }) { const cacheKey = `${language}:${category}`; const cached = newsCache.get(cacheKey); // Cache gültig? if (cached && (Date.now() - cached.timestamp) < CACHE_TTL) { // Wenn wir mehr Artikel brauchen, erweitern if (cached.articles.length >= minArticles) { return cached.articles; } } // Neue Daten laden const collected = cached?.articles || []; let nextPageToken = cached?.nextPage || null; while (collected.length < minArticles) { try { const page = await fetchNewsPage({ language, category, nextPageToken: nextPageToken || undefined }); const items = page.results ?? []; // Duplikate vermeiden (nach title) const existingTitles = new Set(collected.map(a => a.title)); for (const item of items) { if (!existingTitles.has(item.title)) { collected.push(item); existingTitles.add(item.title); } } nextPageToken = page.nextPage ?? null; if (items.length === 0 || !nextPageToken) break; } catch (error) { console.error('News fetch error:', error); break; } } // Cache aktualisieren newsCache.set(cacheKey, { articles: collected, nextPage: nextPageToken, timestamp: Date.now() }); return collected; } /** * Liefert den N-ten Artikel (counter = 0, 1, 2, …) für das N-te News-Widget. * Nutzt Cache, damit mehrere Widgets unterschiedliche Artikel bekommen. * * @param {object} options * @param {number} options.counter - Index des Artikels (0 = erster, 1 = zweiter, …) * @param {string} [options.language] * @param {string} [options.category] * @returns {Promise<{ results: Array, nextPage: string|null }>} */ async function getNews({ counter = 0, language = 'de', category = 'top' }) { const neededIndex = Math.max(0, counter); // Mindestens so viele Artikel laden wie benötigt const articles = await getCachedNews({ language, category, minArticles: neededIndex + 1 }); const single = articles[neededIndex] ? [articles[neededIndex]] : []; return { results: single, nextPage: null }; } export default { getNews };