diff --git a/app/api/matches/route.ts b/app/api/matches/route.ts index 02fc379..8f40dd0 100644 --- a/app/api/matches/route.ts +++ b/app/api/matches/route.ts @@ -1,5 +1,5 @@ import { NextResponse } from "next/server"; -import { fetchMatchesAndTeams, fetchOdds, attachOdds } from "@/lib/feeds"; +import { fetchMatchesAndTeams, fetchOdds, attachOdds, fetchLiveScores, applyLiveScores } from "@/lib/feeds"; import { computeGroupTables, computeThirdPlaceTable } from "@/lib/standings"; import { qualifiedThirdGroups, resolveAnnexC } from "@/lib/bracket"; @@ -20,6 +20,14 @@ export async function GET() { console.error("[polymarket] fetchOdds fehlgeschlagen:", err); } + // Live-Scores von worldcup26.ir (additiv, Fallback auf football-data) + try { + const liveScores = await fetchLiveScores(); + matches = applyLiveScores(matches, teams, liveScores); + } catch (err) { + console.error("[worldcup26] fetchLiveScores fehlgeschlagen:", err); + } + const groupTables = computeGroupTables(teams, matches); const groupTablesLive = computeGroupTables(teams, matches, true); diff --git a/lib/feeds.ts b/lib/feeds.ts index 74cc72b..ade506b 100644 --- a/lib/feeds.ts +++ b/lib/feeds.ts @@ -333,4 +333,118 @@ export function attachOdds(matches: Match[], teams: Team[], odds: ParsedOdds[]): } return m; }); +} + +// ---------------------------------------------------------------------------- +// worldcup26.ir: schnelle Live-Scores (additiv, Fallback auf football-data) +// ---------------------------------------------------------------------------- +const WC26_BASE = "https://worldcup26.ir"; + +interface Wc26Game { + home_team_name_en?: string; + away_team_name_en?: string; + group?: string; + matchday?: string; + home_score?: string; + away_score?: string; + time_elapsed?: string; +} + +interface LiveScore { + homeName: string; + awayName: string; + group: string; + matchday: string; + homeScore: number | null; + awayScore: number | null; + status: string; // "IN_PLAY" | "FINISHED" +} + +// Ruft alle Spiele von worldcup26.ir ab. +export async function fetchLiveScores(): Promise { + const res = await fetch(`${WC26_BASE}/get/games`, { + cache: "no-store", + headers: { "User-Agent": "wm2026-board/1.0" }, + signal: AbortSignal.timeout(8000), + }); + if (!res.ok) throw new Error(`worldcup26 ${res.status}`); + const data = (await res.json()) as { games: Wc26Game[] }; + const games = data.games ?? []; + + const scores: LiveScore[] = []; + for (const g of games) { + if (!g.home_team_name_en || !g.away_team_name_en) continue; + const hScore = parseScore(g.home_score); + const aScore = parseScore(g.away_score); + let status = ""; + switch (g.time_elapsed) { + case "live": status = "IN_PLAY"; break; + case "finished": status = "FINISHED"; break; + default: continue; // notstarted → überspringen + } + // Nur anwenden, wenn mindestens ein Score vorhanden ist + if (hScore == null && aScore == null) continue; + scores.push({ + homeName: g.home_team_name_en, + awayName: g.away_team_name_en, + group: g.group ?? "", + matchday: String(g.matchday ?? ""), + homeScore: hScore, + awayScore: aScore, + status, + }); + } + console.log("[worldcup26] spiele:", scores.length); + return scores; +} + +function parseScore(s: string | undefined): number | null { + if (!s || s === "null") return null; + const n = Number(s); + return Number.isFinite(n) ? n : null; +} + +// Wendet worldcup26-Live-Scores auf football-data-Matches an. +// Matching über normalisierte Teamnamen + group + matchday. +export function applyLiveScores( + matches: Match[], teams: Team[], liveScores: LiveScore[], +): Match[] { + // Build lookup: normName(home)::normName(away)::group::matchday → match + // Für Group-Phase ist die Paarung eindeutig (jedes Paar spielt 1x). + const byPair = new Map(); + for (const m of matches) { + if (m.group == null) continue; // nur Gruppenphase + if (!m.homeTeamId || !m.awayTeamId) continue; + const ht = teams.find((t) => t.id === m.homeTeamId); + const at = teams.find((t) => t.id === m.awayTeamId); + if (!ht || !at) continue; + const hn = normName(ht.name); + const an = normName(at.name); + // Beide Richtungen + byPair.set(`${hn}::${an}::${m.group}`, m); + byPair.set(`${an}::${hn}::${m.group}`, m); + } + + const result = [...matches]; + let applied = 0; + for (const ls of liveScores) { + const hn = normName(ls.homeName); + const an = normName(ls.awayName); + const key = `${hn}::${an}::${ls.group}`; + const fdMatch = byPair.get(key); + if (!fdMatch) continue; + + const idx = result.findIndex((m) => m.id === fdMatch.id); + if (idx < 0) continue; + + result[idx] = { + ...result[idx], + homeScore: ls.homeScore ?? result[idx].homeScore, + awayScore: ls.awayScore ?? result[idx].awayScore, + status: ls.status as Match["status"], + }; + applied++; + } + if (applied > 0) console.log("[worldcup26] auf matches angewandt:", applied); + return result; } \ No newline at end of file