refactor: separate regional briefing and weather sources

This commit is contained in:
Sameh Khamis committed 2026-09-12 12:59:39 -07:00
1 parent 213edd80ce
commit 318d37ad8d
11 files changed
+475 -348

No files matched your search

+80
View File
@@ -0,0 +1,80 @@
import { fetchRegionalText, fetchRegionalJson } from './http.js';
import { normalizeRegionalArticles } from '../../../src/data/regionalBrief.js';
function decodeRssText(value) {
return String(value || '')
.replace(/<!\[CDATA\[([\s\S]*?)\]\]>/g, '$1')
.replace(/&amp;/g, '&').replace(/&quot;/g, '"').replace(/&#39;/g, "'")
.replace(/&lt;/g, '<').replace(/&gt;/g, '>')
.replace(/<[^>]+>/g, ' ').replace(/\s+/g, ' ').trim();
}
function rssTag(block, tag) {
return decodeRssText(new RegExp(`<${tag}(?:\\s[^>]*)?>([\\s\\S]*?)<\\/${tag}>`, 'i').exec(block)?.[1] || '');
}
function normalizeRssArticles(xml, limit = 5) {
const seen = new Set();
const articles = [];
for (const match of String(xml || '').matchAll(/<item>([\s\S]*?)<\/item>/gi)) {
const item = match[1];
const title = rssTag(item, 'title').slice(0, 180);
const url = rssTag(item, 'link');
let parsedUrl;
try { parsedUrl = new URL(url); } catch { continue; }
if (!title || !['http:', 'https:'].includes(parsedUrl.protocol)) continue;
const source = rssTag(item, 'source');
const signature = `${title.toLowerCase()}|${source.toLowerCase() || parsedUrl.hostname}`;
if (seen.has(signature)) continue;
seen.add(signature);
const rawDate = rssTag(item, 'pubDate');
articles.push({
title,
url: parsedUrl.href,
domain: source || parsedUrl.hostname.replace(/^www\./, ''),
publishedAt: Number.isNaN(Date.parse(rawDate)) ? null : new Date(rawDate).toISOString(),
sourceCountry: null,
});
if (articles.length >= limit) break;
}
return articles;
}
async function fetchRegionalNews(place) {
const query = place?.locality || place?.region || place?.country;
if (!query) return { status: 'unavailable', query: null, articles: [], source: null };
const rssParams = new URLSearchParams({
q: String(query).replace(/["\\]/g, ' ').trim(),
hl: 'en-US',
gl: 'US',
ceid: 'US:en',
});
try {
const xml = await fetchRegionalText(`https://news.google.com/rss/search?${rssParams}`, {
headers: { 'User-Agent': 'GodsEyeView/0.1' },
timeoutMs: 12_000,
});
const articles = normalizeRssArticles(xml, 5);
if (articles.length) return { status: 'ready', query, articles, source: 'Google News RSS' };
} catch { /* fall through to the existing free index */ }
const params = new URLSearchParams({
query: `"${String(query).replace(/["\\]/g, ' ').trim()}"`,
mode: 'artlist',
format: 'json',
maxrecords: '5',
sort: 'datedesc',
timespan: '48h',
});
try {
const payload = await fetchRegionalJson(`https://api.gdeltproject.org/api/v2/doc/doc?${params}`, {
headers: { 'User-Agent': 'GodsEyeView/0.1' },
timeoutMs: 12_000,
});
const articles = normalizeRegionalArticles(payload, 5);
return { status: articles.length ? 'ready' : 'empty', query, articles, source: 'GDELT fallback' };
} catch {
return { status: 'unavailable', query, articles: [], source: null };
}
}
export { fetchRegionalNews };