fitzroy 1.0.1 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +2283 -505
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -1,83 +1,1851 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { createRequire } from "node:module";
|
|
3
2
|
var __defProp = Object.defineProperty;
|
|
4
|
-
var
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
}
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __esm = (fn, res) => function __init() {
|
|
5
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
6
|
+
};
|
|
8
7
|
var __export = (target, all) => {
|
|
9
8
|
for (var name in all)
|
|
10
|
-
__defProp(target, name, {
|
|
11
|
-
get: all[name],
|
|
12
|
-
enumerable: true,
|
|
13
|
-
configurable: true,
|
|
14
|
-
set: __exportSetter.bind(all, name)
|
|
15
|
-
});
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
16
10
|
};
|
|
17
|
-
|
|
11
|
+
|
|
12
|
+
// src/lib/errors.ts
|
|
13
|
+
var AflApiError, ScrapeError, UnsupportedSourceError, ValidationError;
|
|
14
|
+
var init_errors = __esm({
|
|
15
|
+
"src/lib/errors.ts"() {
|
|
16
|
+
"use strict";
|
|
17
|
+
AflApiError = class extends Error {
|
|
18
|
+
constructor(message, statusCode) {
|
|
19
|
+
super(message);
|
|
20
|
+
this.statusCode = statusCode;
|
|
21
|
+
}
|
|
22
|
+
name = "AflApiError";
|
|
23
|
+
};
|
|
24
|
+
ScrapeError = class extends Error {
|
|
25
|
+
constructor(message, source) {
|
|
26
|
+
super(message);
|
|
27
|
+
this.source = source;
|
|
28
|
+
}
|
|
29
|
+
name = "ScrapeError";
|
|
30
|
+
};
|
|
31
|
+
UnsupportedSourceError = class extends Error {
|
|
32
|
+
constructor(message, source) {
|
|
33
|
+
super(message);
|
|
34
|
+
this.source = source;
|
|
35
|
+
}
|
|
36
|
+
name = "UnsupportedSourceError";
|
|
37
|
+
};
|
|
38
|
+
ValidationError = class extends Error {
|
|
39
|
+
constructor(message, issues) {
|
|
40
|
+
super(message);
|
|
41
|
+
this.issues = issues;
|
|
42
|
+
}
|
|
43
|
+
name = "ValidationError";
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
// src/lib/result.ts
|
|
49
|
+
function ok(data) {
|
|
50
|
+
return { success: true, data };
|
|
51
|
+
}
|
|
52
|
+
function err(error) {
|
|
53
|
+
return { success: false, error };
|
|
54
|
+
}
|
|
55
|
+
var init_result = __esm({
|
|
56
|
+
"src/lib/result.ts"() {
|
|
57
|
+
"use strict";
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
// src/lib/team-mapping.ts
|
|
62
|
+
function normaliseTeamName(raw) {
|
|
63
|
+
const trimmed = raw.trim();
|
|
64
|
+
return ALIAS_MAP.get(trimmed.toLowerCase()) ?? trimmed;
|
|
65
|
+
}
|
|
66
|
+
var TEAM_ALIASES, ALIAS_MAP;
|
|
67
|
+
var init_team_mapping = __esm({
|
|
68
|
+
"src/lib/team-mapping.ts"() {
|
|
69
|
+
"use strict";
|
|
70
|
+
TEAM_ALIASES = [
|
|
71
|
+
["Adelaide Crows", "Adelaide", "Crows", "ADEL", "AD"],
|
|
72
|
+
["Brisbane Lions", "Brisbane", "Brisbane Bears", "Bears", "Fitzroy Lions", "BL", "BRIS"],
|
|
73
|
+
["Carlton", "Carlton Blues", "Blues", "CARL", "CA"],
|
|
74
|
+
["Collingwood", "Collingwood Magpies", "Magpies", "COLL", "CW"],
|
|
75
|
+
["Essendon", "Essendon Bombers", "Bombers", "ESS", "ES"],
|
|
76
|
+
["Fremantle", "Fremantle Dockers", "Dockers", "FRE", "FR"],
|
|
77
|
+
["Geelong Cats", "Geelong", "Cats", "GEEL", "GE"],
|
|
78
|
+
[
|
|
79
|
+
"Gold Coast Suns",
|
|
80
|
+
"Gold Coast",
|
|
81
|
+
"Gold Coast SUNS",
|
|
82
|
+
"Gold Coast Football Club",
|
|
83
|
+
"Suns",
|
|
84
|
+
"GCFC",
|
|
85
|
+
"GC"
|
|
86
|
+
],
|
|
87
|
+
[
|
|
88
|
+
"GWS Giants",
|
|
89
|
+
"GWS",
|
|
90
|
+
"GWS GIANTS",
|
|
91
|
+
"Greater Western Sydney",
|
|
92
|
+
"Giants",
|
|
93
|
+
"Greater Western Sydney Giants",
|
|
94
|
+
"GW"
|
|
95
|
+
],
|
|
96
|
+
["Hawthorn", "Hawthorn Hawks", "Hawks", "HAW", "HW"],
|
|
97
|
+
["Melbourne", "Melbourne Demons", "Demons", "MELB", "ME"],
|
|
98
|
+
["North Melbourne", "North Melbourne Kangaroos", "Kangaroos", "Kangas", "North", "NMFC", "NM"],
|
|
99
|
+
["Port Adelaide", "Port Adelaide Power", "Power", "Port", "PA", "PAFC"],
|
|
100
|
+
["Richmond", "Richmond Tigers", "Tigers", "RICH", "RI"],
|
|
101
|
+
["St Kilda", "St Kilda Saints", "Saints", "Saint Kilda", "STK", "SK"],
|
|
102
|
+
["Sydney Swans", "Sydney", "Swans", "South Melbourne", "South Melbourne Swans", "SYD", "SY"],
|
|
103
|
+
["West Coast Eagles", "West Coast", "Eagles", "WCE", "WC"],
|
|
104
|
+
["Western Bulldogs", "Bulldogs", "Footscray", "Footscray Bulldogs", "WB", "WBD"],
|
|
105
|
+
// Historical / defunct VFL teams
|
|
106
|
+
["Fitzroy", "Fitzroy Reds", "Fitzroy Gorillas", "Fitzroy Maroons", "FI"],
|
|
107
|
+
["University", "University Blacks"]
|
|
108
|
+
];
|
|
109
|
+
ALIAS_MAP = (() => {
|
|
110
|
+
const map = /* @__PURE__ */ new Map();
|
|
111
|
+
for (const [canonical, ...aliases] of TEAM_ALIASES) {
|
|
112
|
+
map.set(canonical.toLowerCase(), canonical);
|
|
113
|
+
for (const alias of aliases) {
|
|
114
|
+
map.set(alias.toLowerCase(), canonical);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return map;
|
|
118
|
+
})();
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
// src/lib/validation.ts
|
|
123
|
+
import { z } from "zod/v4";
|
|
124
|
+
var AflApiTokenSchema, CompetitionSchema, CompetitionListSchema, CompseasonSchema, CompseasonListSchema, RoundSchema, RoundListSchema, ScoreSchema, PeriodScoreSchema, TeamScoreSchema, CfsMatchTeamSchema, CfsMatchSchema, CfsScoreSchema, CfsVenueSchema, MatchItemSchema, MatchItemListSchema, CfsPlayerInnerSchema, PlayerGameStatsSchema, PlayerStatsItemSchema, PlayerStatsListSchema, RosterPlayerSchema, TeamPlayersSchema, MatchRosterSchema, TeamItemSchema, TeamListSchema, SquadPlayerInnerSchema, SquadPlayerItemSchema, SquadSchema, SquadListSchema, WinLossRecordSchema, LadderEntryRawSchema, LadderResponseSchema;
|
|
125
|
+
var init_validation = __esm({
|
|
126
|
+
"src/lib/validation.ts"() {
|
|
127
|
+
"use strict";
|
|
128
|
+
AflApiTokenSchema = z.object({
|
|
129
|
+
token: z.string(),
|
|
130
|
+
disclaimer: z.string().optional()
|
|
131
|
+
}).passthrough();
|
|
132
|
+
CompetitionSchema = z.object({
|
|
133
|
+
id: z.number(),
|
|
134
|
+
name: z.string(),
|
|
135
|
+
code: z.string().optional()
|
|
136
|
+
}).passthrough();
|
|
137
|
+
CompetitionListSchema = z.object({
|
|
138
|
+
competitions: z.array(CompetitionSchema)
|
|
139
|
+
}).passthrough();
|
|
140
|
+
CompseasonSchema = z.object({
|
|
141
|
+
id: z.number(),
|
|
142
|
+
name: z.string(),
|
|
143
|
+
shortName: z.string().optional(),
|
|
144
|
+
currentRoundNumber: z.number().optional()
|
|
145
|
+
}).passthrough();
|
|
146
|
+
CompseasonListSchema = z.object({
|
|
147
|
+
compSeasons: z.array(CompseasonSchema)
|
|
148
|
+
}).passthrough();
|
|
149
|
+
RoundSchema = z.object({
|
|
150
|
+
id: z.number(),
|
|
151
|
+
/** Provider ID used by /cfs/ endpoints (e.g. "CD_R202501401"). */
|
|
152
|
+
providerId: z.string().optional(),
|
|
153
|
+
name: z.string(),
|
|
154
|
+
abbreviation: z.string().optional(),
|
|
155
|
+
roundNumber: z.number(),
|
|
156
|
+
utcStartTime: z.string().optional(),
|
|
157
|
+
utcEndTime: z.string().optional()
|
|
158
|
+
}).passthrough();
|
|
159
|
+
RoundListSchema = z.object({
|
|
160
|
+
rounds: z.array(RoundSchema)
|
|
161
|
+
}).passthrough();
|
|
162
|
+
ScoreSchema = z.object({
|
|
163
|
+
totalScore: z.number(),
|
|
164
|
+
goals: z.number(),
|
|
165
|
+
behinds: z.number(),
|
|
166
|
+
superGoals: z.number().nullable().optional()
|
|
167
|
+
}).passthrough();
|
|
168
|
+
PeriodScoreSchema = z.object({
|
|
169
|
+
periodNumber: z.number(),
|
|
170
|
+
score: ScoreSchema
|
|
171
|
+
}).passthrough();
|
|
172
|
+
TeamScoreSchema = z.object({
|
|
173
|
+
matchScore: ScoreSchema,
|
|
174
|
+
periodScore: z.array(PeriodScoreSchema).optional(),
|
|
175
|
+
rushedBehinds: z.number().optional(),
|
|
176
|
+
minutesInFront: z.number().optional()
|
|
177
|
+
}).passthrough();
|
|
178
|
+
CfsMatchTeamSchema = z.object({
|
|
179
|
+
name: z.string(),
|
|
180
|
+
teamId: z.string(),
|
|
181
|
+
abbr: z.string().optional(),
|
|
182
|
+
nickname: z.string().optional()
|
|
183
|
+
}).passthrough();
|
|
184
|
+
CfsMatchSchema = z.object({
|
|
185
|
+
matchId: z.string(),
|
|
186
|
+
name: z.string().optional(),
|
|
187
|
+
status: z.string(),
|
|
188
|
+
utcStartTime: z.string(),
|
|
189
|
+
homeTeamId: z.string(),
|
|
190
|
+
awayTeamId: z.string(),
|
|
191
|
+
homeTeam: CfsMatchTeamSchema,
|
|
192
|
+
awayTeam: CfsMatchTeamSchema,
|
|
193
|
+
round: z.string().optional(),
|
|
194
|
+
abbr: z.string().optional()
|
|
195
|
+
}).passthrough();
|
|
196
|
+
CfsScoreSchema = z.object({
|
|
197
|
+
status: z.string(),
|
|
198
|
+
matchId: z.string(),
|
|
199
|
+
homeTeamScore: TeamScoreSchema,
|
|
200
|
+
awayTeamScore: TeamScoreSchema
|
|
201
|
+
}).passthrough();
|
|
202
|
+
CfsVenueSchema = z.object({
|
|
203
|
+
name: z.string(),
|
|
204
|
+
venueId: z.string().optional(),
|
|
205
|
+
state: z.string().optional(),
|
|
206
|
+
timeZone: z.string().optional()
|
|
207
|
+
}).passthrough();
|
|
208
|
+
MatchItemSchema = z.object({
|
|
209
|
+
match: CfsMatchSchema,
|
|
210
|
+
score: CfsScoreSchema.optional(),
|
|
211
|
+
venue: CfsVenueSchema.optional(),
|
|
212
|
+
round: z.object({
|
|
213
|
+
name: z.string(),
|
|
214
|
+
roundId: z.string(),
|
|
215
|
+
roundNumber: z.number()
|
|
216
|
+
}).passthrough().optional()
|
|
217
|
+
}).passthrough();
|
|
218
|
+
MatchItemListSchema = z.object({
|
|
219
|
+
roundId: z.string().optional(),
|
|
220
|
+
items: z.array(MatchItemSchema)
|
|
221
|
+
}).passthrough();
|
|
222
|
+
CfsPlayerInnerSchema = z.object({
|
|
223
|
+
playerId: z.string(),
|
|
224
|
+
playerName: z.object({
|
|
225
|
+
givenName: z.string(),
|
|
226
|
+
surname: z.string()
|
|
227
|
+
}).passthrough(),
|
|
228
|
+
captain: z.boolean().optional(),
|
|
229
|
+
playerJumperNumber: z.number().optional()
|
|
230
|
+
}).passthrough();
|
|
231
|
+
PlayerGameStatsSchema = z.object({
|
|
232
|
+
goals: z.number().optional(),
|
|
233
|
+
behinds: z.number().optional(),
|
|
234
|
+
kicks: z.number().optional(),
|
|
235
|
+
handballs: z.number().optional(),
|
|
236
|
+
disposals: z.number().optional(),
|
|
237
|
+
marks: z.number().optional(),
|
|
238
|
+
bounces: z.number().optional(),
|
|
239
|
+
tackles: z.number().optional(),
|
|
240
|
+
contestedPossessions: z.number().optional(),
|
|
241
|
+
uncontestedPossessions: z.number().optional(),
|
|
242
|
+
totalPossessions: z.number().optional(),
|
|
243
|
+
inside50s: z.number().optional(),
|
|
244
|
+
marksInside50: z.number().optional(),
|
|
245
|
+
contestedMarks: z.number().optional(),
|
|
246
|
+
hitouts: z.number().optional(),
|
|
247
|
+
onePercenters: z.number().optional(),
|
|
248
|
+
disposalEfficiency: z.number().optional(),
|
|
249
|
+
clangers: z.number().optional(),
|
|
250
|
+
freesFor: z.number().optional(),
|
|
251
|
+
freesAgainst: z.number().optional(),
|
|
252
|
+
dreamTeamPoints: z.number().optional(),
|
|
253
|
+
clearances: z.object({
|
|
254
|
+
centreClearances: z.number().optional(),
|
|
255
|
+
stoppageClearances: z.number().optional(),
|
|
256
|
+
totalClearances: z.number().optional()
|
|
257
|
+
}).passthrough().optional(),
|
|
258
|
+
rebound50s: z.number().optional(),
|
|
259
|
+
goalAssists: z.number().optional(),
|
|
260
|
+
goalAccuracy: z.number().optional(),
|
|
261
|
+
turnovers: z.number().optional(),
|
|
262
|
+
intercepts: z.number().optional(),
|
|
263
|
+
tacklesInside50: z.number().optional(),
|
|
264
|
+
shotsAtGoal: z.number().optional(),
|
|
265
|
+
metresGained: z.number().optional(),
|
|
266
|
+
scoreInvolvements: z.number().optional(),
|
|
267
|
+
ratingPoints: z.number().optional(),
|
|
268
|
+
extendedStats: z.object({
|
|
269
|
+
effectiveDisposals: z.number().optional(),
|
|
270
|
+
effectiveKicks: z.number().optional(),
|
|
271
|
+
kickEfficiency: z.number().optional(),
|
|
272
|
+
kickToHandballRatio: z.number().optional(),
|
|
273
|
+
pressureActs: z.number().optional(),
|
|
274
|
+
defHalfPressureActs: z.number().optional(),
|
|
275
|
+
spoils: z.number().optional(),
|
|
276
|
+
hitoutsToAdvantage: z.number().optional(),
|
|
277
|
+
hitoutWinPercentage: z.number().optional(),
|
|
278
|
+
hitoutToAdvantageRate: z.number().optional(),
|
|
279
|
+
groundBallGets: z.number().optional(),
|
|
280
|
+
f50GroundBallGets: z.number().optional(),
|
|
281
|
+
interceptMarks: z.number().optional(),
|
|
282
|
+
marksOnLead: z.number().optional(),
|
|
283
|
+
contestedPossessionRate: z.number().optional(),
|
|
284
|
+
contestOffOneOnOnes: z.number().optional(),
|
|
285
|
+
contestOffWins: z.number().optional(),
|
|
286
|
+
contestOffWinsPercentage: z.number().optional(),
|
|
287
|
+
contestDefOneOnOnes: z.number().optional(),
|
|
288
|
+
contestDefLosses: z.number().optional(),
|
|
289
|
+
contestDefLossPercentage: z.number().optional(),
|
|
290
|
+
centreBounceAttendances: z.number().optional(),
|
|
291
|
+
kickins: z.number().optional(),
|
|
292
|
+
kickinsPlayon: z.number().optional(),
|
|
293
|
+
ruckContests: z.number().optional(),
|
|
294
|
+
scoreLaunches: z.number().optional()
|
|
295
|
+
}).passthrough().optional()
|
|
296
|
+
}).passthrough();
|
|
297
|
+
PlayerStatsItemSchema = z.object({
|
|
298
|
+
player: z.object({
|
|
299
|
+
player: z.object({
|
|
300
|
+
position: z.string().optional(),
|
|
301
|
+
player: CfsPlayerInnerSchema
|
|
302
|
+
}).passthrough(),
|
|
303
|
+
jumperNumber: z.number().optional()
|
|
304
|
+
}).passthrough(),
|
|
305
|
+
teamId: z.string(),
|
|
306
|
+
playerStats: z.object({
|
|
307
|
+
stats: PlayerGameStatsSchema,
|
|
308
|
+
timeOnGroundPercentage: z.number().optional()
|
|
309
|
+
}).passthrough()
|
|
310
|
+
}).passthrough();
|
|
311
|
+
PlayerStatsListSchema = z.object({
|
|
312
|
+
homeTeamPlayerStats: z.array(PlayerStatsItemSchema),
|
|
313
|
+
awayTeamPlayerStats: z.array(PlayerStatsItemSchema)
|
|
314
|
+
}).passthrough();
|
|
315
|
+
RosterPlayerSchema = z.object({
|
|
316
|
+
player: z.object({
|
|
317
|
+
position: z.string().optional(),
|
|
318
|
+
player: CfsPlayerInnerSchema
|
|
319
|
+
}).passthrough(),
|
|
320
|
+
jumperNumber: z.number().optional()
|
|
321
|
+
}).passthrough();
|
|
322
|
+
TeamPlayersSchema = z.object({
|
|
323
|
+
teamId: z.string(),
|
|
324
|
+
players: z.array(RosterPlayerSchema)
|
|
325
|
+
}).passthrough();
|
|
326
|
+
MatchRosterSchema = z.object({
|
|
327
|
+
match: CfsMatchSchema,
|
|
328
|
+
teamPlayers: z.array(TeamPlayersSchema)
|
|
329
|
+
}).passthrough();
|
|
330
|
+
TeamItemSchema = z.object({
|
|
331
|
+
id: z.number(),
|
|
332
|
+
name: z.string(),
|
|
333
|
+
abbreviation: z.string().optional(),
|
|
334
|
+
teamType: z.string().optional()
|
|
335
|
+
}).passthrough();
|
|
336
|
+
TeamListSchema = z.object({
|
|
337
|
+
teams: z.array(TeamItemSchema)
|
|
338
|
+
}).passthrough();
|
|
339
|
+
SquadPlayerInnerSchema = z.object({
|
|
340
|
+
id: z.number(),
|
|
341
|
+
providerId: z.string().optional(),
|
|
342
|
+
firstName: z.string(),
|
|
343
|
+
surname: z.string(),
|
|
344
|
+
dateOfBirth: z.string().optional(),
|
|
345
|
+
heightInCm: z.number().optional(),
|
|
346
|
+
weightInKg: z.number().optional(),
|
|
347
|
+
draftYear: z.string().optional(),
|
|
348
|
+
draftPosition: z.string().optional(),
|
|
349
|
+
draftType: z.string().optional(),
|
|
350
|
+
debutYear: z.string().optional(),
|
|
351
|
+
recruitedFrom: z.string().optional()
|
|
352
|
+
}).passthrough();
|
|
353
|
+
SquadPlayerItemSchema = z.object({
|
|
354
|
+
player: SquadPlayerInnerSchema,
|
|
355
|
+
jumperNumber: z.number().optional(),
|
|
356
|
+
position: z.string().optional()
|
|
357
|
+
}).passthrough();
|
|
358
|
+
SquadSchema = z.object({
|
|
359
|
+
team: z.object({
|
|
360
|
+
name: z.string()
|
|
361
|
+
}).passthrough().optional(),
|
|
362
|
+
players: z.array(SquadPlayerItemSchema)
|
|
363
|
+
}).passthrough();
|
|
364
|
+
SquadListSchema = z.object({
|
|
365
|
+
squad: SquadSchema
|
|
366
|
+
}).passthrough();
|
|
367
|
+
WinLossRecordSchema = z.object({
|
|
368
|
+
wins: z.number(),
|
|
369
|
+
losses: z.number(),
|
|
370
|
+
draws: z.number(),
|
|
371
|
+
played: z.number().optional()
|
|
372
|
+
}).passthrough();
|
|
373
|
+
LadderEntryRawSchema = z.object({
|
|
374
|
+
position: z.number(),
|
|
375
|
+
team: z.object({
|
|
376
|
+
name: z.string(),
|
|
377
|
+
id: z.number().optional(),
|
|
378
|
+
abbreviation: z.string().optional()
|
|
379
|
+
}).passthrough(),
|
|
380
|
+
played: z.number().optional(),
|
|
381
|
+
pointsFor: z.number().optional(),
|
|
382
|
+
pointsAgainst: z.number().optional(),
|
|
383
|
+
thisSeasonRecord: z.object({
|
|
384
|
+
aggregatePoints: z.number().optional(),
|
|
385
|
+
percentage: z.number().optional(),
|
|
386
|
+
winLossRecord: WinLossRecordSchema.optional()
|
|
387
|
+
}).passthrough().optional(),
|
|
388
|
+
form: z.string().optional()
|
|
389
|
+
}).passthrough();
|
|
390
|
+
LadderResponseSchema = z.object({
|
|
391
|
+
ladders: z.array(
|
|
392
|
+
z.object({
|
|
393
|
+
entries: z.array(LadderEntryRawSchema)
|
|
394
|
+
}).passthrough()
|
|
395
|
+
),
|
|
396
|
+
round: z.object({
|
|
397
|
+
roundNumber: z.number(),
|
|
398
|
+
name: z.string().optional()
|
|
399
|
+
}).passthrough().optional()
|
|
400
|
+
}).passthrough();
|
|
401
|
+
}
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
// src/sources/afl-api.ts
|
|
405
|
+
var TOKEN_URL, API_BASE, CFS_BASE, AflApiClient;
|
|
406
|
+
var init_afl_api = __esm({
|
|
407
|
+
"src/sources/afl-api.ts"() {
|
|
408
|
+
"use strict";
|
|
409
|
+
init_errors();
|
|
410
|
+
init_result();
|
|
411
|
+
init_validation();
|
|
412
|
+
TOKEN_URL = "https://api.afl.com.au/cfs/afl/WMCTok";
|
|
413
|
+
API_BASE = "https://aflapi.afl.com.au/afl/v2";
|
|
414
|
+
CFS_BASE = "https://api.afl.com.au/cfs/afl";
|
|
415
|
+
AflApiClient = class {
|
|
416
|
+
fetchFn;
|
|
417
|
+
tokenUrl;
|
|
418
|
+
cachedToken = null;
|
|
419
|
+
constructor(options) {
|
|
420
|
+
this.fetchFn = options?.fetchFn ?? globalThis.fetch;
|
|
421
|
+
this.tokenUrl = options?.tokenUrl ?? TOKEN_URL;
|
|
422
|
+
}
|
|
423
|
+
/**
|
|
424
|
+
* Authenticate with the WMCTok token endpoint and cache the token.
|
|
425
|
+
*
|
|
426
|
+
* @returns The access token on success, or an error Result.
|
|
427
|
+
*/
|
|
428
|
+
async authenticate() {
|
|
429
|
+
try {
|
|
430
|
+
const response = await this.fetchFn(this.tokenUrl, {
|
|
431
|
+
method: "POST",
|
|
432
|
+
headers: { "Content-Length": "0" }
|
|
433
|
+
});
|
|
434
|
+
if (!response.ok) {
|
|
435
|
+
return err(new AflApiError(`Token request failed: ${response.status}`, response.status));
|
|
436
|
+
}
|
|
437
|
+
const json = await response.json();
|
|
438
|
+
const parsed = AflApiTokenSchema.safeParse(json);
|
|
439
|
+
if (!parsed.success) {
|
|
440
|
+
return err(new AflApiError("Invalid token response format"));
|
|
441
|
+
}
|
|
442
|
+
const ttlMs = 30 * 60 * 1e3;
|
|
443
|
+
this.cachedToken = {
|
|
444
|
+
accessToken: parsed.data.token,
|
|
445
|
+
expiresAt: Date.now() + ttlMs
|
|
446
|
+
};
|
|
447
|
+
return ok(parsed.data.token);
|
|
448
|
+
} catch (cause) {
|
|
449
|
+
return err(
|
|
450
|
+
new AflApiError(
|
|
451
|
+
`Token request failed: ${cause instanceof Error ? cause.message : String(cause)}`
|
|
452
|
+
)
|
|
453
|
+
);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
/**
|
|
457
|
+
* Whether the cached token is still valid (not expired).
|
|
458
|
+
*/
|
|
459
|
+
get isAuthenticated() {
|
|
460
|
+
return this.cachedToken !== null && Date.now() < this.cachedToken.expiresAt;
|
|
461
|
+
}
|
|
462
|
+
/**
|
|
463
|
+
* Perform an authenticated fetch, automatically adding the bearer token.
|
|
464
|
+
* Retries once on 401 by re-authenticating.
|
|
465
|
+
*
|
|
466
|
+
* @param url - The URL to fetch.
|
|
467
|
+
* @param init - Additional fetch options.
|
|
468
|
+
* @returns The Response on success, or an error Result.
|
|
469
|
+
*/
|
|
470
|
+
async authedFetch(url, init) {
|
|
471
|
+
if (!this.isAuthenticated) {
|
|
472
|
+
const authResult = await this.authenticate();
|
|
473
|
+
if (!authResult.success) {
|
|
474
|
+
return authResult;
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
const doFetch = async () => {
|
|
478
|
+
const token = this.cachedToken;
|
|
479
|
+
if (!token) {
|
|
480
|
+
throw new AflApiError("No cached token available");
|
|
481
|
+
}
|
|
482
|
+
const headers = new Headers(init?.headers);
|
|
483
|
+
headers.set("x-media-mis-token", token.accessToken);
|
|
484
|
+
return this.fetchFn(url, { ...init, headers });
|
|
485
|
+
};
|
|
486
|
+
try {
|
|
487
|
+
let response = await doFetch();
|
|
488
|
+
if (response.status === 401) {
|
|
489
|
+
const authResult = await this.authenticate();
|
|
490
|
+
if (!authResult.success) {
|
|
491
|
+
return authResult;
|
|
492
|
+
}
|
|
493
|
+
response = await doFetch();
|
|
494
|
+
}
|
|
495
|
+
if (!response.ok) {
|
|
496
|
+
return err(
|
|
497
|
+
new AflApiError(
|
|
498
|
+
`Request failed: ${response.status} ${response.statusText}`,
|
|
499
|
+
response.status
|
|
500
|
+
)
|
|
501
|
+
);
|
|
502
|
+
}
|
|
503
|
+
return ok(response);
|
|
504
|
+
} catch (cause) {
|
|
505
|
+
return err(
|
|
506
|
+
new AflApiError(
|
|
507
|
+
`Request failed: ${cause instanceof Error ? cause.message : String(cause)}`
|
|
508
|
+
)
|
|
509
|
+
);
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
/**
|
|
513
|
+
* Fetch JSON from a URL, validate with a Zod schema, and return a typed Result.
|
|
514
|
+
*
|
|
515
|
+
* @param url - The URL to fetch.
|
|
516
|
+
* @param schema - Zod schema to validate the response against.
|
|
517
|
+
* @returns Validated data on success, or an error Result.
|
|
518
|
+
*/
|
|
519
|
+
async fetchJson(url, schema) {
|
|
520
|
+
const isPublic = url.startsWith(API_BASE);
|
|
521
|
+
let response;
|
|
522
|
+
if (isPublic) {
|
|
523
|
+
try {
|
|
524
|
+
response = await this.fetchFn(url);
|
|
525
|
+
if (!response.ok) {
|
|
526
|
+
return err(
|
|
527
|
+
new AflApiError(
|
|
528
|
+
`Request failed: ${response.status} ${response.statusText}`,
|
|
529
|
+
response.status
|
|
530
|
+
)
|
|
531
|
+
);
|
|
532
|
+
}
|
|
533
|
+
} catch (cause) {
|
|
534
|
+
return err(
|
|
535
|
+
new AflApiError(
|
|
536
|
+
`Request failed: ${cause instanceof Error ? cause.message : String(cause)}`
|
|
537
|
+
)
|
|
538
|
+
);
|
|
539
|
+
}
|
|
540
|
+
} else {
|
|
541
|
+
const fetchResult = await this.authedFetch(url);
|
|
542
|
+
if (!fetchResult.success) {
|
|
543
|
+
return fetchResult;
|
|
544
|
+
}
|
|
545
|
+
response = fetchResult.data;
|
|
546
|
+
}
|
|
547
|
+
try {
|
|
548
|
+
const json = await response.json();
|
|
549
|
+
const parsed = schema.safeParse(json);
|
|
550
|
+
if (!parsed.success) {
|
|
551
|
+
return err(
|
|
552
|
+
new ValidationError("Response validation failed", [
|
|
553
|
+
{ path: url, message: String(parsed.error) }
|
|
554
|
+
])
|
|
555
|
+
);
|
|
556
|
+
}
|
|
557
|
+
return ok(parsed.data);
|
|
558
|
+
} catch (cause) {
|
|
559
|
+
return err(
|
|
560
|
+
new AflApiError(
|
|
561
|
+
`JSON parse failed: ${cause instanceof Error ? cause.message : String(cause)}`
|
|
562
|
+
)
|
|
563
|
+
);
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
/**
|
|
567
|
+
* Resolve a competition code (e.g. "AFLM") to its API competition ID.
|
|
568
|
+
*
|
|
569
|
+
* @param code - The competition code to resolve.
|
|
570
|
+
* @returns The competition ID string on success.
|
|
571
|
+
*/
|
|
572
|
+
async resolveCompetitionId(code) {
|
|
573
|
+
const result = await this.fetchJson(
|
|
574
|
+
`${API_BASE}/competitions?pageSize=50`,
|
|
575
|
+
CompetitionListSchema
|
|
576
|
+
);
|
|
577
|
+
if (!result.success) {
|
|
578
|
+
return result;
|
|
579
|
+
}
|
|
580
|
+
const apiCode = code === "AFLM" ? "AFL" : code;
|
|
581
|
+
const competition = result.data.competitions.find((c) => c.code === apiCode);
|
|
582
|
+
if (!competition) {
|
|
583
|
+
return err(new AflApiError(`Competition not found for code: ${code}`));
|
|
584
|
+
}
|
|
585
|
+
return ok(competition.id);
|
|
586
|
+
}
|
|
587
|
+
/**
|
|
588
|
+
* Resolve a season (compseason) ID from a competition ID and year.
|
|
589
|
+
*
|
|
590
|
+
* @param competitionId - The competition ID (from {@link resolveCompetitionId}).
|
|
591
|
+
* @param year - The season year (e.g. 2024).
|
|
592
|
+
* @returns The compseason ID string on success.
|
|
593
|
+
*/
|
|
594
|
+
async resolveSeasonId(competitionId, year) {
|
|
595
|
+
const result = await this.fetchJson(
|
|
596
|
+
`${API_BASE}/competitions/${competitionId}/compseasons?pageSize=100`,
|
|
597
|
+
CompseasonListSchema
|
|
598
|
+
);
|
|
599
|
+
if (!result.success) {
|
|
600
|
+
return result;
|
|
601
|
+
}
|
|
602
|
+
const yearStr = String(year);
|
|
603
|
+
const season = result.data.compSeasons.find((cs) => cs.name.includes(yearStr));
|
|
604
|
+
if (!season) {
|
|
605
|
+
return err(new AflApiError(`Season not found for year: ${year}`));
|
|
606
|
+
}
|
|
607
|
+
return ok(season.id);
|
|
608
|
+
}
|
|
609
|
+
/**
|
|
610
|
+
* Resolve a season ID from a competition code and year in one step.
|
|
611
|
+
*
|
|
612
|
+
* @param code - The competition code (e.g. "AFLM").
|
|
613
|
+
* @param year - The season year (e.g. 2025).
|
|
614
|
+
* @returns The compseason ID on success.
|
|
615
|
+
*/
|
|
616
|
+
async resolveCompSeason(code, year) {
|
|
617
|
+
const compResult = await this.resolveCompetitionId(code);
|
|
618
|
+
if (!compResult.success) return compResult;
|
|
619
|
+
return this.resolveSeasonId(compResult.data, year);
|
|
620
|
+
}
|
|
621
|
+
/**
|
|
622
|
+
* Fetch all rounds for a season with their metadata.
|
|
623
|
+
*
|
|
624
|
+
* @param seasonId - The compseason ID (from {@link resolveSeasonId}).
|
|
625
|
+
* @returns Array of round objects on success.
|
|
626
|
+
*/
|
|
627
|
+
async resolveRounds(seasonId) {
|
|
628
|
+
const result = await this.fetchJson(
|
|
629
|
+
`${API_BASE}/compseasons/${seasonId}/rounds?pageSize=50`,
|
|
630
|
+
RoundListSchema
|
|
631
|
+
);
|
|
632
|
+
if (!result.success) {
|
|
633
|
+
return result;
|
|
634
|
+
}
|
|
635
|
+
return ok(result.data.rounds);
|
|
636
|
+
}
|
|
637
|
+
/**
|
|
638
|
+
* Fetch match items for a round using the /cfs/ endpoint.
|
|
639
|
+
*
|
|
640
|
+
* @param roundProviderId - The round provider ID (e.g. "CD_R202501401").
|
|
641
|
+
* @returns Array of match items on success.
|
|
642
|
+
*/
|
|
643
|
+
async fetchRoundMatchItems(roundProviderId) {
|
|
644
|
+
const result = await this.fetchJson(
|
|
645
|
+
`${CFS_BASE}/matchItems/round/${roundProviderId}`,
|
|
646
|
+
MatchItemListSchema
|
|
647
|
+
);
|
|
648
|
+
if (!result.success) {
|
|
649
|
+
return result;
|
|
650
|
+
}
|
|
651
|
+
return ok(result.data.items);
|
|
652
|
+
}
|
|
653
|
+
/**
|
|
654
|
+
* Fetch match items for a round by resolving the round provider ID from season and round number.
|
|
655
|
+
*
|
|
656
|
+
* @param seasonId - The compseason ID.
|
|
657
|
+
* @param roundNumber - The round number.
|
|
658
|
+
* @returns Array of match items on success.
|
|
659
|
+
*/
|
|
660
|
+
async fetchRoundMatchItemsByNumber(seasonId, roundNumber) {
|
|
661
|
+
const roundsResult = await this.resolveRounds(seasonId);
|
|
662
|
+
if (!roundsResult.success) {
|
|
663
|
+
return roundsResult;
|
|
664
|
+
}
|
|
665
|
+
const round = roundsResult.data.find((r) => r.roundNumber === roundNumber);
|
|
666
|
+
if (!round?.providerId) {
|
|
667
|
+
return err(new AflApiError(`Round not found or missing providerId: round ${roundNumber}`));
|
|
668
|
+
}
|
|
669
|
+
return this.fetchRoundMatchItems(round.providerId);
|
|
670
|
+
}
|
|
671
|
+
/**
|
|
672
|
+
* Fetch match items for all completed rounds in a season.
|
|
673
|
+
*
|
|
674
|
+
* @param seasonId - The compseason ID.
|
|
675
|
+
* @returns Aggregated array of match items from all completed rounds.
|
|
676
|
+
*/
|
|
677
|
+
async fetchSeasonMatchItems(seasonId) {
|
|
678
|
+
const roundsResult = await this.resolveRounds(seasonId);
|
|
679
|
+
if (!roundsResult.success) {
|
|
680
|
+
return roundsResult;
|
|
681
|
+
}
|
|
682
|
+
const providerIds = roundsResult.data.flatMap((r) => r.providerId ? [r.providerId] : []);
|
|
683
|
+
const results = await Promise.all(providerIds.map((id) => this.fetchRoundMatchItems(id)));
|
|
684
|
+
const allItems = [];
|
|
685
|
+
for (const result of results) {
|
|
686
|
+
if (!result.success) {
|
|
687
|
+
return result;
|
|
688
|
+
}
|
|
689
|
+
const concluded = result.data.filter(
|
|
690
|
+
(item) => item.match.status === "CONCLUDED" || item.match.status === "COMPLETE"
|
|
691
|
+
);
|
|
692
|
+
allItems.push(...concluded);
|
|
693
|
+
}
|
|
694
|
+
return ok(allItems);
|
|
695
|
+
}
|
|
696
|
+
/**
|
|
697
|
+
* Fetch per-player statistics for a match.
|
|
698
|
+
*
|
|
699
|
+
* @param matchProviderId - The match provider ID (e.g. "CD_M20250140101").
|
|
700
|
+
* @returns Player stats list with home and away arrays.
|
|
701
|
+
*/
|
|
702
|
+
async fetchPlayerStats(matchProviderId) {
|
|
703
|
+
return this.fetchJson(
|
|
704
|
+
`${CFS_BASE}/playerStats/match/${matchProviderId}`,
|
|
705
|
+
PlayerStatsListSchema
|
|
706
|
+
);
|
|
707
|
+
}
|
|
708
|
+
/**
|
|
709
|
+
* Fetch match roster (lineup) for a match.
|
|
710
|
+
*
|
|
711
|
+
* @param matchProviderId - The match provider ID (e.g. "CD_M20250140101").
|
|
712
|
+
* @returns Match roster with team players.
|
|
713
|
+
*/
|
|
714
|
+
async fetchMatchRoster(matchProviderId) {
|
|
715
|
+
return this.fetchJson(`${CFS_BASE}/matchRoster/full/${matchProviderId}`, MatchRosterSchema);
|
|
716
|
+
}
|
|
717
|
+
/**
|
|
718
|
+
* Fetch team list, optionally filtered by team type.
|
|
719
|
+
*
|
|
720
|
+
* @param teamType - Optional filter (e.g. "MEN", "WOMEN").
|
|
721
|
+
* @returns Array of team items.
|
|
722
|
+
*/
|
|
723
|
+
async fetchTeams(teamType) {
|
|
724
|
+
const result = await this.fetchJson(`${API_BASE}/teams?pageSize=100`, TeamListSchema);
|
|
725
|
+
if (!result.success) {
|
|
726
|
+
return result;
|
|
727
|
+
}
|
|
728
|
+
if (teamType) {
|
|
729
|
+
return ok(result.data.teams.filter((t) => t.teamType === teamType));
|
|
730
|
+
}
|
|
731
|
+
return ok(result.data.teams);
|
|
732
|
+
}
|
|
733
|
+
/**
|
|
734
|
+
* Fetch squad (roster) for a team in a specific season.
|
|
735
|
+
*
|
|
736
|
+
* @param teamId - The numeric team ID.
|
|
737
|
+
* @param compSeasonId - The compseason ID.
|
|
738
|
+
* @returns Squad list response.
|
|
739
|
+
*/
|
|
740
|
+
async fetchSquad(teamId, compSeasonId) {
|
|
741
|
+
return this.fetchJson(
|
|
742
|
+
`${API_BASE}/squads?teamId=${teamId}&compSeasonId=${compSeasonId}`,
|
|
743
|
+
SquadListSchema
|
|
744
|
+
);
|
|
745
|
+
}
|
|
746
|
+
/**
|
|
747
|
+
* Fetch ladder standings for a season (optionally for a specific round).
|
|
748
|
+
*
|
|
749
|
+
* @param seasonId - The compseason ID.
|
|
750
|
+
* @param roundId - Optional round ID (numeric `id`, not `providerId`).
|
|
751
|
+
* @returns Ladder response with entries.
|
|
752
|
+
*/
|
|
753
|
+
async fetchLadder(seasonId, roundId) {
|
|
754
|
+
let url = `${API_BASE}/compseasons/${seasonId}/ladders`;
|
|
755
|
+
if (roundId != null) {
|
|
756
|
+
url += `?roundId=${roundId}`;
|
|
757
|
+
}
|
|
758
|
+
return this.fetchJson(url, LadderResponseSchema);
|
|
759
|
+
}
|
|
760
|
+
};
|
|
761
|
+
}
|
|
762
|
+
});
|
|
763
|
+
|
|
764
|
+
// src/transforms/match-results.ts
|
|
765
|
+
function inferRoundType(roundName) {
|
|
766
|
+
return FINALS_PATTERN.test(roundName) ? "Finals" : "HomeAndAway";
|
|
767
|
+
}
|
|
768
|
+
function toMatchStatus(raw) {
|
|
769
|
+
switch (raw) {
|
|
770
|
+
case "CONCLUDED":
|
|
771
|
+
case "COMPLETE":
|
|
772
|
+
return "Complete";
|
|
773
|
+
case "LIVE":
|
|
774
|
+
case "IN_PROGRESS":
|
|
775
|
+
return "Live";
|
|
776
|
+
case "UPCOMING":
|
|
777
|
+
case "SCHEDULED":
|
|
778
|
+
return "Upcoming";
|
|
779
|
+
case "POSTPONED":
|
|
780
|
+
return "Postponed";
|
|
781
|
+
case "CANCELLED":
|
|
782
|
+
return "Cancelled";
|
|
783
|
+
default:
|
|
784
|
+
return "Complete";
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
function toQuarterScore(period) {
|
|
788
|
+
return {
|
|
789
|
+
goals: period.score.goals,
|
|
790
|
+
behinds: period.score.behinds,
|
|
791
|
+
points: period.score.totalScore
|
|
792
|
+
};
|
|
793
|
+
}
|
|
794
|
+
function findPeriod(periods, quarter) {
|
|
795
|
+
if (!periods) return null;
|
|
796
|
+
const period = periods.find((p) => p.periodNumber === quarter);
|
|
797
|
+
return period ? toQuarterScore(period) : null;
|
|
798
|
+
}
|
|
799
|
+
function transformMatchItems(items, season, competition, source = "afl-api") {
|
|
800
|
+
return items.map((item) => {
|
|
801
|
+
const homeScore = item.score?.homeTeamScore;
|
|
802
|
+
const awayScore = item.score?.awayTeamScore;
|
|
803
|
+
const homePoints = homeScore?.matchScore.totalScore ?? 0;
|
|
804
|
+
const awayPoints = awayScore?.matchScore.totalScore ?? 0;
|
|
805
|
+
return {
|
|
806
|
+
matchId: item.match.matchId,
|
|
807
|
+
season,
|
|
808
|
+
roundNumber: item.round?.roundNumber ?? 0,
|
|
809
|
+
roundType: inferRoundType(item.round?.name ?? ""),
|
|
810
|
+
date: new Date(item.match.utcStartTime),
|
|
811
|
+
venue: item.venue?.name ?? "",
|
|
812
|
+
homeTeam: normaliseTeamName(item.match.homeTeam.name),
|
|
813
|
+
awayTeam: normaliseTeamName(item.match.awayTeam.name),
|
|
814
|
+
homeGoals: homeScore?.matchScore.goals ?? 0,
|
|
815
|
+
homeBehinds: homeScore?.matchScore.behinds ?? 0,
|
|
816
|
+
homePoints,
|
|
817
|
+
awayGoals: awayScore?.matchScore.goals ?? 0,
|
|
818
|
+
awayBehinds: awayScore?.matchScore.behinds ?? 0,
|
|
819
|
+
awayPoints,
|
|
820
|
+
margin: homePoints - awayPoints,
|
|
821
|
+
q1Home: findPeriod(homeScore?.periodScore, 1),
|
|
822
|
+
q2Home: findPeriod(homeScore?.periodScore, 2),
|
|
823
|
+
q3Home: findPeriod(homeScore?.periodScore, 3),
|
|
824
|
+
q4Home: findPeriod(homeScore?.periodScore, 4),
|
|
825
|
+
q1Away: findPeriod(awayScore?.periodScore, 1),
|
|
826
|
+
q2Away: findPeriod(awayScore?.periodScore, 2),
|
|
827
|
+
q3Away: findPeriod(awayScore?.periodScore, 3),
|
|
828
|
+
q4Away: findPeriod(awayScore?.periodScore, 4),
|
|
829
|
+
status: toMatchStatus(item.match.status),
|
|
830
|
+
attendance: null,
|
|
831
|
+
venueState: item.venue?.state ?? null,
|
|
832
|
+
venueTimezone: item.venue?.timeZone ?? null,
|
|
833
|
+
homeRushedBehinds: homeScore?.rushedBehinds ?? null,
|
|
834
|
+
awayRushedBehinds: awayScore?.rushedBehinds ?? null,
|
|
835
|
+
homeMinutesInFront: homeScore?.minutesInFront ?? null,
|
|
836
|
+
awayMinutesInFront: awayScore?.minutesInFront ?? null,
|
|
837
|
+
source,
|
|
838
|
+
competition
|
|
839
|
+
};
|
|
840
|
+
});
|
|
841
|
+
}
|
|
842
|
+
var FINALS_PATTERN;
|
|
843
|
+
var init_match_results = __esm({
|
|
844
|
+
"src/transforms/match-results.ts"() {
|
|
845
|
+
"use strict";
|
|
846
|
+
init_team_mapping();
|
|
847
|
+
FINALS_PATTERN = /final|elimination|qualifying|preliminary|semi|grand/i;
|
|
848
|
+
}
|
|
849
|
+
});
|
|
850
|
+
|
|
851
|
+
// src/api/fixture.ts
|
|
852
|
+
function toFixture(item, season, fallbackRoundNumber, competition) {
|
|
853
|
+
return {
|
|
854
|
+
matchId: item.match.matchId,
|
|
855
|
+
season,
|
|
856
|
+
roundNumber: item.round?.roundNumber ?? fallbackRoundNumber,
|
|
857
|
+
roundType: inferRoundType(item.round?.name ?? ""),
|
|
858
|
+
date: new Date(item.match.utcStartTime),
|
|
859
|
+
venue: item.venue?.name ?? "",
|
|
860
|
+
homeTeam: normaliseTeamName(item.match.homeTeam.name),
|
|
861
|
+
awayTeam: normaliseTeamName(item.match.awayTeam.name),
|
|
862
|
+
status: toMatchStatus(item.match.status),
|
|
863
|
+
competition
|
|
864
|
+
};
|
|
865
|
+
}
|
|
866
|
+
async function fetchFixture(query) {
|
|
867
|
+
const competition = query.competition ?? "AFLM";
|
|
868
|
+
if (query.source !== "afl-api") {
|
|
869
|
+
return err(
|
|
870
|
+
new UnsupportedSourceError(
|
|
871
|
+
"Fixture data is only available from the AFL API source.",
|
|
872
|
+
query.source
|
|
873
|
+
)
|
|
874
|
+
);
|
|
875
|
+
}
|
|
876
|
+
const client = new AflApiClient();
|
|
877
|
+
const seasonResult = await client.resolveCompSeason(competition, query.season);
|
|
878
|
+
if (!seasonResult.success) return seasonResult;
|
|
879
|
+
if (query.round != null) {
|
|
880
|
+
const itemsResult = await client.fetchRoundMatchItemsByNumber(seasonResult.data, query.round);
|
|
881
|
+
if (!itemsResult.success) return itemsResult;
|
|
882
|
+
return ok(itemsResult.data.map((item) => toFixture(item, query.season, 0, competition)));
|
|
883
|
+
}
|
|
884
|
+
const roundsResult = await client.resolveRounds(seasonResult.data);
|
|
885
|
+
if (!roundsResult.success) return roundsResult;
|
|
886
|
+
const roundProviderIds = roundsResult.data.flatMap(
|
|
887
|
+
(r) => r.providerId ? [{ providerId: r.providerId, roundNumber: r.roundNumber }] : []
|
|
888
|
+
);
|
|
889
|
+
const roundResults = await Promise.all(
|
|
890
|
+
roundProviderIds.map((r) => client.fetchRoundMatchItems(r.providerId))
|
|
891
|
+
);
|
|
892
|
+
const fixtures = [];
|
|
893
|
+
for (let i = 0; i < roundResults.length; i++) {
|
|
894
|
+
const result = roundResults[i];
|
|
895
|
+
if (!result?.success) continue;
|
|
896
|
+
const roundNumber = roundProviderIds[i]?.roundNumber ?? 0;
|
|
897
|
+
for (const item of result.data) {
|
|
898
|
+
fixtures.push(toFixture(item, query.season, roundNumber, competition));
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
return ok(fixtures);
|
|
902
|
+
}
|
|
903
|
+
var init_fixture = __esm({
|
|
904
|
+
"src/api/fixture.ts"() {
|
|
905
|
+
"use strict";
|
|
906
|
+
init_errors();
|
|
907
|
+
init_result();
|
|
908
|
+
init_team_mapping();
|
|
909
|
+
init_afl_api();
|
|
910
|
+
init_match_results();
|
|
911
|
+
}
|
|
912
|
+
});
|
|
913
|
+
|
|
914
|
+
// src/transforms/ladder.ts
|
|
915
|
+
function transformLadderEntries(entries) {
|
|
916
|
+
return entries.map((entry) => {
|
|
917
|
+
const record = entry.thisSeasonRecord;
|
|
918
|
+
const wl = record?.winLossRecord;
|
|
919
|
+
return {
|
|
920
|
+
position: entry.position,
|
|
921
|
+
team: normaliseTeamName(entry.team.name),
|
|
922
|
+
played: entry.played ?? wl?.played ?? 0,
|
|
923
|
+
wins: wl?.wins ?? 0,
|
|
924
|
+
losses: wl?.losses ?? 0,
|
|
925
|
+
draws: wl?.draws ?? 0,
|
|
926
|
+
pointsFor: entry.pointsFor ?? 0,
|
|
927
|
+
pointsAgainst: entry.pointsAgainst ?? 0,
|
|
928
|
+
percentage: record?.percentage ?? 0,
|
|
929
|
+
premiershipsPoints: record?.aggregatePoints ?? 0,
|
|
930
|
+
form: entry.form ?? null
|
|
931
|
+
};
|
|
932
|
+
});
|
|
933
|
+
}
|
|
934
|
+
var init_ladder = __esm({
|
|
935
|
+
"src/transforms/ladder.ts"() {
|
|
936
|
+
"use strict";
|
|
937
|
+
init_team_mapping();
|
|
938
|
+
}
|
|
939
|
+
});
|
|
940
|
+
|
|
941
|
+
// src/api/ladder.ts
|
|
942
|
+
async function fetchLadder(query) {
|
|
943
|
+
const competition = query.competition ?? "AFLM";
|
|
944
|
+
if (query.source !== "afl-api") {
|
|
945
|
+
return err(
|
|
946
|
+
new UnsupportedSourceError(
|
|
947
|
+
"Ladder data is only available from the AFL API source.",
|
|
948
|
+
query.source
|
|
949
|
+
)
|
|
950
|
+
);
|
|
951
|
+
}
|
|
952
|
+
const client = new AflApiClient();
|
|
953
|
+
const seasonResult = await client.resolveCompSeason(competition, query.season);
|
|
954
|
+
if (!seasonResult.success) return seasonResult;
|
|
955
|
+
let roundId;
|
|
956
|
+
if (query.round != null) {
|
|
957
|
+
const roundsResult = await client.resolveRounds(seasonResult.data);
|
|
958
|
+
if (!roundsResult.success) return roundsResult;
|
|
959
|
+
const round = roundsResult.data.find((r) => r.roundNumber === query.round);
|
|
960
|
+
if (round) {
|
|
961
|
+
roundId = round.id;
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
const ladderResult = await client.fetchLadder(seasonResult.data, roundId);
|
|
965
|
+
if (!ladderResult.success) return ladderResult;
|
|
966
|
+
const firstLadder = ladderResult.data.ladders[0];
|
|
967
|
+
const entries = firstLadder ? transformLadderEntries(firstLadder.entries) : [];
|
|
968
|
+
return ok({
|
|
969
|
+
season: query.season,
|
|
970
|
+
roundNumber: ladderResult.data.round?.roundNumber ?? null,
|
|
971
|
+
entries,
|
|
972
|
+
competition
|
|
973
|
+
});
|
|
974
|
+
}
|
|
975
|
+
var init_ladder2 = __esm({
|
|
976
|
+
"src/api/ladder.ts"() {
|
|
977
|
+
"use strict";
|
|
978
|
+
init_errors();
|
|
979
|
+
init_result();
|
|
980
|
+
init_afl_api();
|
|
981
|
+
init_ladder();
|
|
982
|
+
}
|
|
983
|
+
});
|
|
984
|
+
|
|
985
|
+
// src/transforms/lineup.ts
|
|
986
|
+
function transformMatchRoster(roster, season, roundNumber, competition) {
|
|
987
|
+
const homeTeamId = roster.match.homeTeamId;
|
|
988
|
+
const awayTeamId = roster.match.awayTeamId;
|
|
989
|
+
const homeTeamPlayers = roster.teamPlayers.find((tp) => tp.teamId === homeTeamId);
|
|
990
|
+
const awayTeamPlayers = roster.teamPlayers.find((tp) => tp.teamId === awayTeamId);
|
|
991
|
+
const mapPlayers = (players) => players.map((p) => {
|
|
992
|
+
const inner = p.player.player;
|
|
993
|
+
const position = p.player.position ?? null;
|
|
994
|
+
return {
|
|
995
|
+
playerId: inner.playerId,
|
|
996
|
+
givenName: inner.playerName.givenName,
|
|
997
|
+
surname: inner.playerName.surname,
|
|
998
|
+
displayName: `${inner.playerName.givenName} ${inner.playerName.surname}`,
|
|
999
|
+
jumperNumber: p.jumperNumber ?? null,
|
|
1000
|
+
position,
|
|
1001
|
+
isEmergency: position !== null && EMERGENCY_POSITIONS.has(position),
|
|
1002
|
+
isSubstitute: position !== null && SUBSTITUTE_POSITIONS.has(position)
|
|
1003
|
+
};
|
|
1004
|
+
});
|
|
1005
|
+
return {
|
|
1006
|
+
matchId: roster.match.matchId,
|
|
1007
|
+
season,
|
|
1008
|
+
roundNumber,
|
|
1009
|
+
homeTeam: normaliseTeamName(roster.match.homeTeam.name),
|
|
1010
|
+
awayTeam: normaliseTeamName(roster.match.awayTeam.name),
|
|
1011
|
+
homePlayers: homeTeamPlayers ? mapPlayers(homeTeamPlayers.players) : [],
|
|
1012
|
+
awayPlayers: awayTeamPlayers ? mapPlayers(awayTeamPlayers.players) : [],
|
|
1013
|
+
competition
|
|
1014
|
+
};
|
|
1015
|
+
}
|
|
1016
|
+
var EMERGENCY_POSITIONS, SUBSTITUTE_POSITIONS;
|
|
1017
|
+
var init_lineup = __esm({
|
|
1018
|
+
"src/transforms/lineup.ts"() {
|
|
1019
|
+
"use strict";
|
|
1020
|
+
init_team_mapping();
|
|
1021
|
+
EMERGENCY_POSITIONS = /* @__PURE__ */ new Set(["EMG", "EMERG"]);
|
|
1022
|
+
SUBSTITUTE_POSITIONS = /* @__PURE__ */ new Set(["SUB", "INT"]);
|
|
1023
|
+
}
|
|
1024
|
+
});
|
|
1025
|
+
|
|
1026
|
+
// src/api/lineup.ts
|
|
1027
|
+
async function fetchLineup(query) {
|
|
1028
|
+
const competition = query.competition ?? "AFLM";
|
|
1029
|
+
if (query.source !== "afl-api") {
|
|
1030
|
+
return err(
|
|
1031
|
+
new UnsupportedSourceError(
|
|
1032
|
+
"Lineup data is only available from the AFL API source.",
|
|
1033
|
+
query.source
|
|
1034
|
+
)
|
|
1035
|
+
);
|
|
1036
|
+
}
|
|
1037
|
+
const client = new AflApiClient();
|
|
1038
|
+
if (query.matchId) {
|
|
1039
|
+
const rosterResult = await client.fetchMatchRoster(query.matchId);
|
|
1040
|
+
if (!rosterResult.success) return rosterResult;
|
|
1041
|
+
return ok([transformMatchRoster(rosterResult.data, query.season, query.round, competition)]);
|
|
1042
|
+
}
|
|
1043
|
+
const seasonResult = await client.resolveCompSeason(competition, query.season);
|
|
1044
|
+
if (!seasonResult.success) return seasonResult;
|
|
1045
|
+
const matchItems = await client.fetchRoundMatchItemsByNumber(seasonResult.data, query.round);
|
|
1046
|
+
if (!matchItems.success) return matchItems;
|
|
1047
|
+
if (matchItems.data.length === 0) {
|
|
1048
|
+
return err(new AflApiError(`No matches found for round ${query.round}`));
|
|
1049
|
+
}
|
|
1050
|
+
const rosterResults = await Promise.all(
|
|
1051
|
+
matchItems.data.map((item) => client.fetchMatchRoster(item.match.matchId))
|
|
1052
|
+
);
|
|
1053
|
+
const lineups = [];
|
|
1054
|
+
for (const rosterResult of rosterResults) {
|
|
1055
|
+
if (!rosterResult.success) return rosterResult;
|
|
1056
|
+
lineups.push(transformMatchRoster(rosterResult.data, query.season, query.round, competition));
|
|
1057
|
+
}
|
|
1058
|
+
return ok(lineups);
|
|
1059
|
+
}
|
|
1060
|
+
var init_lineup2 = __esm({
|
|
1061
|
+
"src/api/lineup.ts"() {
|
|
1062
|
+
"use strict";
|
|
1063
|
+
init_errors();
|
|
1064
|
+
init_result();
|
|
1065
|
+
init_afl_api();
|
|
1066
|
+
init_lineup();
|
|
1067
|
+
}
|
|
1068
|
+
});
|
|
1069
|
+
|
|
1070
|
+
// src/lib/date-utils.ts
|
|
1071
|
+
function parseFootyWireDate(dateStr) {
|
|
1072
|
+
const trimmed = dateStr.trim();
|
|
1073
|
+
if (trimmed === "") {
|
|
1074
|
+
return null;
|
|
1075
|
+
}
|
|
1076
|
+
const withoutDow = trimmed.replace(/^(?:Mon|Tue|Wed|Thu|Fri|Sat|Sun)\w*\s+/i, "");
|
|
1077
|
+
const normalised = withoutDow.replace(/-/g, " ");
|
|
1078
|
+
const match = /^(\d{1,2})\s+([A-Za-z]+)\s+(\d{4})$/.exec(normalised);
|
|
1079
|
+
if (!match) {
|
|
1080
|
+
return null;
|
|
1081
|
+
}
|
|
1082
|
+
const [, dayStr, monthStr, yearStr] = match;
|
|
1083
|
+
if (!dayStr || !monthStr || !yearStr) {
|
|
1084
|
+
return null;
|
|
1085
|
+
}
|
|
1086
|
+
const monthIndex = MONTH_ABBREV_TO_INDEX.get(monthStr.toLowerCase());
|
|
1087
|
+
if (monthIndex === void 0) {
|
|
1088
|
+
return null;
|
|
1089
|
+
}
|
|
1090
|
+
const year = Number.parseInt(yearStr, 10);
|
|
1091
|
+
const day = Number.parseInt(dayStr, 10);
|
|
1092
|
+
const date = new Date(Date.UTC(year, monthIndex, day));
|
|
1093
|
+
if (Number.isNaN(date.getTime())) {
|
|
1094
|
+
return null;
|
|
1095
|
+
}
|
|
1096
|
+
if (date.getUTCFullYear() !== year || date.getUTCMonth() !== monthIndex || date.getUTCDate() !== day) {
|
|
1097
|
+
return null;
|
|
1098
|
+
}
|
|
1099
|
+
return date;
|
|
1100
|
+
}
|
|
1101
|
+
function parseAflTablesDate(dateStr) {
|
|
1102
|
+
const trimmed = dateStr.trim();
|
|
1103
|
+
if (trimmed === "") {
|
|
1104
|
+
return null;
|
|
1105
|
+
}
|
|
1106
|
+
const withoutDow = trimmed.replace(/^(?:Mon|Tue|Wed|Thu|Fri|Sat|Sun)\w*\s+/i, "");
|
|
1107
|
+
const normalised = withoutDow.replace(/[-/]/g, " ");
|
|
1108
|
+
const dmy = /^(\d{1,2})\s+([A-Za-z]+)\s+(\d{4})$/.exec(normalised);
|
|
1109
|
+
if (dmy) {
|
|
1110
|
+
const [, dayStr, monthStr, yearStr] = dmy;
|
|
1111
|
+
if (dayStr && monthStr && yearStr) {
|
|
1112
|
+
return buildUtcDate(Number.parseInt(yearStr, 10), monthStr, Number.parseInt(dayStr, 10));
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
const mdy = /^([A-Za-z]+)\s+(\d{1,2})\s+(\d{4})$/.exec(normalised);
|
|
1116
|
+
if (mdy) {
|
|
1117
|
+
const [, monthStr, dayStr, yearStr] = mdy;
|
|
1118
|
+
if (dayStr && monthStr && yearStr) {
|
|
1119
|
+
return buildUtcDate(Number.parseInt(yearStr, 10), monthStr, Number.parseInt(dayStr, 10));
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
return null;
|
|
1123
|
+
}
|
|
1124
|
+
function buildUtcDate(year, monthStr, day) {
|
|
1125
|
+
const monthIndex = MONTH_ABBREV_TO_INDEX.get(monthStr.toLowerCase());
|
|
1126
|
+
if (monthIndex === void 0) {
|
|
1127
|
+
return null;
|
|
1128
|
+
}
|
|
1129
|
+
const date = new Date(Date.UTC(year, monthIndex, day));
|
|
1130
|
+
if (Number.isNaN(date.getTime())) {
|
|
1131
|
+
return null;
|
|
1132
|
+
}
|
|
1133
|
+
if (date.getUTCFullYear() !== year || date.getUTCMonth() !== monthIndex || date.getUTCDate() !== day) {
|
|
1134
|
+
return null;
|
|
1135
|
+
}
|
|
1136
|
+
return date;
|
|
1137
|
+
}
|
|
1138
|
+
var MONTH_ABBREV_TO_INDEX;
|
|
1139
|
+
var init_date_utils = __esm({
|
|
1140
|
+
"src/lib/date-utils.ts"() {
|
|
1141
|
+
"use strict";
|
|
1142
|
+
MONTH_ABBREV_TO_INDEX = /* @__PURE__ */ new Map([
|
|
1143
|
+
["jan", 0],
|
|
1144
|
+
["feb", 1],
|
|
1145
|
+
["mar", 2],
|
|
1146
|
+
["apr", 3],
|
|
1147
|
+
["may", 4],
|
|
1148
|
+
["jun", 5],
|
|
1149
|
+
["jul", 6],
|
|
1150
|
+
["aug", 7],
|
|
1151
|
+
["sep", 8],
|
|
1152
|
+
["oct", 9],
|
|
1153
|
+
["nov", 10],
|
|
1154
|
+
["dec", 11],
|
|
1155
|
+
["january", 0],
|
|
1156
|
+
["february", 1],
|
|
1157
|
+
["march", 2],
|
|
1158
|
+
["april", 3],
|
|
1159
|
+
["june", 5],
|
|
1160
|
+
["july", 6],
|
|
1161
|
+
["august", 7],
|
|
1162
|
+
["september", 8],
|
|
1163
|
+
["october", 9],
|
|
1164
|
+
["november", 10],
|
|
1165
|
+
["december", 11]
|
|
1166
|
+
]);
|
|
1167
|
+
}
|
|
1168
|
+
});
|
|
1169
|
+
|
|
1170
|
+
// src/sources/afl-tables.ts
|
|
1171
|
+
import * as cheerio from "cheerio";
|
|
1172
|
+
function parseSeasonPage(html, year) {
|
|
1173
|
+
const $ = cheerio.load(html);
|
|
1174
|
+
const results = [];
|
|
1175
|
+
let currentRound = 0;
|
|
1176
|
+
let currentRoundType = "HomeAndAway";
|
|
1177
|
+
let matchCounter = 0;
|
|
1178
|
+
$("table").each((_i, table) => {
|
|
1179
|
+
const $table = $(table);
|
|
1180
|
+
const text = $table.text().trim();
|
|
1181
|
+
const roundMatch = /^Round\s+(\d+)/i.exec(text);
|
|
1182
|
+
if (roundMatch?.[1] && !$table.attr("border")) {
|
|
1183
|
+
currentRound = Number.parseInt(roundMatch[1], 10);
|
|
1184
|
+
currentRoundType = inferRoundType(text);
|
|
1185
|
+
return;
|
|
1186
|
+
}
|
|
1187
|
+
if (!$table.attr("border") && inferRoundType(text) === "Finals") {
|
|
1188
|
+
currentRoundType = "Finals";
|
|
1189
|
+
return;
|
|
1190
|
+
}
|
|
1191
|
+
if ($table.attr("border") !== "1") return;
|
|
1192
|
+
const rows = $table.find("tr");
|
|
1193
|
+
if (rows.length !== 2) return;
|
|
1194
|
+
const homeRow = $(rows[0]);
|
|
1195
|
+
const awayRow = $(rows[1]);
|
|
1196
|
+
const homeCells = homeRow.find("td");
|
|
1197
|
+
const awayCells = awayRow.find("td");
|
|
1198
|
+
if (homeCells.length < 4 || awayCells.length < 3) return;
|
|
1199
|
+
const homeTeam = normaliseTeamName($(homeCells[0]).find("a").text().trim());
|
|
1200
|
+
const awayTeam = normaliseTeamName($(awayCells[0]).find("a").text().trim());
|
|
1201
|
+
if (!homeTeam || !awayTeam) return;
|
|
1202
|
+
const homeQuarters = parseQuarterScores($(homeCells[1]).text());
|
|
1203
|
+
const awayQuarters = parseQuarterScores($(awayCells[1]).text());
|
|
1204
|
+
const homePoints = Number.parseInt($(homeCells[2]).text().trim(), 10) || 0;
|
|
1205
|
+
const awayPoints = Number.parseInt($(awayCells[2]).text().trim(), 10) || 0;
|
|
1206
|
+
const infoText = $(homeCells[3]).text().trim();
|
|
1207
|
+
const date = parseDateFromInfo(infoText, year);
|
|
1208
|
+
const venue = parseVenueFromInfo($(homeCells[3]).html() ?? "");
|
|
1209
|
+
const attendance = parseAttendanceFromInfo(infoText);
|
|
1210
|
+
const homeFinal = homeQuarters[3];
|
|
1211
|
+
const awayFinal = awayQuarters[3];
|
|
1212
|
+
matchCounter++;
|
|
1213
|
+
results.push({
|
|
1214
|
+
matchId: `AT_${year}_${matchCounter}`,
|
|
1215
|
+
season: year,
|
|
1216
|
+
roundNumber: currentRound,
|
|
1217
|
+
roundType: currentRoundType,
|
|
1218
|
+
date,
|
|
1219
|
+
venue,
|
|
1220
|
+
homeTeam,
|
|
1221
|
+
awayTeam,
|
|
1222
|
+
homeGoals: homeFinal?.goals ?? 0,
|
|
1223
|
+
homeBehinds: homeFinal?.behinds ?? 0,
|
|
1224
|
+
homePoints,
|
|
1225
|
+
awayGoals: awayFinal?.goals ?? 0,
|
|
1226
|
+
awayBehinds: awayFinal?.behinds ?? 0,
|
|
1227
|
+
awayPoints,
|
|
1228
|
+
margin: homePoints - awayPoints,
|
|
1229
|
+
q1Home: homeQuarters[0] ?? null,
|
|
1230
|
+
q2Home: homeQuarters[1] ?? null,
|
|
1231
|
+
q3Home: homeQuarters[2] ?? null,
|
|
1232
|
+
q4Home: homeQuarters[3] ?? null,
|
|
1233
|
+
q1Away: awayQuarters[0] ?? null,
|
|
1234
|
+
q2Away: awayQuarters[1] ?? null,
|
|
1235
|
+
q3Away: awayQuarters[2] ?? null,
|
|
1236
|
+
q4Away: awayQuarters[3] ?? null,
|
|
1237
|
+
status: "Complete",
|
|
1238
|
+
attendance,
|
|
1239
|
+
venueState: null,
|
|
1240
|
+
venueTimezone: null,
|
|
1241
|
+
homeRushedBehinds: null,
|
|
1242
|
+
awayRushedBehinds: null,
|
|
1243
|
+
homeMinutesInFront: null,
|
|
1244
|
+
awayMinutesInFront: null,
|
|
1245
|
+
source: "afl-tables",
|
|
1246
|
+
competition: "AFLM"
|
|
1247
|
+
});
|
|
1248
|
+
});
|
|
1249
|
+
return results;
|
|
1250
|
+
}
|
|
1251
|
+
function parseQuarterScores(text) {
|
|
1252
|
+
const clean = text.replace(/\u00a0/g, " ").trim();
|
|
1253
|
+
const matches = [...clean.matchAll(/(\d+)\.(\d+)/g)];
|
|
1254
|
+
return matches.map((m) => {
|
|
1255
|
+
const goals = Number.parseInt(m[1] ?? "0", 10);
|
|
1256
|
+
const behinds = Number.parseInt(m[2] ?? "0", 10);
|
|
1257
|
+
return { goals, behinds, points: goals * 6 + behinds };
|
|
1258
|
+
});
|
|
1259
|
+
}
|
|
1260
|
+
function parseDateFromInfo(text, year) {
|
|
1261
|
+
const dateMatch = /(\d{1,2}-[A-Z][a-z]{2}-\d{4})/.exec(text);
|
|
1262
|
+
if (dateMatch?.[1]) {
|
|
1263
|
+
return parseAflTablesDate(dateMatch[1]) ?? new Date(year, 0, 1);
|
|
1264
|
+
}
|
|
1265
|
+
return parseAflTablesDate(text) ?? new Date(year, 0, 1);
|
|
1266
|
+
}
|
|
1267
|
+
function parseVenueFromInfo(html) {
|
|
1268
|
+
const $ = cheerio.load(html);
|
|
1269
|
+
const venueLink = $("a[href*='venues']");
|
|
1270
|
+
if (venueLink.length > 0) {
|
|
1271
|
+
return venueLink.text().trim();
|
|
1272
|
+
}
|
|
1273
|
+
const venueMatch = /Venue:\s*(.+?)(?:<|$)/i.exec(html);
|
|
1274
|
+
return venueMatch?.[1]?.trim() ?? "";
|
|
1275
|
+
}
|
|
1276
|
+
function parseAttendanceFromInfo(text) {
|
|
1277
|
+
const match = /Att:\s*([\d,]+)/i.exec(text);
|
|
1278
|
+
if (!match?.[1]) return null;
|
|
1279
|
+
return Number.parseInt(match[1].replace(/,/g, ""), 10) || null;
|
|
1280
|
+
}
|
|
1281
|
+
var AFL_TABLES_BASE, AflTablesClient;
|
|
1282
|
+
var init_afl_tables = __esm({
|
|
1283
|
+
"src/sources/afl-tables.ts"() {
|
|
1284
|
+
"use strict";
|
|
1285
|
+
init_date_utils();
|
|
1286
|
+
init_errors();
|
|
1287
|
+
init_result();
|
|
1288
|
+
init_team_mapping();
|
|
1289
|
+
init_match_results();
|
|
1290
|
+
AFL_TABLES_BASE = "https://afltables.com/afl/seas";
|
|
1291
|
+
AflTablesClient = class {
|
|
1292
|
+
fetchFn;
|
|
1293
|
+
constructor(options) {
|
|
1294
|
+
this.fetchFn = options?.fetchFn ?? globalThis.fetch;
|
|
1295
|
+
}
|
|
1296
|
+
/**
|
|
1297
|
+
* Fetch season match results from AFL Tables.
|
|
1298
|
+
*
|
|
1299
|
+
* @param year - The season year (1897 to present).
|
|
1300
|
+
* @returns Array of match results.
|
|
1301
|
+
*/
|
|
1302
|
+
async fetchSeasonResults(year) {
|
|
1303
|
+
const url = `${AFL_TABLES_BASE}/${year}.html`;
|
|
1304
|
+
try {
|
|
1305
|
+
const response = await this.fetchFn(url, {
|
|
1306
|
+
headers: { "User-Agent": "Mozilla/5.0" }
|
|
1307
|
+
});
|
|
1308
|
+
if (!response.ok) {
|
|
1309
|
+
return err(
|
|
1310
|
+
new ScrapeError(`AFL Tables request failed: ${response.status} (${url})`, "afl-tables")
|
|
1311
|
+
);
|
|
1312
|
+
}
|
|
1313
|
+
const html = await response.text();
|
|
1314
|
+
const results = parseSeasonPage(html, year);
|
|
1315
|
+
return ok(results);
|
|
1316
|
+
} catch (cause) {
|
|
1317
|
+
return err(
|
|
1318
|
+
new ScrapeError(
|
|
1319
|
+
`AFL Tables request failed: ${cause instanceof Error ? cause.message : String(cause)}`,
|
|
1320
|
+
"afl-tables"
|
|
1321
|
+
)
|
|
1322
|
+
);
|
|
1323
|
+
}
|
|
1324
|
+
}
|
|
1325
|
+
};
|
|
1326
|
+
}
|
|
1327
|
+
});
|
|
1328
|
+
|
|
1329
|
+
// src/sources/footywire.ts
|
|
1330
|
+
import * as cheerio2 from "cheerio";
|
|
1331
|
+
function parseMatchList(html, year) {
|
|
1332
|
+
const $ = cheerio2.load(html);
|
|
1333
|
+
const results = [];
|
|
1334
|
+
let currentRound = 0;
|
|
1335
|
+
let currentRoundType = "HomeAndAway";
|
|
1336
|
+
$("tr").each((_i, row) => {
|
|
1337
|
+
const roundHeader = $(row).find("td[colspan='7']");
|
|
1338
|
+
if (roundHeader.length > 0) {
|
|
1339
|
+
const text = roundHeader.text().trim();
|
|
1340
|
+
currentRoundType = inferRoundType(text);
|
|
1341
|
+
const roundMatch = /Round\s+(\d+)/i.exec(text);
|
|
1342
|
+
if (roundMatch?.[1]) {
|
|
1343
|
+
currentRound = Number.parseInt(roundMatch[1], 10);
|
|
1344
|
+
}
|
|
1345
|
+
return;
|
|
1346
|
+
}
|
|
1347
|
+
const cells = $(row).find("td.data");
|
|
1348
|
+
if (cells.length < 5) return;
|
|
1349
|
+
const dateText = $(cells[0]).text().trim();
|
|
1350
|
+
const teamsCell = $(cells[1]);
|
|
1351
|
+
const venue = $(cells[2]).text().trim();
|
|
1352
|
+
const attendance = $(cells[3]).text().trim();
|
|
1353
|
+
const scoreCell = $(cells[4]);
|
|
1354
|
+
if (venue === "BYE") return;
|
|
1355
|
+
const teamLinks = teamsCell.find("a");
|
|
1356
|
+
if (teamLinks.length < 2) return;
|
|
1357
|
+
const homeTeam = normaliseTeamName($(teamLinks[0]).text().trim());
|
|
1358
|
+
const awayTeam = normaliseTeamName($(teamLinks[1]).text().trim());
|
|
1359
|
+
const scoreText = scoreCell.text().trim();
|
|
1360
|
+
const scoreMatch = /(\d+)-(\d+)/.exec(scoreText);
|
|
1361
|
+
if (!scoreMatch) return;
|
|
1362
|
+
const homePoints = Number.parseInt(scoreMatch[1] ?? "0", 10);
|
|
1363
|
+
const awayPoints = Number.parseInt(scoreMatch[2] ?? "0", 10);
|
|
1364
|
+
const scoreLink = scoreCell.find("a").attr("href") ?? "";
|
|
1365
|
+
const midMatch = /mid=(\d+)/.exec(scoreLink);
|
|
1366
|
+
const matchId = midMatch?.[1] ? `FW_${midMatch[1]}` : `FW_${year}_R${currentRound}_${homeTeam}`;
|
|
1367
|
+
const date = parseFootyWireDate(dateText) ?? new Date(year, 0, 1);
|
|
1368
|
+
const homeGoals = Math.floor(homePoints / 6);
|
|
1369
|
+
const homeBehinds = homePoints - homeGoals * 6;
|
|
1370
|
+
const awayGoals = Math.floor(awayPoints / 6);
|
|
1371
|
+
const awayBehinds = awayPoints - awayGoals * 6;
|
|
1372
|
+
results.push({
|
|
1373
|
+
matchId,
|
|
1374
|
+
season: year,
|
|
1375
|
+
roundNumber: currentRound,
|
|
1376
|
+
roundType: currentRoundType,
|
|
1377
|
+
date,
|
|
1378
|
+
venue,
|
|
1379
|
+
homeTeam,
|
|
1380
|
+
awayTeam,
|
|
1381
|
+
homeGoals,
|
|
1382
|
+
homeBehinds,
|
|
1383
|
+
homePoints,
|
|
1384
|
+
awayGoals,
|
|
1385
|
+
awayBehinds,
|
|
1386
|
+
awayPoints,
|
|
1387
|
+
margin: homePoints - awayPoints,
|
|
1388
|
+
q1Home: null,
|
|
1389
|
+
q2Home: null,
|
|
1390
|
+
q3Home: null,
|
|
1391
|
+
q4Home: null,
|
|
1392
|
+
q1Away: null,
|
|
1393
|
+
q2Away: null,
|
|
1394
|
+
q3Away: null,
|
|
1395
|
+
q4Away: null,
|
|
1396
|
+
status: "Complete",
|
|
1397
|
+
attendance: attendance ? Number.parseInt(attendance, 10) || null : null,
|
|
1398
|
+
venueState: null,
|
|
1399
|
+
venueTimezone: null,
|
|
1400
|
+
homeRushedBehinds: null,
|
|
1401
|
+
awayRushedBehinds: null,
|
|
1402
|
+
homeMinutesInFront: null,
|
|
1403
|
+
awayMinutesInFront: null,
|
|
1404
|
+
source: "footywire",
|
|
1405
|
+
competition: "AFLM"
|
|
1406
|
+
});
|
|
1407
|
+
});
|
|
1408
|
+
return results;
|
|
1409
|
+
}
|
|
1410
|
+
var FOOTYWIRE_BASE, FootyWireClient;
|
|
1411
|
+
var init_footywire = __esm({
|
|
1412
|
+
"src/sources/footywire.ts"() {
|
|
1413
|
+
"use strict";
|
|
1414
|
+
init_date_utils();
|
|
1415
|
+
init_errors();
|
|
1416
|
+
init_result();
|
|
1417
|
+
init_team_mapping();
|
|
1418
|
+
init_match_results();
|
|
1419
|
+
FOOTYWIRE_BASE = "https://www.footywire.com/afl/footy";
|
|
1420
|
+
FootyWireClient = class {
|
|
1421
|
+
fetchFn;
|
|
1422
|
+
constructor(options) {
|
|
1423
|
+
this.fetchFn = options?.fetchFn ?? globalThis.fetch;
|
|
1424
|
+
}
|
|
1425
|
+
/**
|
|
1426
|
+
* Fetch the HTML content of a FootyWire page.
|
|
1427
|
+
*/
|
|
1428
|
+
async fetchHtml(url) {
|
|
1429
|
+
try {
|
|
1430
|
+
const response = await this.fetchFn(url, {
|
|
1431
|
+
headers: {
|
|
1432
|
+
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
|
|
1433
|
+
}
|
|
1434
|
+
});
|
|
1435
|
+
if (!response.ok) {
|
|
1436
|
+
return err(
|
|
1437
|
+
new ScrapeError(`FootyWire request failed: ${response.status} (${url})`, "footywire")
|
|
1438
|
+
);
|
|
1439
|
+
}
|
|
1440
|
+
const html = await response.text();
|
|
1441
|
+
return ok(html);
|
|
1442
|
+
} catch (cause) {
|
|
1443
|
+
return err(
|
|
1444
|
+
new ScrapeError(
|
|
1445
|
+
`FootyWire request failed: ${cause instanceof Error ? cause.message : String(cause)}`,
|
|
1446
|
+
"footywire"
|
|
1447
|
+
)
|
|
1448
|
+
);
|
|
1449
|
+
}
|
|
1450
|
+
}
|
|
1451
|
+
/**
|
|
1452
|
+
* Fetch season match results from FootyWire.
|
|
1453
|
+
*
|
|
1454
|
+
* @param year - The season year.
|
|
1455
|
+
* @returns Array of match results.
|
|
1456
|
+
*/
|
|
1457
|
+
async fetchSeasonResults(year) {
|
|
1458
|
+
const url = `${FOOTYWIRE_BASE}/ft_match_list?year=${year}`;
|
|
1459
|
+
const htmlResult = await this.fetchHtml(url);
|
|
1460
|
+
if (!htmlResult.success) {
|
|
1461
|
+
return htmlResult;
|
|
1462
|
+
}
|
|
1463
|
+
try {
|
|
1464
|
+
const results = parseMatchList(htmlResult.data, year);
|
|
1465
|
+
return ok(results);
|
|
1466
|
+
} catch (cause) {
|
|
1467
|
+
return err(
|
|
1468
|
+
new ScrapeError(
|
|
1469
|
+
`Failed to parse match list: ${cause instanceof Error ? cause.message : String(cause)}`,
|
|
1470
|
+
"footywire"
|
|
1471
|
+
)
|
|
1472
|
+
);
|
|
1473
|
+
}
|
|
1474
|
+
}
|
|
1475
|
+
};
|
|
1476
|
+
}
|
|
1477
|
+
});
|
|
1478
|
+
|
|
1479
|
+
// src/api/match-results.ts
|
|
1480
|
+
async function fetchMatchResults(query) {
|
|
1481
|
+
const competition = query.competition ?? "AFLM";
|
|
1482
|
+
switch (query.source) {
|
|
1483
|
+
case "afl-api": {
|
|
1484
|
+
const client = new AflApiClient();
|
|
1485
|
+
const seasonResult = await client.resolveCompSeason(competition, query.season);
|
|
1486
|
+
if (!seasonResult.success) return seasonResult;
|
|
1487
|
+
if (query.round != null) {
|
|
1488
|
+
const itemsResult2 = await client.fetchRoundMatchItemsByNumber(
|
|
1489
|
+
seasonResult.data,
|
|
1490
|
+
query.round
|
|
1491
|
+
);
|
|
1492
|
+
if (!itemsResult2.success) return itemsResult2;
|
|
1493
|
+
return ok(transformMatchItems(itemsResult2.data, query.season, competition));
|
|
1494
|
+
}
|
|
1495
|
+
const itemsResult = await client.fetchSeasonMatchItems(seasonResult.data);
|
|
1496
|
+
if (!itemsResult.success) return itemsResult;
|
|
1497
|
+
return ok(transformMatchItems(itemsResult.data, query.season, competition));
|
|
1498
|
+
}
|
|
1499
|
+
case "footywire": {
|
|
1500
|
+
const client = new FootyWireClient();
|
|
1501
|
+
const result = await client.fetchSeasonResults(query.season);
|
|
1502
|
+
if (!result.success) return result;
|
|
1503
|
+
if (query.round != null) {
|
|
1504
|
+
return ok(result.data.filter((m) => m.roundNumber === query.round));
|
|
1505
|
+
}
|
|
1506
|
+
return result;
|
|
1507
|
+
}
|
|
1508
|
+
case "afl-tables": {
|
|
1509
|
+
const client = new AflTablesClient();
|
|
1510
|
+
const result = await client.fetchSeasonResults(query.season);
|
|
1511
|
+
if (!result.success) return result;
|
|
1512
|
+
if (query.round != null) {
|
|
1513
|
+
return ok(result.data.filter((m) => m.roundNumber === query.round));
|
|
1514
|
+
}
|
|
1515
|
+
return result;
|
|
1516
|
+
}
|
|
1517
|
+
default:
|
|
1518
|
+
return err(new UnsupportedSourceError(`Unsupported source: ${query.source}`, query.source));
|
|
1519
|
+
}
|
|
1520
|
+
}
|
|
1521
|
+
var init_match_results2 = __esm({
|
|
1522
|
+
"src/api/match-results.ts"() {
|
|
1523
|
+
"use strict";
|
|
1524
|
+
init_errors();
|
|
1525
|
+
init_result();
|
|
1526
|
+
init_afl_api();
|
|
1527
|
+
init_afl_tables();
|
|
1528
|
+
init_footywire();
|
|
1529
|
+
init_match_results();
|
|
1530
|
+
}
|
|
1531
|
+
});
|
|
1532
|
+
|
|
1533
|
+
// src/transforms/player-stats.ts
|
|
1534
|
+
function toNullable(value) {
|
|
1535
|
+
return value ?? null;
|
|
1536
|
+
}
|
|
1537
|
+
function transformOne(item, matchId, season, roundNumber, competition, source, teamIdMap) {
|
|
1538
|
+
const inner = item.player.player.player;
|
|
1539
|
+
const stats = item.playerStats.stats;
|
|
1540
|
+
const clearances = stats.clearances;
|
|
1541
|
+
return {
|
|
1542
|
+
matchId,
|
|
1543
|
+
season,
|
|
1544
|
+
roundNumber,
|
|
1545
|
+
team: normaliseTeamName(teamIdMap?.get(item.teamId) ?? item.teamId),
|
|
1546
|
+
competition,
|
|
1547
|
+
playerId: inner.playerId,
|
|
1548
|
+
givenName: inner.playerName.givenName,
|
|
1549
|
+
surname: inner.playerName.surname,
|
|
1550
|
+
displayName: `${inner.playerName.givenName} ${inner.playerName.surname}`,
|
|
1551
|
+
jumperNumber: item.player.jumperNumber ?? null,
|
|
1552
|
+
kicks: toNullable(stats.kicks),
|
|
1553
|
+
handballs: toNullable(stats.handballs),
|
|
1554
|
+
disposals: toNullable(stats.disposals),
|
|
1555
|
+
marks: toNullable(stats.marks),
|
|
1556
|
+
goals: toNullable(stats.goals),
|
|
1557
|
+
behinds: toNullable(stats.behinds),
|
|
1558
|
+
tackles: toNullable(stats.tackles),
|
|
1559
|
+
hitouts: toNullable(stats.hitouts),
|
|
1560
|
+
freesFor: toNullable(stats.freesFor),
|
|
1561
|
+
freesAgainst: toNullable(stats.freesAgainst),
|
|
1562
|
+
contestedPossessions: toNullable(stats.contestedPossessions),
|
|
1563
|
+
uncontestedPossessions: toNullable(stats.uncontestedPossessions),
|
|
1564
|
+
contestedMarks: toNullable(stats.contestedMarks),
|
|
1565
|
+
intercepts: toNullable(stats.intercepts),
|
|
1566
|
+
centreClearances: toNullable(clearances?.centreClearances),
|
|
1567
|
+
stoppageClearances: toNullable(clearances?.stoppageClearances),
|
|
1568
|
+
totalClearances: toNullable(clearances?.totalClearances),
|
|
1569
|
+
inside50s: toNullable(stats.inside50s),
|
|
1570
|
+
rebound50s: toNullable(stats.rebound50s),
|
|
1571
|
+
clangers: toNullable(stats.clangers),
|
|
1572
|
+
turnovers: toNullable(stats.turnovers),
|
|
1573
|
+
onePercenters: toNullable(stats.onePercenters),
|
|
1574
|
+
bounces: toNullable(stats.bounces),
|
|
1575
|
+
goalAssists: toNullable(stats.goalAssists),
|
|
1576
|
+
disposalEfficiency: toNullable(stats.disposalEfficiency),
|
|
1577
|
+
metresGained: toNullable(stats.metresGained),
|
|
1578
|
+
goalAccuracy: toNullable(stats.goalAccuracy),
|
|
1579
|
+
marksInside50: toNullable(stats.marksInside50),
|
|
1580
|
+
tacklesInside50: toNullable(stats.tacklesInside50),
|
|
1581
|
+
shotsAtGoal: toNullable(stats.shotsAtGoal),
|
|
1582
|
+
scoreInvolvements: toNullable(stats.scoreInvolvements),
|
|
1583
|
+
totalPossessions: toNullable(stats.totalPossessions),
|
|
1584
|
+
timeOnGroundPercentage: toNullable(item.playerStats.timeOnGroundPercentage),
|
|
1585
|
+
ratingPoints: toNullable(stats.ratingPoints),
|
|
1586
|
+
dreamTeamPoints: toNullable(stats.dreamTeamPoints),
|
|
1587
|
+
effectiveDisposals: toNullable(stats.extendedStats?.effectiveDisposals),
|
|
1588
|
+
effectiveKicks: toNullable(stats.extendedStats?.effectiveKicks),
|
|
1589
|
+
kickEfficiency: toNullable(stats.extendedStats?.kickEfficiency),
|
|
1590
|
+
kickToHandballRatio: toNullable(stats.extendedStats?.kickToHandballRatio),
|
|
1591
|
+
pressureActs: toNullable(stats.extendedStats?.pressureActs),
|
|
1592
|
+
defHalfPressureActs: toNullable(stats.extendedStats?.defHalfPressureActs),
|
|
1593
|
+
spoils: toNullable(stats.extendedStats?.spoils),
|
|
1594
|
+
hitoutsToAdvantage: toNullable(stats.extendedStats?.hitoutsToAdvantage),
|
|
1595
|
+
hitoutWinPercentage: toNullable(stats.extendedStats?.hitoutWinPercentage),
|
|
1596
|
+
hitoutToAdvantageRate: toNullable(stats.extendedStats?.hitoutToAdvantageRate),
|
|
1597
|
+
groundBallGets: toNullable(stats.extendedStats?.groundBallGets),
|
|
1598
|
+
f50GroundBallGets: toNullable(stats.extendedStats?.f50GroundBallGets),
|
|
1599
|
+
interceptMarks: toNullable(stats.extendedStats?.interceptMarks),
|
|
1600
|
+
marksOnLead: toNullable(stats.extendedStats?.marksOnLead),
|
|
1601
|
+
contestedPossessionRate: toNullable(stats.extendedStats?.contestedPossessionRate),
|
|
1602
|
+
contestOffOneOnOnes: toNullable(stats.extendedStats?.contestOffOneOnOnes),
|
|
1603
|
+
contestOffWins: toNullable(stats.extendedStats?.contestOffWins),
|
|
1604
|
+
contestOffWinsPercentage: toNullable(stats.extendedStats?.contestOffWinsPercentage),
|
|
1605
|
+
contestDefOneOnOnes: toNullable(stats.extendedStats?.contestDefOneOnOnes),
|
|
1606
|
+
contestDefLosses: toNullable(stats.extendedStats?.contestDefLosses),
|
|
1607
|
+
contestDefLossPercentage: toNullable(stats.extendedStats?.contestDefLossPercentage),
|
|
1608
|
+
centreBounceAttendances: toNullable(stats.extendedStats?.centreBounceAttendances),
|
|
1609
|
+
kickins: toNullable(stats.extendedStats?.kickins),
|
|
1610
|
+
kickinsPlayon: toNullable(stats.extendedStats?.kickinsPlayon),
|
|
1611
|
+
ruckContests: toNullable(stats.extendedStats?.ruckContests),
|
|
1612
|
+
scoreLaunches: toNullable(stats.extendedStats?.scoreLaunches),
|
|
1613
|
+
source
|
|
1614
|
+
};
|
|
1615
|
+
}
|
|
1616
|
+
function transformPlayerStats(data, matchId, season, roundNumber, competition, source = "afl-api", teamIdMap) {
|
|
1617
|
+
const home = data.homeTeamPlayerStats.map(
|
|
1618
|
+
(item) => transformOne(item, matchId, season, roundNumber, competition, source, teamIdMap)
|
|
1619
|
+
);
|
|
1620
|
+
const away = data.awayTeamPlayerStats.map(
|
|
1621
|
+
(item) => transformOne(item, matchId, season, roundNumber, competition, source, teamIdMap)
|
|
1622
|
+
);
|
|
1623
|
+
return [...home, ...away];
|
|
1624
|
+
}
|
|
1625
|
+
var init_player_stats = __esm({
|
|
1626
|
+
"src/transforms/player-stats.ts"() {
|
|
1627
|
+
"use strict";
|
|
1628
|
+
init_team_mapping();
|
|
1629
|
+
}
|
|
1630
|
+
});
|
|
1631
|
+
|
|
1632
|
+
// src/api/player-stats.ts
|
|
1633
|
+
async function fetchPlayerStats(query) {
|
|
1634
|
+
const competition = query.competition ?? "AFLM";
|
|
1635
|
+
switch (query.source) {
|
|
1636
|
+
case "afl-api": {
|
|
1637
|
+
const client = new AflApiClient();
|
|
1638
|
+
if (query.matchId) {
|
|
1639
|
+
const result = await client.fetchPlayerStats(query.matchId);
|
|
1640
|
+
if (!result.success) return result;
|
|
1641
|
+
return ok(
|
|
1642
|
+
transformPlayerStats(
|
|
1643
|
+
result.data,
|
|
1644
|
+
query.matchId,
|
|
1645
|
+
query.season,
|
|
1646
|
+
query.round ?? 0,
|
|
1647
|
+
competition
|
|
1648
|
+
)
|
|
1649
|
+
);
|
|
1650
|
+
}
|
|
1651
|
+
const seasonResult = await client.resolveCompSeason(competition, query.season);
|
|
1652
|
+
if (!seasonResult.success) return seasonResult;
|
|
1653
|
+
const roundNumber = query.round ?? 1;
|
|
1654
|
+
const matchItemsResult = await client.fetchRoundMatchItemsByNumber(
|
|
1655
|
+
seasonResult.data,
|
|
1656
|
+
roundNumber
|
|
1657
|
+
);
|
|
1658
|
+
if (!matchItemsResult.success) return matchItemsResult;
|
|
1659
|
+
const teamIdMap = /* @__PURE__ */ new Map();
|
|
1660
|
+
for (const item of matchItemsResult.data) {
|
|
1661
|
+
teamIdMap.set(item.match.homeTeamId, item.match.homeTeam.name);
|
|
1662
|
+
teamIdMap.set(item.match.awayTeamId, item.match.awayTeam.name);
|
|
1663
|
+
}
|
|
1664
|
+
const statsResults = await Promise.all(
|
|
1665
|
+
matchItemsResult.data.map((item) => client.fetchPlayerStats(item.match.matchId))
|
|
1666
|
+
);
|
|
1667
|
+
const allStats = [];
|
|
1668
|
+
for (let i = 0; i < statsResults.length; i++) {
|
|
1669
|
+
const statsResult = statsResults[i];
|
|
1670
|
+
if (!statsResult?.success)
|
|
1671
|
+
return statsResult ?? err(new AflApiError("Missing stats result"));
|
|
1672
|
+
const item = matchItemsResult.data[i];
|
|
1673
|
+
if (!item) continue;
|
|
1674
|
+
allStats.push(
|
|
1675
|
+
...transformPlayerStats(
|
|
1676
|
+
statsResult.data,
|
|
1677
|
+
item.match.matchId,
|
|
1678
|
+
query.season,
|
|
1679
|
+
roundNumber,
|
|
1680
|
+
competition,
|
|
1681
|
+
"afl-api",
|
|
1682
|
+
teamIdMap
|
|
1683
|
+
)
|
|
1684
|
+
);
|
|
1685
|
+
}
|
|
1686
|
+
return ok(allStats);
|
|
1687
|
+
}
|
|
1688
|
+
case "footywire":
|
|
1689
|
+
return err(
|
|
1690
|
+
new UnsupportedSourceError(
|
|
1691
|
+
"Player stats from FootyWire are not yet supported. Use source: 'afl-api'.",
|
|
1692
|
+
"footywire"
|
|
1693
|
+
)
|
|
1694
|
+
);
|
|
1695
|
+
case "afl-tables":
|
|
1696
|
+
return err(
|
|
1697
|
+
new UnsupportedSourceError(
|
|
1698
|
+
"Player stats from AFL Tables are not yet supported. Use source: 'afl-api'.",
|
|
1699
|
+
"afl-tables"
|
|
1700
|
+
)
|
|
1701
|
+
);
|
|
1702
|
+
default:
|
|
1703
|
+
return err(new UnsupportedSourceError(`Unsupported source: ${query.source}`, query.source));
|
|
1704
|
+
}
|
|
1705
|
+
}
|
|
1706
|
+
var init_player_stats2 = __esm({
|
|
1707
|
+
"src/api/player-stats.ts"() {
|
|
1708
|
+
"use strict";
|
|
1709
|
+
init_errors();
|
|
1710
|
+
init_result();
|
|
1711
|
+
init_afl_api();
|
|
1712
|
+
init_player_stats();
|
|
1713
|
+
}
|
|
1714
|
+
});
|
|
1715
|
+
|
|
1716
|
+
// src/api/teams.ts
|
|
1717
|
+
function teamTypeForComp(comp) {
|
|
1718
|
+
return comp === "AFLW" ? "WOMEN" : "MEN";
|
|
1719
|
+
}
|
|
1720
|
+
async function fetchTeams(query) {
|
|
1721
|
+
const client = new AflApiClient();
|
|
1722
|
+
const teamType = query?.teamType ?? (query?.competition ? teamTypeForComp(query.competition) : void 0);
|
|
1723
|
+
const result = await client.fetchTeams(teamType);
|
|
1724
|
+
if (!result.success) return result;
|
|
1725
|
+
const competition = query?.competition ?? "AFLM";
|
|
1726
|
+
return ok(
|
|
1727
|
+
result.data.map((t) => ({
|
|
1728
|
+
teamId: String(t.id),
|
|
1729
|
+
name: normaliseTeamName(t.name),
|
|
1730
|
+
abbreviation: t.abbreviation ?? "",
|
|
1731
|
+
competition
|
|
1732
|
+
}))
|
|
1733
|
+
);
|
|
1734
|
+
}
|
|
1735
|
+
async function fetchSquad(query) {
|
|
1736
|
+
const client = new AflApiClient();
|
|
1737
|
+
const competition = query.competition ?? "AFLM";
|
|
1738
|
+
const seasonResult = await client.resolveCompSeason(competition, query.season);
|
|
1739
|
+
if (!seasonResult.success) return seasonResult;
|
|
1740
|
+
const teamId = Number.parseInt(query.teamId, 10);
|
|
1741
|
+
if (Number.isNaN(teamId)) {
|
|
1742
|
+
return err(new ValidationError(`Invalid team ID: ${query.teamId}`));
|
|
1743
|
+
}
|
|
1744
|
+
const squadResult = await client.fetchSquad(teamId, seasonResult.data);
|
|
1745
|
+
if (!squadResult.success) return squadResult;
|
|
1746
|
+
const players = squadResult.data.squad.players.map((p) => ({
|
|
1747
|
+
playerId: p.player.providerId ?? String(p.player.id),
|
|
1748
|
+
givenName: p.player.firstName,
|
|
1749
|
+
surname: p.player.surname,
|
|
1750
|
+
displayName: `${p.player.firstName} ${p.player.surname}`,
|
|
1751
|
+
jumperNumber: p.jumperNumber ?? null,
|
|
1752
|
+
position: p.position ?? null,
|
|
1753
|
+
dateOfBirth: p.player.dateOfBirth ? new Date(p.player.dateOfBirth) : null,
|
|
1754
|
+
heightCm: p.player.heightInCm ?? null,
|
|
1755
|
+
weightKg: p.player.weightInKg ?? null,
|
|
1756
|
+
draftYear: p.player.draftYear ? Number.parseInt(p.player.draftYear, 10) || null : null,
|
|
1757
|
+
draftPosition: p.player.draftPosition ? Number.parseInt(p.player.draftPosition, 10) || null : null,
|
|
1758
|
+
draftType: p.player.draftType ?? null,
|
|
1759
|
+
debutYear: p.player.debutYear ? Number.parseInt(p.player.debutYear, 10) || null : null,
|
|
1760
|
+
recruitedFrom: p.player.recruitedFrom ?? null
|
|
1761
|
+
}));
|
|
1762
|
+
return ok({
|
|
1763
|
+
teamId: query.teamId,
|
|
1764
|
+
teamName: normaliseTeamName(squadResult.data.squad.team?.name ?? query.teamId),
|
|
1765
|
+
season: query.season,
|
|
1766
|
+
players,
|
|
1767
|
+
competition
|
|
1768
|
+
});
|
|
1769
|
+
}
|
|
1770
|
+
var init_teams = __esm({
|
|
1771
|
+
"src/api/teams.ts"() {
|
|
1772
|
+
"use strict";
|
|
1773
|
+
init_errors();
|
|
1774
|
+
init_result();
|
|
1775
|
+
init_team_mapping();
|
|
1776
|
+
init_afl_api();
|
|
1777
|
+
}
|
|
1778
|
+
});
|
|
18
1779
|
|
|
19
1780
|
// src/index.ts
|
|
20
|
-
var
|
|
1781
|
+
var init_index = __esm({
|
|
1782
|
+
"src/index.ts"() {
|
|
1783
|
+
"use strict";
|
|
1784
|
+
init_fixture();
|
|
1785
|
+
init_ladder2();
|
|
1786
|
+
init_lineup2();
|
|
1787
|
+
init_match_results2();
|
|
1788
|
+
init_player_stats2();
|
|
1789
|
+
init_teams();
|
|
1790
|
+
}
|
|
1791
|
+
});
|
|
21
1792
|
|
|
22
1793
|
// src/cli/formatters/csv.ts
|
|
23
1794
|
function escapeField(value) {
|
|
24
|
-
if (value.includes(",") || value.includes('"') || value.includes(
|
|
25
|
-
`) || value.includes("\r")) {
|
|
1795
|
+
if (value.includes(",") || value.includes('"') || value.includes("\n") || value.includes("\r")) {
|
|
26
1796
|
return `"${value.replace(/"/g, '""')}"`;
|
|
27
1797
|
}
|
|
28
1798
|
return value;
|
|
29
1799
|
}
|
|
30
1800
|
function toStringValue(value) {
|
|
31
|
-
if (value === null || value ===
|
|
32
|
-
|
|
33
|
-
if (value
|
|
34
|
-
return value.toISOString();
|
|
35
|
-
if (typeof value === "object")
|
|
36
|
-
return JSON.stringify(value);
|
|
1801
|
+
if (value === null || value === void 0) return "";
|
|
1802
|
+
if (value instanceof Date) return value.toISOString();
|
|
1803
|
+
if (typeof value === "object") return JSON.stringify(value);
|
|
37
1804
|
return String(value);
|
|
38
1805
|
}
|
|
39
1806
|
function formatCsv(data) {
|
|
40
|
-
if (data.length === 0)
|
|
41
|
-
return "";
|
|
1807
|
+
if (data.length === 0) return "";
|
|
42
1808
|
const firstRow = data[0];
|
|
43
|
-
if (!firstRow)
|
|
44
|
-
return "";
|
|
1809
|
+
if (!firstRow) return "";
|
|
45
1810
|
const headers = Object.keys(firstRow);
|
|
46
1811
|
const lines = [headers.map(escapeField).join(",")];
|
|
47
1812
|
for (const row of data) {
|
|
48
1813
|
const values = headers.map((h) => escapeField(toStringValue(row[h])));
|
|
49
1814
|
lines.push(values.join(","));
|
|
50
1815
|
}
|
|
51
|
-
return lines.join(
|
|
52
|
-
`);
|
|
1816
|
+
return lines.join("\n");
|
|
53
1817
|
}
|
|
1818
|
+
var init_csv = __esm({
|
|
1819
|
+
"src/cli/formatters/csv.ts"() {
|
|
1820
|
+
"use strict";
|
|
1821
|
+
}
|
|
1822
|
+
});
|
|
54
1823
|
|
|
55
1824
|
// src/cli/formatters/json.ts
|
|
56
1825
|
function formatJson(data) {
|
|
57
1826
|
return JSON.stringify(data, null, 2);
|
|
58
1827
|
}
|
|
1828
|
+
var init_json = __esm({
|
|
1829
|
+
"src/cli/formatters/json.ts"() {
|
|
1830
|
+
"use strict";
|
|
1831
|
+
}
|
|
1832
|
+
});
|
|
59
1833
|
|
|
60
1834
|
// src/cli/formatters/table.ts
|
|
61
1835
|
function toDisplayValue(value) {
|
|
62
|
-
if (value === null || value ===
|
|
63
|
-
|
|
64
|
-
if (value
|
|
65
|
-
return value.toISOString().slice(0, 16).replace("T", " ");
|
|
66
|
-
if (typeof value === "object")
|
|
67
|
-
return JSON.stringify(value);
|
|
1836
|
+
if (value === null || value === void 0) return "-";
|
|
1837
|
+
if (value instanceof Date) return value.toISOString().slice(0, 16).replace("T", " ");
|
|
1838
|
+
if (typeof value === "object") return JSON.stringify(value);
|
|
68
1839
|
return String(value);
|
|
69
1840
|
}
|
|
70
1841
|
function truncate(str, maxLen) {
|
|
71
|
-
if (str.length <= maxLen)
|
|
72
|
-
|
|
73
|
-
return `${str.slice(0, maxLen - 1)}…`;
|
|
1842
|
+
if (str.length <= maxLen) return str;
|
|
1843
|
+
return `${str.slice(0, maxLen - 1)}\u2026`;
|
|
74
1844
|
}
|
|
75
1845
|
function formatTable(data, options = {}) {
|
|
76
|
-
if (data.length === 0)
|
|
77
|
-
return "No data.";
|
|
1846
|
+
if (data.length === 0) return "No data.";
|
|
78
1847
|
const firstRow = data[0];
|
|
79
|
-
if (!firstRow)
|
|
80
|
-
return "No data.";
|
|
1848
|
+
if (!firstRow) return "No data.";
|
|
81
1849
|
const termWidth = options.terminalWidth ?? process.stdout.columns ?? 120;
|
|
82
1850
|
const allKeys = Object.keys(firstRow);
|
|
83
1851
|
let columns;
|
|
@@ -88,78 +1856,72 @@ function formatTable(data, options = {}) {
|
|
|
88
1856
|
}
|
|
89
1857
|
const colWidths = columns.map((col) => (col.label ?? col.key).length);
|
|
90
1858
|
for (const row of data) {
|
|
91
|
-
for (let i = 0;i < columns.length; i++) {
|
|
1859
|
+
for (let i = 0; i < columns.length; i++) {
|
|
92
1860
|
const col = columns[i];
|
|
93
|
-
if (!col)
|
|
94
|
-
continue;
|
|
1861
|
+
if (!col) continue;
|
|
95
1862
|
const len = toDisplayValue(row[col.key]).length;
|
|
96
1863
|
const current = colWidths[i];
|
|
97
|
-
if (current !==
|
|
1864
|
+
if (current !== void 0 && len > current) {
|
|
98
1865
|
colWidths[i] = len;
|
|
99
1866
|
}
|
|
100
1867
|
}
|
|
101
1868
|
}
|
|
102
|
-
for (let i = 0;i < columns.length; i++) {
|
|
1869
|
+
for (let i = 0; i < columns.length; i++) {
|
|
103
1870
|
const col = columns[i];
|
|
104
1871
|
const width = colWidths[i];
|
|
105
|
-
if (!col || width ===
|
|
106
|
-
continue;
|
|
1872
|
+
if (!col || width === void 0) continue;
|
|
107
1873
|
colWidths[i] = Math.min(col.maxWidth ?? 30, width);
|
|
108
1874
|
}
|
|
109
1875
|
const gap = 2;
|
|
110
1876
|
const visibleCols = [];
|
|
111
1877
|
let usedWidth = 0;
|
|
112
|
-
for (let i = 0;i < columns.length; i++) {
|
|
1878
|
+
for (let i = 0; i < columns.length; i++) {
|
|
113
1879
|
const colWidth = colWidths[i];
|
|
114
|
-
if (colWidth ===
|
|
115
|
-
continue;
|
|
1880
|
+
if (colWidth === void 0) continue;
|
|
116
1881
|
const needed = usedWidth > 0 ? colWidth + gap : colWidth;
|
|
117
|
-
if (usedWidth + needed > termWidth && visibleCols.length > 0)
|
|
118
|
-
break;
|
|
1882
|
+
if (usedWidth + needed > termWidth && visibleCols.length > 0) break;
|
|
119
1883
|
visibleCols.push(i);
|
|
120
1884
|
usedWidth += needed;
|
|
121
1885
|
}
|
|
122
1886
|
const headerParts = visibleCols.map((i) => {
|
|
123
1887
|
const col = columns[i];
|
|
124
1888
|
const width = colWidths[i];
|
|
125
|
-
if (!col || width ===
|
|
126
|
-
return "";
|
|
1889
|
+
if (!col || width === void 0) return "";
|
|
127
1890
|
const label = (col.label ?? col.key).toUpperCase();
|
|
128
1891
|
return truncate(label, width).padEnd(width);
|
|
129
1892
|
});
|
|
130
1893
|
const header = headerParts.join(" ");
|
|
131
1894
|
const separator = visibleCols.map((i) => {
|
|
132
1895
|
const width = colWidths[i];
|
|
133
|
-
if (width ===
|
|
134
|
-
|
|
135
|
-
return "─".repeat(width);
|
|
1896
|
+
if (width === void 0) return "";
|
|
1897
|
+
return "\u2500".repeat(width);
|
|
136
1898
|
}).join(" ");
|
|
137
1899
|
const rows = data.map((row) => {
|
|
138
1900
|
const parts = visibleCols.map((i) => {
|
|
139
1901
|
const col = columns[i];
|
|
140
1902
|
const width = colWidths[i];
|
|
141
|
-
if (!col || width ===
|
|
142
|
-
return "";
|
|
1903
|
+
if (!col || width === void 0) return "";
|
|
143
1904
|
const val = toDisplayValue(row[col.key]);
|
|
144
1905
|
return truncate(val, width).padEnd(width);
|
|
145
1906
|
});
|
|
146
1907
|
return parts.join(" ");
|
|
147
1908
|
});
|
|
148
|
-
return [header, separator, ...rows].join(
|
|
149
|
-
`);
|
|
1909
|
+
return [header, separator, ...rows].join("\n");
|
|
150
1910
|
}
|
|
1911
|
+
var init_table = __esm({
|
|
1912
|
+
"src/cli/formatters/table.ts"() {
|
|
1913
|
+
"use strict";
|
|
1914
|
+
}
|
|
1915
|
+
});
|
|
151
1916
|
|
|
152
1917
|
// src/cli/formatters/index.ts
|
|
153
1918
|
function resolveFormat(options) {
|
|
154
|
-
if (options.json)
|
|
155
|
-
|
|
156
|
-
if (options.csv)
|
|
157
|
-
return "csv";
|
|
1919
|
+
if (options.json) return "json";
|
|
1920
|
+
if (options.csv) return "csv";
|
|
158
1921
|
if (options.format === "json" || options.format === "csv" || options.format === "table") {
|
|
159
1922
|
return options.format;
|
|
160
1923
|
}
|
|
161
|
-
if (!process.stdout.isTTY)
|
|
162
|
-
return "json";
|
|
1924
|
+
if (!process.stdout.isTTY) return "json";
|
|
163
1925
|
return "table";
|
|
164
1926
|
}
|
|
165
1927
|
function formatOutput(data, options) {
|
|
@@ -177,7 +1939,14 @@ function formatOutput(data, options) {
|
|
|
177
1939
|
});
|
|
178
1940
|
}
|
|
179
1941
|
}
|
|
180
|
-
var init_formatters = (
|
|
1942
|
+
var init_formatters = __esm({
|
|
1943
|
+
"src/cli/formatters/index.ts"() {
|
|
1944
|
+
"use strict";
|
|
1945
|
+
init_csv();
|
|
1946
|
+
init_json();
|
|
1947
|
+
init_table();
|
|
1948
|
+
}
|
|
1949
|
+
});
|
|
181
1950
|
|
|
182
1951
|
// src/cli/ui.ts
|
|
183
1952
|
import { spinner } from "@clack/prompts";
|
|
@@ -198,517 +1967,526 @@ async function withSpinner(message, fn) {
|
|
|
198
1967
|
}
|
|
199
1968
|
}
|
|
200
1969
|
function showSummary(message) {
|
|
201
|
-
if (!isTTY)
|
|
202
|
-
return;
|
|
1970
|
+
if (!isTTY) return;
|
|
203
1971
|
console.error(pc.dim(message));
|
|
204
1972
|
}
|
|
205
1973
|
var isTTY;
|
|
206
|
-
var init_ui = __esm(
|
|
207
|
-
|
|
1974
|
+
var init_ui = __esm({
|
|
1975
|
+
"src/cli/ui.ts"() {
|
|
1976
|
+
"use strict";
|
|
1977
|
+
isTTY = process.stdout.isTTY === true;
|
|
1978
|
+
}
|
|
208
1979
|
});
|
|
209
1980
|
|
|
210
1981
|
// src/cli/commands/matches.ts
|
|
211
|
-
var
|
|
212
|
-
__export(
|
|
1982
|
+
var matches_exports = {};
|
|
1983
|
+
__export(matches_exports, {
|
|
213
1984
|
matchesCommand: () => matchesCommand
|
|
214
1985
|
});
|
|
215
1986
|
import { defineCommand } from "citty";
|
|
216
1987
|
var DEFAULT_COLUMNS, matchesCommand;
|
|
217
|
-
var init_matches = __esm(
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
season: { type: "string", description: "Season year (e.g. 2025)", required: true },
|
|
237
|
-
round: { type: "string", description: "Round number" },
|
|
238
|
-
source: { type: "string", description: "Data source", default: "afl-api" },
|
|
239
|
-
competition: {
|
|
240
|
-
type: "string",
|
|
241
|
-
description: "Competition code (AFLM or AFLW)",
|
|
242
|
-
default: "AFLM"
|
|
1988
|
+
var init_matches = __esm({
|
|
1989
|
+
"src/cli/commands/matches.ts"() {
|
|
1990
|
+
"use strict";
|
|
1991
|
+
init_index();
|
|
1992
|
+
init_formatters();
|
|
1993
|
+
init_ui();
|
|
1994
|
+
DEFAULT_COLUMNS = [
|
|
1995
|
+
{ key: "date", label: "Date", maxWidth: 16 },
|
|
1996
|
+
{ key: "roundNumber", label: "Round", maxWidth: 6 },
|
|
1997
|
+
{ key: "homeTeam", label: "Home", maxWidth: 20 },
|
|
1998
|
+
{ key: "awayTeam", label: "Away", maxWidth: 20 },
|
|
1999
|
+
{ key: "homePoints", label: "H.Pts", maxWidth: 6 },
|
|
2000
|
+
{ key: "awayPoints", label: "A.Pts", maxWidth: 6 },
|
|
2001
|
+
{ key: "venue", label: "Venue", maxWidth: 24 }
|
|
2002
|
+
];
|
|
2003
|
+
matchesCommand = defineCommand({
|
|
2004
|
+
meta: {
|
|
2005
|
+
name: "matches",
|
|
2006
|
+
description: "Fetch match results for a season"
|
|
243
2007
|
},
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
2008
|
+
args: {
|
|
2009
|
+
season: { type: "string", description: "Season year (e.g. 2025)", required: true },
|
|
2010
|
+
round: { type: "string", description: "Round number" },
|
|
2011
|
+
source: { type: "string", description: "Data source", default: "afl-api" },
|
|
2012
|
+
competition: {
|
|
2013
|
+
type: "string",
|
|
2014
|
+
description: "Competition code (AFLM or AFLW)",
|
|
2015
|
+
default: "AFLM"
|
|
2016
|
+
},
|
|
2017
|
+
json: { type: "boolean", description: "Output as JSON" },
|
|
2018
|
+
csv: { type: "boolean", description: "Output as CSV" },
|
|
2019
|
+
format: { type: "string", description: "Output format: table, json, csv" },
|
|
2020
|
+
full: { type: "boolean", description: "Show all columns in table output" }
|
|
2021
|
+
},
|
|
2022
|
+
async run({ args }) {
|
|
2023
|
+
const season = Number(args.season);
|
|
2024
|
+
const round = args.round ? Number(args.round) : void 0;
|
|
2025
|
+
const result = await withSpinner(
|
|
2026
|
+
"Fetching match results\u2026",
|
|
2027
|
+
() => fetchMatchResults({
|
|
2028
|
+
source: args.source,
|
|
2029
|
+
season,
|
|
2030
|
+
round,
|
|
2031
|
+
competition: args.competition
|
|
2032
|
+
})
|
|
2033
|
+
);
|
|
2034
|
+
if (!result.success) {
|
|
2035
|
+
throw result.error;
|
|
2036
|
+
}
|
|
2037
|
+
const data = result.data;
|
|
2038
|
+
showSummary(`Loaded ${data.length} matches for ${season}${round ? ` round ${round}` : ""}`);
|
|
2039
|
+
const formatOptions = {
|
|
2040
|
+
json: args.json,
|
|
2041
|
+
csv: args.csv,
|
|
2042
|
+
format: args.format,
|
|
2043
|
+
full: args.full,
|
|
2044
|
+
columns: DEFAULT_COLUMNS
|
|
2045
|
+
};
|
|
2046
|
+
console.log(formatOutput(data, formatOptions));
|
|
2047
|
+
}
|
|
2048
|
+
});
|
|
2049
|
+
}
|
|
273
2050
|
});
|
|
274
2051
|
|
|
275
2052
|
// src/cli/commands/stats.ts
|
|
276
|
-
var
|
|
277
|
-
__export(
|
|
2053
|
+
var stats_exports = {};
|
|
2054
|
+
__export(stats_exports, {
|
|
278
2055
|
statsCommand: () => statsCommand
|
|
279
2056
|
});
|
|
280
2057
|
import { defineCommand as defineCommand2 } from "citty";
|
|
281
2058
|
var DEFAULT_COLUMNS2, statsCommand;
|
|
282
|
-
var init_stats = __esm(
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
season: { type: "string", description: "Season year (e.g. 2025)", required: true },
|
|
302
|
-
round: { type: "string", description: "Round number" },
|
|
303
|
-
"match-id": { type: "string", description: "Specific match ID" },
|
|
304
|
-
source: { type: "string", description: "Data source", default: "afl-api" },
|
|
305
|
-
competition: {
|
|
306
|
-
type: "string",
|
|
307
|
-
description: "Competition code (AFLM or AFLW)",
|
|
308
|
-
default: "AFLM"
|
|
2059
|
+
var init_stats = __esm({
|
|
2060
|
+
"src/cli/commands/stats.ts"() {
|
|
2061
|
+
"use strict";
|
|
2062
|
+
init_index();
|
|
2063
|
+
init_formatters();
|
|
2064
|
+
init_ui();
|
|
2065
|
+
DEFAULT_COLUMNS2 = [
|
|
2066
|
+
{ key: "displayName", label: "Player", maxWidth: 22 },
|
|
2067
|
+
{ key: "team", label: "Team", maxWidth: 18 },
|
|
2068
|
+
{ key: "disposals", label: "Disp", maxWidth: 6 },
|
|
2069
|
+
{ key: "kicks", label: "Kicks", maxWidth: 6 },
|
|
2070
|
+
{ key: "handballs", label: "HB", maxWidth: 6 },
|
|
2071
|
+
{ key: "marks", label: "Marks", maxWidth: 6 },
|
|
2072
|
+
{ key: "goals", label: "Goals", maxWidth: 6 }
|
|
2073
|
+
];
|
|
2074
|
+
statsCommand = defineCommand2({
|
|
2075
|
+
meta: {
|
|
2076
|
+
name: "stats",
|
|
2077
|
+
description: "Fetch player statistics for a season"
|
|
309
2078
|
},
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
})
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
2079
|
+
args: {
|
|
2080
|
+
season: { type: "string", description: "Season year (e.g. 2025)", required: true },
|
|
2081
|
+
round: { type: "string", description: "Round number" },
|
|
2082
|
+
"match-id": { type: "string", description: "Specific match ID" },
|
|
2083
|
+
source: { type: "string", description: "Data source", default: "afl-api" },
|
|
2084
|
+
competition: {
|
|
2085
|
+
type: "string",
|
|
2086
|
+
description: "Competition code (AFLM or AFLW)",
|
|
2087
|
+
default: "AFLM"
|
|
2088
|
+
},
|
|
2089
|
+
json: { type: "boolean", description: "Output as JSON" },
|
|
2090
|
+
csv: { type: "boolean", description: "Output as CSV" },
|
|
2091
|
+
format: { type: "string", description: "Output format: table, json, csv" },
|
|
2092
|
+
full: { type: "boolean", description: "Show all columns in table output" }
|
|
2093
|
+
},
|
|
2094
|
+
async run({ args }) {
|
|
2095
|
+
const season = Number(args.season);
|
|
2096
|
+
const round = args.round ? Number(args.round) : void 0;
|
|
2097
|
+
const matchId = args["match-id"];
|
|
2098
|
+
const result = await withSpinner(
|
|
2099
|
+
"Fetching player stats\u2026",
|
|
2100
|
+
() => fetchPlayerStats({
|
|
2101
|
+
source: args.source,
|
|
2102
|
+
season,
|
|
2103
|
+
round,
|
|
2104
|
+
matchId,
|
|
2105
|
+
competition: args.competition
|
|
2106
|
+
})
|
|
2107
|
+
);
|
|
2108
|
+
if (!result.success) {
|
|
2109
|
+
throw result.error;
|
|
2110
|
+
}
|
|
2111
|
+
const data = result.data;
|
|
2112
|
+
showSummary(
|
|
2113
|
+
`Loaded ${data.length} player stat lines for ${season}${round ? ` round ${round}` : ""}`
|
|
2114
|
+
);
|
|
2115
|
+
const formatOptions = {
|
|
2116
|
+
json: args.json,
|
|
2117
|
+
csv: args.csv,
|
|
2118
|
+
format: args.format,
|
|
2119
|
+
full: args.full,
|
|
2120
|
+
columns: DEFAULT_COLUMNS2
|
|
2121
|
+
};
|
|
2122
|
+
console.log(formatOutput(data, formatOptions));
|
|
2123
|
+
}
|
|
2124
|
+
});
|
|
2125
|
+
}
|
|
341
2126
|
});
|
|
342
2127
|
|
|
343
2128
|
// src/cli/commands/fixture.ts
|
|
344
|
-
var
|
|
345
|
-
__export(
|
|
2129
|
+
var fixture_exports = {};
|
|
2130
|
+
__export(fixture_exports, {
|
|
346
2131
|
fixtureCommand: () => fixtureCommand
|
|
347
2132
|
});
|
|
348
2133
|
import { defineCommand as defineCommand3 } from "citty";
|
|
349
2134
|
var DEFAULT_COLUMNS3, fixtureCommand;
|
|
350
|
-
var
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
season: { type: "string", description: "Season year (e.g. 2025)", required: true },
|
|
368
|
-
round: { type: "string", description: "Round number" },
|
|
369
|
-
source: { type: "string", description: "Data source", default: "afl-api" },
|
|
370
|
-
competition: {
|
|
371
|
-
type: "string",
|
|
372
|
-
description: "Competition code (AFLM or AFLW)",
|
|
373
|
-
default: "AFLM"
|
|
2135
|
+
var init_fixture2 = __esm({
|
|
2136
|
+
"src/cli/commands/fixture.ts"() {
|
|
2137
|
+
"use strict";
|
|
2138
|
+
init_index();
|
|
2139
|
+
init_formatters();
|
|
2140
|
+
init_ui();
|
|
2141
|
+
DEFAULT_COLUMNS3 = [
|
|
2142
|
+
{ key: "roundNumber", label: "Round", maxWidth: 6 },
|
|
2143
|
+
{ key: "date", label: "Date", maxWidth: 16 },
|
|
2144
|
+
{ key: "homeTeam", label: "Home", maxWidth: 20 },
|
|
2145
|
+
{ key: "awayTeam", label: "Away", maxWidth: 20 },
|
|
2146
|
+
{ key: "venue", label: "Venue", maxWidth: 24 }
|
|
2147
|
+
];
|
|
2148
|
+
fixtureCommand = defineCommand3({
|
|
2149
|
+
meta: {
|
|
2150
|
+
name: "fixture",
|
|
2151
|
+
description: "Fetch fixture/schedule for a season"
|
|
374
2152
|
},
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
2153
|
+
args: {
|
|
2154
|
+
season: { type: "string", description: "Season year (e.g. 2025)", required: true },
|
|
2155
|
+
round: { type: "string", description: "Round number" },
|
|
2156
|
+
source: { type: "string", description: "Data source", default: "afl-api" },
|
|
2157
|
+
competition: {
|
|
2158
|
+
type: "string",
|
|
2159
|
+
description: "Competition code (AFLM or AFLW)",
|
|
2160
|
+
default: "AFLM"
|
|
2161
|
+
},
|
|
2162
|
+
json: { type: "boolean", description: "Output as JSON" },
|
|
2163
|
+
csv: { type: "boolean", description: "Output as CSV" },
|
|
2164
|
+
format: { type: "string", description: "Output format: table, json, csv" },
|
|
2165
|
+
full: { type: "boolean", description: "Show all columns in table output" }
|
|
2166
|
+
},
|
|
2167
|
+
async run({ args }) {
|
|
2168
|
+
const season = Number(args.season);
|
|
2169
|
+
const round = args.round ? Number(args.round) : void 0;
|
|
2170
|
+
const result = await withSpinner(
|
|
2171
|
+
"Fetching fixture\u2026",
|
|
2172
|
+
() => fetchFixture({
|
|
2173
|
+
source: args.source,
|
|
2174
|
+
season,
|
|
2175
|
+
round,
|
|
2176
|
+
competition: args.competition
|
|
2177
|
+
})
|
|
2178
|
+
);
|
|
2179
|
+
if (!result.success) {
|
|
2180
|
+
throw result.error;
|
|
2181
|
+
}
|
|
2182
|
+
const data = result.data;
|
|
2183
|
+
showSummary(`Loaded ${data.length} fixtures for ${season}${round ? ` round ${round}` : ""}`);
|
|
2184
|
+
const formatOptions = {
|
|
2185
|
+
json: args.json,
|
|
2186
|
+
csv: args.csv,
|
|
2187
|
+
format: args.format,
|
|
2188
|
+
full: args.full,
|
|
2189
|
+
columns: DEFAULT_COLUMNS3
|
|
2190
|
+
};
|
|
2191
|
+
console.log(formatOutput(data, formatOptions));
|
|
2192
|
+
}
|
|
2193
|
+
});
|
|
2194
|
+
}
|
|
404
2195
|
});
|
|
405
2196
|
|
|
406
2197
|
// src/cli/commands/ladder.ts
|
|
407
|
-
var
|
|
408
|
-
__export(
|
|
2198
|
+
var ladder_exports = {};
|
|
2199
|
+
__export(ladder_exports, {
|
|
409
2200
|
ladderCommand: () => ladderCommand
|
|
410
2201
|
});
|
|
411
2202
|
import { defineCommand as defineCommand4 } from "citty";
|
|
412
2203
|
var DEFAULT_COLUMNS4, ladderCommand;
|
|
413
|
-
var
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
season: { type: "string", description: "Season year (e.g. 2025)", required: true },
|
|
433
|
-
round: { type: "string", description: "Round number" },
|
|
434
|
-
source: { type: "string", description: "Data source", default: "afl-api" },
|
|
435
|
-
competition: {
|
|
436
|
-
type: "string",
|
|
437
|
-
description: "Competition code (AFLM or AFLW)",
|
|
438
|
-
default: "AFLM"
|
|
2204
|
+
var init_ladder3 = __esm({
|
|
2205
|
+
"src/cli/commands/ladder.ts"() {
|
|
2206
|
+
"use strict";
|
|
2207
|
+
init_index();
|
|
2208
|
+
init_formatters();
|
|
2209
|
+
init_ui();
|
|
2210
|
+
DEFAULT_COLUMNS4 = [
|
|
2211
|
+
{ key: "position", label: "Pos", maxWidth: 4 },
|
|
2212
|
+
{ key: "team", label: "Team", maxWidth: 24 },
|
|
2213
|
+
{ key: "wins", label: "W", maxWidth: 4 },
|
|
2214
|
+
{ key: "losses", label: "L", maxWidth: 4 },
|
|
2215
|
+
{ key: "draws", label: "D", maxWidth: 4 },
|
|
2216
|
+
{ key: "percentage", label: "Pct", maxWidth: 8 },
|
|
2217
|
+
{ key: "premiershipsPoints", label: "Pts", maxWidth: 5 }
|
|
2218
|
+
];
|
|
2219
|
+
ladderCommand = defineCommand4({
|
|
2220
|
+
meta: {
|
|
2221
|
+
name: "ladder",
|
|
2222
|
+
description: "Fetch ladder standings for a season"
|
|
439
2223
|
},
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
2224
|
+
args: {
|
|
2225
|
+
season: { type: "string", description: "Season year (e.g. 2025)", required: true },
|
|
2226
|
+
round: { type: "string", description: "Round number" },
|
|
2227
|
+
source: { type: "string", description: "Data source", default: "afl-api" },
|
|
2228
|
+
competition: {
|
|
2229
|
+
type: "string",
|
|
2230
|
+
description: "Competition code (AFLM or AFLW)",
|
|
2231
|
+
default: "AFLM"
|
|
2232
|
+
},
|
|
2233
|
+
json: { type: "boolean", description: "Output as JSON" },
|
|
2234
|
+
csv: { type: "boolean", description: "Output as CSV" },
|
|
2235
|
+
format: { type: "string", description: "Output format: table, json, csv" },
|
|
2236
|
+
full: { type: "boolean", description: "Show all columns in table output" }
|
|
2237
|
+
},
|
|
2238
|
+
async run({ args }) {
|
|
2239
|
+
const season = Number(args.season);
|
|
2240
|
+
const round = args.round ? Number(args.round) : void 0;
|
|
2241
|
+
const result = await withSpinner(
|
|
2242
|
+
"Fetching ladder\u2026",
|
|
2243
|
+
() => fetchLadder({
|
|
2244
|
+
source: args.source,
|
|
2245
|
+
season,
|
|
2246
|
+
round,
|
|
2247
|
+
competition: args.competition
|
|
2248
|
+
})
|
|
2249
|
+
);
|
|
2250
|
+
if (!result.success) {
|
|
2251
|
+
throw result.error;
|
|
2252
|
+
}
|
|
2253
|
+
const data = result.data;
|
|
2254
|
+
showSummary(
|
|
2255
|
+
`Loaded ladder for ${season}${round ? ` round ${round}` : ""} (${data.entries.length} teams)`
|
|
2256
|
+
);
|
|
2257
|
+
const formatOptions = {
|
|
2258
|
+
json: args.json,
|
|
2259
|
+
csv: args.csv,
|
|
2260
|
+
format: args.format,
|
|
2261
|
+
full: args.full,
|
|
2262
|
+
columns: DEFAULT_COLUMNS4
|
|
2263
|
+
};
|
|
2264
|
+
console.log(formatOutput(data.entries, formatOptions));
|
|
2265
|
+
}
|
|
2266
|
+
});
|
|
2267
|
+
}
|
|
469
2268
|
});
|
|
470
2269
|
|
|
471
2270
|
// src/cli/commands/lineup.ts
|
|
472
|
-
var
|
|
473
|
-
__export(
|
|
2271
|
+
var lineup_exports = {};
|
|
2272
|
+
__export(lineup_exports, {
|
|
474
2273
|
lineupCommand: () => lineupCommand
|
|
475
2274
|
});
|
|
476
2275
|
import { defineCommand as defineCommand5 } from "citty";
|
|
477
2276
|
var DEFAULT_COLUMNS5, lineupCommand;
|
|
478
|
-
var
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
season: { type: "string", description: "Season year (e.g. 2025)", required: true },
|
|
494
|
-
round: { type: "string", description: "Round number", required: true },
|
|
495
|
-
"match-id": { type: "string", description: "Specific match ID" },
|
|
496
|
-
source: { type: "string", description: "Data source", default: "afl-api" },
|
|
497
|
-
competition: {
|
|
498
|
-
type: "string",
|
|
499
|
-
description: "Competition code (AFLM or AFLW)",
|
|
500
|
-
default: "AFLM"
|
|
2277
|
+
var init_lineup3 = __esm({
|
|
2278
|
+
"src/cli/commands/lineup.ts"() {
|
|
2279
|
+
"use strict";
|
|
2280
|
+
init_index();
|
|
2281
|
+
init_formatters();
|
|
2282
|
+
init_ui();
|
|
2283
|
+
DEFAULT_COLUMNS5 = [
|
|
2284
|
+
{ key: "matchId", label: "Match", maxWidth: 12 },
|
|
2285
|
+
{ key: "homeTeam", label: "Home", maxWidth: 20 },
|
|
2286
|
+
{ key: "awayTeam", label: "Away", maxWidth: 20 }
|
|
2287
|
+
];
|
|
2288
|
+
lineupCommand = defineCommand5({
|
|
2289
|
+
meta: {
|
|
2290
|
+
name: "lineup",
|
|
2291
|
+
description: "Fetch match lineups for a round"
|
|
501
2292
|
},
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
})
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
2293
|
+
args: {
|
|
2294
|
+
season: { type: "string", description: "Season year (e.g. 2025)", required: true },
|
|
2295
|
+
round: { type: "string", description: "Round number", required: true },
|
|
2296
|
+
"match-id": { type: "string", description: "Specific match ID" },
|
|
2297
|
+
source: { type: "string", description: "Data source", default: "afl-api" },
|
|
2298
|
+
competition: {
|
|
2299
|
+
type: "string",
|
|
2300
|
+
description: "Competition code (AFLM or AFLW)",
|
|
2301
|
+
default: "AFLM"
|
|
2302
|
+
},
|
|
2303
|
+
json: { type: "boolean", description: "Output as JSON" },
|
|
2304
|
+
csv: { type: "boolean", description: "Output as CSV" },
|
|
2305
|
+
format: { type: "string", description: "Output format: table, json, csv" },
|
|
2306
|
+
full: { type: "boolean", description: "Show all columns in table output" }
|
|
2307
|
+
},
|
|
2308
|
+
async run({ args }) {
|
|
2309
|
+
const season = Number(args.season);
|
|
2310
|
+
const round = Number(args.round);
|
|
2311
|
+
const matchId = args["match-id"];
|
|
2312
|
+
const result = await withSpinner(
|
|
2313
|
+
"Fetching lineups\u2026",
|
|
2314
|
+
() => fetchLineup({
|
|
2315
|
+
source: args.source,
|
|
2316
|
+
season,
|
|
2317
|
+
round,
|
|
2318
|
+
matchId,
|
|
2319
|
+
competition: args.competition
|
|
2320
|
+
})
|
|
2321
|
+
);
|
|
2322
|
+
if (!result.success) {
|
|
2323
|
+
throw result.error;
|
|
2324
|
+
}
|
|
2325
|
+
const data = result.data;
|
|
2326
|
+
showSummary(`Loaded ${data.length} lineups for ${season} round ${round}`);
|
|
2327
|
+
const formatOptions = {
|
|
2328
|
+
json: args.json,
|
|
2329
|
+
csv: args.csv,
|
|
2330
|
+
format: args.format,
|
|
2331
|
+
full: args.full,
|
|
2332
|
+
columns: DEFAULT_COLUMNS5
|
|
2333
|
+
};
|
|
2334
|
+
console.log(formatOutput(data, formatOptions));
|
|
2335
|
+
}
|
|
2336
|
+
});
|
|
2337
|
+
}
|
|
533
2338
|
});
|
|
534
2339
|
|
|
535
2340
|
// src/cli/commands/squad.ts
|
|
536
|
-
var
|
|
537
|
-
__export(
|
|
2341
|
+
var squad_exports = {};
|
|
2342
|
+
__export(squad_exports, {
|
|
538
2343
|
squadCommand: () => squadCommand
|
|
539
2344
|
});
|
|
540
2345
|
import { defineCommand as defineCommand6 } from "citty";
|
|
541
2346
|
var DEFAULT_COLUMNS6, squadCommand;
|
|
542
|
-
var init_squad = __esm(
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
"team-id": { type: "string", description: "Team ID", required: true },
|
|
560
|
-
season: { type: "string", description: "Season year (e.g. 2025)", required: true },
|
|
561
|
-
competition: {
|
|
562
|
-
type: "string",
|
|
563
|
-
description: "Competition code (AFLM or AFLW)",
|
|
564
|
-
default: "AFLM"
|
|
2347
|
+
var init_squad = __esm({
|
|
2348
|
+
"src/cli/commands/squad.ts"() {
|
|
2349
|
+
"use strict";
|
|
2350
|
+
init_index();
|
|
2351
|
+
init_formatters();
|
|
2352
|
+
init_ui();
|
|
2353
|
+
DEFAULT_COLUMNS6 = [
|
|
2354
|
+
{ key: "displayName", label: "Player", maxWidth: 24 },
|
|
2355
|
+
{ key: "jumperNumber", label: "#", maxWidth: 4 },
|
|
2356
|
+
{ key: "position", label: "Pos", maxWidth: 12 },
|
|
2357
|
+
{ key: "heightCm", label: "Ht", maxWidth: 5 },
|
|
2358
|
+
{ key: "weightKg", label: "Wt", maxWidth: 5 }
|
|
2359
|
+
];
|
|
2360
|
+
squadCommand = defineCommand6({
|
|
2361
|
+
meta: {
|
|
2362
|
+
name: "squad",
|
|
2363
|
+
description: "Fetch team squad for a season"
|
|
565
2364
|
},
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
}
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
2365
|
+
args: {
|
|
2366
|
+
"team-id": { type: "string", description: "Team ID", required: true },
|
|
2367
|
+
season: { type: "string", description: "Season year (e.g. 2025)", required: true },
|
|
2368
|
+
competition: {
|
|
2369
|
+
type: "string",
|
|
2370
|
+
description: "Competition code (AFLM or AFLW)",
|
|
2371
|
+
default: "AFLM"
|
|
2372
|
+
},
|
|
2373
|
+
json: { type: "boolean", description: "Output as JSON" },
|
|
2374
|
+
csv: { type: "boolean", description: "Output as CSV" },
|
|
2375
|
+
format: { type: "string", description: "Output format: table, json, csv" },
|
|
2376
|
+
full: { type: "boolean", description: "Show all columns in table output" }
|
|
2377
|
+
},
|
|
2378
|
+
async run({ args }) {
|
|
2379
|
+
const teamId = args["team-id"];
|
|
2380
|
+
const season = Number(args.season);
|
|
2381
|
+
const result = await withSpinner(
|
|
2382
|
+
"Fetching squad\u2026",
|
|
2383
|
+
() => fetchSquad({
|
|
2384
|
+
teamId,
|
|
2385
|
+
season,
|
|
2386
|
+
competition: args.competition
|
|
2387
|
+
})
|
|
2388
|
+
);
|
|
2389
|
+
if (!result.success) {
|
|
2390
|
+
throw result.error;
|
|
2391
|
+
}
|
|
2392
|
+
const data = result.data;
|
|
2393
|
+
showSummary(`Loaded ${data.players.length} players for ${data.teamName} ${season}`);
|
|
2394
|
+
const formatOptions = {
|
|
2395
|
+
json: args.json,
|
|
2396
|
+
csv: args.csv,
|
|
2397
|
+
format: args.format,
|
|
2398
|
+
full: args.full,
|
|
2399
|
+
columns: DEFAULT_COLUMNS6
|
|
2400
|
+
};
|
|
2401
|
+
console.log(formatOutput(data.players, formatOptions));
|
|
2402
|
+
}
|
|
2403
|
+
});
|
|
2404
|
+
}
|
|
594
2405
|
});
|
|
595
2406
|
|
|
596
2407
|
// src/cli/commands/teams.ts
|
|
597
|
-
var
|
|
598
|
-
__export(
|
|
2408
|
+
var teams_exports = {};
|
|
2409
|
+
__export(teams_exports, {
|
|
599
2410
|
teamsCommand: () => teamsCommand
|
|
600
2411
|
});
|
|
601
2412
|
import { defineCommand as defineCommand7 } from "citty";
|
|
602
2413
|
var DEFAULT_COLUMNS7, teamsCommand;
|
|
603
|
-
var
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
2414
|
+
var init_teams2 = __esm({
|
|
2415
|
+
"src/cli/commands/teams.ts"() {
|
|
2416
|
+
"use strict";
|
|
2417
|
+
init_index();
|
|
2418
|
+
init_formatters();
|
|
2419
|
+
init_ui();
|
|
2420
|
+
DEFAULT_COLUMNS7 = [
|
|
2421
|
+
{ key: "teamId", label: "ID", maxWidth: 8 },
|
|
2422
|
+
{ key: "name", label: "Team", maxWidth: 24 },
|
|
2423
|
+
{ key: "abbreviation", label: "Abbr", maxWidth: 6 },
|
|
2424
|
+
{ key: "competition", label: "Comp", maxWidth: 6 }
|
|
2425
|
+
];
|
|
2426
|
+
teamsCommand = defineCommand7({
|
|
2427
|
+
meta: {
|
|
2428
|
+
name: "teams",
|
|
2429
|
+
description: "Fetch team list"
|
|
2430
|
+
},
|
|
2431
|
+
args: {
|
|
2432
|
+
competition: { type: "string", description: "Competition code (AFLM or AFLW)" },
|
|
2433
|
+
"team-type": { type: "string", description: "Team type filter" },
|
|
2434
|
+
json: { type: "boolean", description: "Output as JSON" },
|
|
2435
|
+
csv: { type: "boolean", description: "Output as CSV" },
|
|
2436
|
+
format: { type: "string", description: "Output format: table, json, csv" },
|
|
2437
|
+
full: { type: "boolean", description: "Show all columns in table output" }
|
|
2438
|
+
},
|
|
2439
|
+
async run({ args }) {
|
|
2440
|
+
const result = await withSpinner(
|
|
2441
|
+
"Fetching teams\u2026",
|
|
2442
|
+
() => fetchTeams({
|
|
2443
|
+
competition: args.competition,
|
|
2444
|
+
teamType: args["team-type"]
|
|
2445
|
+
})
|
|
2446
|
+
);
|
|
2447
|
+
if (!result.success) {
|
|
2448
|
+
throw result.error;
|
|
2449
|
+
}
|
|
2450
|
+
const data = result.data;
|
|
2451
|
+
showSummary(`Loaded ${data.length} teams`);
|
|
2452
|
+
const formatOptions = {
|
|
2453
|
+
json: args.json,
|
|
2454
|
+
csv: args.csv,
|
|
2455
|
+
format: args.format,
|
|
2456
|
+
full: args.full,
|
|
2457
|
+
columns: DEFAULT_COLUMNS7
|
|
2458
|
+
};
|
|
2459
|
+
console.log(formatOutput(data, formatOptions));
|
|
2460
|
+
}
|
|
2461
|
+
});
|
|
2462
|
+
}
|
|
646
2463
|
});
|
|
647
2464
|
|
|
648
2465
|
// src/cli.ts
|
|
2466
|
+
init_errors();
|
|
649
2467
|
import { defineCommand as defineCommand8, runMain } from "citty";
|
|
650
2468
|
import pc2 from "picocolors";
|
|
651
|
-
|
|
652
|
-
// src/lib/errors.ts
|
|
653
|
-
class AflApiError extends Error {
|
|
654
|
-
statusCode;
|
|
655
|
-
name = "AflApiError";
|
|
656
|
-
constructor(message, statusCode) {
|
|
657
|
-
super(message);
|
|
658
|
-
this.statusCode = statusCode;
|
|
659
|
-
}
|
|
660
|
-
}
|
|
661
|
-
|
|
662
|
-
class ScrapeError extends Error {
|
|
663
|
-
source;
|
|
664
|
-
name = "ScrapeError";
|
|
665
|
-
constructor(message, source) {
|
|
666
|
-
super(message);
|
|
667
|
-
this.source = source;
|
|
668
|
-
}
|
|
669
|
-
}
|
|
670
|
-
|
|
671
|
-
class UnsupportedSourceError extends Error {
|
|
672
|
-
source;
|
|
673
|
-
name = "UnsupportedSourceError";
|
|
674
|
-
constructor(message, source) {
|
|
675
|
-
super(message);
|
|
676
|
-
this.source = source;
|
|
677
|
-
}
|
|
678
|
-
}
|
|
679
|
-
|
|
680
|
-
class ValidationError extends Error {
|
|
681
|
-
issues;
|
|
682
|
-
name = "ValidationError";
|
|
683
|
-
constructor(message, issues) {
|
|
684
|
-
super(message);
|
|
685
|
-
this.issues = issues;
|
|
686
|
-
}
|
|
687
|
-
}
|
|
688
|
-
|
|
689
|
-
// src/cli.ts
|
|
690
2469
|
var main = defineCommand8({
|
|
691
2470
|
meta: {
|
|
692
2471
|
name: "fitzroy",
|
|
693
|
-
version: "1.0.
|
|
694
|
-
description: "CLI for fetching AFL data
|
|
2472
|
+
version: "1.0.2",
|
|
2473
|
+
description: "CLI for fetching AFL data \u2014 match results, player stats, fixtures, ladders, and more"
|
|
695
2474
|
},
|
|
696
2475
|
subCommands: {
|
|
697
|
-
matches: () => Promise.resolve().then(() => (init_matches(),
|
|
698
|
-
stats: () => Promise.resolve().then(() => (init_stats(),
|
|
699
|
-
fixture: () => Promise.resolve().then(() => (
|
|
700
|
-
ladder: () => Promise.resolve().then(() => (
|
|
701
|
-
lineup: () => Promise.resolve().then(() => (
|
|
702
|
-
squad: () => Promise.resolve().then(() => (init_squad(),
|
|
703
|
-
teams: () => Promise.resolve().then(() => (
|
|
2476
|
+
matches: () => Promise.resolve().then(() => (init_matches(), matches_exports)).then((m) => m.matchesCommand),
|
|
2477
|
+
stats: () => Promise.resolve().then(() => (init_stats(), stats_exports)).then((m) => m.statsCommand),
|
|
2478
|
+
fixture: () => Promise.resolve().then(() => (init_fixture2(), fixture_exports)).then((m) => m.fixtureCommand),
|
|
2479
|
+
ladder: () => Promise.resolve().then(() => (init_ladder3(), ladder_exports)).then((m) => m.ladderCommand),
|
|
2480
|
+
lineup: () => Promise.resolve().then(() => (init_lineup3(), lineup_exports)).then((m) => m.lineupCommand),
|
|
2481
|
+
squad: () => Promise.resolve().then(() => (init_squad(), squad_exports)).then((m) => m.squadCommand),
|
|
2482
|
+
teams: () => Promise.resolve().then(() => (init_teams2(), teams_exports)).then((m) => m.teamsCommand)
|
|
704
2483
|
}
|
|
705
2484
|
});
|
|
706
2485
|
function formatError(error) {
|
|
707
2486
|
if (error instanceof ValidationError && error.issues) {
|
|
708
2487
|
const issueLines = error.issues.map((i) => ` ${pc2.yellow(i.path)}: ${i.message}`);
|
|
709
2488
|
return `${pc2.red("Validation error:")}
|
|
710
|
-
${issueLines.join(
|
|
711
|
-
`)}`;
|
|
2489
|
+
${issueLines.join("\n")}`;
|
|
712
2490
|
}
|
|
713
2491
|
if (error instanceof AflApiError) {
|
|
714
2492
|
const status = error.statusCode ? ` (HTTP ${error.statusCode})` : "";
|