fitzroy 1.4.2 → 1.6.0
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 +220 -102
- package/dist/index.d.ts +75 -15
- package/dist/index.js +209 -94
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -489,6 +489,9 @@ function mergeFootyWireStats(basicTeams, advancedTeams, matchId, season, roundNu
|
|
|
489
489
|
roundNumber,
|
|
490
490
|
team: teamName,
|
|
491
491
|
competition: "AFLM",
|
|
492
|
+
date: null,
|
|
493
|
+
homeTeam: null,
|
|
494
|
+
awayTeam: null,
|
|
492
495
|
playerId: `FW_${basic.player.replace(/\s+/g, "_")}`,
|
|
493
496
|
givenName: firstName,
|
|
494
497
|
surname,
|
|
@@ -528,6 +531,12 @@ function mergeFootyWireStats(basicTeams, advancedTeams, matchId, season, roundNu
|
|
|
528
531
|
totalPossessions: null,
|
|
529
532
|
timeOnGroundPercentage: adv?.timeOnGroundPercentage ?? null,
|
|
530
533
|
ratingPoints: null,
|
|
534
|
+
position: null,
|
|
535
|
+
goalEfficiency: null,
|
|
536
|
+
shotEfficiency: null,
|
|
537
|
+
interchangeCounts: null,
|
|
538
|
+
brownlowVotes: null,
|
|
539
|
+
supercoachScore: basic.supercoachPoints,
|
|
531
540
|
dreamTeamPoints: basic.dreamTeamPoints,
|
|
532
541
|
effectiveDisposals: adv?.effectiveDisposals ?? null,
|
|
533
542
|
effectiveKicks: null,
|
|
@@ -564,6 +573,22 @@ function mergeFootyWireStats(basicTeams, advancedTeams, matchId, season, roundNu
|
|
|
564
573
|
|
|
565
574
|
// src/transforms/match-results.ts
|
|
566
575
|
var FINALS_PATTERN = /final|elimination|qualifying|preliminary|semi|grand/i;
|
|
576
|
+
var ROUND_CODE_MAP = /* @__PURE__ */ new Map([
|
|
577
|
+
["Qualifying Final", "QF"],
|
|
578
|
+
["Elimination Final", "EF"],
|
|
579
|
+
["Semi Final", "SF"],
|
|
580
|
+
["Preliminary Final", "PF"],
|
|
581
|
+
["Grand Final", "GF"]
|
|
582
|
+
]);
|
|
583
|
+
var ROUND_NUMBER_PATTERN = /^Round\s+(\d+)$/i;
|
|
584
|
+
function toRoundCode(roundName) {
|
|
585
|
+
if (!roundName) return null;
|
|
586
|
+
const mapped = ROUND_CODE_MAP.get(roundName);
|
|
587
|
+
if (mapped) return mapped;
|
|
588
|
+
const m = ROUND_NUMBER_PATTERN.exec(roundName);
|
|
589
|
+
if (m?.[1]) return `R${m[1]}`;
|
|
590
|
+
return roundName;
|
|
591
|
+
}
|
|
567
592
|
function inferRoundType(roundName) {
|
|
568
593
|
return FINALS_PATTERN.test(roundName) ? "Finals" : "HomeAndAway";
|
|
569
594
|
}
|
|
@@ -617,6 +642,7 @@ function transformMatchItems(items, season, competition, source = "afl-api") {
|
|
|
617
642
|
season,
|
|
618
643
|
roundNumber: item.round?.roundNumber ?? 0,
|
|
619
644
|
roundType: inferRoundType(item.round?.name ?? ""),
|
|
645
|
+
roundName: item.round?.name ?? null,
|
|
620
646
|
date: new Date(item.match.utcStartTime),
|
|
621
647
|
venue: item.venue?.name ? normaliseVenueName(item.venue.name) : "",
|
|
622
648
|
homeTeam: normaliseTeamName(item.match.homeTeam.name),
|
|
@@ -637,7 +663,10 @@ function transformMatchItems(items, season, competition, source = "afl-api") {
|
|
|
637
663
|
q3Away: findPeriod(awayScore?.periodScore, 3),
|
|
638
664
|
q4Away: findPeriod(awayScore?.periodScore, 4),
|
|
639
665
|
status: toMatchStatus(item.match.status),
|
|
640
|
-
attendance: null,
|
|
666
|
+
attendance: item.attendance ?? null,
|
|
667
|
+
weatherTempCelsius: item.weather?.tempInCelsius ?? null,
|
|
668
|
+
weatherType: item.weather?.weatherType ?? null,
|
|
669
|
+
roundCode: toRoundCode(item.round?.name),
|
|
641
670
|
venueState: item.venue?.state ?? null,
|
|
642
671
|
venueTimezone: item.venue?.timeZone ?? null,
|
|
643
672
|
homeRushedBehinds: homeScore?.rushedBehinds ?? null,
|
|
@@ -750,10 +779,10 @@ var FootyWireClient = class {
|
|
|
750
779
|
/**
|
|
751
780
|
* Fetch match IDs from a season's match list page.
|
|
752
781
|
*
|
|
753
|
-
* Extracts `mid=XXXX` values from score links.
|
|
782
|
+
* Extracts `mid=XXXX` values from score links alongside round numbers.
|
|
754
783
|
*
|
|
755
784
|
* @param year - The season year.
|
|
756
|
-
* @returns Array of match ID
|
|
785
|
+
* @returns Array of match ID + round number pairs.
|
|
757
786
|
*/
|
|
758
787
|
async fetchSeasonMatchIds(year) {
|
|
759
788
|
const url = `${FOOTYWIRE_BASE}/ft_match_list?year=${year}`;
|
|
@@ -761,15 +790,33 @@ var FootyWireClient = class {
|
|
|
761
790
|
if (!htmlResult.success) return htmlResult;
|
|
762
791
|
try {
|
|
763
792
|
const $ = cheerio2.load(htmlResult.data);
|
|
764
|
-
const
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
793
|
+
const entries = [];
|
|
794
|
+
let currentRound = 0;
|
|
795
|
+
let lastHARound = 0;
|
|
796
|
+
$("tr").each((_i, row) => {
|
|
797
|
+
const roundHeader = $(row).find("td[colspan='7']");
|
|
798
|
+
if (roundHeader.length > 0) {
|
|
799
|
+
const text = roundHeader.text().trim();
|
|
800
|
+
const roundMatch = /Round\s+(\d+)/i.exec(text);
|
|
801
|
+
if (roundMatch?.[1]) {
|
|
802
|
+
currentRound = Number.parseInt(roundMatch[1], 10);
|
|
803
|
+
if (inferRoundType(text) === "HomeAndAway") {
|
|
804
|
+
lastHARound = currentRound;
|
|
805
|
+
}
|
|
806
|
+
} else if (inferRoundType(text) === "Finals") {
|
|
807
|
+
currentRound = finalsRoundNumber(text, lastHARound);
|
|
808
|
+
}
|
|
809
|
+
return;
|
|
810
|
+
}
|
|
811
|
+
const scoreLink = $(row).find(".data:nth-child(5) a");
|
|
812
|
+
if (scoreLink.length === 0) return;
|
|
813
|
+
const href = scoreLink.attr("href") ?? "";
|
|
814
|
+
const midMatch = /mid=(\d+)/.exec(href);
|
|
815
|
+
if (midMatch?.[1]) {
|
|
816
|
+
entries.push({ matchId: midMatch[1], roundNumber: currentRound });
|
|
770
817
|
}
|
|
771
818
|
});
|
|
772
|
-
return ok(
|
|
819
|
+
return ok(entries);
|
|
773
820
|
} catch (cause) {
|
|
774
821
|
return err(
|
|
775
822
|
new ScrapeError(
|
|
@@ -873,10 +920,12 @@ function parseMatchList(html, year) {
|
|
|
873
920
|
let currentRound = 0;
|
|
874
921
|
let lastHARound = 0;
|
|
875
922
|
let currentRoundType = "HomeAndAway";
|
|
923
|
+
let currentRoundName = "";
|
|
876
924
|
$("tr").each((_i, row) => {
|
|
877
925
|
const roundHeader = $(row).find("td[colspan='7']");
|
|
878
926
|
if (roundHeader.length > 0) {
|
|
879
927
|
const text = roundHeader.text().trim();
|
|
928
|
+
currentRoundName = text;
|
|
880
929
|
currentRoundType = inferRoundType(text);
|
|
881
930
|
const roundMatch = /Round\s+(\d+)/i.exec(text);
|
|
882
931
|
if (roundMatch?.[1]) {
|
|
@@ -919,6 +968,7 @@ function parseMatchList(html, year) {
|
|
|
919
968
|
season: year,
|
|
920
969
|
roundNumber: currentRound,
|
|
921
970
|
roundType: currentRoundType,
|
|
971
|
+
roundName: currentRoundName || null,
|
|
922
972
|
date,
|
|
923
973
|
venue: normaliseVenueName(venue),
|
|
924
974
|
homeTeam,
|
|
@@ -940,6 +990,9 @@ function parseMatchList(html, year) {
|
|
|
940
990
|
q4Away: null,
|
|
941
991
|
status: "Complete",
|
|
942
992
|
attendance: attendance ? Number.parseInt(attendance, 10) || null : null,
|
|
993
|
+
weatherTempCelsius: null,
|
|
994
|
+
weatherType: null,
|
|
995
|
+
roundCode: toRoundCode(currentRoundName),
|
|
943
996
|
venueState: null,
|
|
944
997
|
venueTimezone: null,
|
|
945
998
|
homeRushedBehinds: null,
|
|
@@ -1605,6 +1658,10 @@ var CfsVenueSchema = z.object({
|
|
|
1605
1658
|
state: z.string().optional(),
|
|
1606
1659
|
timeZone: z.string().optional()
|
|
1607
1660
|
}).passthrough();
|
|
1661
|
+
var CfsWeatherSchema = z.object({
|
|
1662
|
+
tempInCelsius: z.number().nullable().optional(),
|
|
1663
|
+
weatherType: z.string().nullable().optional()
|
|
1664
|
+
}).passthrough();
|
|
1608
1665
|
var MatchItemSchema = z.object({
|
|
1609
1666
|
match: CfsMatchSchema,
|
|
1610
1667
|
score: CfsScoreSchema.nullish(),
|
|
@@ -1613,7 +1670,9 @@ var MatchItemSchema = z.object({
|
|
|
1613
1670
|
name: z.string(),
|
|
1614
1671
|
roundId: z.string(),
|
|
1615
1672
|
roundNumber: z.number()
|
|
1616
|
-
}).passthrough().optional()
|
|
1673
|
+
}).passthrough().optional(),
|
|
1674
|
+
attendance: z.number().nullable().optional(),
|
|
1675
|
+
weather: CfsWeatherSchema.nullable().optional()
|
|
1617
1676
|
}).passthrough();
|
|
1618
1677
|
var MatchItemListSchema = z.object({
|
|
1619
1678
|
roundId: z.string().optional(),
|
|
@@ -1674,6 +1733,10 @@ var PlayerGameStatsSchema = z.object({
|
|
|
1674
1733
|
metresGained: statNum,
|
|
1675
1734
|
scoreInvolvements: statNum,
|
|
1676
1735
|
ratingPoints: statNum,
|
|
1736
|
+
goalEfficiency: statNum,
|
|
1737
|
+
shotEfficiency: statNum,
|
|
1738
|
+
interchangeCounts: statNum,
|
|
1739
|
+
brownlowVotes: statNum,
|
|
1677
1740
|
extendedStats: z.object({
|
|
1678
1741
|
effectiveDisposals: statNum,
|
|
1679
1742
|
effectiveKicks: statNum,
|
|
@@ -1718,8 +1781,8 @@ var PlayerStatsItemSchema = z.object({
|
|
|
1718
1781
|
}).passthrough().nullable().optional()
|
|
1719
1782
|
}).passthrough();
|
|
1720
1783
|
var PlayerStatsListSchema = z.object({
|
|
1721
|
-
homeTeamPlayerStats: z.array(PlayerStatsItemSchema),
|
|
1722
|
-
awayTeamPlayerStats: z.array(PlayerStatsItemSchema)
|
|
1784
|
+
homeTeamPlayerStats: z.array(PlayerStatsItemSchema).nullable().default([]),
|
|
1785
|
+
awayTeamPlayerStats: z.array(PlayerStatsItemSchema).nullable().default([])
|
|
1723
1786
|
}).passthrough();
|
|
1724
1787
|
var RosterPlayerSchema = z.object({
|
|
1725
1788
|
player: z.object({
|
|
@@ -2313,6 +2376,7 @@ function transformSquiggleGamesToResults(games, season) {
|
|
|
2313
2376
|
season,
|
|
2314
2377
|
roundNumber: g.round,
|
|
2315
2378
|
roundType: inferRoundType(g.roundname),
|
|
2379
|
+
roundName: g.roundname || null,
|
|
2316
2380
|
date: new Date(g.unixtime * 1e3),
|
|
2317
2381
|
venue: normaliseVenueName(g.venue),
|
|
2318
2382
|
homeTeam: normaliseTeamName(g.hteam),
|
|
@@ -2334,6 +2398,9 @@ function transformSquiggleGamesToResults(games, season) {
|
|
|
2334
2398
|
q4Away: null,
|
|
2335
2399
|
status: "Complete",
|
|
2336
2400
|
attendance: null,
|
|
2401
|
+
weatherTempCelsius: null,
|
|
2402
|
+
weatherType: null,
|
|
2403
|
+
roundCode: toRoundCode(g.roundname),
|
|
2337
2404
|
venueState: null,
|
|
2338
2405
|
venueTimezone: g.tz || null,
|
|
2339
2406
|
homeRushedBehinds: null,
|
|
@@ -2481,6 +2548,9 @@ function parseAflTablesGameStats(html, matchId, season, roundNumber) {
|
|
|
2481
2548
|
roundNumber,
|
|
2482
2549
|
team: teamName,
|
|
2483
2550
|
competition: "AFLM",
|
|
2551
|
+
date: null,
|
|
2552
|
+
homeTeam: null,
|
|
2553
|
+
awayTeam: null,
|
|
2484
2554
|
playerId: `AT_${displayName.replace(/\s+/g, "_")}`,
|
|
2485
2555
|
givenName,
|
|
2486
2556
|
surname,
|
|
@@ -2520,6 +2590,12 @@ function parseAflTablesGameStats(html, matchId, season, roundNumber) {
|
|
|
2520
2590
|
totalPossessions: null,
|
|
2521
2591
|
timeOnGroundPercentage: safeInt(cells[24] ?? ""),
|
|
2522
2592
|
ratingPoints: null,
|
|
2593
|
+
position: null,
|
|
2594
|
+
goalEfficiency: null,
|
|
2595
|
+
shotEfficiency: null,
|
|
2596
|
+
interchangeCounts: null,
|
|
2597
|
+
brownlowVotes: null,
|
|
2598
|
+
supercoachScore: null,
|
|
2523
2599
|
dreamTeamPoints: null,
|
|
2524
2600
|
effectiveDisposals: null,
|
|
2525
2601
|
effectiveKicks: null,
|
|
@@ -2743,6 +2819,7 @@ function parseSeasonPage(html, year) {
|
|
|
2743
2819
|
const results = [];
|
|
2744
2820
|
let currentRound = 0;
|
|
2745
2821
|
let currentRoundType = "HomeAndAway";
|
|
2822
|
+
let currentRoundName = "";
|
|
2746
2823
|
let lastHARound = 0;
|
|
2747
2824
|
let matchCounter = 0;
|
|
2748
2825
|
$("table").each((_i, table) => {
|
|
@@ -2753,6 +2830,7 @@ function parseSeasonPage(html, year) {
|
|
|
2753
2830
|
if (roundMatch?.[1] && border !== "1") {
|
|
2754
2831
|
currentRound = Number.parseInt(roundMatch[1], 10);
|
|
2755
2832
|
currentRoundType = inferRoundType(text);
|
|
2833
|
+
currentRoundName = text;
|
|
2756
2834
|
if (currentRoundType === "HomeAndAway") {
|
|
2757
2835
|
lastHARound = currentRound;
|
|
2758
2836
|
}
|
|
@@ -2760,6 +2838,7 @@ function parseSeasonPage(html, year) {
|
|
|
2760
2838
|
}
|
|
2761
2839
|
if (border !== "1" && inferRoundType(text) === "Finals") {
|
|
2762
2840
|
currentRoundType = "Finals";
|
|
2841
|
+
currentRoundName = text;
|
|
2763
2842
|
currentRound = finalsRoundNumber(text, lastHARound);
|
|
2764
2843
|
return;
|
|
2765
2844
|
}
|
|
@@ -2790,6 +2869,7 @@ function parseSeasonPage(html, year) {
|
|
|
2790
2869
|
season: year,
|
|
2791
2870
|
roundNumber: currentRound,
|
|
2792
2871
|
roundType: currentRoundType,
|
|
2872
|
+
roundName: currentRoundName || null,
|
|
2793
2873
|
date,
|
|
2794
2874
|
venue,
|
|
2795
2875
|
homeTeam,
|
|
@@ -2811,6 +2891,9 @@ function parseSeasonPage(html, year) {
|
|
|
2811
2891
|
q4Away: awayQuarters[3] ?? null,
|
|
2812
2892
|
status: "Complete",
|
|
2813
2893
|
attendance,
|
|
2894
|
+
weatherTempCelsius: null,
|
|
2895
|
+
weatherType: null,
|
|
2896
|
+
roundCode: toRoundCode(currentRoundName),
|
|
2814
2897
|
venueState: null,
|
|
2815
2898
|
venueTimezone: null,
|
|
2816
2899
|
homeRushedBehinds: null,
|
|
@@ -3265,29 +3348,14 @@ async function resolveTeamId(client, teamName, competition) {
|
|
|
3265
3348
|
}
|
|
3266
3349
|
return ok(String(match.id));
|
|
3267
3350
|
}
|
|
3268
|
-
|
|
3269
|
-
const
|
|
3270
|
-
|
|
3271
|
-
const season = query.season ?? resolveDefaultSeason(competition);
|
|
3272
|
-
const [teamIdResult, seasonResult] = await Promise.all([
|
|
3273
|
-
resolveTeamId(client, query.team, competition),
|
|
3274
|
-
client.resolveCompSeason(competition, season)
|
|
3275
|
-
]);
|
|
3276
|
-
if (!teamIdResult.success) return teamIdResult;
|
|
3277
|
-
if (!seasonResult.success) return seasonResult;
|
|
3278
|
-
const teamId = Number.parseInt(teamIdResult.data, 10);
|
|
3279
|
-
if (Number.isNaN(teamId)) {
|
|
3280
|
-
return err(new ValidationError(`Invalid team ID: ${teamIdResult.data}`));
|
|
3281
|
-
}
|
|
3282
|
-
const squadResult = await client.fetchSquad(teamId, seasonResult.data);
|
|
3283
|
-
if (!squadResult.success) return squadResult;
|
|
3284
|
-
const teamName = normaliseTeamName(squadResult.data.squad.team?.name ?? query.team);
|
|
3285
|
-
const players = squadResult.data.squad.players.map((p) => ({
|
|
3351
|
+
function mapSquadToPlayerDetails(data, fallbackTeamName, competition) {
|
|
3352
|
+
const resolvedName = normaliseTeamName(data.squad.team?.name ?? fallbackTeamName);
|
|
3353
|
+
return data.squad.players.map((p) => ({
|
|
3286
3354
|
playerId: p.player.providerId ?? String(p.player.id),
|
|
3287
3355
|
givenName: p.player.firstName,
|
|
3288
3356
|
surname: p.player.surname,
|
|
3289
3357
|
displayName: `${p.player.firstName} ${p.player.surname}`,
|
|
3290
|
-
team:
|
|
3358
|
+
team: resolvedName,
|
|
3291
3359
|
jumperNumber: p.jumperNumber ?? null,
|
|
3292
3360
|
position: p.position ?? null,
|
|
3293
3361
|
dateOfBirth: p.player.dateOfBirth ?? null,
|
|
@@ -3303,35 +3371,83 @@ async function fetchFromAflApi(query) {
|
|
|
3303
3371
|
source: "afl-api",
|
|
3304
3372
|
competition
|
|
3305
3373
|
}));
|
|
3306
|
-
|
|
3374
|
+
}
|
|
3375
|
+
async function fetchFromAflApi(query) {
|
|
3376
|
+
const client = new AflApiClient();
|
|
3377
|
+
const competition = query.competition ?? "AFLM";
|
|
3378
|
+
const season = query.season ?? resolveDefaultSeason(competition);
|
|
3379
|
+
const seasonResult = await client.resolveCompSeason(competition, season);
|
|
3380
|
+
if (!seasonResult.success) return seasonResult;
|
|
3381
|
+
if (query.team) {
|
|
3382
|
+
const teamIdResult = await resolveTeamId(client, query.team, competition);
|
|
3383
|
+
if (!teamIdResult.success) return teamIdResult;
|
|
3384
|
+
const teamId = Number.parseInt(teamIdResult.data, 10);
|
|
3385
|
+
if (Number.isNaN(teamId)) {
|
|
3386
|
+
return err(new ValidationError(`Invalid team ID: ${teamIdResult.data}`));
|
|
3387
|
+
}
|
|
3388
|
+
const squadResult = await client.fetchSquad(teamId, seasonResult.data);
|
|
3389
|
+
if (!squadResult.success) return squadResult;
|
|
3390
|
+
return ok(mapSquadToPlayerDetails(squadResult.data, query.team, competition));
|
|
3391
|
+
}
|
|
3392
|
+
const teamType = competition === "AFLW" ? "WOMEN" : "MEN";
|
|
3393
|
+
const teamsResult = await client.fetchTeams(teamType);
|
|
3394
|
+
if (!teamsResult.success) return teamsResult;
|
|
3395
|
+
const teamEntries = teamsResult.data.map((t) => ({
|
|
3396
|
+
id: Number.parseInt(String(t.id), 10),
|
|
3397
|
+
name: normaliseTeamName(t.name)
|
|
3398
|
+
}));
|
|
3399
|
+
const results = await batchedMap(
|
|
3400
|
+
teamEntries,
|
|
3401
|
+
(entry) => client.fetchSquad(entry.id, seasonResult.data)
|
|
3402
|
+
);
|
|
3403
|
+
const allPlayers = [];
|
|
3404
|
+
for (let i = 0; i < results.length; i++) {
|
|
3405
|
+
const result = results[i];
|
|
3406
|
+
const entry = teamEntries[i];
|
|
3407
|
+
if (result?.success && entry) {
|
|
3408
|
+
allPlayers.push(...mapSquadToPlayerDetails(result.data, entry.name, competition));
|
|
3409
|
+
}
|
|
3410
|
+
}
|
|
3411
|
+
return ok(allPlayers);
|
|
3412
|
+
}
|
|
3413
|
+
async function fetchAllTeamsFromScraper(fetchFn, source, competition) {
|
|
3414
|
+
const teamNames = [...AFL_SENIOR_TEAMS];
|
|
3415
|
+
const results = await batchedMap(teamNames, (name) => fetchFn(name));
|
|
3416
|
+
const allPlayers = [];
|
|
3417
|
+
for (const result of results) {
|
|
3418
|
+
if (result.success) {
|
|
3419
|
+
allPlayers.push(...result.data.map((p) => ({ ...p, source, competition })));
|
|
3420
|
+
}
|
|
3421
|
+
}
|
|
3422
|
+
return ok(allPlayers);
|
|
3307
3423
|
}
|
|
3308
3424
|
async function fetchFromFootyWire(query) {
|
|
3309
3425
|
const competition = query.competition ?? "AFLM";
|
|
3310
3426
|
if (competition === "AFLW") return err(aflwUnsupportedError("footywire"));
|
|
3311
3427
|
const client = new FootyWireClient();
|
|
3312
|
-
|
|
3313
|
-
|
|
3314
|
-
|
|
3315
|
-
|
|
3316
|
-
...p,
|
|
3317
|
-
|
|
3318
|
-
|
|
3319
|
-
}));
|
|
3320
|
-
return ok(players);
|
|
3428
|
+
if (query.team) {
|
|
3429
|
+
const teamName = normaliseTeamName(query.team);
|
|
3430
|
+
const result = await client.fetchPlayerList(teamName);
|
|
3431
|
+
if (!result.success) return result;
|
|
3432
|
+
return ok(result.data.map((p) => ({ ...p, source: "footywire", competition })));
|
|
3433
|
+
}
|
|
3434
|
+
return fetchAllTeamsFromScraper((name) => client.fetchPlayerList(name), "footywire", competition);
|
|
3321
3435
|
}
|
|
3322
3436
|
async function fetchFromAflTables(query) {
|
|
3323
3437
|
const competition = query.competition ?? "AFLM";
|
|
3324
3438
|
if (competition === "AFLW") return err(aflwUnsupportedError("afl-tables"));
|
|
3325
3439
|
const client = new AflTablesClient();
|
|
3326
|
-
|
|
3327
|
-
|
|
3328
|
-
|
|
3329
|
-
|
|
3330
|
-
...p,
|
|
3331
|
-
|
|
3440
|
+
if (query.team) {
|
|
3441
|
+
const teamName = normaliseTeamName(query.team);
|
|
3442
|
+
const result = await client.fetchPlayerList(teamName);
|
|
3443
|
+
if (!result.success) return result;
|
|
3444
|
+
return ok(result.data.map((p) => ({ ...p, source: "afl-tables", competition })));
|
|
3445
|
+
}
|
|
3446
|
+
return fetchAllTeamsFromScraper(
|
|
3447
|
+
(name) => client.fetchPlayerList(name),
|
|
3448
|
+
"afl-tables",
|
|
3332
3449
|
competition
|
|
3333
|
-
|
|
3334
|
-
return ok(players);
|
|
3450
|
+
);
|
|
3335
3451
|
}
|
|
3336
3452
|
async function fetchPlayerDetails(query) {
|
|
3337
3453
|
switch (query.source) {
|
|
@@ -3355,18 +3471,21 @@ async function fetchPlayerDetails(query) {
|
|
|
3355
3471
|
function toNullable(value) {
|
|
3356
3472
|
return value ?? null;
|
|
3357
3473
|
}
|
|
3358
|
-
function transformOne(item,
|
|
3474
|
+
function transformOne(item, ctx) {
|
|
3359
3475
|
const inner = item.player.player.player;
|
|
3360
3476
|
const stats = item.playerStats?.stats;
|
|
3361
3477
|
const clearances = stats?.clearances;
|
|
3362
3478
|
return {
|
|
3363
|
-
matchId,
|
|
3364
|
-
season,
|
|
3365
|
-
roundNumber,
|
|
3479
|
+
matchId: ctx.matchId,
|
|
3480
|
+
season: ctx.season,
|
|
3481
|
+
roundNumber: ctx.roundNumber,
|
|
3366
3482
|
team: normaliseTeamName(
|
|
3367
|
-
teamIdMap?.get(item.teamId) ?? AFL_API_TEAM_IDS.get(item.teamId) ?? item.teamId
|
|
3483
|
+
ctx.teamIdMap?.get(item.teamId) ?? AFL_API_TEAM_IDS.get(item.teamId) ?? item.teamId
|
|
3368
3484
|
),
|
|
3369
|
-
competition,
|
|
3485
|
+
competition: ctx.competition,
|
|
3486
|
+
date: ctx.date ?? null,
|
|
3487
|
+
homeTeam: ctx.homeTeam ?? null,
|
|
3488
|
+
awayTeam: ctx.awayTeam ?? null,
|
|
3370
3489
|
playerId: inner.playerId,
|
|
3371
3490
|
givenName: inner.playerName.givenName,
|
|
3372
3491
|
surname: inner.playerName.surname,
|
|
@@ -3406,6 +3525,12 @@ function transformOne(item, matchId, season, roundNumber, competition, source, t
|
|
|
3406
3525
|
totalPossessions: toNullable(stats?.totalPossessions),
|
|
3407
3526
|
timeOnGroundPercentage: toNullable(item.playerStats?.timeOnGroundPercentage),
|
|
3408
3527
|
ratingPoints: toNullable(stats?.ratingPoints),
|
|
3528
|
+
position: item.player.player.position ?? null,
|
|
3529
|
+
goalEfficiency: toNullable(stats?.goalEfficiency),
|
|
3530
|
+
shotEfficiency: toNullable(stats?.shotEfficiency),
|
|
3531
|
+
interchangeCounts: toNullable(stats?.interchangeCounts),
|
|
3532
|
+
brownlowVotes: toNullable(stats?.brownlowVotes),
|
|
3533
|
+
supercoachScore: null,
|
|
3409
3534
|
dreamTeamPoints: toNullable(stats?.dreamTeamPoints),
|
|
3410
3535
|
effectiveDisposals: toNullable(stats?.extendedStats?.effectiveDisposals),
|
|
3411
3536
|
effectiveKicks: toNullable(stats?.extendedStats?.effectiveKicks),
|
|
@@ -3433,16 +3558,12 @@ function transformOne(item, matchId, season, roundNumber, competition, source, t
|
|
|
3433
3558
|
kickinsPlayon: toNullable(stats?.extendedStats?.kickinsPlayon),
|
|
3434
3559
|
ruckContests: toNullable(stats?.extendedStats?.ruckContests),
|
|
3435
3560
|
scoreLaunches: toNullable(stats?.extendedStats?.scoreLaunches),
|
|
3436
|
-
source
|
|
3561
|
+
source: ctx.source
|
|
3437
3562
|
};
|
|
3438
3563
|
}
|
|
3439
|
-
function transformPlayerStats(data,
|
|
3440
|
-
const home = data.homeTeamPlayerStats.map(
|
|
3441
|
-
|
|
3442
|
-
);
|
|
3443
|
-
const away = data.awayTeamPlayerStats.map(
|
|
3444
|
-
(item) => transformOne(item, matchId, season, roundNumber, competition, source, teamIdMap)
|
|
3445
|
-
);
|
|
3564
|
+
function transformPlayerStats(data, ctx) {
|
|
3565
|
+
const home = (data.homeTeamPlayerStats ?? []).map((item) => transformOne(item, ctx));
|
|
3566
|
+
const away = (data.awayTeamPlayerStats ?? []).map((item) => transformOne(item, ctx));
|
|
3446
3567
|
return [...home, ...away];
|
|
3447
3568
|
}
|
|
3448
3569
|
|
|
@@ -3465,24 +3586,19 @@ async function fetchPlayerStats(query) {
|
|
|
3465
3586
|
teamIdMap2.set(match.awayTeamId, normaliseTeamName(match.awayTeam.name));
|
|
3466
3587
|
}
|
|
3467
3588
|
return ok(
|
|
3468
|
-
transformPlayerStats(
|
|
3469
|
-
|
|
3470
|
-
query.
|
|
3471
|
-
query.
|
|
3472
|
-
query.round ?? 0,
|
|
3589
|
+
transformPlayerStats(statsResult.data, {
|
|
3590
|
+
matchId: query.matchId,
|
|
3591
|
+
season: query.season,
|
|
3592
|
+
roundNumber: query.round ?? 0,
|
|
3473
3593
|
competition,
|
|
3474
|
-
"afl-api",
|
|
3475
|
-
teamIdMap2
|
|
3476
|
-
)
|
|
3594
|
+
source: "afl-api",
|
|
3595
|
+
teamIdMap: teamIdMap2
|
|
3596
|
+
})
|
|
3477
3597
|
);
|
|
3478
3598
|
}
|
|
3479
3599
|
const seasonResult = await client.resolveCompSeason(competition, query.season);
|
|
3480
3600
|
if (!seasonResult.success) return seasonResult;
|
|
3481
|
-
const
|
|
3482
|
-
const matchItemsResult = await client.fetchRoundMatchItemsByNumber(
|
|
3483
|
-
seasonResult.data,
|
|
3484
|
-
roundNumber
|
|
3485
|
-
);
|
|
3601
|
+
const matchItemsResult = query.round != null ? await client.fetchRoundMatchItemsByNumber(seasonResult.data, query.round) : await client.fetchSeasonMatchItems(seasonResult.data);
|
|
3486
3602
|
if (!matchItemsResult.success) return matchItemsResult;
|
|
3487
3603
|
const teamIdMap = /* @__PURE__ */ new Map();
|
|
3488
3604
|
for (const item of matchItemsResult.data) {
|
|
@@ -3501,15 +3617,17 @@ async function fetchPlayerStats(query) {
|
|
|
3501
3617
|
const item = matchItemsResult.data[i];
|
|
3502
3618
|
if (!item) continue;
|
|
3503
3619
|
allStats.push(
|
|
3504
|
-
...transformPlayerStats(
|
|
3505
|
-
|
|
3506
|
-
|
|
3507
|
-
query.
|
|
3508
|
-
roundNumber,
|
|
3620
|
+
...transformPlayerStats(statsResult.data, {
|
|
3621
|
+
matchId: item.match.matchId,
|
|
3622
|
+
season: query.season,
|
|
3623
|
+
roundNumber: item.round?.roundNumber ?? query.round ?? 0,
|
|
3509
3624
|
competition,
|
|
3510
|
-
"afl-api",
|
|
3511
|
-
teamIdMap
|
|
3512
|
-
|
|
3625
|
+
source: "afl-api",
|
|
3626
|
+
teamIdMap,
|
|
3627
|
+
date: new Date(item.match.utcStartTime),
|
|
3628
|
+
homeTeam: normaliseTeamName(item.match.homeTeam.name),
|
|
3629
|
+
awayTeam: normaliseTeamName(item.match.awayTeam.name)
|
|
3630
|
+
})
|
|
3513
3631
|
);
|
|
3514
3632
|
}
|
|
3515
3633
|
return ok(allStats);
|
|
@@ -3519,29 +3637,26 @@ async function fetchPlayerStats(query) {
|
|
|
3519
3637
|
const fwClient = new FootyWireClient();
|
|
3520
3638
|
const idsResult = await fwClient.fetchSeasonMatchIds(query.season);
|
|
3521
3639
|
if (!idsResult.success) return idsResult;
|
|
3522
|
-
const
|
|
3523
|
-
if (
|
|
3640
|
+
const entries = query.round != null ? idsResult.data.filter((e) => e.roundNumber === query.round) : idsResult.data;
|
|
3641
|
+
if (entries.length === 0) {
|
|
3524
3642
|
return ok([]);
|
|
3525
3643
|
}
|
|
3526
3644
|
const allStats = [];
|
|
3527
3645
|
const batchSize = 5;
|
|
3528
|
-
for (let i = 0; i <
|
|
3529
|
-
const batch =
|
|
3646
|
+
for (let i = 0; i < entries.length; i += batchSize) {
|
|
3647
|
+
const batch = entries.slice(i, i + batchSize);
|
|
3530
3648
|
const results = await Promise.all(
|
|
3531
|
-
batch.map((
|
|
3649
|
+
batch.map((e) => fwClient.fetchMatchPlayerStats(e.matchId, query.season, e.roundNumber))
|
|
3532
3650
|
);
|
|
3533
3651
|
for (const result of results) {
|
|
3534
3652
|
if (result.success) {
|
|
3535
3653
|
allStats.push(...result.data);
|
|
3536
3654
|
}
|
|
3537
3655
|
}
|
|
3538
|
-
if (i + batchSize <
|
|
3656
|
+
if (i + batchSize < entries.length) {
|
|
3539
3657
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
3540
3658
|
}
|
|
3541
3659
|
}
|
|
3542
|
-
if (query.round != null) {
|
|
3543
|
-
return ok(allStats.filter((s) => s.roundNumber === query.round));
|
|
3544
|
-
}
|
|
3545
3660
|
return ok(allStats);
|
|
3546
3661
|
}
|
|
3547
3662
|
case "afl-tables": {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fitzroy",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.0",
|
|
4
4
|
"description": "TypeScript port of the fitzRoy R package — programmatic access to AFL data including match results, player stats, fixtures, ladders, and more",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|