import type { WikiArticle, Puzzle } from "./types"; import { getFallbackPuzzle } from "./puzzles"; const WIKI_API_BASE = "https://fr.wikipedia.org/w/api.php"; const MIN_ARTICLE_BYTES = 10000; const BAD_TITLE_PREFIXES = ["Index de", "Portail:"]; const BAD_TITLE_SUFFIXES = [ "(disambiguation)", // Géographie administrative française "(département)", "(canton)", "(commune)", "(arrondissement)", "(circonscription législative)", "(région)", ]; const BAD_TITLE_PATTERNS = [ // Cantons français : "Canton de X", "Canton de X (Yvelines)", etc /^Canton (de |d'|du )/i, // Arrondissements /^Arrondissement (de |d'|du )/i, // Communes très petites ou listes géo /^Communes (de |d'|du )/i, ]; // Cache de promesses module-level const articleCache = new Map>(); function doFetchArticle(title: string): Promise { const params = new URLSearchParams({ action: "parse", page: title, format: "json", origin: "*", prop: "text|displaytitle", disableeditsection: "1", redirects: "1", }); return fetch(`${WIKI_API_BASE}?${params}`) .then((res) => { if (!res.ok) throw new Error("Erreur reseau"); return res.json(); }) .then((data): WikiArticle => { if (data.error) throw new Error(data.error.info ?? "Article introuvable"); return { html: data.parse.text["*"] as string, title: data.parse.title as string, }; }) .catch((err) => { articleCache.delete(title); throw err; }); } function getCachedArticle(title: string): Promise { if (!articleCache.has(title)) { articleCache.set(title, doFetchArticle(title)); } return articleCache.get(title)!; } export function prefetchArticle(title: string): void { getCachedArticle(title).catch(() => {}); } export async function fetchArticle(title: string): Promise { try { return await getCachedArticle(title); } catch { return null; } } interface WikiPageInfo { title: string; length: number; } function isGoodArticle(page: WikiPageInfo): boolean { const t = page.title; return ( page.length >= MIN_ARTICLE_BYTES && !BAD_TITLE_PREFIXES.some((p) => t.startsWith(p)) && !BAD_TITLE_SUFFIXES.some((s) => t.endsWith(s)) && !BAD_TITLE_PATTERNS.some((r) => r.test(t)) ); } async function fetchRandomCandidates(): Promise { const params = new URLSearchParams({ action: "query", generator: "random", grnnamespace: "0", grnlimit: "50", grnfilterredir: "nonredirects", prop: "info", format: "json", origin: "*", }); const res = await fetch(`${WIKI_API_BASE}?${params}`); if (!res.ok) throw new Error("Erreur reseau"); const data = (await res.json()) as { query: { pages: Record }; }; return Object.values(data.query.pages) .filter(isGoodArticle) .map((p) => p.title); } export async function pickTwoArticles(): Promise { try { const collected: string[] = []; for (let i = 0; i < 3; i++) { const batch = await fetchRandomCandidates(); for (const title of batch) { if (!collected.includes(title)) collected.push(title); if (collected.length >= 2) return { start: collected[0], target: collected[1] }; } } } catch { // Fallback si l'API est indisponible } return getFallbackPuzzle(); } export function normalizeTitle(s: string): string { return decodeURIComponent(s).replace(/_/g, " ").toLowerCase().trim(); } export const POLL_INTERVAL = 2000; export const COUNTDOWN_DURATION = 3000;