@ekmanss/5eplay 20260716.0.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.
@@ -0,0 +1,113 @@
1
+ # Data contracts
2
+
3
+ ## Match Detail 1.0.0
4
+
5
+ `getFiveEPlayMatch()` returns `{ data, diagnostics }`. `data` has `schemaVersion: '1.0.0'`,
6
+ `sport: 'cs2'`, a canonical 5EPlay source URL, and the following stable sections.
7
+
8
+ ### Identity and series state
9
+
10
+ - `match` contains the `csgo_mc_*` identity, numeric projection, live status, CS2 version,
11
+ best-of, schedule, stage, and per-team series score.
12
+ - `tournament` contains the event identity, logo, grade, location, prize, dates, and source status.
13
+ - `teams` contains exactly the source teams with 5E/Valve ranks, logos, series scores, odds, and
14
+ displayed odds percentages.
15
+ - `veto` preserves source order and normalizes `ban`, `pick`, and `left` actions.
16
+
17
+ When an in-progress API response temporarily omits an unplayed decider from `bouts_state`, the
18
+ collector reconstructs that `upcoming` map from the authoritative veto. It does not invent scores
19
+ or player data.
20
+
21
+ ### Maps
22
+
23
+ Every map contains:
24
+
25
+ - status, pick ownership, winner, score, current sides, first/second-half and overtime results;
26
+ - live round number/stage, round start, timer, bomb state, player health, money, armor, kit, weapon;
27
+ - `playerStats[].overall`, `.ct`, and `.t`, covering Rating, K-D-A, K/D, KD difference, KAST, ADR,
28
+ Round Swing, KPR, DPR, impact, multi-kill rating, headshots, first kills, flash assists, trades,
29
+ clutches, MVPs, and multi-kills;
30
+ - `playerDuels`, the exact Player Comparison matrix represented as typed player/opponent kill rows;
31
+ - public highlights and milestones;
32
+ - `eventLog.events` in chronological order.
33
+
34
+ An HTTP log page with `not_more: '1'` is marked `complete: true`. Each event retains the provider's
35
+ monotonic `updateVersion` and typed round, kill, assist, flash, bomb, join/leave, suicide, restart,
36
+ weapon, special-kill flag, and coordinate fields. Unknown future event types remain present as
37
+ `kind: 'unknown'` instead of being discarded.
38
+
39
+ `current` is the same live map object projected at the top level, or `null` when no map is live.
40
+
41
+ ### Analysis and community ratings
42
+
43
+ `analysis` covers the visible pre-match team metrics, player metrics, map history, player-power
44
+ records, recent matches, and head-to-head matches. Provider-specific power records remain JSON
45
+ objects so new metrics are preserved without a schema bump.
46
+
47
+ `communityRatings` contains every public tab and returned card, including player, coach, and
48
+ big-event cards. Account-specific `my_*` voting state, write actions, and chat messages are not
49
+ returned.
50
+
51
+ ### Missing values
52
+
53
+ Missing numeric/string scalars are `null`; collections are empty arrays. Source percentage strings
54
+ are returned as numbers without `%`. Provider IDs stay strings because team, player, tournament,
55
+ and match namespaces are distinct.
56
+
57
+ ## Realtime updates
58
+
59
+ A `FiveEPlayMatchSession` is an async iterable of:
60
+
61
+ - `snapshot`: the initial complete HTTP result;
62
+ - `state`: a merged MQTT scoreboard/state update;
63
+ - `log`: one deduplicated MQTT event-log update.
64
+
65
+ Every update includes a complete immutable snapshot. MQTT messages can be partial: map states are
66
+ merged by `bout_num`, while match/tournament/global sections are shallow-merged over the verified
67
+ HTTP baseline. Log events are merged by `updateVersion` and remain chronological.
68
+
69
+ The credential endpoint's `client_id`, `username`, and `password` are transport-only. They are not
70
+ part of either contract.
71
+
72
+ ## Live Matches 1.0.0
73
+
74
+ `getFiveEPlayLiveMatches()` returns a lightweight `{ data, diagnostics }` result intended for
75
+ frequent polling. `data.hasLiveMatches` is exactly `data.matches.length > 0`. Every match contains
76
+ its canonical ID/URL, BO format, schedule/stage, tournament identity, two teams and series scores,
77
+ available map summaries, and `currentMap`.
78
+
79
+ The schedule endpoint groups both live and upcoming rows. A row is included only when its series
80
+ state is live or it contains a live map. A series remains live between maps even though
81
+ `currentMap` is then `null`. Normal polling uses the source's first 20 ordered rows and one HTTP
82
+ request; another page is requested only when all 20 rows are live. No detail, analysis, event-log,
83
+ community-rating, Markdown, or MQTT credential request is made.
84
+
85
+ ## Diagnostics
86
+
87
+ Diagnostics include operation timing, canonical input where applicable, warnings, and one sanitized
88
+ entry per HTTP request with only request kind, HTTP status, duration, byte size, and optional
89
+ map/tab/page identity.
90
+ URLs with credentials, request/response bodies, MQTT credentials, cookies, and chat state are never
91
+ included.
92
+
93
+ ## Markdown Report 1.0.0
94
+
95
+ `writeFiveEPlayMatchMarkdown(input, target)` fetches a snapshot, renders a reader-focused report,
96
+ creates missing parent directories, and atomically replaces the destination. A target ending in
97
+ `.md` is the exact file; any other target is treated as a directory and receives
98
+ `<match-id>.md`. `renderFiveEPlayMatchMarkdown(result)` returns the same content without writing.
99
+
100
+ The report includes:
101
+
102
+ - a compact match/team overview and the complete veto;
103
+ - completed/live map scores, half splits, readable round sequences, player overview/side tables,
104
+ player duels, concise highlights/milestones, and readable event logs;
105
+ - a single-line placeholder for each upcoming map;
106
+ - compact team/player/map analysis, head-to-head scores, recent results, and seven player-power
107
+ category scores;
108
+ - sanitized capture diagnostics.
109
+
110
+ The report intentionally omits community ratings, odds, logos, schedule/event metadata, transport
111
+ versions, coordinates, raw round/milestone/power JSON, and provider-only codes. Those values remain
112
+ available in the typed Match Detail contract. The Markdown writer also disables the community
113
+ rating HTTP requests, while all other report data is fetched normally.
@@ -0,0 +1,129 @@
1
+ # Manual recipes
2
+
3
+ Run these commands from the repository root after `pnpm build`.
4
+
5
+ ## Poll for newly started matches
6
+
7
+ ```ts
8
+ import { setTimeout as delay } from 'node:timers/promises';
9
+ import { getFiveEPlayLiveMatches } from '@ekmanss/5eplay';
10
+
11
+ const known = new Set<string>();
12
+
13
+ while (true) {
14
+ const { data } = await getFiveEPlayLiveMatches({ timeoutMs: 5_000 });
15
+ for (const match of data.matches) {
16
+ if (!known.has(match.id)) {
17
+ console.log('started', match.id, match.url);
18
+ known.add(match.id);
19
+ }
20
+ }
21
+ for (const id of [...known]) {
22
+ if (!data.matches.some((match) => match.id === id)) known.delete(id);
23
+ }
24
+ await delay(5_000);
25
+ }
26
+ ```
27
+
28
+ Calls are serialized, so a slow request never overlaps the next poll. A normal call uses one list
29
+ request and performs no match-detail, log, analysis, community, Markdown, browser, or MQTT work.
30
+
31
+ ## Generate the human-readable Markdown report
32
+
33
+ Pass an exact Markdown filename:
34
+
35
+ ```bash
36
+ pnpm 5eplay:md -- \
37
+ 'https://event.5eplay.com/csgo/matches/csgo_mc_2395709' \
38
+ './outputs/5eplay/csgo_mc_2395709.md'
39
+ ```
40
+
41
+ Or pass a directory and let the writer use `<match-id>.md`:
42
+
43
+ ```bash
44
+ pnpm 5eplay:md -- 'csgo_mc_2395709' './outputs/5eplay'
45
+ ```
46
+
47
+ The writer creates missing parent directories and atomically replaces an existing destination.
48
+ Community ratings are not requested because they are not part of the Markdown report.
49
+
50
+ From another Node.js program:
51
+
52
+ ```ts
53
+ import { writeFiveEPlayMatchMarkdown } from '@ekmanss/5eplay';
54
+
55
+ const written = await writeFiveEPlayMatchMarkdown(
56
+ 'csgo_mc_2395709',
57
+ '/absolute/path/current-match.md',
58
+ );
59
+
60
+ console.log(written.outputPath, written.bytes);
61
+ ```
62
+
63
+ ## Save a complete snapshot
64
+
65
+ ```bash
66
+ FIVEEPLAY_MATCH_URL='https://event.5eplay.com/csgo/matches/csgo_mc_2395709' \
67
+ node --input-type=module <<'EOF' > match.json
68
+ import { getFiveEPlayMatch } from './packages/5eplay/dist/index.js';
69
+
70
+ const result = await getFiveEPlayMatch(process.env.FIVEEPLAY_MATCH_URL);
71
+ process.stdout.write(JSON.stringify(result, null, 2) + '\n');
72
+ EOF
73
+ ```
74
+
75
+ ## Inspect maps and live players
76
+
77
+ ```bash
78
+ jq '{
79
+ match: .data.match,
80
+ maps: [.data.maps[] | {number, name, status, teams}],
81
+ livePlayers: [.data.current.playerStats[]?.overall[]? | {
82
+ name,
83
+ health: .equipment.health,
84
+ money: .equipment.money,
85
+ weapon: .equipment.weapon,
86
+ kills: .metrics.kills,
87
+ deaths: .metrics.deaths,
88
+ assists: .metrics.assists,
89
+ adr: .metrics.adr
90
+ }]
91
+ }' match.json
92
+ ```
93
+
94
+ ## Stream realtime changes as NDJSON
95
+
96
+ ```bash
97
+ FIVEEPLAY_MATCH_URL='csgo_mc_2395709' \
98
+ node --input-type=module <<'EOF'
99
+ import { createFiveEPlayMatchSession } from './packages/5eplay/dist/index.js';
100
+
101
+ const controller = new AbortController();
102
+ process.once('SIGINT', () => controller.abort());
103
+
104
+ await using session = await createFiveEPlayMatchSession(
105
+ process.env.FIVEEPLAY_MATCH_URL,
106
+ { signal: controller.signal },
107
+ );
108
+
109
+ for await (const update of session) {
110
+ const compact = update.type === 'log'
111
+ ? { type: update.type, capturedAt: update.capturedAt, event: update.event }
112
+ : { type: update.type, capturedAt: update.capturedAt, current: update.snapshot.current };
113
+ process.stdout.write(JSON.stringify(compact) + '\n');
114
+ }
115
+ EOF
116
+ ```
117
+
118
+ ## Compact scoreboard-only snapshot
119
+
120
+ ```js
121
+ const result = await getFiveEPlayMatch(match, {
122
+ includeAnalysis: false,
123
+ includeCommunityRatings: false,
124
+ includeLogs: false,
125
+ });
126
+ ```
127
+
128
+ This mode makes only the match-data request and is appropriate for high-frequency read-only
129
+ scoreboard refreshes when a persistent MQTT session is not desired.
package/package.json ADDED
@@ -0,0 +1,55 @@
1
+ {
2
+ "name": "@ekmanss/5eplay",
3
+ "version": "20260716.0.0",
4
+ "description": "Typed 5EPlay CS2 live-match discovery, match-detail snapshots, and MQTT updates without a browser.",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js"
12
+ }
13
+ },
14
+ "bin": {
15
+ "5eplay-match-md": "dist/cli.js"
16
+ },
17
+ "files": [
18
+ "dist",
19
+ "docs",
20
+ "README.md",
21
+ "LICENSE"
22
+ ],
23
+ "engines": {
24
+ "node": ">=22"
25
+ },
26
+ "scripts": {
27
+ "check": "tsc --noEmit",
28
+ "build": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\" && tsc -p tsconfig.build.json",
29
+ "match:md": "tsx src/cli.ts",
30
+ "test": "tsx --test tests/match.test.ts",
31
+ "test:live": "tsx tests/live.ts",
32
+ "test:live:list": "tsx tests/live_list.ts",
33
+ "verify": "pnpm check && pnpm test && pnpm build",
34
+ "prepack": "pnpm verify"
35
+ },
36
+ "devDependencies": {
37
+ "@types/node": "24.13.3",
38
+ "tsx": "4.23.0",
39
+ "typescript": "5.9.3"
40
+ },
41
+ "repository": {
42
+ "type": "git",
43
+ "url": "git+https://github.com/ekmanss/sports-data-collectors.git",
44
+ "directory": "packages/5eplay"
45
+ },
46
+ "homepage": "https://github.com/ekmanss/sports-data-collectors/tree/main/packages/5eplay#readme",
47
+ "bugs": {
48
+ "url": "https://github.com/ekmanss/sports-data-collectors/issues"
49
+ },
50
+ "author": "ekmanss",
51
+ "license": "MIT",
52
+ "publishConfig": {
53
+ "access": "public"
54
+ }
55
+ }