Skip to main content
Glama
footballData.ts6.77 kB
import axios, { AxiosInstance } from "axios"; import { config } from "../config.js"; import { FixtureSummary, RecentMatchSummary, TeamSummary } from "../types.js"; import { MemoryCache } from "../utils/cache.js"; interface MatchesResponse { matches: FootballMatch[]; competition: { code: string; id: number; name: string; }; } interface FootballMatch { id: number; utcDate: string; status: string; matchday?: number; venue?: string | null; homeTeam: FootballTeam; awayTeam: FootballTeam; score: { fullTime: { home: number | null; away: number | null; }; }; } interface FootballTeam { id: number; name: string; shortName?: string; tla?: string; } interface StandingsResponse { standings: Array<{ table: Array<{ team: FootballTeam; position: number; points: number; form?: string; }>; }>; } interface TeamMatchesResponse { matches: FootballMatch[]; } export interface TeamStatistics { avgGoalsFor: number; avgGoalsAgainst: number; } export class FootballDataClient { private readonly http: AxiosInstance; private readonly cache = new MemoryCache<any>(config.cache.ttlSeconds); constructor() { this.http = axios.create({ baseURL: config.footballData.baseUrl, headers: { "X-Auth-Token": config.footballData.token, }, }); } public async getUpcomingFixtures(days: number): Promise<FixtureSummary[]> { const matches = await this.fetchSeasonMatches(); const now = Date.now(); const horizon = now + days * 24 * 60 * 60 * 1000; return matches .filter((match) => match.utcDate) .filter((match) => { const kickoff = Date.parse(match.utcDate); return kickoff >= now && kickoff <= horizon && match.status !== "FINISHED"; }) .sort((a, b) => Date.parse(a.utcDate) - Date.parse(b.utcDate)) .map(mapFixtureSummary); } public async getFixture(matchId: number): Promise<FixtureSummary | undefined> { const matches = await this.fetchSeasonMatches(); const match = matches.find((item) => item.id === matchId); return match ? mapFixtureSummary(match) : undefined; } public async getStandingsMap(): Promise<Map<number, { position: number; points: number; form?: string }>> { const cacheKey = this.getCacheKey("standings"); const cached = this.cache.get(cacheKey); if (cached) return cached; const res = await this.http.get<StandingsResponse>( `/competitions/${config.footballData.competition}/standings`, { params: { season: config.footballData.season, }, }, ); const tableMap = new Map<number, { position: number; points: number; form?: string }>(); const table = res.data.standings.find((standing) => standing.table.length)?.table ?? []; table.forEach((row) => { tableMap.set(row.team.id, { position: row.position, points: row.points, form: row.form, }); }); this.cache.set(cacheKey, tableMap); return tableMap; } public async getTeamStatistics(teamId: number): Promise<TeamStatistics | undefined> { const cacheKey = this.getCacheKey(`stats:${teamId}`); const cached = this.cache.get(cacheKey); if (cached) return cached; const matches = await this.fetchTeamMatches(teamId); let played = 0; let goalsFor = 0; let goalsAgainst = 0; matches.forEach((match) => { if (match.status !== "FINISHED") return; played += 1; const homeScore = match.score.fullTime.home ?? 0; const awayScore = match.score.fullTime.away ?? 0; if (match.homeTeam.id === teamId) { goalsFor += homeScore; goalsAgainst += awayScore; } else { goalsFor += awayScore; goalsAgainst += homeScore; } }); if (!played) return undefined; const stats = { avgGoalsFor: goalsFor / played, avgGoalsAgainst: goalsAgainst / played, } satisfies TeamStatistics; this.cache.set(cacheKey, stats); return stats; } public async getRecentMatches(teamId: number, limit = 5): Promise<RecentMatchSummary[]> { const matches = await this.fetchTeamMatches(teamId); return matches .filter((match) => match.status === "FINISHED") .sort((a, b) => Date.parse(b.utcDate) - Date.parse(a.utcDate)) .slice(0, limit) .map((match) => { const homeScore = match.score.fullTime.home ?? 0; const awayScore = match.score.fullTime.away ?? 0; const isHome = match.homeTeam.id === teamId; const result: RecentMatchSummary["result"] = homeScore === awayScore ? "DRAW" : isHome ? homeScore > awayScore ? "WIN" : "LOSS" : awayScore > homeScore ? "WIN" : "LOSS"; return { id: match.id, dateUtc: match.utcDate, home: match.homeTeam.name, away: match.awayTeam.name, score: `${homeScore}-${awayScore}`, result, } satisfies RecentMatchSummary; }); } private async fetchSeasonMatches(): Promise<FootballMatch[]> { const cacheKey = this.getCacheKey("season_matches"); const cached = this.cache.get(cacheKey); if (cached) return cached; const res = await this.http.get<MatchesResponse>( `/competitions/${config.footballData.competition}/matches`, { params: { season: config.footballData.season, }, }, ); this.cache.set(cacheKey, res.data.matches); return res.data.matches; } private async fetchTeamMatches(teamId: number): Promise<FootballMatch[]> { const cacheKey = this.getCacheKey(`team:${teamId}`); const cached = this.cache.get(cacheKey); if (cached) return cached; const res = await this.http.get<TeamMatchesResponse>( `/teams/${teamId}/matches`, { params: { season: config.footballData.season, competitions: config.footballData.competition, }, }, ); this.cache.set(cacheKey, res.data.matches); return res.data.matches; } private getCacheKey(suffix: string): string { return `${suffix}:${config.footballData.competition}:${config.footballData.season}`; } } const mapFixtureSummary = (match: FootballMatch): FixtureSummary => ({ matchId: match.id, leagueId: 0, season: Number(config.footballData.season), kickoffUtc: match.utcDate, venue: match.venue ?? null, homeTeam: mapTeam(match.homeTeam), awayTeam: mapTeam(match.awayTeam), }); const mapTeam = (team: FootballTeam): TeamSummary => ({ id: team.id, name: team.name, shortName: team.shortName ?? team.tla ?? undefined, });

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Valerio357/bet-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server