campus-stats 0.4.3 → 0.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/CHANGELOG.md +33 -0
- package/LICENSE +1 -1
- package/README.md +190 -69
- package/dist/cache.js +0 -3
- package/dist/cli.js +145 -42
- package/dist/client.d.ts +1 -6
- package/dist/client.js +3 -13
- package/dist/credentials.d.ts +91 -0
- package/dist/credentials.js +145 -0
- package/dist/data-bundle.d.ts +8 -7
- package/dist/data-bundle.js +12 -11
- package/dist/db/sqlite-store.d.ts +1 -1
- package/dist/db/sqlite-store.js +0 -36
- package/dist/fantasy.js +0 -4
- package/dist/http.js +1 -1
- package/dist/index.d.ts +6 -6
- package/dist/index.js +3 -4
- package/dist/sources/create.d.ts +16 -0
- package/dist/sources/create.js +23 -0
- package/dist/sources/fbref.d.ts +3 -0
- package/dist/sources/fbref.js +3 -0
- package/dist/sources/index.d.ts +6 -0
- package/dist/sources/index.js +2 -0
- package/dist/sources/statsbomb-endpoints.d.ts +57 -0
- package/dist/sources/statsbomb-endpoints.js +169 -0
- package/dist/sources/statsbomb.d.ts +59 -9
- package/dist/sources/statsbomb.js +177 -19
- package/dist/sources/types.d.ts +1 -2
- package/dist/types.d.ts +0 -16
- package/package.json +5 -4
- package/dist/injuries.d.ts +0 -12
- package/dist/injuries.js +0 -15
package/dist/data-bundle.js
CHANGED
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Published women's fantasy data bundle (refreshed by
|
|
3
|
-
*
|
|
2
|
+
* Published women's fantasy data bundle (refreshed by GitHub Actions cron).
|
|
3
|
+
* Served as assets on the rolling Release tag `data-latest`.
|
|
4
4
|
*/
|
|
5
|
-
export const
|
|
6
|
-
export const
|
|
7
|
-
export const
|
|
5
|
+
export const DATA_BUNDLE_OWNER = "Minacava";
|
|
6
|
+
export const DATA_BUNDLE_REPO = "campus-stats";
|
|
7
|
+
export const DATA_BUNDLE_RELEASE_TAG = "data-latest";
|
|
8
8
|
export const DATA_BUNDLE_FILE = "cache.json";
|
|
9
|
+
export const DATA_BUNDLE_META_FILE = "meta.json";
|
|
9
10
|
/** Absolute URL of the periodically refreshed cache snapshot. */
|
|
10
|
-
export function dataBundleUrl(
|
|
11
|
-
return (`https://
|
|
12
|
-
|
|
11
|
+
export function dataBundleUrl(tag = DATA_BUNDLE_RELEASE_TAG) {
|
|
12
|
+
return (`https://github.com/${DATA_BUNDLE_OWNER}/${DATA_BUNDLE_REPO}` +
|
|
13
|
+
`/releases/download/${tag}/${DATA_BUNDLE_FILE}`);
|
|
13
14
|
}
|
|
14
|
-
export function dataBundleMetaUrl(
|
|
15
|
-
return (`https://
|
|
16
|
-
|
|
15
|
+
export function dataBundleMetaUrl(tag = DATA_BUNDLE_RELEASE_TAG) {
|
|
16
|
+
return (`https://github.com/${DATA_BUNDLE_OWNER}/${DATA_BUNDLE_REPO}` +
|
|
17
|
+
`/releases/download/${tag}/${DATA_BUNDLE_META_FILE}`);
|
|
17
18
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { CampusCache } from "../types.js";
|
|
2
|
-
export declare const SCHEMA_SQL = "\nCREATE TABLE IF NOT EXISTS competitions (\n id TEXT PRIMARY KEY,\n name TEXT NOT NULL,\n country TEXT,\n gender TEXT NOT NULL,\n sources_json TEXT NOT NULL\n);\nCREATE TABLE IF NOT EXISTS seasons (\n id TEXT PRIMARY KEY,\n name TEXT NOT NULL,\n competition_id TEXT NOT NULL,\n sources_json TEXT NOT NULL\n);\nCREATE TABLE IF NOT EXISTS teams (\n id TEXT PRIMARY KEY,\n name TEXT NOT NULL,\n country TEXT,\n sources_json TEXT NOT NULL\n);\nCREATE TABLE IF NOT EXISTS matches (\n id TEXT PRIMARY KEY,\n competition_id TEXT NOT NULL,\n season_id TEXT NOT NULL,\n date TEXT,\n home_team_id TEXT NOT NULL,\n away_team_id TEXT NOT NULL,\n home_score INTEGER,\n away_score INTEGER,\n sources_json TEXT NOT NULL\n);\nCREATE TABLE IF NOT EXISTS identities (\n id TEXT PRIMARY KEY,\n kind TEXT NOT NULL,\n name TEXT NOT NULL,\n aliases_json TEXT NOT NULL,\n sources_json TEXT NOT NULL,\n confidence TEXT NOT NULL,\n status TEXT NOT NULL,\n competition_hint TEXT\n);\nCREATE TABLE IF NOT EXISTS players (\n id TEXT PRIMARY KEY,\n name TEXT NOT NULL,\n nickname TEXT,\n country TEXT,\n sources_json TEXT NOT NULL\n);\nCREATE TABLE IF NOT EXISTS player_match_stats (\n id TEXT PRIMARY KEY,\n match_id TEXT NOT NULL,\n player_id TEXT NOT NULL,\n team_id TEXT NOT NULL,\n minutes INTEGER,\n goals INTEGER NOT NULL,\n assists INTEGER NOT NULL,\n yellow_cards INTEGER NOT NULL,\n red_cards INTEGER NOT NULL,\n sources_json TEXT NOT NULL\n);\nCREATE TABLE IF NOT EXISTS lineups (\n id TEXT PRIMARY KEY,\n match_id TEXT NOT NULL,\n team_id TEXT NOT NULL,\n player_id TEXT NOT NULL,\n started INTEGER NOT NULL,\n jersey_number INTEGER,\n sources_json TEXT NOT NULL\n);\
|
|
2
|
+
export declare const SCHEMA_SQL = "\nCREATE TABLE IF NOT EXISTS competitions (\n id TEXT PRIMARY KEY,\n name TEXT NOT NULL,\n country TEXT,\n gender TEXT NOT NULL,\n sources_json TEXT NOT NULL\n);\nCREATE TABLE IF NOT EXISTS seasons (\n id TEXT PRIMARY KEY,\n name TEXT NOT NULL,\n competition_id TEXT NOT NULL,\n sources_json TEXT NOT NULL\n);\nCREATE TABLE IF NOT EXISTS teams (\n id TEXT PRIMARY KEY,\n name TEXT NOT NULL,\n country TEXT,\n sources_json TEXT NOT NULL\n);\nCREATE TABLE IF NOT EXISTS matches (\n id TEXT PRIMARY KEY,\n competition_id TEXT NOT NULL,\n season_id TEXT NOT NULL,\n date TEXT,\n home_team_id TEXT NOT NULL,\n away_team_id TEXT NOT NULL,\n home_score INTEGER,\n away_score INTEGER,\n sources_json TEXT NOT NULL\n);\nCREATE TABLE IF NOT EXISTS identities (\n id TEXT PRIMARY KEY,\n kind TEXT NOT NULL,\n name TEXT NOT NULL,\n aliases_json TEXT NOT NULL,\n sources_json TEXT NOT NULL,\n confidence TEXT NOT NULL,\n status TEXT NOT NULL,\n competition_hint TEXT\n);\nCREATE TABLE IF NOT EXISTS players (\n id TEXT PRIMARY KEY,\n name TEXT NOT NULL,\n nickname TEXT,\n country TEXT,\n sources_json TEXT NOT NULL\n);\nCREATE TABLE IF NOT EXISTS player_match_stats (\n id TEXT PRIMARY KEY,\n match_id TEXT NOT NULL,\n player_id TEXT NOT NULL,\n team_id TEXT NOT NULL,\n minutes INTEGER,\n goals INTEGER NOT NULL,\n assists INTEGER NOT NULL,\n yellow_cards INTEGER NOT NULL,\n red_cards INTEGER NOT NULL,\n sources_json TEXT NOT NULL\n);\nCREATE TABLE IF NOT EXISTS lineups (\n id TEXT PRIMARY KEY,\n match_id TEXT NOT NULL,\n team_id TEXT NOT NULL,\n player_id TEXT NOT NULL,\n started INTEGER NOT NULL,\n jersey_number INTEGER,\n sources_json TEXT NOT NULL\n);\n";
|
|
3
3
|
export declare class SqliteStore {
|
|
4
4
|
private readonly db;
|
|
5
5
|
constructor(dbPath: string);
|
package/dist/db/sqlite-store.js
CHANGED
|
@@ -69,16 +69,6 @@ CREATE TABLE IF NOT EXISTS lineups (
|
|
|
69
69
|
jersey_number INTEGER,
|
|
70
70
|
sources_json TEXT NOT NULL
|
|
71
71
|
);
|
|
72
|
-
CREATE TABLE IF NOT EXISTS injuries (
|
|
73
|
-
id TEXT PRIMARY KEY,
|
|
74
|
-
player_id TEXT NOT NULL,
|
|
75
|
-
team_id TEXT,
|
|
76
|
-
status TEXT NOT NULL,
|
|
77
|
-
description TEXT,
|
|
78
|
-
from_date TEXT,
|
|
79
|
-
to_date TEXT,
|
|
80
|
-
sources_json TEXT NOT NULL
|
|
81
|
-
);
|
|
82
72
|
`;
|
|
83
73
|
function j(value) {
|
|
84
74
|
return JSON.stringify(value);
|
|
@@ -109,7 +99,6 @@ export class SqliteStore {
|
|
|
109
99
|
"players",
|
|
110
100
|
"player_match_stats",
|
|
111
101
|
"lineups",
|
|
112
|
-
"injuries",
|
|
113
102
|
]) {
|
|
114
103
|
this.db.prepare(`DELETE FROM ${table}`).run();
|
|
115
104
|
}
|
|
@@ -228,19 +217,6 @@ export class SqliteStore {
|
|
|
228
217
|
jerseyNumber: row.jersey_number == null ? null : Number(row.jersey_number),
|
|
229
218
|
sources: parseJson(String(row.sources_json)),
|
|
230
219
|
}));
|
|
231
|
-
const injuries = this.db
|
|
232
|
-
.prepare("SELECT * FROM injuries")
|
|
233
|
-
.all()
|
|
234
|
-
.map((row) => ({
|
|
235
|
-
id: String(row.id),
|
|
236
|
-
playerId: String(row.player_id),
|
|
237
|
-
teamId: row.team_id == null ? undefined : String(row.team_id),
|
|
238
|
-
status: String(row.status),
|
|
239
|
-
description: row.description == null ? undefined : String(row.description),
|
|
240
|
-
fromDate: row.from_date == null ? undefined : String(row.from_date),
|
|
241
|
-
toDate: row.to_date == null ? undefined : String(row.to_date),
|
|
242
|
-
sources: parseJson(String(row.sources_json)),
|
|
243
|
-
}));
|
|
244
220
|
return {
|
|
245
221
|
competitions,
|
|
246
222
|
seasons,
|
|
@@ -250,7 +226,6 @@ export class SqliteStore {
|
|
|
250
226
|
players,
|
|
251
227
|
playerMatchStats,
|
|
252
228
|
lineups,
|
|
253
|
-
injuries,
|
|
254
229
|
};
|
|
255
230
|
}
|
|
256
231
|
insertCache(cache) {
|
|
@@ -329,16 +304,5 @@ export class SqliteStore {
|
|
|
329
304
|
for (const l of cache.lineups) {
|
|
330
305
|
upsertLineup.run(l.id, l.matchId, l.teamId, l.playerId, l.started ? 1 : 0, l.jerseyNumber ?? null, j(l.sources));
|
|
331
306
|
}
|
|
332
|
-
const upsertInjury = this.db.prepare(`INSERT INTO injuries (
|
|
333
|
-
id, player_id, team_id, status, description, from_date, to_date, sources_json
|
|
334
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
335
|
-
ON CONFLICT(id) DO UPDATE SET
|
|
336
|
-
player_id=excluded.player_id, team_id=excluded.team_id,
|
|
337
|
-
status=excluded.status, description=excluded.description,
|
|
338
|
-
from_date=excluded.from_date, to_date=excluded.to_date,
|
|
339
|
-
sources_json=excluded.sources_json`);
|
|
340
|
-
for (const i of cache.injuries) {
|
|
341
|
-
upsertInjury.run(i.id, i.playerId, i.teamId ?? null, i.status, i.description ?? null, i.fromDate ?? null, i.toDate ?? null, j(i.sources));
|
|
342
|
-
}
|
|
343
307
|
}
|
|
344
308
|
}
|
package/dist/fantasy.js
CHANGED
|
@@ -100,7 +100,6 @@ function emptySyncResult() {
|
|
|
100
100
|
players: [],
|
|
101
101
|
playerMatchStats: [],
|
|
102
102
|
lineups: [],
|
|
103
|
-
injuries: [],
|
|
104
103
|
};
|
|
105
104
|
}
|
|
106
105
|
function mergeSyncResults(a, b) {
|
|
@@ -109,13 +108,11 @@ function mergeSyncResults(a, b) {
|
|
|
109
108
|
players: a.players ?? [],
|
|
110
109
|
playerMatchStats: a.playerMatchStats ?? [],
|
|
111
110
|
lineups: a.lineups ?? [],
|
|
112
|
-
injuries: a.injuries ?? [],
|
|
113
111
|
}), {
|
|
114
112
|
...b,
|
|
115
113
|
players: b.players ?? [],
|
|
116
114
|
playerMatchStats: b.playerMatchStats ?? [],
|
|
117
115
|
lineups: b.lineups ?? [],
|
|
118
|
-
injuries: b.injuries ?? [],
|
|
119
116
|
});
|
|
120
117
|
return {
|
|
121
118
|
competitions: cache.competitions,
|
|
@@ -125,7 +122,6 @@ function mergeSyncResults(a, b) {
|
|
|
125
122
|
players: cache.players,
|
|
126
123
|
playerMatchStats: cache.playerMatchStats,
|
|
127
124
|
lineups: cache.lineups,
|
|
128
|
-
injuries: cache.injuries,
|
|
129
125
|
};
|
|
130
126
|
}
|
|
131
127
|
function pickMatchesForPlayerStats(result, opts) {
|
package/dist/http.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Polite HTTP helper for HTML sources (FBref, etc.).
|
|
3
3
|
* Sequential requests share a minimum delay; failed GETs retry with backoff.
|
|
4
4
|
*/
|
|
5
|
-
const DEFAULT_UA = "campus/0.0.1 (women's football research; +https://
|
|
5
|
+
const DEFAULT_UA = "campus/0.0.1 (women's football research; +https://github.com/Minacava/campus-stats)";
|
|
6
6
|
function defaultSleep(ms) {
|
|
7
7
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
8
8
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
export type { SourceRef, TeamKind, Competition, Season, Team, Match, Player, PlayerMatchStats, LineupEntry,
|
|
1
|
+
export type { SourceRef, TeamKind, Competition, Season, Team, Match, Player, PlayerMatchStats, LineupEntry, CampusCache, } from "./types.js";
|
|
2
2
|
export type { IdentityKind, IdentityConfidence, IdentityStatus, CanonicalIdentity, } from "./identity/types.js";
|
|
3
3
|
export type { FootballSource, SyncResult } from "./sources/types.js";
|
|
4
|
-
export { StatsBombSource } from "./sources/
|
|
5
|
-
export type { WomenCompetitionInfo } from "./sources/
|
|
6
|
-
export {
|
|
4
|
+
export { StatsBombSource, FbrefSource, listFbrefPilotNames, createFootballSource, KNOWN_SOURCE_IDS, listStatsBombEndpoints, paidExtrasSummary, statsBombPaidUrls, STATSBOMB_API_VERSIONS, STATSBOMB_ENDPOINT_CATALOG, } from "./sources/index.js";
|
|
5
|
+
export type { WomenCompetitionInfo, StatsBombAccessMode, StatsBombSourceOptions, FbrefSourceOptions, SourceId, CreateSourceOptions, StatsBombPaidExtras, StatsBombEndpointInfo, StatsBombEndpointId, StatsBombEndpointAvailability, } from "./sources/index.js";
|
|
6
|
+
export { SB_USERNAME_ENV, SB_PASSWORD_ENV, CAMPUS_STATSBOMB_USERNAME_ENV, CAMPUS_STATSBOMB_PASSWORD_ENV, CAMPUS_STATSBOMB_API_BASE_URL_ENV, DEFAULT_STATSBOMB_API_BASE, defaultConfigPath, loadConfigFile, resolveCredentials, requireStatsBombPaidLogin, maskSecret, describeCredentials, requireApiKey, } from "./credentials.js";
|
|
7
|
+
export type { StatsBombCredentials, CampusConfigFile, CredentialSource, ResolvedCredentials, ResolveCredentialsOptions, } from "./credentials.js";
|
|
7
8
|
export { emptyCache, cachePath, loadCache, saveCache, mergeSyncResult, DEFAULT_CACHE_DIR, DEFAULT_CACHE_FILE, } from "./cache.js";
|
|
8
9
|
export { SqliteStore, SCHEMA_SQL } from "./db/sqlite-store.js";
|
|
9
10
|
export { FANTASY_PLAYER_STATS_LIMIT, listWomenCompetitions, syncFantasyBundle, updateCachedCompetitions, competitionIsInternational, } from "./fantasy.js";
|
|
10
11
|
export type { FantasySyncOptions } from "./fantasy.js";
|
|
11
|
-
export {
|
|
12
|
+
export { DATA_BUNDLE_OWNER, DATA_BUNDLE_REPO, DATA_BUNDLE_RELEASE_TAG, DATA_BUNDLE_FILE, DATA_BUNDLE_META_FILE, dataBundleUrl, dataBundleMetaUrl, } from "./data-bundle.js";
|
|
12
13
|
export { CampusClient } from "./client.js";
|
|
13
14
|
export type { SquadMember } from "./client.js";
|
|
14
15
|
export { DEFAULT_FANTASY_RULES, scorePlayerMatchStats, scoreFantasyPoints, } from "./scoring.js";
|
|
15
16
|
export type { FantasyScoringRules, FantasyPointRow } from "./scoring.js";
|
|
16
|
-
export { INJURIES_AVAILABLE, INJURIES_STATUS_MESSAGE, listInjuries, } from "./injuries.js";
|
package/dist/index.js
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
export { StatsBombSource } from "./sources/
|
|
2
|
-
export {
|
|
1
|
+
export { StatsBombSource, FbrefSource, listFbrefPilotNames, createFootballSource, KNOWN_SOURCE_IDS, listStatsBombEndpoints, paidExtrasSummary, statsBombPaidUrls, STATSBOMB_API_VERSIONS, STATSBOMB_ENDPOINT_CATALOG, } from "./sources/index.js";
|
|
2
|
+
export { SB_USERNAME_ENV, SB_PASSWORD_ENV, CAMPUS_STATSBOMB_USERNAME_ENV, CAMPUS_STATSBOMB_PASSWORD_ENV, CAMPUS_STATSBOMB_API_BASE_URL_ENV, DEFAULT_STATSBOMB_API_BASE, defaultConfigPath, loadConfigFile, resolveCredentials, requireStatsBombPaidLogin, maskSecret, describeCredentials, requireApiKey, } from "./credentials.js";
|
|
3
3
|
export { emptyCache, cachePath, loadCache, saveCache, mergeSyncResult, DEFAULT_CACHE_DIR, DEFAULT_CACHE_FILE, } from "./cache.js";
|
|
4
4
|
export { SqliteStore, SCHEMA_SQL } from "./db/sqlite-store.js";
|
|
5
5
|
export { FANTASY_PLAYER_STATS_LIMIT, listWomenCompetitions, syncFantasyBundle, updateCachedCompetitions, competitionIsInternational, } from "./fantasy.js";
|
|
6
|
-
export {
|
|
6
|
+
export { DATA_BUNDLE_OWNER, DATA_BUNDLE_REPO, DATA_BUNDLE_RELEASE_TAG, DATA_BUNDLE_FILE, DATA_BUNDLE_META_FILE, dataBundleUrl, dataBundleMetaUrl, } from "./data-bundle.js";
|
|
7
7
|
export { CampusClient } from "./client.js";
|
|
8
8
|
export { DEFAULT_FANTASY_RULES, scorePlayerMatchStats, scoreFantasyPoints, } from "./scoring.js";
|
|
9
|
-
export { INJURIES_AVAILABLE, INJURIES_STATUS_MESSAGE, listInjuries, } from "./injuries.js";
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Factory for CLI / library source selection.
|
|
3
|
+
*/
|
|
4
|
+
import type { StatsBombCredentials } from "../credentials.js";
|
|
5
|
+
import { type FbrefSourceOptions } from "./fbref.js";
|
|
6
|
+
import { type StatsBombSourceOptions } from "./statsbomb.js";
|
|
7
|
+
import type { FootballSource } from "./types.js";
|
|
8
|
+
export type SourceId = "statsbomb" | "fbref";
|
|
9
|
+
export declare const KNOWN_SOURCE_IDS: SourceId[];
|
|
10
|
+
export interface CreateSourceOptions {
|
|
11
|
+
statsbomb?: StatsBombSourceOptions;
|
|
12
|
+
fbref?: FbrefSourceOptions;
|
|
13
|
+
/** Convenience: StatsBomb customer login for paid API mode. */
|
|
14
|
+
credentials?: StatsBombCredentials;
|
|
15
|
+
}
|
|
16
|
+
export declare function createFootballSource(id: string, options?: CreateSourceOptions): FootballSource;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Factory for CLI / library source selection.
|
|
3
|
+
*/
|
|
4
|
+
import { FbrefSource } from "./fbref.js";
|
|
5
|
+
import { StatsBombSource, } from "./statsbomb.js";
|
|
6
|
+
export const KNOWN_SOURCE_IDS = ["statsbomb", "fbref"];
|
|
7
|
+
export function createFootballSource(id, options = {}) {
|
|
8
|
+
const normalized = id.trim().toLowerCase();
|
|
9
|
+
if (normalized === "statsbomb") {
|
|
10
|
+
return new StatsBombSource({
|
|
11
|
+
...options.statsbomb,
|
|
12
|
+
credentials: options.credentials ?? options.statsbomb?.credentials,
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
if (normalized === "fbref") {
|
|
16
|
+
return new FbrefSource(options.fbref);
|
|
17
|
+
}
|
|
18
|
+
if (normalized === "live") {
|
|
19
|
+
throw new Error('Source "live" was removed. Use --source statsbomb with SB_USERNAME / ' +
|
|
20
|
+
"SB_PASSWORD for the StatsBomb paid API (see docs/byok.md).");
|
|
21
|
+
}
|
|
22
|
+
throw new Error(`Unknown source: "${id}". Use ${KNOWN_SOURCE_IDS.join(", ")}.`);
|
|
23
|
+
}
|
package/dist/sources/fbref.d.ts
CHANGED
|
@@ -6,6 +6,9 @@
|
|
|
6
6
|
* polite HttpClient defaults. This environment often receives Cloudflare
|
|
7
7
|
* 403s; use fixtures / --fixture for offline sync.
|
|
8
8
|
*
|
|
9
|
+
* Sports Reference does not offer a public FBref API key — Campus has no
|
|
10
|
+
* paid-key path for this source (see docs/byok.md).
|
|
11
|
+
*
|
|
9
12
|
* See docs/fbref-pilot.md for competition ids and HTML shape.
|
|
10
13
|
*/
|
|
11
14
|
import { HttpClient } from "../http.js";
|
package/dist/sources/fbref.js
CHANGED
|
@@ -6,6 +6,9 @@
|
|
|
6
6
|
* polite HttpClient defaults. This environment often receives Cloudflare
|
|
7
7
|
* 403s; use fixtures / --fixture for offline sync.
|
|
8
8
|
*
|
|
9
|
+
* Sports Reference does not offer a public FBref API key — Campus has no
|
|
10
|
+
* paid-key path for this source (see docs/byok.md).
|
|
11
|
+
*
|
|
9
12
|
* See docs/fbref-pilot.md for competition ids and HTML shape.
|
|
10
13
|
*/
|
|
11
14
|
import { HttpClient } from "../http.js";
|
package/dist/sources/index.d.ts
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
1
|
export type { FootballSource, SyncResult } from "./types.js";
|
|
2
2
|
export { StatsBombSource } from "./statsbomb.js";
|
|
3
|
+
export type { StatsBombAccessMode, StatsBombSourceOptions, WomenCompetitionInfo, } from "./statsbomb.js";
|
|
3
4
|
export { FbrefSource, listFbrefPilotNames } from "./fbref.js";
|
|
5
|
+
export type { FbrefSourceOptions } from "./fbref.js";
|
|
6
|
+
export { createFootballSource, KNOWN_SOURCE_IDS } from "./create.js";
|
|
7
|
+
export type { SourceId, CreateSourceOptions } from "./create.js";
|
|
8
|
+
export { listStatsBombEndpoints, paidExtrasSummary, statsBombPaidUrls, buildPaidEndpointUrl, STATSBOMB_API_VERSIONS, STATSBOMB_ENDPOINT_CATALOG, } from "./statsbomb-endpoints.js";
|
|
9
|
+
export type { StatsBombPaidExtras, StatsBombEndpointInfo, StatsBombEndpointId, StatsBombEndpointAvailability, } from "./statsbomb-endpoints.js";
|
package/dist/sources/index.js
CHANGED
|
@@ -1,2 +1,4 @@
|
|
|
1
1
|
export { StatsBombSource } from "./statsbomb.js";
|
|
2
2
|
export { FbrefSource, listFbrefPilotNames } from "./fbref.js";
|
|
3
|
+
export { createFootballSource, KNOWN_SOURCE_IDS } from "./create.js";
|
|
4
|
+
export { listStatsBombEndpoints, paidExtrasSummary, statsBombPaidUrls, buildPaidEndpointUrl, STATSBOMB_API_VERSIONS, STATSBOMB_ENDPOINT_CATALOG, } from "./statsbomb-endpoints.js";
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* StatsBomb Data API endpoint catalogue.
|
|
3
|
+
*
|
|
4
|
+
* Free Open Data and the paid customer API share many concepts; paid adds
|
|
5
|
+
* aggregate / 360 / mapping endpoints and licensed competition coverage.
|
|
6
|
+
*
|
|
7
|
+
* Host (paid): https://data.statsbombservices.com
|
|
8
|
+
* Auth: HTTP Basic (SB_USERNAME / SB_PASSWORD) — same as statsbombpy.
|
|
9
|
+
*/
|
|
10
|
+
/** Default API versions (statsbombpy fallbacks when live map is unavailable). */
|
|
11
|
+
export declare const STATSBOMB_API_VERSIONS: {
|
|
12
|
+
readonly competitions: "v4";
|
|
13
|
+
readonly matches: "v6";
|
|
14
|
+
readonly lineups: "v5";
|
|
15
|
+
readonly events: "v11";
|
|
16
|
+
readonly frames360: "v2";
|
|
17
|
+
readonly playerMatchStats: "v8";
|
|
18
|
+
readonly playerSeasonStats: "v7";
|
|
19
|
+
readonly teamMatchStats: "v4";
|
|
20
|
+
readonly teamSeasonStats: "v4";
|
|
21
|
+
};
|
|
22
|
+
export type StatsBombEndpointId = "competitions" | "matches" | "lineups" | "events" | "frames360" | "playerMatchStats" | "teamMatchStats" | "playerSeasonStats" | "teamSeasonStats" | "playerMapping" | "endpointVersions";
|
|
23
|
+
export type StatsBombEndpointAvailability = "wired" | "optional-paid";
|
|
24
|
+
export interface StatsBombEndpointInfo {
|
|
25
|
+
id: StatsBombEndpointId;
|
|
26
|
+
name: string;
|
|
27
|
+
paidPath?: string;
|
|
28
|
+
openDataPath?: string;
|
|
29
|
+
availability: StatsBombEndpointAvailability;
|
|
30
|
+
wiredInCampus: boolean;
|
|
31
|
+
requiresPaidLicense: boolean;
|
|
32
|
+
notes: string;
|
|
33
|
+
}
|
|
34
|
+
export declare const STATSBOMB_ENDPOINT_CATALOG: StatsBombEndpointInfo[];
|
|
35
|
+
/** Optional paid extras the CLI can request once a login is present. */
|
|
36
|
+
export interface StatsBombPaidExtras {
|
|
37
|
+
playerMatchStats?: boolean;
|
|
38
|
+
teamMatchStats?: boolean;
|
|
39
|
+
playerSeasonStats?: boolean;
|
|
40
|
+
teamSeasonStats?: boolean;
|
|
41
|
+
frames360?: boolean;
|
|
42
|
+
}
|
|
43
|
+
export declare function listStatsBombEndpoints(): StatsBombEndpointInfo[];
|
|
44
|
+
export declare function paidExtrasSummary(extras?: StatsBombPaidExtras): string[];
|
|
45
|
+
export declare function buildPaidEndpointUrl(template: string, params: Record<string, string | number>, apiBaseUrl?: string): string;
|
|
46
|
+
export declare function statsBombPaidUrls(apiBaseUrl?: string): {
|
|
47
|
+
competitions: () => string;
|
|
48
|
+
matches: (competitionId: number, seasonId: number) => string;
|
|
49
|
+
lineups: (matchId: string | number) => string;
|
|
50
|
+
events: (matchId: string | number) => string;
|
|
51
|
+
frames360: (matchId: string | number) => string;
|
|
52
|
+
playerMatchStats: (matchId: string | number) => string;
|
|
53
|
+
teamMatchStats: (matchId: string | number) => string;
|
|
54
|
+
playerSeasonStats: (competitionId: number, seasonId: number) => string;
|
|
55
|
+
teamSeasonStats: (competitionId: number, seasonId: number) => string;
|
|
56
|
+
endpointVersions: () => string;
|
|
57
|
+
};
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* StatsBomb Data API endpoint catalogue.
|
|
3
|
+
*
|
|
4
|
+
* Free Open Data and the paid customer API share many concepts; paid adds
|
|
5
|
+
* aggregate / 360 / mapping endpoints and licensed competition coverage.
|
|
6
|
+
*
|
|
7
|
+
* Host (paid): https://data.statsbombservices.com
|
|
8
|
+
* Auth: HTTP Basic (SB_USERNAME / SB_PASSWORD) — same as statsbombpy.
|
|
9
|
+
*/
|
|
10
|
+
import { DEFAULT_STATSBOMB_API_BASE } from "../credentials.js";
|
|
11
|
+
/** Default API versions (statsbombpy fallbacks when live map is unavailable). */
|
|
12
|
+
export const STATSBOMB_API_VERSIONS = {
|
|
13
|
+
competitions: "v4",
|
|
14
|
+
matches: "v6",
|
|
15
|
+
lineups: "v5",
|
|
16
|
+
events: "v11",
|
|
17
|
+
frames360: "v2",
|
|
18
|
+
playerMatchStats: "v8",
|
|
19
|
+
playerSeasonStats: "v7",
|
|
20
|
+
teamMatchStats: "v4",
|
|
21
|
+
teamSeasonStats: "v4",
|
|
22
|
+
};
|
|
23
|
+
export const STATSBOMB_ENDPOINT_CATALOG = [
|
|
24
|
+
{
|
|
25
|
+
id: "competitions",
|
|
26
|
+
name: "Competitions",
|
|
27
|
+
paidPath: `/api/${STATSBOMB_API_VERSIONS.competitions}/competitions`,
|
|
28
|
+
openDataPath: "/competitions.json",
|
|
29
|
+
availability: "wired",
|
|
30
|
+
wiredInCampus: true,
|
|
31
|
+
requiresPaidLicense: false,
|
|
32
|
+
notes: "Paid returns only competition-seasons in your contract; open-data is the public subset.",
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
id: "matches",
|
|
36
|
+
name: "Matches in competition season",
|
|
37
|
+
paidPath: `/api/${STATSBOMB_API_VERSIONS.matches}/competitions/{competition_id}/seasons/{season_id}/matches`,
|
|
38
|
+
openDataPath: "/matches/{competition_id}/{season_id}.json",
|
|
39
|
+
availability: "wired",
|
|
40
|
+
wiredInCampus: true,
|
|
41
|
+
requiresPaidLicense: false,
|
|
42
|
+
notes: "Core schedule + scores.",
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
id: "lineups",
|
|
46
|
+
name: "Lineups for match",
|
|
47
|
+
paidPath: `/api/${STATSBOMB_API_VERSIONS.lineups}/lineups/{match_id}`,
|
|
48
|
+
openDataPath: "/lineups/{match_id}.json",
|
|
49
|
+
availability: "wired",
|
|
50
|
+
wiredInCampus: true,
|
|
51
|
+
requiresPaidLicense: false,
|
|
52
|
+
notes: "Used when --with-players (capped).",
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
id: "events",
|
|
56
|
+
name: "Events in match",
|
|
57
|
+
paidPath: `/api/${STATSBOMB_API_VERSIONS.events}/events/{match_id}`,
|
|
58
|
+
openDataPath: "/events/{match_id}.json",
|
|
59
|
+
availability: "wired",
|
|
60
|
+
wiredInCampus: true,
|
|
61
|
+
requiresPaidLicense: false,
|
|
62
|
+
notes: "Used when --with-players.",
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
id: "playerMatchStats",
|
|
66
|
+
name: "Player match stats (aggregated)",
|
|
67
|
+
paidPath: `/api/${STATSBOMB_API_VERSIONS.playerMatchStats}/matches/{match_id}/player-stats`,
|
|
68
|
+
availability: "optional-paid",
|
|
69
|
+
wiredInCampus: false,
|
|
70
|
+
requiresPaidLicense: true,
|
|
71
|
+
notes: "From the StatsBomb paid API when your licence includes it. Use --with-paid-player-match-stats.",
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
id: "teamMatchStats",
|
|
75
|
+
name: "Team match stats (aggregated)",
|
|
76
|
+
paidPath: `/api/${STATSBOMB_API_VERSIONS.teamMatchStats}/matches/{match_id}/team-stats`,
|
|
77
|
+
availability: "optional-paid",
|
|
78
|
+
wiredInCampus: false,
|
|
79
|
+
requiresPaidLicense: true,
|
|
80
|
+
notes: "From the StatsBomb paid API when your licence includes it. Use --with-paid-team-match-stats.",
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
id: "playerSeasonStats",
|
|
84
|
+
name: "Player season stats",
|
|
85
|
+
paidPath: `/api/${STATSBOMB_API_VERSIONS.playerSeasonStats}/competitions/{competition_id}/seasons/{season_id}/player-stats`,
|
|
86
|
+
availability: "optional-paid",
|
|
87
|
+
wiredInCampus: false,
|
|
88
|
+
requiresPaidLicense: true,
|
|
89
|
+
notes: "From the StatsBomb paid API when your licence includes it. Use --with-paid-player-season-stats.",
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
id: "teamSeasonStats",
|
|
93
|
+
name: "Team season stats",
|
|
94
|
+
paidPath: `/api/${STATSBOMB_API_VERSIONS.teamSeasonStats}/competitions/{competition_id}/seasons/{season_id}/team-stats`,
|
|
95
|
+
availability: "optional-paid",
|
|
96
|
+
wiredInCampus: false,
|
|
97
|
+
requiresPaidLicense: true,
|
|
98
|
+
notes: "From the StatsBomb paid API when your licence includes it. Use --with-paid-team-season-stats.",
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
id: "frames360",
|
|
102
|
+
name: "360 freeze frames",
|
|
103
|
+
paidPath: `/api/${STATSBOMB_API_VERSIONS.frames360}/360-frames/{match_id}`,
|
|
104
|
+
openDataPath: "/three-sixty/{match_id}.json",
|
|
105
|
+
availability: "optional-paid",
|
|
106
|
+
wiredInCampus: false,
|
|
107
|
+
requiresPaidLicense: true,
|
|
108
|
+
notes: "From the StatsBomb paid API (360 licence). Use --with-paid-360.",
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
id: "playerMapping",
|
|
112
|
+
name: "Player mapping (live ↔ offline ids)",
|
|
113
|
+
paidPath: "/api/v*/player-mapping (see StatsBomb Data Hub)",
|
|
114
|
+
availability: "optional-paid",
|
|
115
|
+
wiredInCampus: false,
|
|
116
|
+
requiresPaidLicense: true,
|
|
117
|
+
notes: "From the StatsBomb paid API / Data Hub when licensed.",
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
id: "endpointVersions",
|
|
121
|
+
name: "Endpoint versions map",
|
|
122
|
+
paidPath: "/api/endpoint-versions",
|
|
123
|
+
availability: "optional-paid",
|
|
124
|
+
wiredInCampus: false,
|
|
125
|
+
requiresPaidLicense: true,
|
|
126
|
+
notes: "From the StatsBomb paid API; Campus uses version fallbacks today.",
|
|
127
|
+
},
|
|
128
|
+
];
|
|
129
|
+
export function listStatsBombEndpoints() {
|
|
130
|
+
return STATSBOMB_ENDPOINT_CATALOG;
|
|
131
|
+
}
|
|
132
|
+
export function paidExtrasSummary(extras = {}) {
|
|
133
|
+
const enabled = [];
|
|
134
|
+
if (extras.playerMatchStats)
|
|
135
|
+
enabled.push("playerMatchStats");
|
|
136
|
+
if (extras.teamMatchStats)
|
|
137
|
+
enabled.push("teamMatchStats");
|
|
138
|
+
if (extras.playerSeasonStats)
|
|
139
|
+
enabled.push("playerSeasonStats");
|
|
140
|
+
if (extras.teamSeasonStats)
|
|
141
|
+
enabled.push("teamSeasonStats");
|
|
142
|
+
if (extras.frames360)
|
|
143
|
+
enabled.push("frames360");
|
|
144
|
+
return enabled;
|
|
145
|
+
}
|
|
146
|
+
export function buildPaidEndpointUrl(template, params, apiBaseUrl = DEFAULT_STATSBOMB_API_BASE) {
|
|
147
|
+
let path = template;
|
|
148
|
+
for (const [key, value] of Object.entries(params)) {
|
|
149
|
+
path = path.replace(`{${key}}`, String(value));
|
|
150
|
+
}
|
|
151
|
+
const base = apiBaseUrl.replace(/\/$/, "");
|
|
152
|
+
return path.startsWith("http") ? path : `${base}${path}`;
|
|
153
|
+
}
|
|
154
|
+
export function statsBombPaidUrls(apiBaseUrl = DEFAULT_STATSBOMB_API_BASE) {
|
|
155
|
+
const base = apiBaseUrl.replace(/\/$/, "");
|
|
156
|
+
const v = STATSBOMB_API_VERSIONS;
|
|
157
|
+
return {
|
|
158
|
+
competitions: () => `${base}/api/${v.competitions}/competitions`,
|
|
159
|
+
matches: (competitionId, seasonId) => `${base}/api/${v.matches}/competitions/${competitionId}/seasons/${seasonId}/matches`,
|
|
160
|
+
lineups: (matchId) => `${base}/api/${v.lineups}/lineups/${matchId}`,
|
|
161
|
+
events: (matchId) => `${base}/api/${v.events}/events/${matchId}`,
|
|
162
|
+
frames360: (matchId) => `${base}/api/${v.frames360}/360-frames/${matchId}`,
|
|
163
|
+
playerMatchStats: (matchId) => `${base}/api/${v.playerMatchStats}/matches/${matchId}/player-stats`,
|
|
164
|
+
teamMatchStats: (matchId) => `${base}/api/${v.teamMatchStats}/matches/${matchId}/team-stats`,
|
|
165
|
+
playerSeasonStats: (competitionId, seasonId) => `${base}/api/${v.playerSeasonStats}/competitions/${competitionId}/seasons/${seasonId}/player-stats`,
|
|
166
|
+
teamSeasonStats: (competitionId, seasonId) => `${base}/api/${v.teamSeasonStats}/competitions/${competitionId}/seasons/${seasonId}/team-stats`,
|
|
167
|
+
endpointVersions: () => `${base}/api/endpoint-versions`,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
@@ -1,16 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* StatsBomb Open Data
|
|
2
|
+
* StatsBomb adapter — free Open Data by default, optional paid API.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* (https://statsbomb.com/media-pack/). Pass that requirement downstream.
|
|
4
|
+
* Free (no login):
|
|
5
|
+
* https://github.com/statsbomb/open-data
|
|
6
|
+
* Credit StatsBomb in published analysis (https://statsbomb.com/media-pack/).
|
|
8
7
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
8
|
+
* Paid (customer login — same as statsbombpy):
|
|
9
|
+
* SB_USERNAME + SB_PASSWORD → https://data.statsbombservices.com
|
|
10
|
+
* Coverage and freshness follow your StatsBomb contract.
|
|
11
|
+
*
|
|
12
|
+
* Women's competitions only (`competition_gender === "female"`).
|
|
11
13
|
*/
|
|
12
14
|
import type { LineupEntry, Match, Player, PlayerMatchStats } from "../types.js";
|
|
15
|
+
import { type StatsBombCredentials } from "../credentials.js";
|
|
13
16
|
import type { FootballSource, SyncResult } from "./types.js";
|
|
17
|
+
import { type StatsBombPaidExtras } from "./statsbomb-endpoints.js";
|
|
18
|
+
export type StatsBombAccessMode = "open-data" | "paid";
|
|
14
19
|
export interface WomenCompetitionInfo {
|
|
15
20
|
id: number;
|
|
16
21
|
name: string;
|
|
@@ -22,25 +27,70 @@ export interface WomenCompetitionInfo {
|
|
|
22
27
|
}>;
|
|
23
28
|
}
|
|
24
29
|
export interface StatsBombSourceOptions {
|
|
25
|
-
/**
|
|
30
|
+
/**
|
|
31
|
+
* Customer login for the paid StatsBomb API. When both username and
|
|
32
|
+
* password are set, Campus hits data.statsbombservices.com instead of
|
|
33
|
+
* free Open Data.
|
|
34
|
+
*/
|
|
35
|
+
credentials?: StatsBombCredentials;
|
|
36
|
+
/**
|
|
37
|
+
* Override open-data root (default: GitHub raw master/data).
|
|
38
|
+
* Alias: `baseUrl` (kept for fixtures / existing callers).
|
|
39
|
+
*/
|
|
40
|
+
openDataBaseUrl?: string;
|
|
41
|
+
/** @deprecated Prefer `openDataBaseUrl`. */
|
|
26
42
|
baseUrl?: string;
|
|
43
|
+
/** Override paid API host (default: data.statsbombservices.com). */
|
|
44
|
+
apiBaseUrl?: string;
|
|
27
45
|
/** Inject fetch for offline fixtures / tests. */
|
|
28
46
|
fetchJson?: <T>(url: string) => Promise<T>;
|
|
29
47
|
/** When true, also pull lineups/events and aggregate v1 player stats. */
|
|
30
48
|
includePlayerStats?: boolean;
|
|
31
49
|
/** Cap matches enriched with player stats (default 5). */
|
|
32
50
|
playerStatsLimit?: number;
|
|
51
|
+
/**
|
|
52
|
+
* Optional paid-only endpoints (aggregates, 360, …). Ignored in open-data
|
|
53
|
+
* mode. Requires a StatsBomb customer login + licence that includes them.
|
|
54
|
+
*/
|
|
55
|
+
paidExtras?: StatsBombPaidExtras;
|
|
33
56
|
}
|
|
34
57
|
export declare class StatsBombSource implements FootballSource {
|
|
35
58
|
readonly id = "statsbomb";
|
|
36
|
-
|
|
59
|
+
readonly accessMode: StatsBombAccessMode;
|
|
60
|
+
private readonly openDataBaseUrl;
|
|
61
|
+
private readonly apiBaseUrl;
|
|
62
|
+
private readonly username?;
|
|
63
|
+
private readonly password?;
|
|
37
64
|
private readonly fetchJson;
|
|
38
65
|
private readonly includePlayerStats;
|
|
39
66
|
private readonly playerStatsLimit;
|
|
67
|
+
private readonly paidExtras;
|
|
40
68
|
constructor(options?: StatsBombSourceOptions);
|
|
69
|
+
private paidUrls;
|
|
70
|
+
private competitionsUrl;
|
|
71
|
+
private matchesUrl;
|
|
72
|
+
private lineupsUrl;
|
|
73
|
+
private eventsUrl;
|
|
41
74
|
/** Catalogue of women's competitions (clubs + national-team tournaments). */
|
|
42
75
|
listWomenCompetitions(): Promise<WomenCompetitionInfo[]>;
|
|
43
76
|
syncCompetition(competitionName: string): Promise<SyncResult>;
|
|
77
|
+
/**
|
|
78
|
+
* Hit optional paid-only endpoints for a sample competition/season/match.
|
|
79
|
+
* Confirms the licence covers them; Campus does not yet map these into the
|
|
80
|
+
* canonical schema (probe only).
|
|
81
|
+
*/
|
|
82
|
+
probePaidExtras(context: {
|
|
83
|
+
competitionId: number;
|
|
84
|
+
seasonId: number;
|
|
85
|
+
matchId?: string | number;
|
|
86
|
+
}): Promise<Array<{
|
|
87
|
+
id: string;
|
|
88
|
+
ok: boolean;
|
|
89
|
+
url: string;
|
|
90
|
+
itemCount?: number;
|
|
91
|
+
error?: string;
|
|
92
|
+
mappedToCampusSchema: false;
|
|
93
|
+
}>>;
|
|
44
94
|
/** Load aggregated v1 player stats + lineups for the given canonical matches. */
|
|
45
95
|
loadPlayerStatsForMatches(matches: Match[]): Promise<{
|
|
46
96
|
players: Player[];
|