67 lines
2.4 KiB
TypeScript
67 lines
2.4 KiB
TypeScript
import type { FetchedStandingsRecord, StandingsSyncAdapter } from "./types";
|
||
|
||
const AFL_STANDINGS_URL = "https://api.squiggle.com.au/?q=standings;year=2026;format=json";
|
||
|
||
/**
|
||
* Squiggle API response shape for a single team's ladder entry.
|
||
* Docs: https://api.squiggle.com.au/
|
||
*/
|
||
interface SquiggleStandingsEntry {
|
||
id: number; // Squiggle team ID (1–18)
|
||
name: string; // Full team name, e.g. "Western Bulldogs"
|
||
wins: number;
|
||
losses: number;
|
||
draws: number;
|
||
played: number; // Games played
|
||
for: number; // Points scored for
|
||
against: number; // Points scored against
|
||
percentage: number;// (for / (for + against)) * 100
|
||
pts: number; // Ladder points (4=win, 2=draw, 0=loss)
|
||
rank: number; // Current ladder position (1-based)
|
||
}
|
||
|
||
interface SquiggleStandingsResponse {
|
||
standings: SquiggleStandingsEntry[];
|
||
}
|
||
|
||
export class AflStandingsAdapter implements StandingsSyncAdapter {
|
||
async fetchStandings(): Promise<FetchedStandingsRecord[]> {
|
||
const response = await fetch(AFL_STANDINGS_URL, {
|
||
headers: {
|
||
// Squiggle asks all clients to identify themselves for rate-limit contact.
|
||
// Set SQUIGGLE_CONTACT_EMAIL in your environment to identify this client.
|
||
"User-Agent": `Brackt.com AFL standings sync - ${process.env.SQUIGGLE_CONTACT_EMAIL ?? "admin@brackt.com"}`,
|
||
},
|
||
});
|
||
|
||
if (!response.ok) {
|
||
throw new Error(
|
||
`AFL Squiggle standings API returned ${response.status}: ${response.statusText}`
|
||
);
|
||
}
|
||
|
||
const json = (await response.json()) as SquiggleStandingsResponse;
|
||
const entries = json.standings;
|
||
|
||
if (!entries || entries.length === 0) {
|
||
throw new Error(
|
||
"AFL Squiggle standings API returned no entries — response shape may have changed"
|
||
);
|
||
}
|
||
|
||
// Squiggle already includes `rank` (ladder position), sorted by ladder position.
|
||
// Sort ascending by rank so leagueRank matches the AFL ladder order.
|
||
const sorted = entries.toSorted((a, b) => a.rank - b.rank);
|
||
|
||
return sorted.map((entry): FetchedStandingsRecord => ({
|
||
teamName: entry.name,
|
||
externalTeamId: String(entry.id),
|
||
wins: entry.wins,
|
||
losses: entry.losses,
|
||
ties: entry.draws, // AFL draws are stored in the `ties` column
|
||
winPct: entry.percentage / 100, // Squiggle returns e.g. 62.5; store as 0.625
|
||
gamesPlayed: entry.played,
|
||
leagueRank: entry.rank,
|
||
}));
|
||
}
|
||
}
|