45 lines
2.8 KiB
TypeScript
45 lines
2.8 KiB
TypeScript
import { z } from 'zod';
|
|
import type { Article } from '../shared/protocol.ts';
|
|
import { STARTER_ARTICLES } from '../shared/catalog.ts';
|
|
|
|
const pageSchema = z.object({
|
|
pageid: z.number().int().positive(), title: z.string(), extract: z.string().default(''),
|
|
lastrevid: z.number().int().nonnegative().default(0),
|
|
thumbnail: z.object({ source: z.string().url() }).optional(),
|
|
categories: z.array(z.object({ title: z.string() })).optional(),
|
|
});
|
|
|
|
function category(title: string, categories: string[]): string {
|
|
const text = `${title} ${categories.join(' ')}`.toLowerCase();
|
|
if (/astronom|planet|space|galax|moon|star\b/.test(text)) return 'Space';
|
|
if (/animal|plant|species|ocean|biolog|mollusc/.test(text)) return 'Nature';
|
|
if (/births|deaths|people|scientist/.test(text)) return 'People';
|
|
if (/physic|chem|math|science|technolog/.test(text)) return 'Science';
|
|
if (/histor|ancient|archaeolog|empire/.test(text)) return 'History';
|
|
return 'Culture';
|
|
}
|
|
export async function wikipediaArticle(): Promise<Article> {
|
|
if (process.env.WIKIDECK_OFFLINE === '1') return { ...STARTER_ARTICLES[Math.floor(Math.random() * STARTER_ARTICLES.length)] };
|
|
try {
|
|
const url = new URL('https://en.wikipedia.org/w/api.php');
|
|
url.search = new URLSearchParams({ action: 'query', format: 'json', formatversion: '2',
|
|
generator: 'random', grnnamespace: '0', grnlimit: '6', prop: 'extracts|pageimages|info|categories',
|
|
exintro: '1', explaintext: '1', exchars: '700', piprop: 'thumbnail', pithumbsize: '900', cllimit: '8' }).toString();
|
|
const response = await fetch(url, { signal: AbortSignal.timeout(6000), redirect: 'error',
|
|
headers: { 'User-Agent': 'Wikideck/0.1 (local educational card-game prototype)', Accept: 'application/json' } });
|
|
if (!response.ok) throw new Error('Wikipedia unavailable');
|
|
const raw = await response.text();
|
|
if (raw.length > 1_000_000) throw new Error('Wikipedia response too large');
|
|
const result = z.object({ query: z.object({ pages: z.array(pageSchema) }) }).parse(JSON.parse(raw));
|
|
const page = result.query.pages.find(p => p.extract.length > 120 && p.thumbnail) ?? result.query.pages.find(p => p.extract.length > 120);
|
|
if (!page) throw new Error('No suitable article');
|
|
const image = page.thumbnail?.source;
|
|
return { pageId: page.pageid, title: page.title, extract: page.extract.slice(0, 700),
|
|
image: image && ['https://upload.wikimedia.org', 'https://thumb.wikimedia.org'].includes(new URL(image).origin) ? image : undefined,
|
|
url: `https://en.wikipedia.org/?curid=${page.pageid}`, revisionId: page.lastrevid,
|
|
source: 'live', category: category(page.title, page.categories?.map(c => c.title) ?? []) };
|
|
} catch {
|
|
return { ...STARTER_ARTICLES[Math.floor(Math.random() * STARTER_ARTICLES.length)] };
|
|
}
|
|
}
|