- Remove unused imports (findMatchSubGamesByMatchId, isNull, or, MatchRecord, STAGE_NAMES) - Replace all != / == with !== / === throughout match-sync and components - Remove all no-non-null-assertion violations: use Map get-or-initialize pattern, remove guarded assertions (playoffMatch.round), use 'as' cast for filter-then-map pattern - Replace .sort() with .toSorted() in 5 locations - Merge duplicate react-router import in tournament.tsx - Remove unused LoaderData type alias in tournament.tsx - Rename unused 'stage' param to '_stage' in Cs2TournamentBracket - Use ?? [] in pandascore.test.ts to eliminate non-null assertions on subGames https://claude.ai/code/session_01WUUM7uWzFoSkGcZRhnEKG6
96 lines
3.5 KiB
TypeScript
96 lines
3.5 KiB
TypeScript
import type { MatchRecord, MatchSyncAdapter, MatchStatusValue } from "./types";
|
|
|
|
interface EspnCompetitor {
|
|
id: string;
|
|
team: { id: string; displayName: string; abbreviation?: string };
|
|
score?: string;
|
|
homeAway?: "home" | "away";
|
|
winner?: boolean;
|
|
}
|
|
interface EspnEvent {
|
|
id: string;
|
|
date: string;
|
|
competitions?: Array<{
|
|
competitors?: EspnCompetitor[];
|
|
status?: { type?: { name?: string; completed?: boolean; description?: string } };
|
|
matchday?: number;
|
|
}>;
|
|
}
|
|
interface EspnScheduleResponse {
|
|
events?: EspnEvent[];
|
|
}
|
|
|
|
function mapStatus(name: string | undefined, completed: boolean | undefined): MatchStatusValue {
|
|
if (completed) return "complete";
|
|
if (name === "STATUS_IN_PROGRESS" || name === "in_progress") return "in_progress";
|
|
if (name === "STATUS_CANCELED" || name === "canceled") return "canceled";
|
|
if (name === "STATUS_POSTPONED" || name === "postponed") return "postponed";
|
|
return "scheduled";
|
|
}
|
|
|
|
function parseScore(s: string | undefined): number | null {
|
|
if (s === undefined || s === "") return null;
|
|
const n = parseInt(s, 10);
|
|
return Number.isFinite(n) ? n : null;
|
|
}
|
|
|
|
export class EspnScheduleAdapter implements MatchSyncAdapter {
|
|
readonly supportsLiveScores = false;
|
|
|
|
// sport: e.g. "baseball/mlb", "basketball/nba"
|
|
// seasonType: ESPN season type (1=preseason, 2=regular, 3=postseason). Pass 3 for bracket sports
|
|
// to avoid the scoreboard limit silently truncating regular-season games.
|
|
constructor(private readonly sport: string, private readonly seasonType?: number) {}
|
|
|
|
// externalSeasonId is "YYYY" year string for ESPN sports
|
|
async fetchMatches(externalSeasonId: string): Promise<MatchRecord[]> {
|
|
const seasonTypeParam = this.seasonType !== undefined ? `&seasontype=${this.seasonType}` : "";
|
|
const url = `https://site.api.espn.com/apis/site/v2/sports/${this.sport}/scoreboard?limit=1000&dates=${externalSeasonId}${seasonTypeParam}`;
|
|
const resp = await fetch(url);
|
|
if (!resp.ok) {
|
|
const body = await resp.text();
|
|
throw new Error(`ESPN API error ${resp.status}: ${body}`);
|
|
}
|
|
|
|
const data = (await resp.json()) as EspnScheduleResponse;
|
|
const matches: MatchRecord[] = [];
|
|
|
|
for (const event of data.events ?? []) {
|
|
const comp = event.competitions?.[0];
|
|
if (!comp) continue;
|
|
|
|
const competitors = comp.competitors ?? [];
|
|
const home = competitors.find((c) => c.homeAway === "home") ?? competitors[0];
|
|
const away = competitors.find((c) => c.homeAway === "away") ?? competitors[1];
|
|
if (!home || !away) continue;
|
|
|
|
const statusType = comp.status?.type;
|
|
const status = mapStatus(statusType?.name, statusType?.completed);
|
|
|
|
const homeScore = parseScore(home.score);
|
|
const awayScore = parseScore(away.score);
|
|
const winnerExternalId = home.winner ? home.team.id : away.winner ? away.team.id : null;
|
|
|
|
matches.push({
|
|
externalMatchId: event.id,
|
|
team1ExternalId: home.team.id,
|
|
team1Name: home.team.displayName,
|
|
team2ExternalId: away.team.id,
|
|
team2Name: away.team.displayName,
|
|
team1Score: homeScore,
|
|
team2Score: awayScore,
|
|
winnerExternalId,
|
|
status,
|
|
scheduledAt: new Date(event.date),
|
|
startedAt: status !== "scheduled" ? new Date(event.date) : null,
|
|
completedAt: status === "complete" ? new Date(event.date) : null,
|
|
matchStage: null,
|
|
matchRound: null,
|
|
matchday: comp.matchday ?? null,
|
|
isSeries: false,
|
|
});
|
|
}
|
|
|
|
return matches;
|
|
}
|
|
}
|